From ab661acc4698fb343e1a218d282f96c60d3b14a8 Mon Sep 17 00:00:00 2001 From: Alex Galvin Date: Tue, 17 Dec 2024 02:06:21 -0500 Subject: [PATCH] feat: add `--subpackage` option to update multiple derivations --- README.md | 51 ++++++++++++++++++++++++++++ nix_update/__init__.py | 8 +++++ nix_update/options.py | 1 + nix_update/update.py | 13 +++++++ tests/test_subpackage.py | 42 +++++++++++++++++++++++ tests/testpkgs/default.nix | 1 + tests/testpkgs/subpackage.nix | 64 +++++++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+) create mode 100644 tests/test_subpackage.py create mode 100644 tests/testpkgs/subpackage.nix diff --git a/README.md b/README.md index a92ff7b..7d7d955 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,57 @@ Arguments can be passed to `nix-shell maintainers/scripts/update.nix` like so $ nix-update sbt --use-update-script --update-script-args "--argstr skip-prompt true" ``` +## Subpackages + +Some packages consist of multiple fixed-output derivations derived from the same upstream source. +For example, a Go project with Go module dependencies might also include a JavaScript project with npm dependencies. + +To support such use cases, `nix-update` allows specifying subpackages directly in the command line. +Consider a package accessible via the `some-package` attribute, which also provides a second fixed-output derivation as a subpackage named `web-ui`: + +```nix +{ + buildGoModule, + fetchFromGitHub, + buildNpmPackage, +}: + +let + pname = "some-package"; + version = "1.53.0"; + src = fetchFromGitHub { + owner = "owner"; + repo = "repo"; + rev = "v${version}"; + hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + + web-ui = buildNpmPackage rec { + ... + npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; +in +buildGoModule rec { + inherit + web-ui + pname + version + src; + + vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + + preBuild = '' + cp -r ${web-ui}/* web/dist + ''; +} +``` + +You can update the package and its subpackage using `nix-update` as follows: + +``` +nix-update --sub-packages web-ui some-package # sub-package can be repeated +``` + ## Development setup First clone the repo to your preferred location (in the following, we assume diff --git a/nix_update/__init__.py b/nix_update/__init__.py index 4c23979..2cae286 100644 --- a/nix_update/__init__.py +++ b/nix_update/__init__.py @@ -107,6 +107,13 @@ def parse_args(args: list[str]) -> Options: help="Path to the directory containing the metadata (e.g. Cargo.toml) referenced by the lockfile", default=".", ) + parser.add_argument( + "-s", + "--subpackage", + action="append", + help="Attribute for a subpackage that nix-update should try to get hashes for", + default=None, + ) a = parser.parse_args(args) return Options( @@ -116,6 +123,7 @@ def parse_args(args: list[str]) -> Options: commit=a.commit, use_update_script=a.use_update_script, update_script_args=a.update_script_args, + subpackages=a.subpackage, url=a.url, write_commit_message=a.write_commit_message, run=a.run, diff --git a/nix_update/options.py b/nix_update/options.py index 5bd34f2..2d07b77 100644 --- a/nix_update/options.py +++ b/nix_update/options.py @@ -13,6 +13,7 @@ class Options: version_preference: VersionPreference = VersionPreference.STABLE version_regex: str = "(.*)" import_path: str = os.getcwd() + subpackages: list[str] | None = None override_filename: str | None = None url: str | None = None commit: bool = False diff --git a/nix_update/update.py b/nix_update/update.py index 8f55f08..b4e6e8d 100644 --- a/nix_update/update.py +++ b/nix_update/update.py @@ -9,6 +9,7 @@ import tomllib from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager +from copy import deepcopy from os import path from pathlib import Path @@ -505,6 +506,18 @@ def update(opts: Options) -> Package: if package.hash and update_hash: update_src_hash(opts, package.filename, package.hash) + if opts.subpackages: + for subpackage in opts.subpackages: + info(f"Updating subpackage {subpackage}") + subpackage_opts = deepcopy(opts) + subpackage_opts.attribute += f".{subpackage}" + # Update escaped package attribute + subpackage_opts.__post_init__() + subpackage_opts.subpackages = None + # Do not update the version number since that's already been done + subpackage_opts.version_preference = VersionPreference.SKIP + update(subpackage_opts) + # if no package.hash was provided we just update the other hashes unconditionally if update_hash or not package.hash: if package.go_modules: diff --git a/tests/test_subpackage.py b/tests/test_subpackage.py new file mode 100644 index 0000000..9fefa38 --- /dev/null +++ b/tests/test_subpackage.py @@ -0,0 +1,42 @@ +import subprocess + +import conftest + +from nix_update.options import Options +from nix_update.update import update + + +def test_update(helpers: conftest.Helpers) -> None: + with helpers.testpkgs() as path: + opts = Options( + attribute="subpackage", subpackages=["autobrr-web"], import_path=str(path) + ) + update(opts) + + def get_attr(attr: str) -> str: + return subprocess.run( + [ + "nix", + "eval", + "--raw", + "--extra-experimental-features", + "nix-command", + "-f", + path, + attr, + ], + text=True, + stdout=subprocess.PIPE, + ).stdout.strip() + + subpackage_hash = get_attr("subpackage.autobrr-web.pnpmDeps.outputHash") + assert subpackage_hash != "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + + src_hash = get_attr("subpackage.src.outputHash") + assert src_hash != "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + + gomodules_hash = get_attr("subpackage.goModules.outputHash") + assert gomodules_hash != "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + + version = get_attr("subpackage.version") + assert tuple(map(int, version.split("."))) >= (1, 53, 0) diff --git a/tests/testpkgs/default.nix b/tests/testpkgs/default.nix index a1cf273..1a798f6 100644 --- a/tests/testpkgs/default.nix +++ b/tests/testpkgs/default.nix @@ -33,4 +33,5 @@ mix = pkgs.callPackage ./mix.nix { }; set = pkgs.callPackage ./set.nix { }; let-bound-version = pkgs.callPackage ./let-bound-version.nix { }; + subpackage = pkgs.callPackage ./subpackage.nix { }; } diff --git a/tests/testpkgs/subpackage.nix b/tests/testpkgs/subpackage.nix new file mode 100644 index 0000000..774981c --- /dev/null +++ b/tests/testpkgs/subpackage.nix @@ -0,0 +1,64 @@ +{ + buildGoModule, + fetchFromGitHub, + stdenvNoCC, + nodejs, + pnpm_9, + typescript, +}: + +let + pname = "autobrr"; + version = "1.53.0"; + src = fetchFromGitHub { + owner = "autobrr"; + repo = "autobrr"; + rev = "v${version}"; + hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + + autobrr-web = stdenvNoCC.mkDerivation { + pname = "${pname}-web"; + inherit src version; + + nativeBuildInputs = [ + nodejs + pnpm_9.configHook + typescript + ]; + + sourceRoot = "${src.name}/web"; + + pnpmDeps = pnpm_9.fetchDeps { + inherit (autobrr-web) + pname + version + src + sourceRoot + ; + hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + + postBuild = '' + pnpm run build + ''; + + installPhase = '' + cp -r dist $out + ''; + }; +in +buildGoModule rec { + inherit + autobrr-web + pname + version + src + ; + + vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + + preBuild = '' + cp -r ${autobrr-web}/* web/dist + ''; +}