nixos-rebuild-ng: don't pass local flake evaluation flags to remote builders

This commit is contained in:
Mathieu Rene
2026-01-25 17:37:14 -05:00
parent c8680a776e
commit 17162ea223
6 changed files with 95 additions and 30 deletions
@@ -40,19 +40,23 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa
common_build_flags.add_argument("--print-build-logs", "-L", action="store_true")
common_build_flags.add_argument("--show-trace", action="store_true")
# Flags that apply to both flake evaluation and building
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")
flake_common_flags.add_argument("--offline", action="store_true")
flake_common_flags.add_argument("--no-net", action="store_true")
flake_common_flags.add_argument("--recreate-lock-file", action="store_true")
flake_common_flags.add_argument("--no-update-lock-file", action="store_true")
flake_common_flags.add_argument("--no-write-lock-file", action="store_true")
flake_common_flags.add_argument("--no-registries", action="store_true")
flake_common_flags.add_argument("--commit-lock-file", action="store_true")
flake_common_flags.add_argument("--update-input", action="append")
flake_common_flags.add_argument("--override-input", nargs=2, action="append")
# Flags that only apply during flake evaluation (and thus aren't passed to remote builders)
flake_eval_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
flake_eval_flags.add_argument("--accept-flake-config", action="store_true")
flake_eval_flags.add_argument("--refresh", action="store_true")
flake_eval_flags.add_argument("--impure", action="store_true")
flake_eval_flags.add_argument("--recreate-lock-file", action="store_true")
flake_eval_flags.add_argument("--no-update-lock-file", action="store_true")
flake_eval_flags.add_argument("--no-write-lock-file", action="store_true")
flake_eval_flags.add_argument("--no-registries", action="store_true")
flake_eval_flags.add_argument("--commit-lock-file", action="store_true")
flake_eval_flags.add_argument("--update-input", action="append")
flake_eval_flags.add_argument("--override-input", nargs=2, action="append")
classic_build_flags = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
classic_build_flags.add_argument(
@@ -74,6 +78,7 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa
"common_flags": common_flags,
"common_build_flags": common_build_flags,
"flake_common_flags": flake_common_flags,
"flake_eval_flags": flake_eval_flags,
"classic_build_flags": classic_build_flags,
"copy_flags": copy_flags,
}
@@ -150,8 +150,8 @@ class GroupedNixArgs:
build_flags: Args
common_flags: Args
copy_flags: Args
flake_eval_flags: Args
flake_build_flags: Args
flake_common_flags: Args
@classmethod
def from_parsed_args_groups(cls, args_groups: dict[str, Namespace]) -> Self:
@@ -159,6 +159,7 @@ class GroupedNixArgs:
common_build_flags = common_flags | vars(args_groups["common_build_flags"])
build_flags = common_build_flags | vars(args_groups["classic_build_flags"])
flake_common_flags = common_flags | vars(args_groups["flake_common_flags"])
flake_eval_flags = vars(args_groups["flake_eval_flags"])
flake_build_flags = common_build_flags | flake_common_flags
copy_flags = common_flags | vars(args_groups["copy_flags"])
# --no-build-output -> --no-link
@@ -169,8 +170,8 @@ class GroupedNixArgs:
build_flags=build_flags,
common_flags=common_flags,
copy_flags=copy_flags,
flake_eval_flags=flake_eval_flags,
flake_build_flags=flake_build_flags,
flake_common_flags=flake_common_flags,
)
@@ -43,7 +43,9 @@ def reexec(
drv = nix.build_flake(
NIXOS_REBUILD_ATTR,
flake,
grouped_nix_args.flake_build_flags | {"no_link": True},
grouped_nix_args.flake_build_flags
| grouped_nix_args.flake_eval_flags
| {"no_link": True},
)
else:
build_attr = BuildAttr.from_arg(args.attr, args.file)
@@ -101,7 +103,8 @@ def _get_system_attr(
case Action.BUILD_IMAGE if flake:
variants = nix.get_build_image_variants_flake(
flake,
eval_flags=grouped_nix_args.flake_common_flags,
eval_flags=grouped_nix_args.flake_build_flags
| grouped_nix_args.flake_eval_flags,
)
_validate_image_variant(args.image_variant, variants)
attr = f"config.system.build.images.{args.image_variant}"
@@ -164,7 +167,8 @@ def _build_system(
attr,
flake,
build_host,
eval_flags=grouped_nix_args.flake_common_flags,
eval_flags=grouped_nix_args.flake_build_flags
| grouped_nix_args.flake_eval_flags,
flake_build_flags={"no_link": no_link, "dry_run": dry_run}
| grouped_nix_args.flake_build_flags,
copy_flags=grouped_nix_args.copy_flags,
@@ -174,7 +178,8 @@ def _build_system(
attr,
flake,
flake_build_flags={"no_link": no_link, "dry_run": dry_run}
| grouped_nix_args.flake_build_flags,
| grouped_nix_args.flake_build_flags
| grouped_nix_args.flake_eval_flags,
)
case (Remote(_), None):
path_to_config = nix.build_remote(
@@ -259,7 +264,8 @@ def _activate_system(
image_name = nix.get_build_image_name_flake(
flake,
args.image_variant,
eval_flags=grouped_nix_args.flake_common_flags,
eval_flags=grouped_nix_args.flake_build_flags
| grouped_nix_args.flake_eval_flags,
)
else:
image_name = nix.get_build_image_name(
@@ -322,7 +328,10 @@ def build_and_activate_system(
def edit(flake: Flake | None, grouped_nix_args: GroupedNixArgs) -> None:
if flake:
nix.edit_flake(flake, grouped_nix_args.flake_build_flags)
nix.edit_flake(
flake,
grouped_nix_args.flake_build_flags | grouped_nix_args.flake_eval_flags,
)
else:
nix.edit()
@@ -353,7 +362,10 @@ def repl(
grouped_nix_args: GroupedNixArgs,
) -> None:
if flake:
nix.repl_flake(flake, grouped_nix_args.flake_build_flags)
nix.repl_flake(
flake,
grouped_nix_args.flake_build_flags | grouped_nix_args.flake_eval_flags,
)
else:
nix.repl(build_attr, grouped_nix_args.build_flags)
@@ -73,13 +73,8 @@ def test_parse_args() -> None:
"foo2",
"bar2",
]
assert nr.utils.dict_to_flags(g1.flake_common_flags) == [
"--option",
"foo1",
"bar1",
"--option",
"foo2",
"bar2",
# flake_eval_flags contains only eval-specific flags
assert nr.utils.dict_to_flags(g1.flake_eval_flags) == [
"--update-input",
"input1",
"--update-input",
@@ -91,6 +86,15 @@ def test_parse_args() -> None:
"override2",
"input2",
]
# flake_build_flags contains common build flags (for remote builds)
assert nr.utils.dict_to_flags(g1.flake_build_flags) == [
"--option",
"foo1",
"bar1",
"--option",
"foo2",
"bar2",
]
r2, g2 = nr.parse_args(
[
@@ -180,3 +180,46 @@ def test_profile_from_arg(mock_mkdir: Mock) -> None:
Path("/nix/var/nix/profiles/system-profiles/something"),
)
mock_mkdir.assert_called_once()
def test_grouped_nix_args_flake_build_flags() -> None:
"""Test that flake_build_flags excludes evaluation-only flags."""
from argparse import Namespace
args_groups = {
"common_flags": Namespace(v=0, quiet=0, max_jobs=None, cores=None),
"common_build_flags": Namespace(builders=None, include=None),
"flake_common_flags": Namespace(offline=True, no_net=False),
"flake_eval_flags": Namespace(
override_input=[["nixpkgs", "/local"]],
impure=True,
refresh=True,
accept_flake_config=False,
recreate_lock_file=False,
no_update_lock_file=False,
no_write_lock_file=False,
no_registries=False,
commit_lock_file=False,
update_input=None,
),
"classic_build_flags": Namespace(no_build_output=False),
"copy_flags": Namespace(s=False),
}
grouped = m.GroupedNixArgs.from_parsed_args_groups(args_groups)
# flake_eval_flags should contain ONLY eval-only flags
assert "override_input" in grouped.flake_eval_flags
assert "impure" in grouped.flake_eval_flags
assert "refresh" in grouped.flake_eval_flags
# flake_eval_flags should NOT contain common/build flags
assert "offline" not in grouped.flake_eval_flags
# flake_build_flags should NOT contain eval-only flags
assert "override_input" not in grouped.flake_build_flags
assert "impure" not in grouped.flake_build_flags
assert "refresh" not in grouped.flake_build_flags
# But flake_build_flags should still have common flags
assert "offline" in grouped.flake_build_flags
assert grouped.flake_build_flags["offline"] is True
@@ -23,8 +23,8 @@ def test_reexec(mock_build: Mock, mock_execve: Mock, monkeypatch: MonkeyPatch) -
build_flags={"build": True},
common_flags={"common": True},
copy_flags={"copy": True},
flake_eval_flags={"flake_eval": True},
flake_build_flags={"flake_build": True},
flake_common_flags={"flake_common": True},
)
s.reexec(argv, args, grouped_nix_args)
mock_build.assert_has_calls(
@@ -76,14 +76,14 @@ def test_reexec_flake(
build_flags={"build": True},
common_flags={"common": True},
copy_flags={"copy": True},
flake_eval_flags={"flake_eval": True},
flake_build_flags={"flake_build": True},
flake_common_flags={"flake_common": True},
)
s.reexec(argv, args, grouped_nix_args)
mock_build.assert_called_once_with(
s.NIXOS_REBUILD_ATTR,
n.models.Flake(ANY, ANY),
{"flake_build": True, "no_link": True},
{"flake_build": True, "flake_eval": True, "no_link": True},
)
# do not exec if there is no new version
mock_execve.assert_not_called()
@@ -122,8 +122,8 @@ def test_reexec_skip_if_already_reexec(mock_build: Mock, mock_execve: Mock) -> N
build_flags={"build": True},
common_flags={"common": True},
copy_flags={"copy": True},
flake_eval_flags={"flake_eval": True},
flake_build_flags={"flake_build": True},
flake_common_flags={"flake_common": True},
)
s.reexec(argv, args, grouped_nix_args)
mock_build.assert_not_called()