diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index bf8a27419ae21..5e5c3bc3ea2c9 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -72,6 +72,7 @@ let nixos-rebuild-ng = pkgs.nixos-rebuild-ng.override { nix = config.nix.package; withNgSuffix = false; + withNom = config.system.rebuild.enableNom; withReexec = true; }; @@ -287,6 +288,14 @@ in ''; }; + options.system.rebuild.enableNom = lib.mkEnableOption "" // { + default = false; + description = '' + Whether to use ‘nix-output-monitor’ in place of ‘nix’ when rebuilding. + This produces a more aesthetically pleasing terminal experience. + ''; + }; + imports = let mkToolModule = diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 3af4002a6aa2e..7f8fb7f1c2b7f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1059,6 +1059,7 @@ in nixos-rebuild-specialisations-ng = runTestOn [ "x86_64-linux" ] { imports = [ ./nixos-rebuild-specialisations.nix ]; _module.args.withNg = true; + _module.args.withNom = true; }; nixos-rebuild-target-host = runTest { imports = [ ./nixos-rebuild-target-host.nix ]; diff --git a/pkgs/by-name/ni/nix-output-monitor/package.nix b/pkgs/by-name/ni/nix-output-monitor/package.nix index c50be7ede2a14..7eec76506f379 100644 --- a/pkgs/by-name/ni/nix-output-monitor/package.nix +++ b/pkgs/by-name/ni/nix-output-monitor/package.nix @@ -3,10 +3,24 @@ haskellPackages, installShellFiles, lib, + nix, + replaceVars, + + # Allow pinning a specific Nix version. + withPinnedNix ? false, }: let inherit (haskell.lib.compose) justStaticExecutables overrideCabal; + # If we're pinning Nix, then substitute with a Nix path; otherwise, just the name of the binary. + ambientOrPinned = name: if withPinnedNix then lib.getExe' nix name else name; + + pinnedNixPatch = replaceVars ./pin-a-specific-nix.patch { + nix = ambientOrPinned "nix"; + nix-build = ambientOrPinned "nix-build"; + nix-shell = ambientOrPinned "nix-shell"; + }; + overrides = { passthru.updateScript = ./update.sh; @@ -15,6 +29,9 @@ let testTargets = [ "unit-tests" ]; buildTools = [ installShellFiles ]; + + patches = [ pinnedNixPatch ]; + postInstall = '' ln -s nom "$out/bin/nom-build" ln -s nom "$out/bin/nom-shell" @@ -22,6 +39,7 @@ let installShellCompletion completions/* ''; }; + raw-pkg = haskellPackages.callPackage ./generated-package.nix { }; in lib.pipe raw-pkg [ diff --git a/pkgs/by-name/ni/nix-output-monitor/pin-a-specific-nix.patch b/pkgs/by-name/ni/nix-output-monitor/pin-a-specific-nix.patch new file mode 100644 index 0000000000000..99be3611ce663 --- /dev/null +++ b/pkgs/by-name/ni/nix-output-monitor/pin-a-specific-nix.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Philip Taron +Date: Thu, 23 Oct 2025 10:32:59 -0700 +Subject: [PATCH] exe: allow pinning a specific Nix version + +--- + exe/Main.hs | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +diff --git a/exe/Main.hs b/exe/Main.hs +index 5e0f010..01b9e06 100644 +--- a/exe/Main.hs ++++ b/exe/Main.hs +@@ -72,18 +72,18 @@ runApp :: String -> [String] -> IO Void + runApp = \cases + _ ["--version"] -> do + hPutStrLn stderr ("nix-output-monitor " <> fromString (showVersion version)) +- exitWith =<< runProcess (proc "nix" ["--version"]) +- "nom-build" args -> exitWith =<< runMonitoredCommand defaultConfig (proc "nix-build" (withJSON args)) ++ exitWith =<< runProcess (proc "@nix@" ["--version"]) ++ "nom-build" args -> exitWith =<< runMonitoredCommand defaultConfig (proc "@nix-build@" (withJSON args)) + "nom-shell" args -> do +- exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "nix-shell" (withJSON args <> ["--run", "exit"])) +- exitWith =<< runProcess (proc "nix-shell" args) +- "nom" ("build" : args) -> exitWith =<< runMonitoredCommand defaultConfig (proc "nix" ("build" : withJSON args)) ++ exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "@nix-shell@" (withJSON args <> ["--run", "exit"])) ++ exitWith =<< runProcess (proc "@nix-shell@" args) ++ "nom" ("build" : args) -> exitWith =<< runMonitoredCommand defaultConfig (proc "@nix@" ("build" : withJSON args)) + "nom" ("shell" : args) -> do +- exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "nix" ("shell" : withJSON (replaceCommandWithExit args))) +- exitWith =<< runProcess (proc "nix" ("shell" : args)) ++ exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "@nix@" ("shell" : withJSON (replaceCommandWithExit args))) ++ exitWith =<< runProcess (proc "@nix@" ("shell" : args)) + "nom" ("develop" : args) -> do +- exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "nix" ("develop" : withJSON (replaceCommandWithExit args))) +- exitWith =<< runProcess (proc "nix" ("develop" : args)) ++ exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "@nix@" ("develop" : withJSON (replaceCommandWithExit args))) ++ exitWith =<< runProcess (proc "@nix@" ("develop" : args)) + "nom" [] -> do + finalState <- monitorHandle @OldStyleInput defaultConfig{piping = True} stdin + if CMap.size finalState.fullSummary.failedBuilds + length finalState.nixErrors == 0 +@@ -108,7 +108,7 @@ printNixCompletion = \cases + exitSuccess + "nom" args@(sub_cmd : _) + | sub_cmd `elem` knownSubCommands -> +- exitWith =<< Process.runProcess (Process.proc "nix" args) ++ exitWith =<< Process.runProcess (Process.proc "@nix@" args) + prog args -> do + putTextLn $ "No completion support for " <> unwords (toText <$> prog : args) + exitFailure +@@ -170,7 +170,7 @@ monitorHandle config input_handle = withParser @a \streamParser -> do + Terminal.hHideCursor outputHandle + hSetBuffering stdout (BlockBuffering (Just 1_000_000)) + +- current_system <- Exception.handle ((Nothing <$) . printIOException) $ Just . decodeUtf8 <$> Process.readProcessStdout_ (Process.proc "nix" ["eval", "--extra-experimental-features", "nix-command", "--impure", "--raw", "--expr", "builtins.currentSystem"]) ++ current_system <- Exception.handle ((Nothing <$) . printIOException) $ Just . decodeUtf8 <$> Process.readProcessStdout_ (Process.proc "@nix@" ["eval", "--extra-experimental-features", "nix-command", "--impure", "--raw", "--expr", "builtins.currentSystem"]) + first_state <- initalStateFromBuildPlatform current_system + -- We enforce here, that the state type is completely strict so that we don‘t accumulate thunks while running the program. + let first_process_state = MkProcessState (StrictType.Strict $ firstState @a first_state) (stateToText config first_state) +-- +2.51.0 + diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/0001-replacements.patch b/pkgs/by-name/ni/nixos-rebuild-ng/0001-replacements.patch new file mode 100644 index 0000000000000..41f12ae69caa6 --- /dev/null +++ b/pkgs/by-name/ni/nixos-rebuild-ng/0001-replacements.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Philip Taron +Date: Wed, 22 Oct 2025 17:02:15 -0700 +Subject: [PATCH] nixos-rebuild-ng: patch out actual replacements + +Produced with `git format-patch -1 --zero-commit --stdout --relative=pkgs/by-name/ni/nixos-rebuild-ng/src HEAD^1` +--- + nixos_rebuild/constants.py | 18 +++++++++--------- + pyproject.toml | 6 +++--- + 2 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/nixos_rebuild/constants.py b/nixos_rebuild/constants.py +index f79922ea5c87..ee4fc0c49373 100644 +--- a/nixos_rebuild/constants.py ++++ b/nixos_rebuild/constants.py +@@ -2,15 +2,15 @@ from typing import Final + + # The name of this executable, for purposes of replacing `nixos-rebuild`. + # The derivation replaces this using a patch file. +-EXECUTABLE: Final[str] = "nixos-rebuild-ng" ++EXECUTABLE: Final[str] = "@executable@" + + # These names are replaced with absolute paths to Nix in the store in the derivation. + # Some of these names could be either `nix` or `nom`, and are called out as such. +-NIX: Final[str] = "nix" +-NIX_OR_NOM: Final[str] = "nix" +-NIX_BUILD: Final[str] = "nix-build" +-NIX_CHANNEL: Final[str] = "nix-channel" +-NIX_COPY_CLOSURE: Final[str] = "nix-copy-closure" +-NIX_ENV: Final[str] = "nix-env" +-NIX_INSTANTIATE: Final[str] = "nix-instantiate" +-NIX_STORE: Final[str] = "nix-store" ++NIX: Final[str] = "@nix@" ++NIX_OR_NOM: Final[str] = "@nix-or-nom@" ++NIX_BUILD: Final[str] = "@nix-build@" ++NIX_CHANNEL: Final[str] = "@nix-channel@" ++NIX_COPY_CLOSURE: Final[str] = "@nix-copy-closure@" ++NIX_ENV: Final[str] = "@nix-env@" ++NIX_INSTANTIATE: Final[str] = "@nix-instantiate@" ++NIX_STORE: Final[str] = "@nix-store@" +diff --git a/pyproject.toml b/pyproject.toml +index 757067db9f06..68dfa825a824 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -3,11 +3,11 @@ requires = ["setuptools"] + build-backend = "setuptools.build_meta" + + [project] +-name = "nixos-rebuild" +-version = "0.0.0" ++name = "@executable@" ++version = "@version@" + + [project.scripts] +-nixos-rebuild = "nixos_rebuild:main" ++@executable@ = "nixos_rebuild:main" + + [tool.setuptools.package-data] + nixos_rebuild = ["*.nix.template"] +-- +2.51.0 + diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/0002-help-runs-man.patch b/pkgs/by-name/ni/nixos-rebuild-ng/0002-help-runs-man.patch new file mode 100644 index 0000000000000..f697797c605d0 --- /dev/null +++ b/pkgs/by-name/ni/nixos-rebuild-ng/0002-help-runs-man.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Philip Taron +Date: Thu, 23 Oct 2025 05:46:07 -0700 +Subject: [PATCH] nixos-rebuild-ng: patch out print_help to use the man page + +Produced with `git format-patch -1 --zero-commit --stdout --relative=pkgs/by-name/ni/nixos-rebuild-ng/src` +--- + nixos_rebuild/help.py | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/nixos_rebuild/help.py b/nixos_rebuild/help.py +index 7437550a37b8..dcb730e09ced 100644 +--- a/nixos_rebuild/help.py ++++ b/nixos_rebuild/help.py +@@ -1,7 +1,10 @@ + import argparse ++from subprocess import run + from typing import NoReturn + ++from .constants import EXECUTABLE ++ + + def print_help(parser: argparse.ArgumentParser) -> NoReturn: +- parser.print_help() +- parser.exit() ++ r = run(["man", "8", EXECUTABLE], check=False) ++ parser.exit(r.returncode) +-- +2.51.0 + diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/package.nix b/pkgs/by-name/ni/nixos-rebuild-ng/package.nix index 33f52e0e1c68c..8db38e7326490 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/package.nix +++ b/pkgs/by-name/ni/nixos-rebuild-ng/package.nix @@ -1,162 +1,44 @@ { lib, stdenv, - callPackage, + makeBinaryWrapper, + python3Packages, + + # These are required to be passed through. installShellFiles, + lixPackageSets, mkShell, nix, - python3, - python3Packages, - runCommand, + nix-output-monitor, + nixVersions, + nixos-rebuild-ng, + nixosTests, scdoc, + withNgSuffix ? true, + withNom ? false, withReexec ? false, withShellFiles ? true, # Very long tmp dirs lead to "too long for Unix domain socket" # SSH ControlPath errors. Especially macOS sets long TMPDIR paths. withTmpdir ? if stdenv.hostPlatform.isDarwin then "/tmp" else null, - # passthru.tests - nixosTests, - nixVersions, - lixPackageSets, - nixos-rebuild-ng, }: -let - executable = if withNgSuffix then "nixos-rebuild-ng" else "nixos-rebuild"; -in -python3Packages.buildPythonApplication rec { - pname = "nixos-rebuild-ng"; - version = lib.trivial.release; - src = ./src; - pyproject = true; - - build-system = with python3Packages; [ - setuptools - ]; - nativeBuildInputs = lib.optionals withShellFiles [ +python3Packages.callPackage ./python.nix { + inherit installShellFiles - python3Packages.shtab + lixPackageSets + mkShell + nix + nix-output-monitor + nixVersions + nixos-rebuild-ng + nixosTests scdoc - ]; - - propagatedBuildInputs = [ - # Make sure that we use the Nix package we depend on, not something - # else from the PATH for nix-{env,instantiate,build}. This is - # important, because NixOS defaults the architecture of the rebuilt - # system to the architecture of the nix-* binaries used. So if on an - # amd64 system the user has an i686 Nix package in her PATH, then we - # would silently downgrade the whole system to be i686 NixOS on the - # next reboot. - # The binary will be included in the wrapper for Python. - (lib.getBin nix) - ]; - - postPatch = '' - substituteInPlace nixos_rebuild/constants.py \ - --subst-var-by executable ${executable} \ - --subst-var-by withReexec ${lib.boolToString withReexec} \ - --subst-var-by withShellFiles ${lib.boolToString withShellFiles} - - substituteInPlace pyproject.toml \ - --replace-fail nixos-rebuild ${executable} - ''; - - postInstall = lib.optionalString withShellFiles '' - scdoc < ${./nixos-rebuild.8.scd} > ${executable}.8 - installManPage ${executable}.8 - - installShellCompletion --cmd ${executable} \ - --bash <(shtab --shell bash nixos_rebuild.get_main_parser) \ - --zsh <(shtab --shell zsh nixos_rebuild.get_main_parser) - ''; - - nativeCheckInputs = with python3Packages; [ - pytestCheckHook - ]; - - pytestFlags = [ "-vv" ]; - - makeWrapperArgs = lib.optionals (withTmpdir != null) [ - "--set TMPDIR ${withTmpdir}" - ]; - - passthru = - let - python-with-pkgs = python3.withPackages ( - ps: with ps; [ - mypy - pytest - # this is to help development (e.g.: better diffs) inside devShell - # only, do not use its helpers like `mocker` - pytest-mock - ruff - ] - ); - in - { - devShell = mkShell { - packages = [ python-with-pkgs ]; - shellHook = '' - cd pkgs/by-name/ni/nixos-rebuild-ng/src || true - ''; - }; - - tests = { - with_reexec = nixos-rebuild-ng.override { - withReexec = true; - withNgSuffix = false; - }; - with_nix_latest = nixos-rebuild-ng.override { - nix = nixVersions.latest; - }; - with_nix_stable = nixos-rebuild-ng.override { - nix = nixVersions.stable; - }; - with_nix_2_28 = nixos-rebuild-ng.override { - # oldest supported version in nixpkgs - nix = nixVersions.nix_2_28; - }; - with_lix_latest = nixos-rebuild-ng.override { - nix = lixPackageSets.latest.lix; - }; - with_lix_stable = nixos-rebuild-ng.override { - nix = lixPackageSets.stable.lix; - }; - - inherit (nixosTests) - # FIXME: this test is disabled since it times out in @ofborg - # nixos-rebuild-install-bootloader-ng - nixos-rebuild-specialisations-ng - nixos-rebuild-target-host-ng - ; - repl = callPackage ./tests/repl.nix { }; - # NOTE: this is a passthru test rather than a build-time test because we - # want to keep the build closures small - linters = runCommand "${pname}-linters" { nativeBuildInputs = [ python-with-pkgs ]; } '' - export MYPY_CACHE_DIR="$(mktemp -d)" - export RUFF_CACHE_DIR="$(mktemp -d)" - - pushd ${src} - echo -e "\x1b[32m## run mypy\x1b[0m" - mypy . - echo -e "\x1b[32m## run ruff\x1b[0m" - ruff check . - echo -e "\x1b[32m## run ruff format\x1b[0m" - ruff format --check . - popd - - touch $out - ''; - }; - }; - - meta = { - description = "Rebuild your NixOS configuration and switch to it, on local hosts and remote"; - homepage = "https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/ni/nixos-rebuild-ng"; - license = lib.licenses.mit; - maintainers = [ ]; - teams = [ lib.teams.nixos-rebuild ]; - mainProgram = executable; - }; + withNgSuffix + withNom + withReexec + withShellFiles + withTmpdir + ; } diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/python.nix b/pkgs/by-name/ni/nixos-rebuild-ng/python.nix new file mode 100644 index 0000000000000..152a0b5105267 --- /dev/null +++ b/pkgs/by-name/ni/nixos-rebuild-ng/python.nix @@ -0,0 +1,174 @@ +{ + lib, + + buildPythonApplication, + pkgs, + pytestCheckHook, + python, + setuptools, + shtab, + + # Passed through from `./package.nix`. + installShellFiles, + lixPackageSets, + mkShell, + nix, + nix-output-monitor, + nixVersions, + nixos-rebuild-ng, + nixosTests, + replaceVars, + scdoc, + + # Override interface, required to be passed in from `./package.nix`. + withNgSuffix, + withNom, + withReexec, + withShellFiles, + withTmpdir, +}: +let + executable = if withNgSuffix then "nixos-rebuild-ng" else "nixos-rebuild"; + version = lib.trivial.release; + + # If we're using `nix-output-monitor`, make sure it's pinned to the right version of Nix. + nix-output-monitor-pinned = nix-output-monitor.override { + withPinnedNix = true; + inherit nix; + }; + + maybeNom = if withNom then nix-output-monitor-pinned else nix; + nix-or-nom = if withNom then "nom" else "nix"; +in +buildPythonApplication { + pname = "nixos-rebuild-ng"; + inherit version; + + src = ./src; + + pyproject = true; + + build-system = [ setuptools ]; + + nativeBuildInputs = lib.optionals withShellFiles [ + installShellFiles + shtab + scdoc + ]; + + patches = [ + (replaceVars ./0001-replacements.patch { + inherit executable version; + + # Make sure that we use the Nix package we depend on, not the ambient Nix in the PATH. + # If we've requested to use `nom`, use that for `nix` and `nix-build` commands. + # + # This is important, because NixOS defaults the architecture of the rebuilt system to the + # architecture of the nix-* binaries used. So if on an amd64 system the user has an i686 Nix + # package in her PATH, then we would silently downgrade the whole system to be i686 NixOS on + # the next reboot. + nix = lib.getExe' nix "nix"; + nix-or-nom = lib.getExe' maybeNom "${nix-or-nom}"; + nix-build = lib.getExe' maybeNom "${nix-or-nom}-build"; + nix-channel = lib.getExe' nix "nix-channel"; + nix-copy-closure = lib.getExe' nix "nix-copy-closure"; + nix-env = lib.getExe' nix "nix-env"; + nix-instantiate = lib.getExe' nix "nix-instantiate"; + nix-store = lib.getExe' nix "nix-store"; + }) + ] + ++ lib.optional withShellFiles ./0002-help-runs-man.patch; + + postInstall = lib.optionalString withShellFiles '' + scdoc < ${./nixos-rebuild.8.scd} > ${executable}.8 + installManPage ${executable}.8 + + installShellCompletion --cmd ${executable} \ + --bash <(shtab --shell bash nixos_rebuild.get_main_parser) \ + --zsh <(shtab --shell zsh nixos_rebuild.get_main_parser) + ''; + + nativeCheckInputs = [ pytestCheckHook ]; + + pytestFlags = [ "-vv" ]; + + makeWrapperArgs = + lib.optional (!withReexec) "--set NIXOS_REBUILD_REEXEC_ENV 1" + ++ lib.optional (withTmpdir != null) "--set TMPDIR ${withTmpdir}"; + + passthru = + let + python-with-pkgs = python.withPackages ( + ps: with ps; [ + mypy + pytest + # this is to help development (e.g.: better diffs) inside devShell + # only, do not use its helpers like `mocker` + pytest-mock + ruff + ] + ); + in + { + devShell = mkShell { + packages = [ python-with-pkgs ]; + shellHook = '' + cd pkgs/by-name/ni/nixos-rebuild-ng/src || true + ''; + }; + + tests = { + with_nom = nixos-rebuild-ng.override { + withNom = true; + }; + + with_reexec = nixos-rebuild-ng.override { + withReexec = true; + withNgSuffix = false; + }; + + with_nix_latest = nixos-rebuild-ng.override { + nix = nixVersions.latest; + }; + + with_nix_stable = nixos-rebuild-ng.override { + nix = nixVersions.stable; + }; + + with_nix_2_28 = nixos-rebuild-ng.override { + # oldest supported version in nixpkgs + nix = nixVersions.nix_2_28; + }; + + with_lix_latest = nixos-rebuild-ng.override { + nix = lixPackageSets.latest.lix; + }; + + with_lix_stable = nixos-rebuild-ng.override { + nix = lixPackageSets.stable.lix; + }; + + inherit (nixosTests) + # FIXME: this test is disabled since it times out in @ofborg + # nixos-rebuild-install-bootloader-ng + nixos-rebuild-specialisations-ng + nixos-rebuild-target-host-ng + ; + + repl = pkgs.callPackage ./tests/repl.nix { }; + + # NOTE: this is a passthru test rather than a build-time test because we + # want to keep the build closures small + linters = pkgs.callPackage ./tests/linters.nix { }; + }; + }; + + meta = { + description = "Rebuild your NixOS configuration and switch to it, on local hosts and remote"; + homepage = "https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/ni/nixos-rebuild-ng"; + license = lib.licenses.mit; + maintainers = [ ]; + teams = [ lib.teams.nixos-rebuild ]; + mainProgram = executable; + }; +} diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py index 6d7ad01baebdc..b5fa1cb2170a4 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py @@ -1,11 +1,11 @@ import argparse import logging import sys -from subprocess import CalledProcessError, run +from subprocess import CalledProcessError from typing import Final, assert_never from . import nix, services -from .constants import EXECUTABLE, WITH_REEXEC, WITH_SHELL_FILES +from .help import print_help from .models import Action, BuildAttr, Flake, Profile from .process import Remote from .utils import LogFormatter @@ -204,12 +204,7 @@ def parse_args( } if args.help or args.action is None: - if WITH_SHELL_FILES: - r = run(["man", "8", EXECUTABLE], check=False) - parser.exit(r.returncode) - else: - parser.print_help() - parser.exit() + print_help(parser) def parser_warn(msg: str) -> None: print(f"{parser.prog}: warning: {msg}", file=sys.stderr) @@ -280,16 +275,15 @@ def execute(argv: list[str]) -> None: nix.upgrade_channels(args.upgrade_all, args.sudo) action = Action(args.action) - # Only run shell scripts from the Nixpkgs tree if the action is - # "switch", "boot", or "test". With other actions (such as "build"), - # the user may reasonably expect that no code from the Nixpkgs tree is - # executed, so it's safe to run nixos-rebuild against a potentially - # untrusted tree. + + # Only run shell scripts from the Nixpkgs tree if the action is "switch", "boot", or "test". + # With other actions (such as "build"), the user may reasonably expect that no code from the + # Nixpkgs tree is executed, so it's safe to run nixos-rebuild against a potentially untrusted + # tree. can_run = action in (Action.SWITCH, Action.BOOT, Action.TEST) - # Re-exec to a newer version of the script before building to ensure we get - # the latest fixes - if WITH_REEXEC and can_run and not args.no_reexec: + # Re-exec to a newer version of the script before building to ensure we get the latest fixes. + if can_run and not args.no_reexec: services.reexec(argv, args, build_flags, flake_build_flags) profile = Profile.from_arg(args.profile_name) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/constants.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/constants.py index 03d4bb79eb618..f79922ea5c876 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/constants.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/constants.py @@ -1,11 +1,16 @@ -# mypy: disable-error-code=comparison-overlap from typing import Final -# Build-time flags -# Use strings to avoid breaking standalone (e.g.: `python -m nixos_rebuild`) -# usage -EXECUTABLE: Final[str] = "@executable@" -# Use either `== "true"` if the default (e.g.: `python -m nixos_rebuild`) is -# `False` or `!= "false"` if the default is `True` -WITH_REEXEC: Final[bool] = "@withReexec@" == "true" -WITH_SHELL_FILES: Final[bool] = "@withShellFiles@" == "true" +# The name of this executable, for purposes of replacing `nixos-rebuild`. +# The derivation replaces this using a patch file. +EXECUTABLE: Final[str] = "nixos-rebuild-ng" + +# These names are replaced with absolute paths to Nix in the store in the derivation. +# Some of these names could be either `nix` or `nom`, and are called out as such. +NIX: Final[str] = "nix" +NIX_OR_NOM: Final[str] = "nix" +NIX_BUILD: Final[str] = "nix-build" +NIX_CHANNEL: Final[str] = "nix-channel" +NIX_COPY_CLOSURE: Final[str] = "nix-copy-closure" +NIX_ENV: Final[str] = "nix-env" +NIX_INSTANTIATE: Final[str] = "nix-instantiate" +NIX_STORE: Final[str] = "nix-store" diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/help.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/help.py new file mode 100644 index 0000000000000..7437550a37b8c --- /dev/null +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/help.py @@ -0,0 +1,7 @@ +import argparse +from typing import NoReturn + + +def print_help(parser: argparse.ArgumentParser) -> NoReturn: + parser.print_help() + parser.exit() diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py index 9a7062c816ebb..b3972f4b58245 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py @@ -13,6 +13,16 @@ from typing import Final, Literal from . import tmpdir +from .constants import ( + NIX, + NIX_BUILD, + NIX_CHANNEL, + NIX_COPY_CLOSURE, + NIX_ENV, + NIX_INSTANTIATE, + NIX_OR_NOM, + NIX_STORE, +) from .models import ( Action, BuildAttr, @@ -57,7 +67,7 @@ def build( Returns the built attribute as path. """ run_args = [ - "nix-build", + NIX_BUILD, build_attr.path, "--attr", build_attr.to_attr(attr), @@ -77,9 +87,9 @@ def build_flake( Returns the built attribute as path. """ run_args = [ - "nix", - *FLAKE_FLAGS, + NIX_OR_NOM, "build", + *FLAKE_FLAGS, "--print-out-paths", flake.to_attr(attr), *dict_to_flags(flake_build_flags), @@ -101,7 +111,7 @@ def build_remote( # > by the garbage collector r = run_wrapper( [ - "nix-instantiate", + NIX_INSTANTIATE, build_attr.path, "--attr", build_attr.to_attr(attr), @@ -122,7 +132,7 @@ def build_remote( try: r = run_wrapper( [ - "nix-store", + NIX_STORE, "--realise", drv, "--add-root", @@ -152,7 +162,7 @@ def build_remote_flake( ) -> Path: r = run_wrapper( [ - "nix", + NIX, *FLAKE_FLAGS, "eval", "--raw", @@ -165,9 +175,9 @@ def build_remote_flake( copy_closure(drv, to_host=build_host, from_host=None, copy_flags=copy_flags) r = run_wrapper( [ - "nix", - *FLAKE_FLAGS, + NIX_OR_NOM, "build", + *FLAKE_FLAGS, f"{drv}^*", "--print-out-paths", *dict_to_flags(flake_build_flags), @@ -196,7 +206,7 @@ def copy_closure( def nix_copy_closure(host: Remote, to: bool) -> None: run_wrapper( [ - "nix-copy-closure", + NIX_COPY_CLOSURE, *dict_to_flags(copy_flags), "--to" if to else "--from", host.host, @@ -208,7 +218,7 @@ def nix_copy_closure(host: Remote, to: bool) -> None: def nix_copy(to_host: Remote, from_host: Remote) -> None: run_wrapper( [ - "nix", + NIX, *FLAKE_FLAGS, "copy", *dict_to_flags(copy_flags), @@ -248,7 +258,7 @@ def edit_flake(flake: Flake | None, flake_flags: Args | None = None) -> None: "Try to find and open NixOS configuration file in editor for Flake config." run_wrapper( [ - "nix", + NIX, *FLAKE_FLAGS, "edit", *dict_to_flags(flake_flags), @@ -262,7 +272,7 @@ def edit_flake(flake: Flake | None, flake_flags: Args | None = None) -> None: def find_file(file: str, nix_flags: Args | None = None) -> Path | None: "Find classic Nix file location." r = run_wrapper( - ["nix-instantiate", "--find-file", file, *dict_to_flags(nix_flags)], + [NIX_INSTANTIATE, "--find-file", file, *dict_to_flags(nix_flags)], stdout=PIPE, check=False, ) @@ -283,7 +293,7 @@ def get_build_image_name( ) r = run_wrapper( [ - "nix-instantiate", + NIX_INSTANTIATE, "--eval", "--strict", "--json", @@ -310,7 +320,7 @@ def get_build_image_name_flake( ) -> str: r = run_wrapper( [ - "nix", + NIX, "eval", "--json", flake.to_attr( @@ -335,7 +345,7 @@ def get_build_image_variants( ) r = run_wrapper( [ - "nix-instantiate", + NIX_INSTANTIATE, "--eval", "--strict", "--json", @@ -361,7 +371,7 @@ def get_build_image_variants_flake( ) -> ImageVariants: r = run_wrapper( [ - "nix", + NIX, "eval", "--json", flake.to_attr("config.system.build.images"), @@ -450,7 +460,7 @@ def get_generations_from_nix_env( # Using `nix-env --list-generations` needs root to lock the profile r = run_wrapper( - ["nix-env", "-p", profile.path, "--list-generations"], + [NIX_ENV, "-p", profile.path, "--list-generations"], stdout=PIPE, remote=target_host, sudo=sudo, @@ -534,7 +544,7 @@ def get_generation_info(generation: Generation) -> GenerationJson: def repl(build_attr: BuildAttr, nix_flags: Args | None = None) -> None: - run_args = ["nix", "repl", "--file", build_attr.path] + run_args = [NIX, "repl", "--file", build_attr.path] if build_attr.attr: run_args.append(build_attr.attr) run_wrapper([*run_args, *dict_to_flags(nix_flags)]) @@ -554,7 +564,7 @@ def repl_flake(flake: Flake, flake_flags: Args | None = None) -> None: ) run_wrapper( [ - "nix", + NIX, *FLAKE_FLAGS, "repl", "--impure", @@ -568,7 +578,7 @@ def repl_flake(flake: Flake, flake_flags: Args | None = None) -> None: def rollback(profile: Profile, target_host: Remote | None, sudo: bool) -> Path: "Rollback Nix profile, like one created by `nixos-rebuild switch`." run_wrapper( - ["nix-env", "--rollback", "-p", profile.path], + [NIX_ENV, "--rollback", "-p", profile.path], remote=target_host, sudo=sudo, ) @@ -631,7 +641,7 @@ def set_profile( raise NixOSRebuildError(msg) run_wrapper( - ["nix-env", "-p", profile.path, "--set", path_to_config], + [NIX_ENV, "-p", profile.path, "--set", path_to_config], remote=target_host, sudo=sudo, ) @@ -700,7 +710,7 @@ def upgrade_channels(all_channels: bool = False, sudo: bool = False) -> None: or (channel_path / ".update-on-nixos-rebuild").exists() ): run_wrapper( - ["nix-channel", "--update", channel_path.name], + [NIX_CHANNEL, "--update", channel_path.name], check=False, sudo=sudo, ) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/utils.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/utils.py index 6cdfaba456c6e..90c12261025dc 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/utils.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/utils.py @@ -90,11 +90,11 @@ def tabulate( def format_row(row: Mapping[str, Any]) -> str: s = (2 * " ").join( f"{str(row[header]).ljust(width)}" - for header, width in zip(data_headers, column_widths) + for header, width in zip(data_headers, column_widths, strict=True) ) return s.strip() - result = [format_row(dict(zip(data_headers, data_headers)))] + result = [format_row(dict(zip(data_headers, data_headers, strict=True)))] for row in data: result.append(format_row(row)) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/helpers.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/helpers.py index c7a2a29ee3dcd..f27e491a632d8 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/helpers.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/helpers.py @@ -1,5 +1,6 @@ +from collections.abc import Callable from types import ModuleType -from typing import Any, Callable +from typing import Any def get_qualified_name( diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py index cdd4e65b17e6f..487a7ea296f66 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py @@ -10,6 +10,15 @@ import pytest import nixos_rebuild as nr +from nixos_rebuild.constants import ( + NIX, + NIX_BUILD, + NIX_COPY_CLOSURE, + NIX_ENV, + NIX_INSTANTIATE, + NIX_OR_NOM, + NIX_STORE, +) from .helpers import get_qualified_name @@ -139,11 +148,11 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None: config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix-instantiate": + if args[0] == NIX_INSTANTIATE: return CompletedProcess([], 0, str(nixpkgs_path)) elif args[0] == "git" and "rev-parse" in args: return CompletedProcess([], 0, "nixpkgs-rev") - elif args[0] == "nix-build": + elif args[0] == NIX_BUILD: return CompletedProcess([], 0, str(config_path)) else: return CompletedProcess([], 0) @@ -156,7 +165,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: mock_run.assert_has_calls( [ call( - ["nix-instantiate", "--find-file", "nixpkgs", "-vvv"], + [NIX_INSTANTIATE, "--find-file", "nixpkgs", "-vvv"], stdout=PIPE, check=False, **DEFAULT_RUN_KWARGS, @@ -174,7 +183,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix-build", + NIX_BUILD, "", "--attr", "config.system.build.toplevel", @@ -187,7 +196,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix-env", + NIX_ENV, "-p", Path("/nix/var/nix/profiles/system"), "--set", @@ -229,7 +238,7 @@ def test_execute_nix_build_vm(mock_run: Mock, tmp_path: Path) -> None: config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix-build": + if args[0] == NIX_BUILD: return CompletedProcess([], 0, str(config_path)) else: return CompletedProcess([], 0) @@ -254,7 +263,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix-build", + NIX_BUILD, "", "--attr", "config.system.build.vm", @@ -278,16 +287,16 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None: config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix" and "eval" in args: + if args[0] not in (NIX, NIX_OR_NOM): + return CompletedProcess([], 0) + elif "eval" in args: return CompletedProcess( [], 0, '"nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd"', ) - elif args[0] == "nix": - return CompletedProcess([], 0, str(config_path)) else: - return CompletedProcess([], 0) + return CompletedProcess([], 0, str(config_path)) mock_run.side_effect = run_side_effect @@ -307,7 +316,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix", + NIX, "eval", "--json", '/path/to/config#nixosConfigurations."hostname".config.system.build.images', @@ -320,10 +329,10 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "nix-command flakes", - "build", "--print-out-paths", '/path/to/config#nixosConfigurations."hostname".config.system.build.images.azure', ], @@ -333,7 +342,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix", + NIX, "eval", "--json", '/path/to/config#nixosConfigurations."hostname".config.system.build.images.azure.passthru.filePath', @@ -357,7 +366,7 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None: config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix": + if args[0] in (NIX, NIX_OR_NOM): return CompletedProcess([], 0, str(config_path)) else: return CompletedProcess([], 0) @@ -386,10 +395,10 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "nix-command flakes", - "build", "--print-out-paths", '/path/to/config#nixosConfigurations."hostname".config.system.build.toplevel', "-v", @@ -405,7 +414,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: call( [ "sudo", - "nix-env", + NIX_ENV, "-p", Path("/nix/var/nix/profiles/system"), "--set", @@ -459,13 +468,13 @@ def test_execute_nix_switch_build_target_host( config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix": + if args[0] == NIX: return CompletedProcess([], 0, str(config_path)) - elif args[0] == "nix-instantiate" and "--find-file" in args: + elif args[0] == NIX_INSTANTIATE and "--find-file" in args: return CompletedProcess([], 1) - elif args[0] == "nix-instantiate": + elif args[0] == NIX_INSTANTIATE: return CompletedProcess([], 0, str(config_path)) - elif args[0] == "ssh" and "nix-store" in args: + elif args[0] == "ssh" and NIX_STORE in args: return CompletedProcess([], 0, "/tmp/tmpdir/config") elif args[0] == "ssh" and "mktemp" in args: return CompletedProcess([], 0, "/tmp/tmpdir") @@ -501,7 +510,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix-instantiate", + NIX_INSTANTIATE, "--find-file", "nixpkgs", "--include", @@ -515,7 +524,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix-instantiate", + NIX_INSTANTIATE, "", "--attr", "config.system.build.toplevel", @@ -531,7 +540,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: **DEFAULT_RUN_KWARGS, ), call( - ["nix-copy-closure", "--to", "user@build-host", config_path], + [NIX_COPY_CLOSURE, "--to", "user@build-host", config_path], check=True, **DEFAULT_RUN_KWARGS, ), @@ -556,7 +565,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: *nr.process.SSH_DEFAULT_OPTS, "user@build-host", "--", - "nix-store", + NIX_STORE, "--realise", str(config_path), "--add-root", @@ -595,7 +604,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix", + NIX, "--extra-experimental-features", "nix-command flakes", "copy", @@ -615,7 +624,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: "user@target-host", "--", "sudo", - "nix-env", + NIX_ENV, "-p", "/nix/var/nix/profiles/system", "--set", @@ -673,7 +682,7 @@ def test_execute_nix_switch_flake_target_host( config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix": + if args[0] in (NIX, NIX_OR_NOM): return CompletedProcess([], 0, str(config_path)) else: return CompletedProcess([], 0) @@ -698,10 +707,10 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "nix-command flakes", - "build", "--print-out-paths", '/path/to/config#nixosConfigurations."hostname".config.system.build.toplevel', "--no-link", @@ -711,7 +720,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: **DEFAULT_RUN_KWARGS, ), call( - ["nix-copy-closure", "--to", "user@localhost", config_path], + [NIX_COPY_CLOSURE, "--to", "user@localhost", config_path], check=True, **DEFAULT_RUN_KWARGS, ), @@ -722,7 +731,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: "user@localhost", "--", "sudo", - "nix-env", + NIX_ENV, "-p", "/nix/var/nix/profiles/system", "--set", @@ -780,9 +789,9 @@ def test_execute_nix_switch_flake_build_host( config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix" and "eval" in args: + if args[0] == NIX and "eval" in args: return CompletedProcess([], 0, str(config_path)) - elif args[0] == "ssh" and "nix" in args: + elif args[0] == "ssh" and (NIX in args or NIX_OR_NOM in args): return CompletedProcess([], 0, str(config_path)) else: return CompletedProcess([], 0) @@ -806,7 +815,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix", + NIX, "--extra-experimental-features", "nix-command flakes", "eval", @@ -818,7 +827,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: **DEFAULT_RUN_KWARGS, ), call( - ["nix-copy-closure", "--to", "user@localhost", config_path], + [NIX_COPY_CLOSURE, "--to", "user@localhost", config_path], check=True, **DEFAULT_RUN_KWARGS, ), @@ -828,10 +837,10 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: *nr.process.SSH_DEFAULT_OPTS, "user@localhost", "--", - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "'nix-command flakes'", - "build", f"'{config_path}^*'", "--print-out-paths", "--no-link", @@ -842,7 +851,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix-copy-closure", + NIX_COPY_CLOSURE, "--from", "user@localhost", config_path, @@ -852,7 +861,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix-env", + NIX_ENV, "-p", Path("/nix/var/nix/profiles/system"), "--set", @@ -885,7 +894,7 @@ def test_execute_switch_rollback(mock_run: Mock, tmp_path: Path) -> None: nixpkgs_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix-instantiate": + if args[0] == NIX_INSTANTIATE: return CompletedProcess([], 0, str(nixpkgs_path)) elif args[0] == "git": return CompletedProcess([], 0, "") @@ -911,7 +920,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: mock_run.assert_has_calls( [ call( - ["nix-instantiate", "--find-file", "nixpkgs"], + [NIX_INSTANTIATE, "--find-file", "nixpkgs"], check=False, stdout=PIPE, **DEFAULT_RUN_KWARGS, @@ -931,7 +940,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: ), call( [ - "nix-env", + NIX_ENV, "--rollback", "-p", Path("/nix/var/nix/profiles/system"), @@ -972,7 +981,7 @@ def test_execute_build(mock_run: Mock, tmp_path: Path) -> None: [ call( [ - "nix-build", + NIX_BUILD, "", "--attr", "config.system.build.toplevel", @@ -1015,7 +1024,7 @@ def test_execute_build_dry_run_build_and_target_remote( [ call( [ - "nix", + NIX, "--extra-experimental-features", "nix-command flakes", "eval", @@ -1027,7 +1036,7 @@ def test_execute_build_dry_run_build_and_target_remote( **DEFAULT_RUN_KWARGS, ), call( - ["nix-copy-closure", "--to", "user@build-host", config_path], + [NIX_COPY_CLOSURE, "--to", "user@build-host", config_path], check=True, **DEFAULT_RUN_KWARGS, ), @@ -1037,10 +1046,10 @@ def test_execute_build_dry_run_build_and_target_remote( *nr.process.SSH_DEFAULT_OPTS, "user@build-host", "--", - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "'nix-command flakes'", - "build", f"'{config_path}^*'", "--print-out-paths", "--dry-run", @@ -1059,7 +1068,7 @@ def test_execute_test_flake(mock_run: Mock, tmp_path: Path) -> None: config_path.touch() def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix": + if args[0] in (NIX, NIX_OR_NOM): return CompletedProcess([], 0, str(config_path)) elif args[0] == "test": return CompletedProcess([], 1) @@ -1077,10 +1086,10 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "nix-command flakes", - "build", "--print-out-paths", 'github:user/repo#nixosConfigurations."hostname".config.system.build.toplevel', "--no-link", @@ -1112,7 +1121,7 @@ def test_execute_test_rollback( mock_run: Mock, ) -> None: def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix-env": + if args[0] == NIX_ENV: return CompletedProcess( [], 0, @@ -1138,7 +1147,7 @@ def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: [ call( [ - "nix-env", + NIX_ENV, "-p", Path("/nix/var/nix/profiles/system-profiles/foo"), "--list-generations", diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py index 6cc525410fe48..071e253ed02c4 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py @@ -11,6 +11,16 @@ import nixos_rebuild.models as m import nixos_rebuild.nix as n import nixos_rebuild.process as p +from nixos_rebuild.constants import ( + NIX, + NIX_BUILD, + NIX_CHANNEL, + NIX_COPY_CLOSURE, + NIX_ENV, + NIX_INSTANTIATE, + NIX_OR_NOM, + NIX_STORE, +) from .helpers import get_qualified_name @@ -28,7 +38,7 @@ def test_build(mock_run: Mock) -> None: ) == Path("/path/to/file") mock_run.assert_called_with( [ - "nix-build", + NIX_BUILD, "", "--attr", "config.system.build.attr", @@ -42,7 +52,7 @@ def test_build(mock_run: Mock) -> None: "config.system.build.attr", m.BuildAttr(Path("file"), "preAttr") ) == Path("/path/to/file") mock_run.assert_called_with( - ["nix-build", Path("file"), "--attr", "preAttr.config.system.build.attr"], + [NIX_BUILD, Path("file"), "--attr", "preAttr.config.system.build.attr"], stdout=PIPE, ) @@ -63,10 +73,10 @@ def test_build_flake(mock_run: Mock, monkeypatch: MonkeyPatch, tmpdir: Path) -> ) == Path("/path/to/file") mock_run.assert_called_with( [ - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "nix-command flakes", - "build", "--print-out-paths", '/flake.nix#nixosConfigurations."hostname".config.system.build.toplevel', "--no-link", @@ -88,11 +98,11 @@ def test_build_remote( def run_wrapper_side_effect( args: list[str], **kwargs: Any ) -> CompletedProcess[str]: - if args[0] == "nix-instantiate": + if args[0] == NIX_INSTANTIATE: return CompletedProcess([], 0, stdout=" \n/path/to/file\n ") elif args[0] == "mktemp": return CompletedProcess([], 0, stdout=" \n/tmp/tmpdir\n ") - elif args[0] == "nix-store": + elif args[0] == NIX_STORE: return CompletedProcess([], 0, stdout=" \n/tmp/tmpdir/config\n ") elif args[0] == "readlink": return CompletedProcess([], 0, stdout=" \n/path/to/config\n ") @@ -115,7 +125,7 @@ def run_wrapper_side_effect( [ call( [ - "nix-instantiate", + NIX_INSTANTIATE, "", "--attr", "preAttr.config.system.build.toplevel", @@ -127,7 +137,7 @@ def run_wrapper_side_effect( ), call( [ - "nix-copy-closure", + NIX_COPY_CLOSURE, "--copy", "--to", "user@host", @@ -144,7 +154,7 @@ def run_wrapper_side_effect( ), call( [ - "nix-store", + NIX_STORE, "--realise", Path("/path/to/file"), "--add-root", @@ -189,7 +199,7 @@ def test_build_remote_flake( [ call( [ - "nix", + NIX, "--extra-experimental-features", "nix-command flakes", "eval", @@ -201,7 +211,7 @@ def test_build_remote_flake( ), call( [ - "nix-copy-closure", + NIX_COPY_CLOSURE, "--copy", "--to", "user@host", @@ -213,10 +223,10 @@ def test_build_remote_flake( ), call( [ - "nix", + NIX_OR_NOM, + "build", "--extra-experimental-features", "nix-command flakes", - "build", "/path/to/file^*", "--print-out-paths", "--build", @@ -239,7 +249,7 @@ def test_copy_closure(monkeypatch: MonkeyPatch) -> None: with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run: n.copy_closure(closure, target_host) mock_run.assert_called_with( - ["nix-copy-closure", "--to", "user@target.host", closure], + [NIX_COPY_CLOSURE, "--to", "user@target.host", closure], extra_env={"NIX_SSHOPTS": " ".join(p.SSH_DEFAULT_OPTS)}, ) @@ -247,7 +257,7 @@ def test_copy_closure(monkeypatch: MonkeyPatch) -> None: with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run: n.copy_closure(closure, None, build_host, {"copy_flag": True}) mock_run.assert_called_with( - ["nix-copy-closure", "--copy-flag", "--from", "user@build.host", closure], + [NIX_COPY_CLOSURE, "--copy-flag", "--from", "user@build.host", closure], extra_env={ "NIX_SSHOPTS": " ".join([*p.SSH_DEFAULT_OPTS, "--ssh build-opt"]) }, @@ -261,7 +271,7 @@ def test_copy_closure(monkeypatch: MonkeyPatch) -> None: n.copy_closure(closure, target_host, build_host, {"copy_flag": True}) mock_run.assert_called_with( [ - "nix", + NIX, "--extra-experimental-features", "nix-command flakes", "copy", @@ -295,7 +305,7 @@ def test_edit_flake(mock_run: Mock) -> None: n.edit_flake(flake, {"commit_lock_file": True}) mock_run.assert_called_with( [ - "nix", + NIX, "--extra-experimental-features", "nix-command flakes", "edit", @@ -329,7 +339,7 @@ def test_get_build_image_variants(mock_run: Mock, tmp_path: Path) -> None: } mock_run.assert_called_with( [ - "nix-instantiate", + NIX_INSTANTIATE, "--eval", "--strict", "--json", @@ -352,7 +362,7 @@ def test_get_build_image_variants(mock_run: Mock, tmp_path: Path) -> None: } mock_run.assert_called_with( [ - "nix-instantiate", + NIX_INSTANTIATE, "--eval", "--strict", "--json", @@ -392,7 +402,7 @@ def test_get_build_image_variants_flake(mock_run: Mock) -> None: } mock_run.assert_called_with( [ - "nix", + NIX, "eval", "--json", "/flake.nix#myAttr.config.system.build.images", @@ -495,7 +505,7 @@ def test_get_generations_from_nix_env(tmp_path: Path) -> None: m.Generation(id=2084, current=True, timestamp="2024-11-07 23:54:17"), ] mock_run.assert_called_with( - ["nix-env", "-p", path, "--list-generations"], + [NIX_ENV, "-p", path, "--list-generations"], stdout=PIPE, remote=None, sudo=False, @@ -513,7 +523,7 @@ def test_get_generations_from_nix_env(tmp_path: Path) -> None: m.Generation(id=2084, current=True, timestamp="2024-11-07 23:54:17"), ] mock_run.assert_called_with( - ["nix-env", "-p", path, "--list-generations"], + [NIX_ENV, "-p", path, "--list-generations"], stdout=PIPE, remote=remote, sudo=True, @@ -565,11 +575,11 @@ def test_list_generations(mock_get_generations: Mock, tmp_path: Path) -> None: def test_repl(mock_run: Mock) -> None: n.repl(m.BuildAttr("", None), {"nix_flag": True}) mock_run.assert_called_with( - ["nix", "repl", "--file", "", "--nix-flag"] + [NIX, "repl", "--file", "", "--nix-flag"] ) n.repl(m.BuildAttr(Path("file.nix"), "myAttr")) - mock_run.assert_called_with(["nix", "repl", "--file", Path("file.nix"), "myAttr"]) + mock_run.assert_called_with([NIX, "repl", "--file", Path("file.nix"), "myAttr"]) @patch(get_qualified_name(n.run_wrapper, n), autospec=True) @@ -589,7 +599,7 @@ def test_rollback(mock_run: Mock, tmp_path: Path) -> None: assert n.rollback(profile, None, False) == profile.path mock_run.assert_called_with( - ["nix-env", "--rollback", "-p", path], + [NIX_ENV, "--rollback", "-p", path], remote=None, sudo=False, ) @@ -597,7 +607,7 @@ def test_rollback(mock_run: Mock, tmp_path: Path) -> None: target_host = m.Remote("user@localhost", [], None) assert n.rollback(profile, target_host, True) == profile.path mock_run.assert_called_with( - ["nix-env", "--rollback", "-p", path], + [NIX_ENV, "--rollback", "-p", path], remote=target_host, sudo=True, ) @@ -624,7 +634,7 @@ def test_rollback_temporary_profile(tmp_path: Path) -> None: ) mock_run.assert_called_with( [ - "nix-env", + NIX_ENV, "-p", path, "--list-generations", @@ -641,7 +651,7 @@ def test_rollback_temporary_profile(tmp_path: Path) -> None: ) mock_run.assert_called_with( [ - "nix-env", + NIX_ENV, "-p", path, "--list-generations", @@ -670,7 +680,7 @@ def test_set_profile(mock_run: Mock) -> None: ) mock_run.assert_called_with( - ["nix-env", "-p", profile_path, "--set", config_path], + [NIX_ENV, "-p", profile_path, "--set", config_path], remote=None, sudo=False, ) @@ -836,17 +846,15 @@ def test_upgrade_channels( n.upgrade_channels(all_channels=False, sudo=True) mock_run.assert_called_once_with( - ["nix-channel", "--update", "nixos"], check=False, sudo=True + [NIX_CHANNEL, "--update", "nixos"], check=False, sudo=True ) mock_geteuid.return_value = 0 n.upgrade_channels(all_channels=True, sudo=False) mock_run.assert_has_calls( [ - call(["nix-channel", "--update", "nixos"], check=False, sudo=False), - call( - ["nix-channel", "--update", "nixos-hardware"], check=False, sudo=False - ), - call(["nix-channel", "--update", "home-manager"], check=False, sudo=False), + call([NIX_CHANNEL, "--update", "nixos"], check=False, sudo=False), + call([NIX_CHANNEL, "--update", "nixos-hardware"], check=False, sudo=False), + call([NIX_CHANNEL, "--update", "home-manager"], check=False, sudo=False), ] ) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/tests/linters.nix b/pkgs/by-name/ni/nixos-rebuild-ng/tests/linters.nix new file mode 100644 index 0000000000000..f52e5c321f0bc --- /dev/null +++ b/pkgs/by-name/ni/nixos-rebuild-ng/tests/linters.nix @@ -0,0 +1,30 @@ +{ + runCommand, + python3, + nixos-rebuild-ng, +}: + +runCommand "lint-nixos-rebuild-ng" + { + nativeBuildInputs = [ + (python3.withPackages (ps: [ + ps.mypy + ps.ruff + ])) + ]; + } + '' + export MYPY_CACHE_DIR="$(mktemp -d)" + export RUFF_CACHE_DIR="$(mktemp -d)" + + pushd ${nixos-rebuild-ng.src} + echo -e "\x1b[32m## run mypy\x1b[0m" + mypy . + echo -e "\x1b[32m## run ruff\x1b[0m" + ruff check . + echo -e "\x1b[32m## run ruff format\x1b[0m" + ruff format --check . + popd + + touch $out + '' diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/tests/repl.nix b/pkgs/by-name/ni/nixos-rebuild-ng/tests/repl.nix index 1a614e8d86553..a5a1322860e29 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/tests/repl.nix +++ b/pkgs/by-name/ni/nixos-rebuild-ng/tests/repl.nix @@ -1,11 +1,11 @@ { lib, + stdenv, expect, nix, nixos-rebuild-ng, path, runCommand, - stdenv, writeText, }: let