Unverified Commit 8ae48733 authored by Thiago Kenji Okada's avatar Thiago Kenji Okada Committed by GitHub
Browse files

nixos-rebuild-ng: refactors (#368619)

parents 24f42ee6 f25fb0d3
Loading
Loading
Loading
Loading
+41 −33
Original line number Diff line number Diff line
@@ -43,19 +43,19 @@ 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")

    flake_build_flags = argparse.ArgumentParser(add_help=False)
    flake_build_flags.add_argument("--accept-flake-config", action="store_true")
    flake_build_flags.add_argument("--refresh", action="store_true")
    flake_build_flags.add_argument("--impure", action="store_true")
    flake_build_flags.add_argument("--offline", action="store_true")
    flake_build_flags.add_argument("--no-net", action="store_true")
    flake_build_flags.add_argument("--recreate-lock-file", action="store_true")
    flake_build_flags.add_argument("--no-update-lock-file", action="store_true")
    flake_build_flags.add_argument("--no-write-lock-file", action="store_true")
    flake_build_flags.add_argument("--no-registries", action="store_true")
    flake_build_flags.add_argument("--commit-lock-file", action="store_true")
    flake_build_flags.add_argument("--update-input")
    flake_build_flags.add_argument("--override-input", nargs=2)
    flake_common_flags = argparse.ArgumentParser(add_help=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")
    flake_common_flags.add_argument("--override-input", nargs=2)

    classic_build_flags = argparse.ArgumentParser(add_help=False)
    classic_build_flags.add_argument("--no-build-output", "-Q", action="store_true")
@@ -74,7 +74,7 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa
    sub_parsers = {
        "common_flags": common_flags,
        "common_build_flags": common_build_flags,
        "flake_build_flags": flake_build_flags,
        "flake_common_flags": flake_common_flags,
        "classic_build_flags": classic_build_flags,
        "copy_flags": copy_flags,
    }
@@ -262,8 +262,8 @@ def parse_args(
def reexec(
    argv: list[str],
    args: argparse.Namespace,
    build_flags: dict[str, Args],
    flake_build_flags: dict[str, Args],
    build_flags: Args,
    flake_build_flags: Args,
) -> None:
    drv = None
    attr = "config.system.build.nixos-rebuild"
@@ -271,10 +271,18 @@ def reexec(
        # Parsing the args here but ignore ask_sudo_password since it is not
        # needed and we would end up asking sudo password twice
        if flake := Flake.from_arg(args.flake, Remote.from_arg(args.target_host, None)):
            drv = nix.build_flake(attr, flake, **flake_build_flags, no_link=True)
            drv = nix.build_flake(
                attr,
                flake,
                flake_build_flags | {"no_link": True},
            )
        else:
            build_attr = BuildAttr.from_arg(args.attr, args.file)
            drv = nix.build(attr, build_attr, **build_flags, no_out_link=True)
            drv = nix.build(
                attr,
                build_attr,
                build_flags | {"no_out_link": True},
            )
    except CalledProcessError:
        logger.warning(
            "could not build a newer version of nixos-rebuild, "
@@ -319,7 +327,8 @@ def execute(argv: list[str]) -> None:
    common_flags = vars(args_groups["common_flags"])
    common_build_flags = common_flags | vars(args_groups["common_build_flags"])
    build_flags = common_build_flags | vars(args_groups["classic_build_flags"])
    flake_build_flags = common_build_flags | vars(args_groups["flake_build_flags"])
    flake_common_flags = common_flags | vars(args_groups["flake_common_flags"])
    flake_build_flags = common_build_flags | flake_common_flags
    copy_flags = common_flags | vars(args_groups["copy_flags"])

    if args.upgrade or args.upgrade_all:
@@ -350,7 +359,7 @@ def execute(argv: list[str]) -> None:
    flake = Flake.from_arg(args.flake, target_host)

    if can_run and not flake:
        nixpkgs_path = nix.find_file("nixpkgs", **build_flags)
        nixpkgs_path = nix.find_file("nixpkgs", build_flags)
        rev = nix.get_nixpkgs_rev(nixpkgs_path)
        if nixpkgs_path and rev:
            (nixpkgs_path / ".version-suffix").write_text(rev)
@@ -370,7 +379,10 @@ def execute(argv: list[str]) -> None:

            dry_run = action == Action.DRY_BUILD
            no_link = action in (Action.SWITCH, Action.BOOT)
            build_flags |= {"no_out_link": no_link, "dry_run": dry_run}
            flake_build_flags |= {"no_link": no_link, "dry_run": dry_run}
            rollback = bool(args.rollback)

            match action:
                case Action.BUILD_VM:
                    attr = "config.system.build.vm"
@@ -395,24 +407,22 @@ def execute(argv: list[str]) -> None:
                case (_, True, _, _):
                    raise NRError(f"--rollback is incompatible with '{action}'")
                case (_, False, Remote(_), Flake(_)):
                    path_to_config = nix.remote_build_flake(
                    path_to_config = nix.build_remote_flake(
                        attr,
                        flake,
                        build_host,
                        eval_flags=flake_common_flags,
                        flake_build_flags=flake_build_flags,
                        copy_flags=copy_flags,
                        build_flags=build_flags,
                    )
                case (_, False, None, Flake(_)):
                    path_to_config = nix.build_flake(
                        attr,
                        flake,
                        no_link=no_link,
                        dry_run=dry_run,
                        **flake_build_flags,
                        flake_build_flags=flake_build_flags,
                    )
                case (_, False, Remote(_), None):
                    path_to_config = nix.remote_build(
                    path_to_config = nix.build_remote(
                        attr,
                        build_attr,
                        build_host,
@@ -424,9 +434,7 @@ def execute(argv: list[str]) -> None:
                    path_to_config = nix.build(
                        attr,
                        build_attr,
                        no_out_link=no_link,
                        dry_run=dry_run,
                        **build_flags,
                        build_flags=build_flags,
                    )
                case never:
                    # should never happen, but mypy is not smart enough to
@@ -442,7 +450,7 @@ def execute(argv: list[str]) -> None:
                    path_to_config,
                    to_host=target_host,
                    from_host=build_host,
                    **copy_flags,
                    copy_flags=copy_flags,
                )
                if action in (Action.SWITCH, Action.BOOT):
                    nix.set_profile(
@@ -468,7 +476,7 @@ def execute(argv: list[str]) -> None:
                    f"Done. The virtual machine can be started by running '{vm_path}'"
                )
        case Action.EDIT:
            nix.edit(flake, **flake_build_flags)
            nix.edit(flake, flake_build_flags)
        case Action.DRY_RUN:
            assert False, "DRY_RUN should be a DRY_BUILD alias"
        case Action.LIST_GENERATIONS:
@@ -488,9 +496,9 @@ def execute(argv: list[str]) -> None:
                print(tabulate(generations, headers=headers))
        case Action.REPL:
            if flake:
                nix.repl_flake("toplevel", flake, **flake_build_flags)
                nix.repl_flake("toplevel", flake, flake_build_flags)
            else:
                nix.repl("system", build_attr, **build_flags)
                nix.repl("system", build_attr, build_flags)
        case _:
            assert_never(action)

+22 −21
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
def build(
    attr: str,
    build_attr: BuildAttr,
    **build_flags: Args,
    build_flags: Args | None = None,
) -> Path:
    """Build NixOS attribute using classic Nix.

@@ -52,7 +52,7 @@ def build(
def build_flake(
    attr: str,
    flake: Flake,
    **flake_build_flags: Args,
    flake_build_flags: Args | None = None,
) -> Path:
    """Build NixOS attribute using Flakes.

@@ -70,13 +70,13 @@ def build_flake(
    return Path(r.stdout.strip())


def remote_build(
def build_remote(
    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,
    build_flags: Args | None = None,
    instantiate_flags: Args | None = None,
    copy_flags: Args | None = None,
) -> Path:
    # We need to use `--add-root` otherwise Nix will print this warning:
    # > warning: you did not specify '--add-root'; the result might be removed
@@ -89,12 +89,12 @@ def remote_build(
            build_attr.to_attr(attr),
            "--add-root",
            tmpdir.TMPDIR_PATH / uuid4().hex,
            *dict_to_flags(instantiate_flags or {}),
            *dict_to_flags(instantiate_flags),
        ],
        stdout=PIPE,
    )
    drv = Path(r.stdout.strip()).resolve()
    copy_closure(drv, to_host=build_host, from_host=None, **(copy_flags or {}))
    copy_closure(drv, to_host=build_host, from_host=None, copy_flags=copy_flags)

    # Need a temporary directory in remote to use in `nix-store --add-root`
    r = run_wrapper(
@@ -109,7 +109,7 @@ def remote_build(
                drv,
                "--add-root",
                remote_tmpdir / uuid4().hex,
                *dict_to_flags(build_flags or {}),
                *dict_to_flags(build_flags),
            ],
            remote=build_host,
            stdout=PIPE,
@@ -124,13 +124,13 @@ def remote_build(
        run_wrapper(["rm", "-rf", remote_tmpdir], remote=build_host, check=False)


def remote_build_flake(
def build_remote_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,
    eval_flags: Args | None = None,
    copy_flags: Args | None = None,
    flake_build_flags: Args | None = None,
) -> Path:
    r = run_wrapper(
        [
@@ -139,12 +139,12 @@ def remote_build_flake(
            "eval",
            "--raw",
            flake.to_attr(attr, "drvPath"),
            *dict_to_flags(flake_build_flags or {}),
            *dict_to_flags(eval_flags),
        ],
        stdout=PIPE,
    )
    drv = Path(r.stdout.strip())
    copy_closure(drv, to_host=build_host, from_host=None, **(copy_flags or {}))
    copy_closure(drv, to_host=build_host, from_host=None, copy_flags=copy_flags)
    r = run_wrapper(
        [
            "nix",
@@ -152,7 +152,7 @@ def remote_build_flake(
            "build",
            f"{drv}^*",
            "--print-out-paths",
            *dict_to_flags(build_flags or {}),
            *dict_to_flags(flake_build_flags),
        ],
        remote=build_host,
        stdout=PIPE,
@@ -164,7 +164,7 @@ def copy_closure(
    closure: Path,
    to_host: Remote | None,
    from_host: Remote | None = None,
    **copy_flags: Args,
    copy_flags: Args | None = None,
) -> None:
    """Copy a nix closure to or from host to localhost.

@@ -192,6 +192,7 @@ def copy_closure(
            [
                "nix",
                "copy",
                *dict_to_flags(copy_flags),
                "--from",
                f"ssh://{from_host.host}",
                "--to",
@@ -221,7 +222,7 @@ def copy_closure(
                nix_copy_closure(to_host, to=True)


def edit(flake: Flake | None, **flake_flags: Args) -> None:
def edit(flake: Flake | None, flake_flags: Args | None = None) -> None:
    "Try to find and open NixOS configuration file in editor."
    if flake:
        run_wrapper(
@@ -250,7 +251,7 @@ def edit(flake: Flake | None, **flake_flags: Args) -> None:
            raise NRError("cannot find NixOS config file")


def find_file(file: str, **nix_flags: Args) -> Path | 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)],
@@ -420,14 +421,14 @@ def list_generations(profile: Profile) -> list[GenerationJson]:
        )


def repl(attr: str, build_attr: BuildAttr, **nix_flags: Args) -> None:
def repl(attr: str, build_attr: BuildAttr, nix_flags: Args | None = None) -> None:
    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)])


def repl_flake(attr: str, flake: Flake, **flake_flags: Args) -> None:
def repl_flake(attr: str, flake: Flake, flake_flags: Args | None = None) -> None:
    expr = Template(
        files(__package__).joinpath(FLAKE_REPL_TEMPLATE).read_text()
    ).substitute(
+7 −3
Original line number Diff line number Diff line
import logging
from collections.abc import Mapping, Sequence
from typing import Any, TypeAlias, assert_never, override
from typing import Any, assert_never, override

Args: TypeAlias = bool | str | list[str] | int | None
type Arg = bool | str | list[str] | int | None
type Args = dict[str, Arg]


class LogFormatter(logging.Formatter):
@@ -19,7 +20,10 @@ class LogFormatter(logging.Formatter):
        return formatter.format(record)


def dict_to_flags(d: Mapping[str, Args]) -> list[str]:
def dict_to_flags(d: Args | None) -> list[str]:
    if not d:
        return []

    flags = []
    for key, value in d.items():
        flag = f"--{'-'.join(key.split('_'))}"
+3 −2
Original line number Diff line number Diff line
@@ -123,8 +123,8 @@ def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None:
                    "<nixpkgs/nixos>",
                    "--attr",
                    "config.system.build.toplevel",
                    "--no-out-link",
                    "-vvv",
                    "--no-out-link",
                ],
                check=True,
                stdout=PIPE,
@@ -188,8 +188,8 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
                    "build",
                    "--print-out-paths",
                    "/path/to/config#nixosConfigurations.hostname.config.system.build.toplevel",
                    "--no-link",
                    "-v",
                    "--no-link",
                ],
                check=True,
                stdout=PIPE,
@@ -371,6 +371,7 @@ def test_execute_nix_switch_flake_build_host(
                    "build",
                    f"'{config_path}^*'",
                    "--print-out-paths",
                    "--no-link",
                ],
                check=True,
                stdout=PIPE,
+17 −15
Original line number Diff line number Diff line
@@ -21,7 +21,9 @@ from .helpers import get_qualified_name
)
def test_build(mock_run: Any, monkeypatch: Any) -> None:
    assert n.build(
        "config.system.build.attr", m.BuildAttr("<nixpkgs/nixos>", None), nix_flag="foo"
        "config.system.build.attr",
        m.BuildAttr("<nixpkgs/nixos>", None),
        {"nix_flag": "foo"},
    ) == Path("/path/to/file")
    mock_run.assert_called_with(
        [
@@ -55,8 +57,7 @@ def test_build_flake(mock_run: Any) -> None:
    assert n.build_flake(
        "config.system.build.toplevel",
        flake,
        no_link=True,
        nix_flag="foo",
        {"no_link": True, "nix_flag": "foo"},
    ) == Path("/path/to/file")
    mock_run.assert_called_with(
        [
@@ -76,7 +77,7 @@ def test_build_flake(mock_run: Any) -> None:

@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
@patch(get_qualified_name(n.uuid4, n), autospec=True)
def test_remote_build(mock_uuid4: Any, mock_run: Any, monkeypatch: Any) -> None:
def test_build_remote(mock_uuid4: Any, mock_run: Any, monkeypatch: Any) -> None:
    build_host = m.Remote("user@host", [], None)
    monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")

@@ -97,7 +98,7 @@ def test_remote_build(mock_uuid4: Any, mock_run: Any, monkeypatch: Any) -> None:
    mock_run.side_effect = run_wrapper_side_effect
    mock_uuid4.side_effect = [uuid.UUID(int=1), uuid.UUID(int=2)]

    assert n.remote_build(
    assert n.build_remote(
        "config.system.build.toplevel",
        m.BuildAttr("<nixpkgs/nixos>", "preAttr"),
        build_host,
@@ -164,18 +165,18 @@ def test_remote_build(mock_uuid4: Any, mock_run: Any, monkeypatch: Any) -> None:
    autospec=True,
    return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
)
def test_remote_build_flake(mock_run: Any, monkeypatch: Any) -> None:
def test_build_remote_flake(mock_run: Any, monkeypatch: Any) -> None:
    flake = m.Flake.parse(".#hostname")
    build_host = m.Remote("user@host", [], None)
    monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")

    assert n.remote_build_flake(
    assert n.build_remote_flake(
        "config.system.build.toplevel",
        flake,
        build_host,
        flake_build_flags={"flake": True},
        eval_flags={"flake": True},
        copy_flags={"copy": True},
        build_flags={"build": True},
        flake_build_flags={"build": True},
    ) == Path("/path/to/file")
    mock_run.assert_has_calls(
        [
@@ -237,9 +238,9 @@ def test_copy_closure(monkeypatch: Any) -> None:

    monkeypatch.setenv("NIX_SSHOPTS", "--ssh build-opt")
    with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
        n.copy_closure(closure, None, build_host)
        n.copy_closure(closure, None, build_host, {"copy_flag": True})
        mock_run.assert_called_with(
            ["nix-copy-closure", "--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"])
            },
@@ -251,11 +252,12 @@ def test_copy_closure(monkeypatch: Any) -> None:
        "NIX_SSHOPTS": " ".join(p.SSH_DEFAULT_OPTS + ["--ssh build-target-opt"])
    }
    with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
        n.copy_closure(closure, target_host, build_host)
        n.copy_closure(closure, target_host, build_host, {"copy_flag": True})
        mock_run.assert_called_with(
            [
                "nix",
                "copy",
                "--copy-flag",
                "--from",
                "ssh://user@build.host",
                "--to",
@@ -286,7 +288,7 @@ def test_copy_closure(monkeypatch: Any) -> None:
def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None:
    # Flake
    flake = m.Flake.parse(".#attr")
    n.edit(flake, commit_lock_file=True)
    n.edit(flake, {"commit_lock_file": True})
    mock_run.assert_called_with(
        [
            "nix",
@@ -471,7 +473,7 @@ def test_list_generations(mock_get_generations: Any, tmp_path: Path) -> None:

@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)
    n.repl("attr", m.BuildAttr("<nixpkgs/nixos>", None), {"nix_flag": True})
    mock_run.assert_called_with(
        ["nix", "repl", "--file", "<nixpkgs/nixos>", "--nix-flag"]
    )
@@ -482,7 +484,7 @@ def test_repl(mock_run: Any) -> None:

@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
def test_repl_flake(mock_run: Any) -> None:
    n.repl_flake("attr", m.Flake(Path("flake.nix"), "myAttr"), nix_flag=True)
    n.repl_flake("attr", m.Flake(Path("flake.nix"), "myAttr"), {"nix_flag": True})
    # See nixos-rebuild-ng.tests.repl for a better test,
    # this is mostly for sanity check
    assert mock_run.call_count == 1
Loading