From b2a735225ccb21c812956f743179853145d685cb Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Thu, 30 Jan 2025 11:35:09 +0000 Subject: [PATCH 1/3] nixos-rebuild-ng: add --no-reexec flag, deprecate --fast `--fast` from nixos-rebuild has an overloaded meaning that basically disables some eval operations pre-build. However since it doesn't describe exactly what it does, it is also used in some context were it doesn't make sense, see this blog post for example: https://www.haskellforall.com/2023/01/announcing-nixos-rebuild-new-deployment.html One of the things that `--fast` does in nixos-rebuild doesn't make sense in nixos-rebuild-ng (skipping Nix build), and the other can be replaced with a new flag, that we will call `--no-reexec` and it is clear what it does. Yes, the name may be non-descriptive if you don't know what re-exec is, but I think if this triggers someone to look at the manpage or at least Google it, it already does what it should. --- .../ni/nixos-rebuild-ng/nixos-rebuild.8.scd | 5 +-- .../src/nixos_rebuild/__init__.py | 35 +++++++++++-------- .../nixos-rebuild-ng/src/tests/test_main.py | 18 +++++----- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd b/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd index cc4fc3a19ca8..0d4c88e5def5 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd +++ b/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd @@ -17,11 +17,12 @@ nixos-rebuild - reconfigure a NixOS machine # SYNOPSIS +; document here only non-deprecated flags _nixos-rebuild_ \[--verbose] [--max-jobs MAX_JOBS] [--cores CORES] [--log-format LOG_FORMAT] [--keep-going] [--keep-failed] [--fallback] [--repair] [--option OPTION OPTION] [--builders BUILDERS]++ \[--include INCLUDE] [--quiet] [--print-build-logs] [--show-trace] [--accept-flake-config] [--refresh] [--impure] [--offline] [--no-net] [--recreate-lock-file]++ \[--no-update-lock-file] [--no-write-lock-file] [--no-registries] [--commit-lock-file] [--update-input UPDATE_INPUT] [--override-input OVERRIDE_INPUT OVERRIDE_INPUT]++ \[--no-build-output] [--use-substitutes] [--help] [--file FILE] [--attr ATTR] [--flake [FLAKE]] [--no-flake] [--install-bootloader] [--profile-name PROFILE_NAME]++ - \[--specialisation SPECIALISATION] [--rollback] [--upgrade] [--upgrade-all] [--json] [--ask-sudo-password] [--sudo] [--fast]++ + \[--specialisation SPECIALISATION] [--rollback] [--upgrade] [--upgrade-all] [--json] [--ask-sudo-password] [--sudo] [--no-reexec]++ \[--image-variant VARIANT]++ \[--build-host BUILD_HOST] [--target-host TARGET_HOST]++ \[{switch,boot,test,build,edit,repl,dry-build,dry-run,dry-activate,build-image,build-vm,build-vm-with-bootloader,list-generations}] @@ -170,7 +171,7 @@ It must be one of the following: Causes the boot loader to be (re)installed on the device specified by the relevant configuration options. -*--fast* +*--no-reexec* Normally, *nixos-rebuild* first finds and builds itself from the _config.system.build.nixos-rebuild_ attribute from the current user channel or flake and exec into it. This allows *nixos-rebuild* to run 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 2b9975e48e98..1c725b60f617 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 @@ -18,7 +18,7 @@ logger.setLevel(logging.INFO) def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentParser]]: - common_flags = argparse.ArgumentParser(add_help=False) + common_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False) common_flags.add_argument( "--verbose", "-v", @@ -37,13 +37,13 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa common_flags.add_argument("--repair", action="store_true") common_flags.add_argument("--option", nargs=2, action="append") - common_build_flags = argparse.ArgumentParser(add_help=False) + common_build_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False) common_build_flags.add_argument("--builders") common_build_flags.add_argument("--include", "-I", action="append") common_build_flags.add_argument("--print-build-logs", "-L", action="store_true") common_build_flags.add_argument("--show-trace", action="store_true") - flake_common_flags = argparse.ArgumentParser(add_help=False) + flake_common_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False) flake_common_flags.add_argument("--accept-flake-config", action="store_true") flake_common_flags.add_argument("--refresh", action="store_true") flake_common_flags.add_argument("--impure", action="store_true") @@ -57,10 +57,10 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa flake_common_flags.add_argument("--update-input", action="append") flake_common_flags.add_argument("--override-input", nargs=2, action="append") - classic_build_flags = argparse.ArgumentParser(add_help=False) + classic_build_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False) classic_build_flags.add_argument("--no-build-output", "-Q", action="store_true") - copy_flags = argparse.ArgumentParser(add_help=False) + copy_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False) copy_flags.add_argument( "--use-substitutes", "--substitute-on-destination", @@ -166,10 +166,15 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa help="Deprecated, use '--sudo' instead", ) main_parser.add_argument("--no-ssh-tty", action="store_true", help="Deprecated") + main_parser.add_argument( + "--no-reexec", + action="store_true", + help="Do not update nixos-rebuild in-place (also known as re-exec) before build", + ) main_parser.add_argument( "--fast", action="store_true", - help="Skip possibly expensive operations", + help="Deprecated, use '--no-reexec' instead", ) main_parser.add_argument("--build-host", help="Specifies host to perform the build") main_parser.add_argument( @@ -223,23 +228,23 @@ def parse_args( if args.ask_sudo_password: args.sudo = True - # TODO: use deprecated=True in Python >=3.13 if args.install_grub: - parser_warn("--install-grub deprecated, use --install-bootloader instead") + parser_warn("--install-grub is deprecated, use --install-bootloader instead") args.install_bootloader = True - # TODO: use deprecated=True in Python >=3.13 if args.use_remote_sudo: - parser_warn("--use-remote-sudo deprecated, use --sudo instead") + parser_warn("--use-remote-sudo is deprecated, use --sudo instead") args.sudo = True - # TODO: use deprecated=True in Python >=3.13 + if args.fast: + parser_warn("--fast is deprecated, use --no-reexec instead") + args.no_reexec = True + if args.no_ssh_tty: - parser_warn("--no-ssh-tty deprecated, SSH's TTY is never used anymore") + parser_warn("--no-ssh-tty is deprecated, SSH's TTY is never used anymore") - # TODO: use deprecated=True in Python >=3.13 if args.no_build_nix: - parser_warn("--no-build-nix deprecated, we do not build nix anymore") + parser_warn("--no-build-nix is deprecated, we do not build nix anymore") if args.action == Action.EDIT.value and (args.file or args.attr): parser.error("--file and --attr are not supported with 'edit'") @@ -351,7 +356,7 @@ def execute(argv: list[str]) -> None: if ( WITH_REEXEC and can_run - and not args.fast + and not args.no_reexec and not os.environ.get("_NIXOS_REBUILD_REEXEC") ): reexec(argv, args, build_flags, flake_build_flags) 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 ba67fa838578..c95840345a51 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 @@ -144,7 +144,7 @@ def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None: mock_run.side_effect = run_side_effect - nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv", "--fast"]) + nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv", "--no-reexec"]) assert mock_run.call_count == 6 mock_run.assert_has_calls( @@ -222,7 +222,7 @@ def test_execute_nix_build_vm(mock_run: Any, tmp_path: Path) -> None: "nixos-config=./configuration.nix", "-I", "nixpkgs=$HOME/.nix-defexpr/channels/pinned_nixpkgs", - "--fast", + "--no-reexec", ] ) @@ -340,7 +340,7 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None: "--install-bootloader", "--sudo", "--verbose", - "--fast", + "--no-reexec", # https://github.com/NixOS/nixpkgs/issues/374050 "--option", "narinfo-cache-negative-ttl", @@ -418,7 +418,7 @@ def test_execute_nix_switch_flake_target_host( "--use-remote-sudo", "--target-host", "user@localhost", - "--fast", + "--no-reexec", ] ) @@ -508,7 +508,7 @@ def test_execute_nix_switch_flake_build_host( "/path/to/config#hostname", "--build-host", "user@localhost", - "--fast", + "--no-reexec", ] ) @@ -587,7 +587,7 @@ def test_execute_switch_rollback(mock_run: Any, tmp_path: Path) -> None: nixpkgs_path.touch() nr.execute( - ["nixos-rebuild", "switch", "--rollback", "--install-bootloader", "--fast"] + ["nixos-rebuild", "switch", "--rollback", "--install-bootloader", "--no-reexec"] ) assert mock_run.call_count >= 2 @@ -625,7 +625,7 @@ def test_execute_build(mock_run: Any, tmp_path: Path) -> None: CompletedProcess([], 0, str(config_path)), ] - nr.execute(["nixos-rebuild", "build", "--no-flake", "--fast"]) + nr.execute(["nixos-rebuild", "build", "--no-flake", "--no-reexec"]) assert mock_run.call_count == 1 mock_run.assert_has_calls( @@ -659,7 +659,7 @@ def test_execute_test_flake(mock_run: Any, tmp_path: Path) -> None: mock_run.side_effect = run_side_effect nr.execute( - ["nixos-rebuild", "test", "--flake", "github:user/repo#hostname", "--fast"] + ["nixos-rebuild", "test", "--flake", "github:user/repo#hostname", "--no-reexec"] ) assert mock_run.call_count == 2 @@ -712,7 +712,7 @@ def test_execute_test_rollback( mock_run.side_effect = run_side_effect nr.execute( - ["nixos-rebuild", "test", "--rollback", "--profile-name", "foo", "--fast"] + ["nixos-rebuild", "test", "--rollback", "--profile-name", "foo", "--no-reexec"] ) assert mock_run.call_count == 2 From 41ae0e99c48d8b9fb86301767945f2f920c29479 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Thu, 30 Jan 2025 11:52:02 +0000 Subject: [PATCH 2/3] nixos-rebuild-ng: fix tests in darwin --- pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 2c3a6c5ce60c..df7ab412d00a 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 @@ -333,7 +333,7 @@ def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None: """, ), ) -def test_get_build_image_variants(mock_run: Any) -> None: +def test_get_build_image_variants(mock_run: Any, tmp_path: Path) -> None: build_attr = m.BuildAttr("", None) assert n.get_build_image_variants(build_attr) == { "azure": "nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd", @@ -357,7 +357,7 @@ def test_get_build_image_variants(mock_run: Any) -> None: stdout=PIPE, ) - build_attr = m.BuildAttr(Path("/tmp"), "preAttr") + build_attr = m.BuildAttr(Path(tmp_path), "preAttr") assert n.get_build_image_variants(build_attr, {"inst_flag": True}) == { "azure": "nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd", "vmware": "nixos-image-vmware-25.05.20250102.6df2492-x86_64-linux.vmdk", @@ -369,10 +369,10 @@ def test_get_build_image_variants(mock_run: Any) -> None: "--strict", "--json", "--expr", - textwrap.dedent(""" + textwrap.dedent(f""" let - value = import "/tmp"; - set = if builtins.isFunction value then value {} else value; + value = import "{tmp_path}"; + set = if builtins.isFunction value then value {{}} else value; in builtins.mapAttrs (n: v: v.passthru.filePath) set.preAttr.config.system.build.images """), From 79a33d9eae43cd3f481746913f6d985f8e1289c2 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Thu, 30 Jan 2025 12:01:58 +0000 Subject: [PATCH 3/3] nixos-rebuild-ng: improve type-safety in tests --- .../ni/nixos-rebuild-ng/src/tests/test_nix.py | 14 +++++++------- .../ni/nixos-rebuild-ng/src/tests/test_process.py | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) 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 df7ab412d00a..bfd8dba0a58c 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 @@ -20,7 +20,7 @@ from .helpers import get_qualified_name autospec=True, return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "), ) -def test_build(mock_run: Any, monkeypatch: Any) -> None: +def test_build(mock_run: Any) -> None: assert n.build( "config.system.build.attr", m.BuildAttr("", None), @@ -79,7 +79,7 @@ def test_build_flake(mock_run: Any, monkeypatch: MonkeyPatch, tmpdir: Path) -> N @patch(get_qualified_name(n.run_wrapper, n), autospec=True) @patch(get_qualified_name(n.uuid4, n), autospec=True) -def test_build_remote(mock_uuid4: Any, mock_run: Any, monkeypatch: Any) -> None: +def test_build_remote(mock_uuid4: Any, mock_run: Any, monkeypatch: MonkeyPatch) -> None: build_host = m.Remote("user@host", [], None) monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts") @@ -226,7 +226,7 @@ def test_build_remote_flake( ) -def test_copy_closure(monkeypatch: Any) -> None: +def test_copy_closure(monkeypatch: MonkeyPatch) -> None: closure = Path("/path/to/closure") with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run: n.copy_closure(closure, None) @@ -290,7 +290,7 @@ def test_copy_closure(monkeypatch: Any) -> None: @patch(get_qualified_name(n.run_wrapper, n), autospec=True) -def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None: +def test_edit(mock_run: Any, monkeypatch: MonkeyPatch, tmpdir: Path) -> None: # Flake flake = m.Flake.parse(f"{tmpdir}#attr") n.edit(flake, {"commit_lock_file": True}) @@ -309,8 +309,8 @@ def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None: # Classic with monkeypatch.context() as mp: - default_nix = tmpdir.join("default.nix") - default_nix.write("{}") + default_nix = tmpdir / "default.nix" + default_nix.write_text("{}", encoding="utf-8") mp.setenv("NIXOS_CONFIG", str(tmpdir)) mp.setenv("EDITOR", "editor") @@ -687,7 +687,7 @@ def test_set_profile(mock_run: Any) -> None: @patch(get_qualified_name(n.run_wrapper, n), autospec=True) -def test_switch_to_configuration(mock_run: Any, monkeypatch: Any) -> None: +def test_switch_to_configuration(mock_run: Any, monkeypatch: MonkeyPatch) -> None: profile_path = Path("/path/to/profile") config_path = Path("/path/to/config") diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_process.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_process.py index 6c2ca2289245..bfd5b180fd5a 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_process.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_process.py @@ -1,6 +1,8 @@ from typing import Any from unittest.mock import patch +from pytest import MonkeyPatch + import nixos_rebuild.models as m import nixos_rebuild.process as p @@ -94,7 +96,7 @@ def test_run(mock_run: Any) -> None: ) -def test_remote_from_name(monkeypatch: Any) -> None: +def test_remote_from_name(monkeypatch: MonkeyPatch) -> None: monkeypatch.setenv("NIX_SSHOPTS", "") assert m.Remote.from_arg("user@localhost", None, False) == m.Remote( "user@localhost",