diff --git a/.gitignore b/.gitignore index e5daf0d15..5ac5ebeb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# mypy .mypy_cache/ diff --git a/README.md b/README.md index 6b657d40b..da3ec473a 100644 --- a/README.md +++ b/README.md @@ -214,14 +214,14 @@ $ git clone https://github.com/nix-community/NUR } ``` -At the moment each URL must point to a git repository. By running `nur/update.py` +At the moment each URL must point to a git repository. By running `bin/nur update` the corresponding `repos.json.lock` is updated and the repository is tested. This will perform also an evaluation check, which must be passed for your repository. Commit the changed `repos.json` but NOT `repos.json.lock` ``` $ git add repos.json -$ ./nur/format_manifest.py # ensure repos.json is sorted alphabetically +$ ./bin/nur format-manifest # ensure repos.json is sorted alphabetically $ git commit -m "add repository" $ git push ``` diff --git a/bin/nur b/bin/nur new file mode 100755 index 000000000..e998a1984 --- /dev/null +++ b/bin/nur @@ -0,0 +1,12 @@ +#!/usr/bin/env nix-shell +#!nix-shell -p python3 -p nix-prefetch-git -p nix -i python3 +import sys +import os + +sys.path.insert(0, os.path.join( + os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from nur import main + +if __name__ == "__main__": + main() diff --git a/ci/deploy.sh b/ci/deploy.sh index 0b6987407..ee82b8789 100755 --- a/ci/deploy.sh +++ b/ci/deploy.sh @@ -14,7 +14,7 @@ fi export encrypted_025d6e877aa4_key= encrypted_025d6e877aa4_iv= -./nur/format-manifest.py +./bin/nur format-manifest if [ -n "$(git diff --exit-code repos.json)" ]; then echo "repos.json was not formatted before committing repos.json:" >&2 git diff --exit-code repos.json @@ -22,7 +22,7 @@ if [ -n "$(git diff --exit-code repos.json)" ]; then exit 1 fi -./nur/update.py +./bin/nur update nix-build # Pull requests and commits to other branches shouldn't try to deploy, just build to verify diff --git a/nur/__init__.py b/nur/__init__.py new file mode 100644 index 000000000..d40072a18 --- /dev/null +++ b/nur/__init__.py @@ -0,0 +1,36 @@ +import argparse +import sys +from typing import List + +from .format_manifest import format_manifest_command +from .index import index_command +from .update import update_command + +# from .build import build_channel_command + + +def parse_arguments(argv: List[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog=argv[0], description="nur management commands" + ) + + subparsers = parser.add_subparsers(description="subcommands") + + # build_channel = subparsers.add_parser("build-channel") + # build_channel.set_defaults(func=build_channel_command) + + format_manifest = subparsers.add_parser("format-manifest") + format_manifest.set_defaults(func=format_manifest_command) + + update = subparsers.add_parser("update") + update.set_defaults(func=update_command) + + index = subparsers.add_parser("index") + index.set_defaults(func=index_command) + + return parser.parse_args(argv[1:]) + + +def main() -> None: + args = parse_arguments(sys.argv) + args.func(args) diff --git a/nur/channel.py b/nur/channel.py new file mode 100644 index 000000000..63c45c4d8 --- /dev/null +++ b/nur/channel.py @@ -0,0 +1,7 @@ +from pathlib import Path + +from .path import LOCK_PATH, MANIFEST_PATH + + +def build_channel_command(_path: str): + pass diff --git a/nur/error.py b/nur/error.py new file mode 100644 index 000000000..9f5c36132 --- /dev/null +++ b/nur/error.py @@ -0,0 +1,2 @@ +class NurError(Exception): + pass diff --git a/nur/format-manifest.py b/nur/format_manifest.py old mode 100755 new mode 100644 similarity index 61% rename from nur/format-manifest.py rename to nur/format_manifest.py index 0c3e4b950..f0aceb529 --- a/nur/format-manifest.py +++ b/nur/format_manifest.py @@ -1,14 +1,11 @@ -#!/usr/bin/env nix-shell -#!nix-shell -p python3 -i python3 - import json import shutil -from pathlib import Path +from argparse import Namespace -ROOT = Path(__file__).parent.parent +from .path import ROOT -def main() -> None: +def format_manifest_command(args: Namespace) -> None: path = ROOT.joinpath("repos.json") manifest = json.load(open(path)) tmp_path = str(path) + ".tmp" @@ -16,7 +13,3 @@ def main() -> None: json.dump(manifest, tmp, indent=4, sort_keys=True) tmp.write("\n") shutil.move(tmp_path, path) - - -if __name__ == "__main__": - main() diff --git a/nur/index.py b/nur/index.py new file mode 100644 index 000000000..12ba95b37 --- /dev/null +++ b/nur/index.py @@ -0,0 +1,49 @@ +import json +import subprocess +from argparse import Namespace +from pathlib import Path +from tempfile import NamedTemporaryFile +from typing import Any, Dict + +from .path import ROOT + + +def index_repo(repo: str, expression_file: str) -> Dict[str, Any]: + fetch_source_cmd = [ + "nix-build", + str(ROOT), + "-A", + f"repo-sources.{repo}", + "--no-out-link", + ] + + repo_path = subprocess.check_output(fetch_source_cmd).strip() + + expression_path = Path(repo_path.decode("utf-8")).joinpath(expression_file) + + with NamedTemporaryFile(mode="w") as f: + expr = f"with import {{}}; callPackage {expression_path} {{}}" + f.write(expr) + f.flush() + query_cmd = ["nix-env", "-qa", "*", "--json", "-f", str(f.name)] + out = subprocess.check_output(query_cmd).strip() + raw_pkgs = json.loads(out) + pkgs = {} + for name, pkg in raw_pkgs.items(): + pkg["_attr"] = name + pkgs[f"nur.repos.{repo}.{name}"] = pkg + return pkgs + + +def index_command(args: Namespace) -> None: + manifest_path = ROOT.joinpath("repos.json") + with open(manifest_path) as f: + manifest = json.load(f) + repos = manifest.get("repos", []) + pkgs: Dict[str, Any] = {} + + for (repo, data) in repos.items(): + pkgs.update(index_repo(repo, data.get("file", "default.nix"))) + + with open(ROOT.joinpath("packages.json"), "w") as f: + json.dump(pkgs, f, indent=4) diff --git a/nur/manifest.py b/nur/manifest.py new file mode 100644 index 000000000..b23ea4bf9 --- /dev/null +++ b/nur/manifest.py @@ -0,0 +1,118 @@ +import json +from enum import Enum, auto +from pathlib import Path +from typing import Dict, List, Optional, Union +from urllib.parse import ParseResult, urlparse + +Url = ParseResult + + +class LockedVersion: + def __init__( + self, url: Url, rev: str, sha256: str, submodules: bool = False + ) -> None: + self.url = url + self.rev = rev + self.sha256 = sha256 + self.submodules = submodules + + def as_json(self) -> Dict[str, Union[bool, str]]: + d = dict( + url=self.url.geturl(), + rev=self.rev, + sha256=self.sha256, + ) + if self.submodules: + d["submodules"] = self.submodules + return d + + +class RepoType(Enum): + GITHUB = auto() + GITLAB = auto() + GIT = auto() + + @staticmethod + def from_repo(repo: "Repo", type_: str) -> "RepoType": + if repo.submodules: + return RepoType.GIT + if repo.url.hostname == "github.com": + return RepoType.GITHUB + if repo.url.hostname == "gitlab.com" or type_ == "gitlab": + return RepoType.GITLAB + else: + return RepoType.GIT + + +class Repo: + def __init__( + self, + name: str, + url: Url, + submodules: bool, + type_: str, + file_: Optional[str], + locked_version: Optional[LockedVersion], + ) -> None: + self.name = name + self.url = url + self.submodules = submodules + if file_ is None: + self.file = "default.nix" + else: + self.file = file_ + self.locked_version = None + + if ( + locked_version is not None + and locked_version.url != url.geturl() + and locked_version.submodules == submodules + ): + self.locked_version = locked_version + + self.type = RepoType.from_repo(self, type_) + + +class Manifest: + def __init__(self, repos: List[Repo]) -> None: + self.repos = repos + + +def _load_locked_versions(path: Path) -> Dict[str, LockedVersion]: + with open(path) as f: + data = json.load(f) + + locked_versions = {} + + for name, repo in data["repos"].items(): + url = urlparse(repo["url"]) + rev = repo["rev"] + sha256 = repo["sha256"] + locked_versions[name] = LockedVersion(url, rev, sha256) + + return locked_versions + + +def load_locked_versions(path: Path) -> Dict[str, LockedVersion]: + if path.exists(): + return _load_locked_versions(path) + else: + return {} + + +def load_manifest(manifest_path: Union[str, Path], lock_path) -> Manifest: + locked_versions = load_locked_versions(lock_path) + + with open(manifest_path) as f: + data = json.load(f) + + repos = [] + for name, repo in data["repos"].items(): + url = urlparse(repo["url"]) + submodules = repo.get("submodules", False) + file_ = repo.get("file", "default.nix") + type_ = repo.get("type", None) + locked_version = locked_versions.get(name) + repos.append(Repo(name, url, submodules, type_, file_, locked_version)) + + return Manifest(repos) diff --git a/nur/path.py b/nur/path.py new file mode 100644 index 000000000..f8fb911a4 --- /dev/null +++ b/nur/path.py @@ -0,0 +1,20 @@ +import subprocess +from pathlib import Path + +ROOT = Path(__file__).parent.parent.resolve() +LOCK_PATH = ROOT.joinpath("repos.json.lock") +MANIFEST_PATH = ROOT.joinpath("repos.json") +EVALREPO_PATH = ROOT.joinpath("lib/evalRepo.nix") + +_NIXPKGS_PATH = None + + +def nixpkgs_path() -> str: + global _NIXPKGS_PATH + if _NIXPKGS_PATH is not None: + return _NIXPKGS_PATH + cmd = ["nix-instantiate", "--find-file", "nixpkgs"] + path = subprocess.check_output(cmd).decode("utf-8").strip() + _NIXPKGS_PATH = str(Path(path).resolve()) + + return _NIXPKGS_PATH diff --git a/nur/prefetch.py b/nur/prefetch.py new file mode 100644 index 000000000..1fb917a43 --- /dev/null +++ b/nur/prefetch.py @@ -0,0 +1,129 @@ +import json +import re +import subprocess +import urllib.error +import urllib.request +import xml.etree.ElementTree as ET +from pathlib import Path +from typing import Any, Dict, Optional, Tuple +from urllib.parse import urljoin, urlparse + +from .error import NurError +from .manifest import LockedVersion, Repo, RepoType + + +def fetch_commit_from_feed(url: str) -> str: + req = urllib.request.urlopen(url) + try: + xml = req.read() + root = ET.fromstring(xml) + ns = "{http://www.w3.org/2005/Atom}" + xpath = f"./{ns}entry/{ns}link" + commit_link = root.find(xpath) + if commit_link is None: + raise NurError(f"No commits found in repository feed {url}") + return Path(urlparse(commit_link.attrib["href"]).path).parts[-1] + except urllib.error.HTTPError as e: + if e.code == 404: + raise NurError(f"Repository feed {url} not found") + raise + + +def nix_prefetch_zip(url: str) -> Tuple[str, Path]: + data = subprocess.check_output( + ["nix-prefetch-url", "--name", "source", "--unpack", "--print-path", url] + ) + sha256, path = data.decode().strip().split("\n") + return sha256, Path(path) + + +class GithubRepo: + def __init__(self, owner: str, name: str) -> None: + self.owner = owner + self.name = name + + def url(self, path: str) -> str: + return urljoin(f"https://github.com/{self.owner}/{self.name}/", path) + + def latest_commit(self) -> str: + return fetch_commit_from_feed(self.url("commits/master.atom")) + + def prefetch(self, ref: str) -> Tuple[str, Path]: + return nix_prefetch_zip(self.url(f"archive/{ref}.tar.gz")) + + +class GitlabRepo: + def __init__(self, domain: str, owner: str, name: str) -> None: + self.domain = domain + self.owner = owner + self.name = name + + def latest_commit(self) -> str: + url = ( + f"https://{self.domain}/{self.owner}/{self.name}/commits/master?format=atom" + ) + return fetch_commit_from_feed(url) + + def prefetch(self, ref: str) -> Tuple[str, Path]: + url = f"https://{self.domain}/api/v4/projects/{self.owner}%2F{self.name}/repository/archive.tar.gz?sha={ref}" + return nix_prefetch_zip(url) + + +def prefetch_git(repo: Repo) -> Tuple[LockedVersion, Path]: + cmd = ["nix-prefetch-git"] + if repo.submodules: + cmd += ["--fetch-submodules"] + cmd += [repo.url.geturl()] + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + raise NurError( + f"Failed to prefetch git repository {repo.url.geturl()}: {result.stderr}" + ) + + metadata = json.loads(result.stdout) + lines = result.stderr.decode("utf-8").split("\n") + repo_path = re.search("path is (.+)", lines[-5]) + assert repo_path is not None + path = Path(repo_path.group(1)) + rev = metadata["rev"] + sha256 = metadata["sha256"] + return LockedVersion(repo.url, rev, sha256, repo.submodules), path + + +def prefetch_github(repo: Repo) -> Tuple[LockedVersion, Optional[Path]]: + github_path = Path(repo.url.path) + gh_repo = GithubRepo(github_path.parts[1], github_path.parts[2]) + commit = gh_repo.latest_commit() + locked_version = repo.locked_version + if locked_version is not None: + if locked_version.rev == commit: + return locked_version, None + sha256, path = gh_repo.prefetch(commit) + + return LockedVersion(repo.url, commit, sha256), path + + +def prefetch_gitlab(repo: Repo) -> Tuple[LockedVersion, Optional[Path]]: + gitlab_path = Path(repo.url.path) + gl_repo = GitlabRepo( + repo.url.hostname, gitlab_path.parts[-2], gitlab_path.parts[-1] + ) + commit = gl_repo.latest_commit() + locked_version = repo.locked_version + if locked_version is not None: + if locked_version.rev == commit: + return locked_version, None + + sha256, path = gl_repo.prefetch(commit) + return LockedVersion(repo.url, commit, sha256), path + + +def prefetch(repo: Repo) -> Tuple[Repo, LockedVersion, Optional[Path]]: + if repo.type == RepoType.GITHUB: + locked_version, path = prefetch_github(repo) + elif repo.type == RepoType.GITLAB: + locked_version, path = prefetch_gitlab(repo) + else: + locked_version, path = prefetch_git(repo) + + return repo, locked_version, path diff --git a/nur/update.py b/nur/update.py old mode 100755 new mode 100644 index ed08f84c6..b7eb3dbf6 --- a/nur/update.py +++ b/nur/update.py @@ -1,231 +1,38 @@ -#!/usr/bin/env nix-shell -#!nix-shell -p python3 -p nix-prefetch-git -p nix -i python3 - import json -import shutil -import re -import sys +import logging import os -from pathlib import Path -from typing import List, Optional, Tuple, Dict, Any, Tuple -import xml.etree.ElementTree as ET -import urllib.request -import urllib.error +import shutil import subprocess import tempfile -#from dataclasses import dataclass, field, InitVar -from enum import Enum, auto -from urllib.parse import urlparse, urljoin, ParseResult -import logging +from argparse import Namespace +from pathlib import Path +from typing import List -ROOT = Path(__file__).parent.parent.resolve(); -LOCK_PATH = ROOT.joinpath("repos.json.lock") -MANIFEST_PATH = ROOT.joinpath("repos.json") -EVALREPO_PATH = ROOT.joinpath("lib/evalRepo.nix") - -Url = ParseResult +from .error import NurError +from .manifest import Repo, load_manifest +from .path import EVALREPO_PATH, LOCK_PATH, MANIFEST_PATH, nixpkgs_path +from .prefetch import prefetch logger = logging.getLogger(__name__) -class NurError(Exception): - pass - - -def fetch_commit_from_feed(url: str) -> str: - req = urllib.request.urlopen(url) - try: - xml = req.read() - root = ET.fromstring(xml) - ns = "{http://www.w3.org/2005/Atom}" - xpath = f"./{ns}entry/{ns}link" - commit_link = root.find(xpath) - if commit_link is None: - raise NurError(f"No commits found in repository feed {url}") - return Path(urlparse(commit_link.attrib["href"]).path).parts[-1] - except urllib.error.HTTPError as e: - if e.code == 404: - raise NurError(f"Repository feed {url} not found") - raise - - -def nix_prefetch_zip(url: str) -> Tuple[str, Path]: - data = subprocess.check_output( - ["nix-prefetch-url", "--name", "source", "--unpack", "--print-path", url]) - sha256, path = data.decode().strip().split("\n") - return sha256, Path(path) - - -#@dataclass -class GithubRepo(): - def __init__(self, owner: str, name: str) -> None: - self.owner = owner - self.name = name - - #owner: str - #name: str - - def url(self, path: str) -> str: - return urljoin(f"https://github.com/{self.owner}/{self.name}/", path) - - def latest_commit(self) -> str: - return fetch_commit_from_feed(self.url("commits/master.atom")) - - def prefetch(self, ref: str) -> Tuple[str, Path]: - return nix_prefetch_zip(self.url(f"archive/{ref}.tar.gz")) - - -class GitlabRepo(): - def __init__(self, domain: str, owner: str, name: str) -> None: - self.domain = domain - self.owner = owner - self.name = name - - def latest_commit(self) -> str: - url = f"https://{self.domain}/{self.owner}/{self.name}/commits/master?format=atom" - return fetch_commit_from_feed(url) - - def prefetch(self, ref: str) -> Tuple[str, Path]: - url = f"https://{self.domain}/api/v4/projects/{self.owner}%2F{self.name}/repository/archive.tar.gz?sha={ref}" - return nix_prefetch_zip(url) - - -class RepoType(Enum): - GITHUB = auto() - GITLAB = auto() - GIT = auto() - - @staticmethod - def from_spec(spec: 'RepoSpec') -> 'RepoType': - if spec.url.hostname == "github.com" and not spec.submodules: - return RepoType.GITHUB - if (spec.url.hostname == "gitlab.com" or spec.type == "gitlab") \ - and not spec.submodules: - return RepoType.GITLAB - else: - return RepoType.GIT - - -#@dataclass -class Repo(): - def __init__(self, spec: 'RepoSpec', rev: str, sha256: str) -> None: - self.__post_init__(spec) - self.rev = rev - self.sha256 = sha256 - - #spec: InitVar['RepoSpec'] - #rev: str - #sha256: str - - #name: str = field(init=False) - #url: Url = field(init=False) - ##type: RepoType = field(init=False) - #submodules: bool = field(init=False) - - def __post_init__(self, spec: 'RepoSpec'): - self.name = spec.name - self.url = spec.url - self.submodules = spec.submodules - self.type = RepoType.from_spec(spec) - - -#@dataclass -class RepoSpec(): - def __init__(self, name: str, url: Url, nix_file: str, submodules: bool, - type_: str) -> None: - self.name = name - self.url = url - self.nix_file = nix_file - self.submodules = submodules - self.type = type_ - - #name: str - #url: Url - #nix_file: str - #submodules: bool - - -def prefetch_git(spec: RepoSpec) -> Tuple[str, str, Path]: - url = spec.url.geturl() - cmd = ["nix-prefetch-git"] - if spec.submodules: - cmd += ["--fetch-submodules"] - cmd += [url] - result = subprocess.run( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if result.returncode != 0: - raise NurError( - f"Failed to prefetch git repository {url}: {result.stderr}") - - metadata = json.loads(result.stdout) - lines = result.stderr.decode("utf-8").split("\n") - repo_path = re.search("path is (.+)", lines[-5]) - assert repo_path is not None - path = Path(repo_path.group(1)) - return metadata["rev"], metadata["sha256"], path - - -def prefetch_github(spec: RepoSpec, locked_repo: Optional[Repo] - ) -> Tuple[str, str, Optional[Path]]: - github_path = Path(spec.url.path) - repo = GithubRepo(github_path.parts[1], github_path.parts[2]) - commit = repo.latest_commit() - if locked_repo is not None: - if locked_repo.rev == commit and \ - locked_repo.submodules == spec.submodules: - return locked_repo.rev, locked_repo.sha256, None - sha256, path = repo.prefetch(commit) - return commit, sha256, path - - -def prefetch_gitlab(spec: RepoSpec, locked_repo: Optional[Repo] - ) -> Tuple[str, str, Optional[Path]]: - gitlab_path = Path(spec.url.path) - repo = GitlabRepo(spec.url.hostname, gitlab_path.parts[-2], - gitlab_path.parts[-1]) - commit = repo.latest_commit() - if locked_repo is not None: - if locked_repo.rev == commit and \ - locked_repo.submodules == spec.submodules: - return locked_repo.rev, locked_repo.sha256, None - sha256, path = repo.prefetch(commit) - return commit, sha256, path - - -def prefetch(spec: RepoSpec, - locked_repo: Optional[Repo]) -> Tuple[Repo, Optional[Path]]: - - repo_type = RepoType.from_spec(spec) - if repo_type == RepoType.GITHUB: - commit, sha256, path = prefetch_github(spec, locked_repo) - elif repo_type == RepoType.GITLAB: - commit, sha256, path = prefetch_gitlab(spec, locked_repo) - else: - commit, sha256, path = prefetch_git(spec) - - return Repo(spec, commit, sha256), path - - -def nixpkgs_path() -> str: - cmd = ["nix-instantiate", "--find-file", "nixpkgs"] - path = subprocess.check_output(cmd).decode("utf-8").strip() - return str(Path(path).resolve()) - - -def eval_repo(spec: RepoSpec, repo_path: Path) -> None: +def eval_repo(repo: Repo, repo_path: Path) -> None: with tempfile.TemporaryDirectory() as d: eval_path = Path(d).joinpath("default.nix") with open(eval_path, "w") as f: - f.write(f""" + f.write( + f""" with import {{}}; import {EVALREPO_PATH} {{ - name = "{spec.name}"; - url = "{spec.url}"; - src = {repo_path.joinpath(spec.nix_file)}; + name = "{repo.name}"; + url = "{repo.url}"; + src = {repo_path.joinpath(repo.file)}; inherit pkgs lib; }} -""") +""" + ) + # fmt: off cmd = [ "nix-env", "-f", str(eval_path), @@ -233,82 +40,58 @@ import {EVALREPO_PATH} {{ "--meta", "--xml", "--option", "restrict-eval", "true", + "--option", "allow-import-from-derivation", "true", "--drv-path", "--show-trace", "-I", f"nixpkgs={nixpkgs_path()}", "-I", str(repo_path), "-I", str(eval_path), "-I", str(EVALREPO_PATH), - ] # yapf: disable + ] + # fmt: on - logger.info(f"Evaluate repository {spec.name}") + logger.info(f"Evaluate repository {repo.name}") proc = subprocess.Popen( - cmd, env=dict(PATH=os.environ["PATH"]), stdout=subprocess.PIPE) + cmd, env=dict(PATH=os.environ["PATH"]), stdout=subprocess.PIPE + ) res = proc.wait() if res != 0: - raise NurError( - f"{spec.name} does not evaluate:\n$ {' '.join(cmd)}") + raise NurError(f"{repo.name} does not evaluate:\n$ {' '.join(cmd)}") -def update(spec: RepoSpec, locked_repo: Optional[Repo]) -> Repo: - repo, repo_path = prefetch(spec, locked_repo) +def update(repo: Repo) -> Repo: + repo, locked_version, repo_path = prefetch(repo) if repo_path: - eval_repo(spec, repo_path) + eval_repo(repo, repo_path) + + repo.locked_version = locked_version return repo def update_lock_file(repos: List[Repo]): locked_repos = {} for repo in repos: - locked_repo: Dict[str, Any] = dict( - rev=repo.rev, sha256=repo.sha256, url=repo.url.geturl()) - if repo.submodules: - locked_repo["submodules"] = True - locked_repos[repo.name] = locked_repo + if repo.locked_version: + locked_repos[repo.name] = repo.locked_version.as_json() tmp_file = str(LOCK_PATH) + "-new" with open(tmp_file, "w") as lock_file: - json.dump( - dict(repos=locked_repos), lock_file, indent=4, sort_keys=True) + json.dump(dict(repos=locked_repos), lock_file, indent=4, sort_keys=True) shutil.move(tmp_file, LOCK_PATH) -def main() -> None: - if LOCK_PATH.exists(): - with open(LOCK_PATH) as f: - lock_manifest = json.load(f) - else: - lock_manifest = dict(repos={}) - - with open(MANIFEST_PATH) as f: - manifest = json.load(f) - - repos = [] - - for name, repo in manifest["repos"].items(): - url = urlparse(repo["url"]) - repo_json = lock_manifest["repos"].get(name, None) - spec = RepoSpec(name, url, repo.get("file", "default.nix"), - repo.get("submodules", False), repo.get("type", None)) - if repo_json and repo_json["url"] != url.geturl(): - repo_json = None - locked_repo = None - if repo_json is not None: - locked_repo = Repo(spec, repo_json["rev"], repo_json["sha256"]) +def update_command(args: Namespace) -> None: + manifest = load_manifest(MANIFEST_PATH, LOCK_PATH) + for repo in manifest.repos: try: - repos.append(update(spec, locked_repo)) - except Exception as e: - if locked_repo is None: + update(repo) + except Exception: + if repo.locked_version is None: # likely a repository added in a pull request, make it fatal then raise - logger.exception(f"Failed to updated repository {spec.name}") - repos.append(locked_repo) + logger.exception(f"Failed to updated repository {repo.name}") - update_lock_file(repos) - - -if __name__ == "__main__": - main() + update_lock_file(manifest.repos)