diff --git a/flake.lock b/flake.lock index 0096ba0..9b09513 100644 --- a/flake.lock +++ b/flake.lock @@ -33,11 +33,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1648390671, - "narHash": "sha256-u69opCeHUx3CsdIerD0wVSR+DjfDQjnztObqfk9Trqc=", + "lastModified": 1649225869, + "narHash": "sha256-u1zLtPmQzhT9mNXyM8Ey9pk7orDrIKdwooeGDEXm5xM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ce8cbe3c01fd8ee2de526ccd84bbf9b82397a510", + "rev": "b6966d911da89e5a7301aaef8b4f0a44c77e103c", "type": "github" }, "original": { @@ -57,11 +57,11 @@ ] }, "locked": { - "lastModified": 1648612483, - "narHash": "sha256-rfQKrZkCZM5RpgTHVk5EFt2IklCNglaf+aRy1xwL93U=", + "lastModified": 1648808959, + "narHash": "sha256-1KZuZ0yJ4j1cWvOvnyyy36xCTcG9+sNe2FDHOHnErQM=", "owner": "nix-community", "repo": "poetry2nix", - "rev": "33960dc1c07ab71f4c7bf5b18297c99fd37cf473", + "rev": "99c79568352799af09edaeefc858d337e6d9c56f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8622329..7686559 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,9 @@ let pkgs = import nixpkgs { inherit system; - overlays = [ poetry2nix.overlay ]; + overlays = [ + poetry2nix.overlay + ]; }; python = pkgs.python310; packageName = "hydra-check"; @@ -35,7 +37,7 @@ nativeBuildInputs = with python.pkgs; [ poetry-core ]; propagatedBuildInputs = with python.pkgs; [ requests beautifulsoup4 docopt ]; src = ./.; - checkInputs = with pkgs; [ mypy ]; + checkInputs = with pkgs; [ python.pkgs.mypy ]; checkPhase = '' export MYPYPATH=$PWD/src mypy src/hydra_check @@ -48,7 +50,6 @@ default = pkgs.mkShell { buildInputs = with pkgs; [ pyright - mypy (pkgs.poetry.override { python = python; }) (pkgs.poetry2nix.mkPoetryEnv { inherit python; @@ -63,6 +64,7 @@ ] ++ (with python.pkgs; [ black pylint + mypy ]); shellHook = '' export MYPYPATH=$PWD/src diff --git a/pyproject.toml b/pyproject.toml index 131dc20..e9ae21b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = ["Felix Richter "] hydra-check = 'hydra_check.cli:main' [tool.poetry.dependencies] -python = "^3.9" +python = "^3.10" requests = "^2.2" beautifulsoup4 = "^4.1" docopt = "^0.6" @@ -42,7 +42,7 @@ ignore_missing_imports = true [tool.black] -line-length = 88 +line-length = 120 target_version = ['py310'] [tool.pylint.master] @@ -50,4 +50,15 @@ init-hook = "import sys; sys.path.append('src')" [tool.pylint.FORMAT] output-format = "colorized" -max-line-length = 88 +max-line-length = 120 + +[tool.pylint.messages_control] +disable = [ + # Many functions (e.g. callbacks) will naturally have unused arguments. + "unused-argument", + + # Disable failure for TODO items in the codebase (code will always have TODOs). + "fixme", + + "missing-docstring" +] diff --git a/src/hydra_check/arguments.py b/src/hydra_check/arguments.py new file mode 100644 index 0000000..1349c73 --- /dev/null +++ b/src/hydra_check/arguments.py @@ -0,0 +1,53 @@ +import argparse +import textwrap + + +def process_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=textwrap.dedent('''\ + Other channels can be: + unstable - alias for nixos/trunk-combined (Default) + master - alias for nixpkgs/trunk + staging - alias for nixos/staging + 19.03 - alias for nixos/release-19.03 + 19.09 - alias for nixos/release-19.09 + 20.03 - alias for nixos/release-20.03 + nixpkgs/nixpkgs-20.03-darwin - verbatim jobset name + + Jobset names can be constructed with the project name (e.g. `nixos/` or `nixpkgs/`) + followed by a branch name. The available jobsets can be found at: + * https://hydra.nixos.org/project/nixos + * https://hydra.nixos.org/project/nixpkgs + ''') + ) + parser.add_argument( + "PACKAGES", + action="append", + ) + parser.add_argument( + "--url", + action="store_true", + help="only print the hydra build url, then exit", + ) + parser.add_argument( + "--json", + action="store_true", + help="output json", + ) + parser.add_argument( + "--short", + action="store_true", + help="write only the latest build even if last build failed", + ) + parser.add_argument( + "--arch", + default="x86_64-linux", + help="system architecture to check", + ) + parser.add_argument( + "--channel", + default="unstable", + help="Channel to check packages for", + ) + return parser.parse_args() diff --git a/src/hydra_check/cli.py b/src/hydra_check/cli.py index a8f11b5..39cc247 100755 --- a/src/hydra_check/cli.py +++ b/src/hydra_check/cli.py @@ -1,33 +1,11 @@ -"""usage: hydra-check [options] PACKAGES... - -options: - --arch=SYSTEM system architecture to check [default: x86_64-linux] - --json write builds in machine-readable format - --short write only the latest build even if last build failed - --url only print the hydra build url, then exit - --channel=CHAN Channel to check packages for [Default: unstable] - -Other channels can be: - unstable - alias for nixos/trunk-combined (Default) - master - alias for nixpkgs/trunk - staging - alias for nixos/staging - 19.03 - alias for nixos/release-19.03 - 19.09 - alias for nixos/release-19.09 - 20.03 - alias for nixos/release-20.03 - nixpkgs/nixpkgs-20.03-darwin - verbatim jobset name - -Jobset names can be constructed with the project name (e.g. `nixos/` or `nixpkgs/`) -followed by a branch name. The available jobsets can be found at: -* https://hydra.nixos.org/project/nixos -* https://hydra.nixos.org/project/nixpkgs - -""" -from bs4 import BeautifulSoup -import requests import json +from sys import exit as sysexit from typing import Dict, Iterator, Union -from sys import exit +import requests +from bs4 import BeautifulSoup + +from hydra_check.arguments import process_args # TODO: use TypedDict BuildStatus = Dict[str, Union[str, bool]] @@ -36,18 +14,18 @@ # guess functions are intended to be fast without external queries def guess_jobset(channel: str) -> str: # TODO guess the latest stable channel - if channel == "master": - return "nixpkgs/trunk" - elif channel == "unstable": - return "nixos/trunk-combined" - elif channel == "staging": - return "nixos/staging" - elif channel[0].isdigit(): - # 19.09, 20.03 etc - return f"nixos/release-{channel}" - else: - # we asume that the user knows the jobset name ( nixos/release-19.09 ) - return channel + match channel: + case "master": + return "nixpkgs/trunk" + case "unstable": + return "nixos/trunk-combined" + case "staging": + return "nixos/staging" + case _: + if channel[0].isdigit(): + # 19.09, 20.03 etc + return f"nixos/release-{channel}" + return channel def guess_packagename(package: str, arch: str, is_channel: bool) -> str: @@ -55,11 +33,12 @@ def guess_packagename(package: str, arch: str, is_channel: bool) -> str: if package.startswith("nixpkgs.") or package.startswith("nixos."): # we assume user knows the full package name return f"{package}.{arch}" - elif is_channel: + + if is_channel: # we simply guess, that the user searches for a package and not a test return f"nixpkgs.{package}.{arch}" - else: - return f"{package}.{arch}" + + return f"{package}.{arch}" def get_url(ident: str) -> str: @@ -75,7 +54,7 @@ def fetch_data(ident: str) -> str: resp = requests.get(url, timeout=20) if resp.status_code == 404: print(f"package {ident} not found at url {url}") - exit(1) + sysexit(1) return resp.text @@ -97,8 +76,7 @@ def parse_build_html(data: str) -> Iterator[BuildStatus]: except ValueError: if row.find("td").find("a")["href"].endswith("/all"): continue - else: - raise + raise status = status.find("img")["title"] build_id = build.find("a").text build_url = build.find("a")["href"] @@ -124,27 +102,27 @@ def print_buildreport(build: BuildStatus) -> None: if build["evals"]: extra = "" if build["success"] else f" ({build['status']})" print( - f"{build['icon']}{extra} {build['name']} from {str(build['timestamp']).split('T')[0]} - {build['build_url']}" + f"{build['icon']}{extra} {build['name']} from " + f"{str(build['timestamp']).split('T', maxsplit=1)[0]} - {build['build_url']}", ) else: print(f"{build['icon']} {build['status']}") def main() -> None: - from docopt import docopt - args = docopt(__doc__) - channel = args["--channel"] - packages = args["PACKAGES"] - arch = args["--arch"] - only_url = args["--url"] + args = process_args() + + channel = args.channel + packages: list[str] = args.PACKAGES + only_url = args.url jobset = guess_jobset(channel) is_channel = jobset.startswith("nixos/") - as_json = args["--json"] + as_json = args.json all_builds = {} for package in packages: - package_name = guess_packagename(package, arch, is_channel) + package_name = guess_packagename(package, args.arch, is_channel) ident = f"{jobset}/{package_name}" if only_url: print(get_url(ident)) @@ -158,7 +136,7 @@ def main() -> None: latest = builds[0] print(f"Build Status for {package_name} on {channel}") print_buildreport(latest) - if not latest["success"] and latest["evals"] and not args["--short"]: + if not latest["success"] and latest["evals"] and not args.short: print() print("Last Builds:") for build in builds[1:]: