nixos-rebuild-ng: add --diff argument (#489208)

This commit is contained in:
Thiago Kenji Okada
2026-02-12 19:56:34 +00:00
committed by GitHub
5 changed files with 79 additions and 0 deletions
@@ -308,6 +308,14 @@ It must be one of the following:
option, it is possible to build non-flake NixOS configurations even if
the current NixOS systems uses flakes.
*--diff*
show the diff between the system closure in /run/current-system
and the newly built system closure.
(avaliable for actions: build, boot, test, switch)
This is similar to running:
"nix store diff-closures /run/current-system result" after build
In addition, *nixos-rebuild* accepts following options from nix commands that
the tool calls:
@@ -197,6 +197,12 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa
help="Selects an image variant to build from the "
"config.system.build.images attribute of the given configuration",
)
main_parser.add_argument(
"--diff",
action="store_true",
help="prints out the diff between the current system "
"and the newly built one using nix store diff-closures"
)
main_parser.add_argument("action", choices=Action.values(), nargs="?")
return main_parser, sub_parsers
@@ -259,6 +265,20 @@ def parse_args(
if args.no_build_nix:
parser_warn("--no-build-nix is deprecated, we do not build nix anymore")
if args.diff and args.action not in (
# case for calling build_and_activate_system
# except excluding DRY_BUILD and DRY_ACTIVATE,
# in which --diff is uniquely a no-op
Action.SWITCH.value,
Action.BOOT.value,
Action.TEST.value,
Action.BUILD.value,
Action.BUILD_IMAGE.value,
Action.BUILD_VM.value,
Action.BUILD_VM_WITH_BOOTLOADER.value,
):
parser_warn(f"--diff is a no-op with '{args.action}'")
if args.action == Action.EDIT.value and (args.file or args.attr):
parser.error("--file and --attr are not supported with 'edit'")
@@ -1,3 +1,4 @@
import sys
import json
import logging
import os
@@ -537,6 +538,25 @@ def list_generations(profile: Profile) -> list[GenerationJson]:
reverse=True,
)
def diff_closures(current_config: Path, new_config: Path, target_host: Remote | None = None):
print(
f"<<< {current_config}\n"
f">>> {new_config}",
file=sys.stderr
)
run_wrapper(
[
"nix",
*FLAKE_FLAGS,
"store",
"diff-closures",
current_config,
new_config,
],
remote=target_host,
stdout=sys.stderr
)
def repl(build_attr: BuildAttr, nix_flags: Args | None = None) -> None:
run_args = ["nix", "repl", "--file", build_attr.path]
@@ -321,6 +321,13 @@ def build_and_activate_system(
grouped_nix_args=grouped_nix_args,
)
current_config = Path("/run/current-system")
if args.diff:
if current_config.exists():
nix.diff_closures(current_config=current_config.readlink(), new_config=path_to_config, target_host=target_host)
else:
logger.warning(f"missing '{str(current_config)}', skipping configuration diff...")
_activate_system(
path_to_config=path_to_config,
action=action,
@@ -1,3 +1,4 @@
import sys
import textwrap
import uuid
from pathlib import Path
@@ -550,6 +551,29 @@ def test_list_generations(mock_get_generations: Mock, tmp_path: Path) -> None:
]
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
def test_diff_closures(mock_run: Mock) -> None:
assert n.diff_closures(
Path("/run/current-system"),
Path("/nix/var/nix/profiles/system"),
None
) == None
mock_run.assert_called_with(
[
"nix",
"--extra-experimental-features",
"nix-command flakes",
"store",
"diff-closures",
Path("/run/current-system"),
Path("/nix/var/nix/profiles/system"),
],
remote=None,
stdout=sys.stderr
)
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
def test_repl(mock_run: Mock) -> None:
n.repl(m.BuildAttr("<nixpkgs/nixos>", None), {"nix_flag": True})