nixos-rebuild-ng: make build functions more generic

This commit is contained in:
Thiago Kenji Okada
2024-11-30 17:54:31 +00:00
parent 359d341535
commit 3cadcd1653
3 changed files with 291 additions and 289 deletions
@@ -194,13 +194,14 @@ def execute(argv: list[str]) -> None:
match action:
case Action.SWITCH | Action.BOOT:
logger.info("building the system configuration...")
attr = "config.system.build.toplevel"
if args.rollback:
path_to_config = nix.rollback(profile, target_host, sudo=args.sudo)
else:
if flake:
if build_host:
path_to_config = nix.nixos_remote_build_flake(
"toplevel",
path_to_config = nix.remote_build_flake(
attr,
flake,
build_host,
flake_build_flags=flake_build_flags,
@@ -208,16 +209,16 @@ def execute(argv: list[str]) -> None:
build_flags=build_flags,
)
else:
path_to_config = nix.nixos_build_flake(
"toplevel",
path_to_config = nix.build_flake(
attr,
flake,
no_link=True,
**flake_build_flags,
)
else:
if build_host:
path_to_config = nix.nixos_remote_build(
"toplevel",
path_to_config = nix.remote_build(
attr,
build_attr,
build_host,
instantiate_flags=common_flags,
@@ -225,8 +226,8 @@ def execute(argv: list[str]) -> None:
build_flags=build_flags,
)
else:
path_to_config = nix.nixos_build(
"toplevel",
path_to_config = nix.build(
attr,
build_attr,
no_out_link=True,
**build_flags,
@@ -253,6 +254,7 @@ def execute(argv: list[str]) -> None:
)
case Action.TEST | Action.BUILD | Action.DRY_BUILD | Action.DRY_ACTIVATE:
logger.info("building the system configuration...")
attr = "config.system.build.toplevel"
dry_run = action == Action.DRY_BUILD
if args.rollback:
if action not in (Action.TEST, Action.BUILD):
@@ -267,15 +269,15 @@ def execute(argv: list[str]) -> None:
else:
raise NRError("could not find previous generation")
elif flake:
path_to_config = nix.nixos_build_flake(
"toplevel",
path_to_config = nix.build_flake(
attr,
flake,
dry_run=dry_run,
**flake_build_flags,
)
else:
path_to_config = nix.nixos_build(
"toplevel",
path_to_config = nix.build(
attr,
build_attr,
dry_run=dry_run,
**build_flags,
@@ -292,14 +294,14 @@ def execute(argv: list[str]) -> None:
logger.info("building the system configuration...")
attr = "vm" if action == Action.BUILD_VM else "vmWithBootLoader"
if flake:
path_to_config = nix.nixos_build_flake(
attr,
path_to_config = nix.build_flake(
f"config.system.build.{attr}",
flake,
**flake_build_flags,
)
else:
path_to_config = nix.nixos_build(
attr,
path_to_config = nix.build(
f"config.system.build.{attr}",
build_attr,
**build_flags,
)
@@ -25,6 +25,112 @@ FLAKE_REPL_TEMPLATE: Final = "repl.template.nix"
logger = logging.getLogger(__name__)
def build(
attr: str,
build_attr: BuildAttr,
**build_flags: Args,
) -> Path:
"""Build NixOS attribute using classic Nix.
Returns the built attribute as path.
"""
run_args = [
"nix-build",
build_attr.path,
"--attr",
build_attr.to_attr(attr),
*dict_to_flags(build_flags),
]
r = run_wrapper(run_args, stdout=PIPE)
return Path(r.stdout.strip())
def build_flake(
attr: str,
flake: Flake,
**flake_build_flags: Args,
) -> Path:
"""Build NixOS attribute using Flakes.
Returns the built attribute as path.
"""
run_args = [
"nix",
*FLAKE_FLAGS,
"build",
"--print-out-paths",
flake.to_attr(attr),
*dict_to_flags(flake_build_flags),
]
r = run_wrapper(run_args, stdout=PIPE)
return Path(r.stdout.strip())
def remote_build(
attr: str,
build_attr: BuildAttr,
build_host: Remote | None,
build_flags: dict[str, Args] | None = None,
instantiate_flags: dict[str, Args] | None = None,
copy_flags: dict[str, Args] | None = None,
) -> Path:
r = run_wrapper(
[
"nix-instantiate",
"--raw",
build_attr.path,
"--attr",
build_attr.to_attr(attr),
*dict_to_flags(instantiate_flags or {}),
],
stdout=PIPE,
)
drv = Path(r.stdout.strip())
copy_closure(drv, to_host=build_host, from_host=None, **(copy_flags or {}))
r = run_wrapper(
["nix-store", "--realise", drv, *dict_to_flags(build_flags or {})],
remote=build_host,
stdout=PIPE,
)
return Path(r.stdout.strip())
def remote_build_flake(
attr: str,
flake: Flake,
build_host: Remote,
flake_build_flags: dict[str, Args] | None = None,
copy_flags: dict[str, Args] | None = None,
build_flags: dict[str, Args] | None = None,
) -> Path:
r = run_wrapper(
[
"nix",
*FLAKE_FLAGS,
"eval",
"--raw",
flake.to_attr(attr, "drvPath"),
*dict_to_flags(flake_build_flags or {}),
],
stdout=PIPE,
)
drv = Path(r.stdout.strip())
copy_closure(drv, to_host=build_host, from_host=None, **(copy_flags or {}))
r = run_wrapper(
[
"nix",
*FLAKE_FLAGS,
"build",
f"{drv}^*",
"--print-out-paths",
*dict_to_flags(build_flags or {}),
],
remote=build_host,
stdout=PIPE,
)
return Path(r.stdout.strip())
def copy_closure(
closure: Path,
to_host: Remote | None,
@@ -248,112 +354,6 @@ def list_generations(profile: Profile) -> list[GenerationJson]:
return result
def nixos_build(
attr: str,
build_attr: BuildAttr,
**build_flags: Args,
) -> Path:
"""Build NixOS attribute using classic Nix.
Returns the built attribute as path.
"""
run_args = [
"nix-build",
build_attr.path,
"--attr",
build_attr.to_attr("config", "system", "build", attr),
*dict_to_flags(build_flags),
]
r = run_wrapper(run_args, stdout=PIPE)
return Path(r.stdout.strip())
def nixos_build_flake(
attr: str,
flake: Flake,
**flake_build_flags: Args,
) -> Path:
"""Build NixOS attribute using Flakes.
Returns the built attribute as path.
"""
run_args = [
"nix",
*FLAKE_FLAGS,
"build",
"--print-out-paths",
flake.to_attr("config", "system", "build", attr),
*dict_to_flags(flake_build_flags),
]
r = run_wrapper(run_args, stdout=PIPE)
return Path(r.stdout.strip())
def nixos_remote_build(
attr: str,
build_attr: BuildAttr,
build_host: Remote | None,
build_flags: dict[str, Args] | None = None,
instantiate_flags: dict[str, Args] | None = None,
copy_flags: dict[str, Args] | None = None,
) -> Path:
r = run_wrapper(
[
"nix-instantiate",
"--raw",
build_attr.path,
"--attr",
build_attr.to_attr("config", "system", "build", attr),
*dict_to_flags(instantiate_flags or {}),
],
stdout=PIPE,
)
drv = Path(r.stdout.strip())
copy_closure(drv, to_host=build_host, from_host=None, **(copy_flags or {}))
r = run_wrapper(
["nix-store", "--realise", drv, *dict_to_flags(build_flags or {})],
remote=build_host,
stdout=PIPE,
)
return Path(r.stdout.strip())
def nixos_remote_build_flake(
attr: str,
flake: Flake,
build_host: Remote,
flake_build_flags: dict[str, Args] | None = None,
copy_flags: dict[str, Args] | None = None,
build_flags: dict[str, Args] | None = None,
) -> Path:
r = run_wrapper(
[
"nix",
*FLAKE_FLAGS,
"eval",
"--raw",
flake.to_attr("config", "system", "build", attr, "drvPath"),
*dict_to_flags(flake_build_flags or {}),
],
stdout=PIPE,
)
drv = Path(r.stdout.strip())
copy_closure(drv, to_host=build_host, from_host=None, **(copy_flags or {}))
r = run_wrapper(
[
"nix",
*FLAKE_FLAGS,
"build",
f"{drv}^*",
"--print-out-paths",
*dict_to_flags(build_flags or {}),
],
remote=build_host,
stdout=PIPE,
)
return Path(r.stdout.strip())
def repl(attr: str, build_attr: BuildAttr, **nix_flags: Args) -> None:
run_args = ["nix", "repl", "--file", build_attr.path]
if build_attr.attr:
@@ -12,6 +12,173 @@ import nixos_rebuild.nix as n
from .helpers import get_qualified_name
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_build(mock_run: Any, monkeypatch: Any) -> None:
assert n.build(
"config.system.build.attr", m.BuildAttr("<nixpkgs/nixos>", None), nix_flag="foo"
) == Path("/path/to/file")
mock_run.assert_called_with(
[
"nix-build",
"<nixpkgs/nixos>",
"--attr",
"config.system.build.attr",
"--nix-flag",
"foo",
],
stdout=PIPE,
)
assert n.build(
"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"],
stdout=PIPE,
)
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_build_flake(mock_run: Any) -> None:
flake = m.Flake.parse(".#hostname")
assert n.build_flake(
"config.system.build.toplevel",
flake,
no_link=True,
nix_flag="foo",
) == Path("/path/to/file")
mock_run.assert_called_with(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"build",
"--print-out-paths",
".#nixosConfigurations.hostname.config.system.build.toplevel",
"--no-link",
"--nix-flag",
"foo",
],
stdout=PIPE,
)
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_remote_build(mock_run: Any, monkeypatch: Any) -> None:
build_host = m.Remote("user@host", ["--ssh", "opts"], None)
assert n.remote_build(
"config.system.build.toplevel",
m.BuildAttr("<nixpkgs/nixos>", "preAttr"),
build_host,
build_flags={"build": True},
instantiate_flags={"inst": True},
copy_flags={"copy": True},
) == Path("/path/to/file")
mock_run.assert_has_calls(
[
call(
[
"nix-instantiate",
"--raw",
"<nixpkgs/nixos>",
"--attr",
"preAttr.config.system.build.toplevel",
"--inst",
],
stdout=PIPE,
),
call(
[
"nix-copy-closure",
"--copy",
"--to",
"user@host",
Path("/path/to/file"),
],
extra_env={"NIX_SSHOPTS": "--ssh opts"},
remote=None,
),
call(
["nix-store", "--realise", Path("/path/to/file"), "--build"],
remote=build_host,
stdout=PIPE,
),
]
)
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_remote_build_flake(mock_run: Any) -> None:
flake = m.Flake.parse(".#hostname")
build_host = m.Remote("user@host", ["--ssh", "opts"], None)
assert n.remote_build_flake(
"config.system.build.toplevel",
flake,
build_host,
flake_build_flags={"flake": True},
copy_flags={"copy": True},
build_flags={"build": True},
) == Path("/path/to/file")
mock_run.assert_has_calls(
[
call(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"eval",
"--raw",
".#nixosConfigurations.hostname.config.system.build.toplevel.drvPath",
"--flake",
],
stdout=PIPE,
),
call(
[
"nix-copy-closure",
"--copy",
"--to",
"user@host",
Path("/path/to/file"),
],
extra_env={"NIX_SSHOPTS": "--ssh opts"},
remote=None,
),
call(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"build",
"/path/to/file^*",
"--print-out-paths",
"--build",
],
remote=build_host,
stdout=PIPE,
),
]
)
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
def test_copy_closure(mock_run: Any, monkeypatch: Any) -> None:
closure = Path("/path/to/closure")
@@ -211,173 +378,6 @@ def test_list_generations(mock_get_generations: Any, tmp_path: Path) -> None:
]
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_nixos_build_flake(mock_run: Any) -> None:
flake = m.Flake.parse(".#hostname")
assert n.nixos_build_flake(
"toplevel",
flake,
no_link=True,
nix_flag="foo",
) == Path("/path/to/file")
mock_run.assert_called_with(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"build",
"--print-out-paths",
".#nixosConfigurations.hostname.config.system.build.toplevel",
"--no-link",
"--nix-flag",
"foo",
],
stdout=PIPE,
)
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
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", m.BuildAttr("<nixpkgs/nixos>", None), nix_flag="foo"
) == Path("/path/to/file")
mock_run.assert_called_with(
[
"nix-build",
"<nixpkgs/nixos>",
"--attr",
"config.system.build.attr",
"--nix-flag",
"foo",
],
stdout=PIPE,
)
assert n.nixos_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"],
stdout=PIPE,
)
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_nixos_remote_build_flake(mock_run: Any) -> None:
flake = m.Flake.parse(".#hostname")
build_host = m.Remote("user@host", ["--ssh", "opts"], None)
assert n.nixos_remote_build_flake(
"toplevel",
flake,
build_host,
flake_build_flags={"flake": True},
copy_flags={"copy": True},
build_flags={"build": True},
) == Path("/path/to/file")
mock_run.assert_has_calls(
[
call(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"eval",
"--raw",
".#nixosConfigurations.hostname.config.system.build.toplevel.drvPath",
"--flake",
],
stdout=PIPE,
),
call(
[
"nix-copy-closure",
"--copy",
"--to",
"user@host",
Path("/path/to/file"),
],
extra_env={"NIX_SSHOPTS": "--ssh opts"},
remote=None,
),
call(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"build",
"/path/to/file^*",
"--print-out-paths",
"--build",
],
remote=build_host,
stdout=PIPE,
),
]
)
@patch(
get_qualified_name(n.run_wrapper, n),
autospec=True,
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_nixos_remote_build(mock_run: Any, monkeypatch: Any) -> None:
build_host = m.Remote("user@host", ["--ssh", "opts"], None)
assert n.nixos_remote_build(
"attr",
m.BuildAttr("<nixpkgs/nixos>", "preAttr"),
build_host,
build_flags={"build": True},
instantiate_flags={"inst": True},
copy_flags={"copy": True},
) == Path("/path/to/file")
mock_run.assert_has_calls(
[
call(
[
"nix-instantiate",
"--raw",
"<nixpkgs/nixos>",
"--attr",
"preAttr.config.system.build.attr",
"--inst",
],
stdout=PIPE,
),
call(
[
"nix-copy-closure",
"--copy",
"--to",
"user@host",
Path("/path/to/file"),
],
extra_env={"NIX_SSHOPTS": "--ssh opts"},
remote=None,
),
call(
["nix-store", "--realise", Path("/path/to/file"), "--build"],
remote=build_host,
stdout=PIPE,
),
]
)
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
def test_repl(mock_run: Any) -> None:
n.repl("attr", m.BuildAttr("<nixpkgs/nixos>", None), nix_flag=True)