nixos-rebuild-ng: explicitly parse Nix flags
This commit is contained in:
parent
3e2f5ef1a6
commit
3b41ec0691
@ -119,7 +119,7 @@ ruff format .
|
||||
## TODO
|
||||
|
||||
- [ ] Remote host/builders (via SSH)
|
||||
- [ ] Improve nix arguments handling (e.g.: `nixFlags` vs `copyFlags` in the
|
||||
- [x] Improve nix arguments handling (e.g.: `nixFlags` vs `copyFlags` in the
|
||||
old `nixos-rebuild`)
|
||||
- [ ] `_NIXOS_REBUILD_EXEC`
|
||||
- [ ] Port `nixos-rebuild.passthru.tests`
|
||||
|
@ -21,19 +21,19 @@ from .nix import (
|
||||
switch_to_configuration,
|
||||
upgrade_channels,
|
||||
)
|
||||
from .utils import info
|
||||
from .utils import flags_to_dict, info
|
||||
|
||||
VERBOSE = False
|
||||
VERBOSE = 0
|
||||
|
||||
|
||||
def parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="nixos-rebuild",
|
||||
description="Reconfigure a NixOS machine",
|
||||
add_help=False,
|
||||
allow_abbrev=False,
|
||||
)
|
||||
parser.add_argument("--help", action="store_true")
|
||||
parser.add_argument("--help", "-h", action="store_true")
|
||||
parser.add_argument("--file", "-f")
|
||||
parser.add_argument("--attr", "-A")
|
||||
parser.add_argument("--flake", nargs="?", const=True)
|
||||
@ -48,13 +48,44 @@ def parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
||||
parser.add_argument("--upgrade-all", action="store_true")
|
||||
parser.add_argument("--json", action="store_true")
|
||||
parser.add_argument("action", choices=Action.values(), nargs="?")
|
||||
parser.add_argument("--verbose", "-v", action="count", default=0)
|
||||
|
||||
args, remainder = parser.parse_known_args(argv[1:])
|
||||
common_group = parser.add_argument_group("Common flags")
|
||||
common_group.add_argument("--include", "-I")
|
||||
common_group.add_argument("--max-jobs", "-j")
|
||||
common_group.add_argument("--cores")
|
||||
common_group.add_argument("--log-format")
|
||||
common_group.add_argument("--quiet", action="store_true")
|
||||
common_group.add_argument("--print-build-logs", "-L", action="store_true")
|
||||
common_group.add_argument("--show-trace", action="store_true")
|
||||
common_group.add_argument("--keep-going", "-k", action="store_true")
|
||||
common_group.add_argument("--keep-failed", "-K", action="store_true")
|
||||
common_group.add_argument("--fallback", action="store_true")
|
||||
common_group.add_argument("--repair", action="store_true")
|
||||
common_group.add_argument("--option", nargs=2)
|
||||
|
||||
nix_group = parser.add_argument_group("Classic Nix flags")
|
||||
nix_group.add_argument("--no-build-output", "-Q", action="store_true")
|
||||
|
||||
flake_group = parser.add_argument_group("Flake flags")
|
||||
flake_group.add_argument("--accept-flake-config", action="store_true")
|
||||
flake_group.add_argument("--refresh", action="store_true")
|
||||
flake_group.add_argument("--impure", action="store_true")
|
||||
flake_group.add_argument("--offline", action="store_true")
|
||||
flake_group.add_argument("--no-net", action="store_true")
|
||||
flake_group.add_argument("--recreate-lock-file", action="store_true")
|
||||
flake_group.add_argument("--no-update-lock-file", action="store_true")
|
||||
flake_group.add_argument("--no-write-lock-file", action="store_true")
|
||||
flake_group.add_argument("--no-registries", action="store_true")
|
||||
flake_group.add_argument("--commit-lock-file", action="store_true")
|
||||
flake_group.add_argument("--update-input")
|
||||
flake_group.add_argument("--override-input", nargs=2)
|
||||
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
global VERBOSE
|
||||
# Manually parse verbose flag since this is a nix flag that also affect
|
||||
# the script
|
||||
VERBOSE = any(v == "--verbose" or v.startswith("-v") for v in remainder)
|
||||
# This flag affects both nix and this script
|
||||
VERBOSE = args.verbose
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh#L56
|
||||
if args.action == Action.DRY_RUN.value:
|
||||
@ -76,11 +107,48 @@ def parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
||||
r = run(["man", "8", "nixos-rebuild"], check=False)
|
||||
parser.exit(r.returncode)
|
||||
|
||||
return args, remainder
|
||||
return args
|
||||
|
||||
|
||||
def execute(argv: list[str]) -> None:
|
||||
args, nix_flags = parse_args(argv)
|
||||
args = parse_args(argv)
|
||||
|
||||
common_flags = flags_to_dict(
|
||||
args,
|
||||
[
|
||||
"verbose",
|
||||
"include",
|
||||
"max_jobs",
|
||||
"cores",
|
||||
"log_format",
|
||||
"quiet",
|
||||
"print_build_logs",
|
||||
"show_trace",
|
||||
"keep_going",
|
||||
"keep_failed",
|
||||
"fallback",
|
||||
"repair",
|
||||
"option",
|
||||
],
|
||||
)
|
||||
nix_flags = common_flags | flags_to_dict(args, ["no_build_output"])
|
||||
flake_flags = common_flags | flags_to_dict(
|
||||
args,
|
||||
[
|
||||
"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",
|
||||
"override_input",
|
||||
],
|
||||
)
|
||||
|
||||
profile = Profile.from_name(args.profile_name)
|
||||
flake = Flake.from_arg(args.flake)
|
||||
@ -96,7 +164,7 @@ def execute(argv: list[str]) -> None:
|
||||
# untrusted tree.
|
||||
can_run = action in (Action.SWITCH, Action.BOOT, Action.TEST)
|
||||
if can_run and not flake:
|
||||
nixpkgs_path = find_file("nixpkgs", nix_flags)
|
||||
nixpkgs_path = find_file("nixpkgs", **nix_flags)
|
||||
rev = get_nixpkgs_rev(nixpkgs_path)
|
||||
if nixpkgs_path and rev:
|
||||
(nixpkgs_path / ".version-suffix").write_text(rev)
|
||||
@ -110,8 +178,8 @@ def execute(argv: list[str]) -> None:
|
||||
path_to_config = nixos_build_flake(
|
||||
"toplevel",
|
||||
flake,
|
||||
nix_flags,
|
||||
no_link=True,
|
||||
**flake_flags,
|
||||
)
|
||||
set_profile(profile, path_to_config)
|
||||
else:
|
||||
@ -119,8 +187,8 @@ def execute(argv: list[str]) -> None:
|
||||
"system",
|
||||
args.attr,
|
||||
args.file,
|
||||
nix_flags,
|
||||
no_out_link=True,
|
||||
**nix_flags,
|
||||
)
|
||||
set_profile(profile, path_to_config)
|
||||
switch_to_configuration(
|
||||
@ -142,18 +210,18 @@ def execute(argv: list[str]) -> None:
|
||||
path_to_config = nixos_build_flake(
|
||||
"toplevel",
|
||||
flake,
|
||||
nix_flags,
|
||||
keep_going=True,
|
||||
dry_run=dry_run,
|
||||
**flake_flags,
|
||||
)
|
||||
else:
|
||||
path_to_config = nixos_build(
|
||||
"system",
|
||||
args.attr,
|
||||
args.file,
|
||||
nix_flags,
|
||||
keep_going=True,
|
||||
dry_run=dry_run,
|
||||
**nix_flags,
|
||||
)
|
||||
if action in (Action.TEST, Action.DRY_ACTIVATE):
|
||||
switch_to_configuration(
|
||||
@ -168,21 +236,21 @@ def execute(argv: list[str]) -> None:
|
||||
path_to_config = nixos_build_flake(
|
||||
attr,
|
||||
flake,
|
||||
nix_flags,
|
||||
keep_going=True,
|
||||
**flake_flags,
|
||||
)
|
||||
else:
|
||||
path_to_config = nixos_build(
|
||||
attr,
|
||||
args.attr,
|
||||
args.file,
|
||||
nix_flags,
|
||||
keep_going=True,
|
||||
**nix_flags,
|
||||
)
|
||||
vm_path = next(path_to_config.glob("bin/run-*-vm"), "./result/bin/run-*-vm")
|
||||
print(f"Done. The virtual machine can be started by running '{vm_path}'")
|
||||
case Action.EDIT:
|
||||
edit(flake, nix_flags)
|
||||
edit(flake, **flake_flags)
|
||||
case Action.DRY_RUN:
|
||||
assert False, "DRY_RUN should be a DRY_BUILD alias"
|
||||
case Action.LIST_GENERATIONS:
|
||||
|
@ -15,20 +15,27 @@ from .models import (
|
||||
NRError,
|
||||
Profile,
|
||||
)
|
||||
from .utils import dict_to_flags, info
|
||||
from .utils import Args, dict_to_flags, info
|
||||
|
||||
FLAKE_FLAGS: Final = ["--extra-experimental-features", "nix-command flakes"]
|
||||
|
||||
|
||||
def edit(flake: Flake | None, nix_flags: list[str] | None = None) -> None:
|
||||
def edit(flake: Flake | None, **flake_flags: Args) -> None:
|
||||
"Try to find and open NixOS configuration file in editor."
|
||||
if flake:
|
||||
run(
|
||||
["nix", *FLAKE_FLAGS, "edit", *(nix_flags or []), "--", str(flake)],
|
||||
[
|
||||
"nix",
|
||||
*FLAKE_FLAGS,
|
||||
"edit",
|
||||
*(dict_to_flags(flake_flags)),
|
||||
"--",
|
||||
str(flake),
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
else:
|
||||
if nix_flags:
|
||||
if flake_flags:
|
||||
raise NRError("'edit' does not support extra Nix flags")
|
||||
nixos_config = Path(
|
||||
os.getenv("NIXOS_CONFIG")
|
||||
@ -49,10 +56,10 @@ def edit(flake: Flake | None, nix_flags: list[str] | None = None) -> None:
|
||||
raise NRError("cannot find NixOS config file")
|
||||
|
||||
|
||||
def find_file(file: str, nix_flags: list[str] | None = None) -> Path | None:
|
||||
def find_file(file: str, **nix_flags: Args) -> Path | None:
|
||||
"Find classic Nixpkgs location."
|
||||
r = run(
|
||||
["nix-instantiate", "--find-file", file, *(nix_flags or [])],
|
||||
["nix-instantiate", "--find-file", file, *dict_to_flags(nix_flags)],
|
||||
stdout=PIPE,
|
||||
check=False,
|
||||
text=True,
|
||||
@ -207,8 +214,7 @@ def nixos_build(
|
||||
attr: str,
|
||||
pre_attr: str | None,
|
||||
file: str | None,
|
||||
nix_flags: list[str] | None = None,
|
||||
**kwargs: bool | str,
|
||||
**nix_flags: Args,
|
||||
) -> Path:
|
||||
"""Build NixOS attribute using classic Nix.
|
||||
|
||||
@ -227,7 +233,7 @@ def nixos_build(
|
||||
]
|
||||
else:
|
||||
run_args = ["nix-build", "<nixpkgs/nixos>", "--attr", attr]
|
||||
run_args += dict_to_flags(kwargs) + (nix_flags or [])
|
||||
run_args += dict_to_flags(nix_flags)
|
||||
r = run(run_args, check=True, text=True, stdout=PIPE)
|
||||
return Path(r.stdout.strip())
|
||||
|
||||
@ -235,8 +241,7 @@ def nixos_build(
|
||||
def nixos_build_flake(
|
||||
attr: str,
|
||||
flake: Flake,
|
||||
nix_flags: list[str] | None = None,
|
||||
**kwargs: bool | str,
|
||||
**flake_flags: Args,
|
||||
) -> Path:
|
||||
"""Build NixOS attribute using Flakes.
|
||||
|
||||
@ -249,7 +254,7 @@ def nixos_build_flake(
|
||||
"--print-out-paths",
|
||||
f"{flake}.config.system.build.{attr}",
|
||||
]
|
||||
run_args += dict_to_flags(kwargs) + (nix_flags or [])
|
||||
run_args += dict_to_flags(flake_flags)
|
||||
r = run(run_args, check=True, text=True, stdout=PIPE)
|
||||
return Path(r.stdout.strip())
|
||||
|
||||
|
@ -1,18 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
from typing import TypeAlias
|
||||
|
||||
info = partial(print, file=sys.stderr)
|
||||
Args: TypeAlias = bool | str | list[str] | int | None
|
||||
|
||||
|
||||
def dict_to_flags(d: dict[str, Any]) -> list[str]:
|
||||
def dict_to_flags(d: dict[str, Args]) -> list[str]:
|
||||
flags = []
|
||||
for key, value in d.items():
|
||||
flag = f"--{'-'.join(key.split('_'))}"
|
||||
match value:
|
||||
case None | False:
|
||||
case None | False | 0 | []:
|
||||
pass
|
||||
case True:
|
||||
flags.append(flag)
|
||||
@ -26,3 +28,8 @@ def dict_to_flags(d: dict[str, Any]) -> list[str]:
|
||||
for v in value:
|
||||
flags.append(v)
|
||||
return flags
|
||||
|
||||
|
||||
def flags_to_dict(args: argparse.Namespace, keys: list[str]) -> dict[str, Args]:
|
||||
d = vars(args)
|
||||
return {k: d[k] for k in keys}
|
||||
|
@ -29,25 +29,27 @@ def test_parse_args() -> None:
|
||||
nr.parse_args(["nixos-rebuild", "edit", "--attr", "attr"])
|
||||
assert e.value.code == 2
|
||||
|
||||
r1, remainder = nr.parse_args(
|
||||
r1 = nr.parse_args(
|
||||
[
|
||||
"nixos-rebuild",
|
||||
"switch",
|
||||
"--install-grub",
|
||||
"--flake",
|
||||
"/etc/nixos",
|
||||
"--extra",
|
||||
"flag",
|
||||
"--option",
|
||||
"foo",
|
||||
"bar",
|
||||
]
|
||||
)
|
||||
assert remainder == ["--extra", "flag"]
|
||||
assert nr.VERBOSE == 0
|
||||
assert r1.flake == "/etc/nixos"
|
||||
assert r1.install_bootloader is True
|
||||
assert r1.install_grub is True
|
||||
assert r1.profile_name == "system"
|
||||
assert r1.action == "switch"
|
||||
assert r1.option == ["foo", "bar"]
|
||||
|
||||
r2, remainder = nr.parse_args(
|
||||
r2 = nr.parse_args(
|
||||
[
|
||||
"nixos-rebuild",
|
||||
"dry-run",
|
||||
@ -57,9 +59,11 @@ def test_parse_args() -> None:
|
||||
"foo",
|
||||
"--attr",
|
||||
"bar",
|
||||
"-vvv",
|
||||
]
|
||||
)
|
||||
assert remainder == []
|
||||
assert nr.VERBOSE == 3
|
||||
assert r2.verbose == 3
|
||||
assert r2.flake is False
|
||||
assert r2.action == "dry-build"
|
||||
assert r2.file == "foo"
|
||||
@ -88,7 +92,6 @@ def test_execute_nix_boot(mock_which: Any, mock_run: Any, tmp_path: Path) -> Non
|
||||
|
||||
nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv"])
|
||||
|
||||
assert nr.VERBOSE is True
|
||||
assert mock_run.call_count == 6
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
@ -164,7 +167,6 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
|
||||
]
|
||||
)
|
||||
|
||||
assert nr.VERBOSE is True
|
||||
assert mock_run.call_count == 3
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
@ -177,7 +179,7 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
|
||||
"--print-out-paths",
|
||||
"/path/to/config#nixosConfigurations.hostname.config.system.build.toplevel",
|
||||
"--no-link",
|
||||
"--verbose",
|
||||
"-v",
|
||||
],
|
||||
check=True,
|
||||
text=True,
|
||||
@ -209,8 +211,7 @@ def test_execute_switch_rollback(mock_run: Any, tmp_path: Path) -> None:
|
||||
|
||||
nr.execute(["nixos-rebuild", "switch", "--rollback", "--install-bootloader"])
|
||||
|
||||
assert nr.VERBOSE is False
|
||||
assert mock_run.call_count == 3
|
||||
assert mock_run.call_count >= 2
|
||||
# ignoring update_nixpkgs_rev calls
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
@ -268,7 +269,6 @@ def test_execute_test_rollback(
|
||||
]
|
||||
)
|
||||
|
||||
assert nr.VERBOSE is False
|
||||
assert mock_run.call_count == 2
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
|
@ -16,7 +16,7 @@ from .helpers import get_qualified_name
|
||||
def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None:
|
||||
# Flake
|
||||
flake = m.Flake.parse(".#attr")
|
||||
n.edit(flake, ["--commit-lock-file"])
|
||||
n.edit(flake, commit_lock_file=True)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
"nix",
|
||||
@ -193,8 +193,8 @@ def test_nixos_build_flake(mock_run: Any) -> None:
|
||||
assert n.nixos_build_flake(
|
||||
"toplevel",
|
||||
flake,
|
||||
["--nix-flag", "foo"],
|
||||
no_link=True,
|
||||
nix_flag="foo",
|
||||
) == Path("/path/to/file")
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@ -220,9 +220,7 @@ def test_nixos_build_flake(mock_run: Any) -> None:
|
||||
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
|
||||
)
|
||||
def test_nixos_build(mock_run: Any, monkeypatch: Any) -> None:
|
||||
assert n.nixos_build("attr", None, None, ["--nix-flag", "foo"]) == Path(
|
||||
"/path/to/file"
|
||||
)
|
||||
assert n.nixos_build("attr", None, None, nix_flag="foo") == Path("/path/to/file")
|
||||
mock_run.assert_called_with(
|
||||
["nix-build", "<nixpkgs/nixos>", "--attr", "attr", "--nix-flag", "foo"],
|
||||
check=True,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import argparse
|
||||
|
||||
from nixos_rebuild import utils as u
|
||||
|
||||
|
||||
def test_dict_to_flags() -> None:
|
||||
r = u.dict_to_flags(
|
||||
r1 = u.dict_to_flags(
|
||||
{
|
||||
"test_flag_1": True,
|
||||
"test_flag_2": False,
|
||||
@ -12,7 +14,7 @@ def test_dict_to_flags() -> None:
|
||||
"verbose": 5,
|
||||
}
|
||||
)
|
||||
assert r == [
|
||||
assert r1 == [
|
||||
"--test-flag-1",
|
||||
"--test-flag-3",
|
||||
"value",
|
||||
@ -21,3 +23,25 @@ def test_dict_to_flags() -> None:
|
||||
"v2",
|
||||
"-vvvvv",
|
||||
]
|
||||
r2 = u.dict_to_flags({"verbose": 0, "empty_list": []})
|
||||
assert r2 == []
|
||||
|
||||
|
||||
def test_flags_to_dict() -> None:
|
||||
r = u.flags_to_dict(
|
||||
argparse.Namespace(
|
||||
test_flag_1=True,
|
||||
test_flag_2=False,
|
||||
test_flag_3="value",
|
||||
test_flag_4=["v1", "v2"],
|
||||
test_flag_5=None,
|
||||
verbose=5,
|
||||
),
|
||||
["test_flag_1", "test_flag_3", "test_flag_4", "verbose"],
|
||||
)
|
||||
assert r == {
|
||||
"test_flag_1": True,
|
||||
"test_flag_3": "value",
|
||||
"test_flag_4": ["v1", "v2"],
|
||||
"verbose": 5,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user