diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 44054818178c..9ff21dd0b162 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -4,6 +4,32 @@ +- The system.nix file has been added has an alternative entry point to configuration.nix (and flake.nix) that allows to configure NixOS without using `nix-channel`. + This file must evaluate to a NixOS system derivation or an attribute set of such derivations, in which case the attribute to build has to be selected with the `--attr` option of `nixos-rebuild` or `nixos-install`. + For example, + ```nix + # system.nix + let + # Pinned Nixpkgs archive + # + # Use `curl -I https://channels.nixos.org/nixos-26.05` to get the + # latest commit of the stable channel and `nix-prefetch-url --unpack` + # to compute its sha256 hash. + nixpkgs = builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/c217913993d6.tar.gz"; + sha256 = "026mprs324330pfazlgbw987qmsa8ligglarvqbcxzig2kgw0lqg"; + }; + in + import "${nixpkgs}/nixos" { + # Build NixOS using an external configuration.nix file, + # or directly set your options here + configuration = ./configuration.nix; + } + ``` + + The default location of system.nix is `/etc/nixos/system.nix` and can be changed by setting the `` search path. + `nixos-rebuild` and `nixos-install` can now also load a system.nix file in the current directory (only if `--attr` is used) or from a directory specified with `--file`. + - The default kernel package has been updated from 6.12 to 6.18. All supported kernels remain available. ## New Modules {#sec-release-26.05-new-modules} diff --git a/pkgs/by-name/ni/nixos-install-tools/package.nix b/pkgs/by-name/ni/nixos-install-tools/package.nix index 03be3c9ea39b..98588f4abc54 100644 --- a/pkgs/by-name/ni/nixos-install-tools/package.nix +++ b/pkgs/by-name/ni/nixos-install-tools/package.nix @@ -56,9 +56,9 @@ in ''; } '' - nixos-install --help | grep -F 'NixOS Reference Pages' + nixos-install --help | grep -F "System Manager's Manual" nixos-install --help | grep -F 'configuration.nix' - nixos-generate-config --help | grep -F 'NixOS Reference Pages' + nixos-generate-config --help | grep -F "System Manager's Manual" nixos-generate-config --help | grep -F 'hardware-configuration.nix' # FIXME: Tries to call unshare, which it must not do for --help diff --git a/pkgs/by-name/ni/nixos-install/nixos-install.8 b/pkgs/by-name/ni/nixos-install/nixos-install.8 index 5b4f8c936720..186c85566416 100644 --- a/pkgs/by-name/ni/nixos-install/nixos-install.8 +++ b/pkgs/by-name/ni/nixos-install/nixos-install.8 @@ -33,9 +33,19 @@ .Sh DESCRIPTION This command installs NixOS in the file system mounted on .Pa /mnt Ns -, based on the NixOS configuration specified in -.Pa /mnt/etc/nixos/configuration.nix Ns -\&. It performs the following steps: +, or defined through the +.Fl -root +option, based on the NixOS configuration specified in +.Pa /mnt/etc/nixos/system.nix Ns +, +.Pa /mnt/etc/nixos/configuration.nix +or specified through the +.Fl -file Ns +, +.Fl -attr Ns +or +.Fl -flake Ns +options. It performs the following steps: . .Bl -enum .It @@ -46,9 +56,7 @@ It copies Nix and its dependencies to .It It runs Nix in .Pa /mnt -to build the NixOS configuration specified in -.Pa /mnt/etc/nixos/configuration.nix Ns -\&. +to build the given NixOS configuration. . .It It installs the current channel @@ -114,7 +122,7 @@ output named \&. . .It Fl -file Ar path , Fl f Ar path -Enable and build the NixOS system from the specified file. The file must +Build the NixOS system from the specified file. The file must evaluate to an attribute set, and it must contain a valid NixOS configuration at attribute .Va attrPath Ns @@ -127,17 +135,19 @@ function in nixpkgs or importing and calling from nixpkgs. If specified without .Fl -attr option, builds the configuration from the top-level -attribute of the file. +attribute set of the file. . .It Fl -attr Ar attrPath , Fl A Ar attrPath -Enable and build the NixOS system from nix file and use the specified attribute +Build the NixOS system from nix file and use the specified attribute path from file specified by the .Fl -file option. If specified without .Fl -file -option, uses -.Va [root] Ns Pa /etc/nixos/default.nix Ns -\&. +option, it tires to find +.Pa system.nix +in +.Va root Ns Pa /etc/nixos Ns +, in current directory and iteratively in parent directories. . .It Fl -channel Ar channel If this option is provided, do not copy the current diff --git a/pkgs/by-name/ni/nixos-install/nixos-install.sh b/pkgs/by-name/ni/nixos-install/nixos-install.sh index fe3a8bf26d45..c9445c36cc7d 100755 --- a/pkgs/by-name/ni/nixos-install/nixos-install.sh +++ b/pkgs/by-name/ni/nixos-install/nixos-install.sh @@ -19,7 +19,8 @@ system= verbosity=() attr= buildFile= -buildingAttribute=1 +# keys: module, by-attrset, flake, system +declare -A requestedBuildMethods while [ "$#" -gt 0 ]; do i="$1"; shift 1 @@ -38,10 +39,12 @@ while [ "$#" -gt 0 ]; do ;; --system|--closure|--store-path) system="$1"; shift 1 + requestedBuildMethods["system"]=1 ;; --flake) flake="$1" flakeFlags=(--extra-experimental-features 'nix-command flakes') + requestedBuildMethods["flake"]=1 shift 1 ;; --file|-f) @@ -49,8 +52,14 @@ while [ "$#" -gt 0 ]; do log "$0: '$i' requires an argument" exit 1 fi - buildFile="$1" - buildingAttribute= + if [ -f "$1" ]; then + buildFile="$1" + elif [ -f "$1/system.nix" ]; then + buildFile="${1%/}/system.nix" + else + buildFile="${1%/}/default.nix" + fi + requestedBuildMethods["by-attrset"]=1 shift 1 ;; --attr|-A) @@ -59,7 +68,7 @@ while [ "$#" -gt 0 ]; do exit 1 fi attr="$1" - buildingAttribute= + requestedBuildMethods["by-attrset"]=1 shift 1 ;; --recreate-lock-file|--no-update-lock-file|--no-write-lock-file|--no-registries|--commit-lock-file) @@ -106,6 +115,20 @@ while [ "$#" -gt 0 ]; do esac done +# Finds a specific file in a directory or its parents +findInParents() { + local dir=$1 + local filename=$2 + while [[ ! -f "$dir/$filename" ]] && [[ "$dir" != / ]]; do + dir=$(dirname "$dir") + done + if [[ -f "$dir/$filename" ]]; then + echo "$dir/$filename" + else + return 1 + fi +} + if ! test -e "$mountPoint"; then echo "mount point $mountPoint doesn't exist" exit 1 @@ -122,30 +145,61 @@ while [[ "$checkPath" != "/" ]]; do checkPath="$(dirname "$checkPath")" done -# Verify that user is not trying to use attribute building and flake -# at the same time -if [[ -z $buildingAttribute && -n $flake ]]; then - echo "$0: '--flake' cannot be used with '--file' or '--attr'" +# If user requested multiple build methods, abort +if [[ ${#requestedBuildMethods[@]} -gt 1 ]]; then + echo "error: multiple build methods requested: ${!requestedBuildMethods[@]}" exit 1 fi # Get the path of the NixOS configuration file. -if [[ -z $flake && -n $buildingAttribute ]]; then - if [[ -z $NIXOS_CONFIG ]]; then - NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix +findByAttrset() { + # - From system.nix up from the current directory + # - Hardcoded to $mountPoint/etc/nixos/system.nix + # - Hardcoded to /etc/nixos/system.nix + # (default.nix is also searched for backward compatibility) + if resolvedBuildFile="$(findInParents "$PWD" system.nix)"; then + buildFile=$resolvedBuildFile + return 0 + elif resolvedBuildFile="$(findInParents "$PWD" default.nix)"; then + buildFile=$resolvedBuildFile + return 0 + elif [[ -f "/etc/nixos/system.nix" ]]; then + buildFile="/etc/nixos/system.nix" + return 0 + elif [[ -f "/etc/nixos/default.nix" ]]; then + buildFile="/etc/nixos/default.nix" + return 0 fi + return 1 +} - if [[ ${NIXOS_CONFIG:0:1} != / ]]; then - echo "$0: \$NIXOS_CONFIG is not an absolute path" +findModule() { + # - From NIXOS_CONFIG environment variable + # - hardcoded to $mountPoint/etc/nixos/configuration.nix + if [[ -n $NIXOS_CONFIG ]]; then + return 0 + elif [[ -f "$mountPoint/etc/nixos/configuration.nix" ]]; then + NIXOS_CONFIG="$mountPoint/etc/nixos/configuration.nix" + return 0 + fi + return 1 +} + +# If user didn't request a specific build method, try to find one +if [[ ${#requestedBuildMethods[@]} -eq 0 ]]; then + # Flakes cannot be resolved without knowing hostname + if findByAttrset; then + requestedBuildMethods["by-attrset"]=1 + elif findModule; then + requestedBuildMethods["module"]=1 + else + echo "error: no build method found" exit 1 fi -elif [[ -z $buildingAttribute ]]; then - if [[ -z $buildFile ]]; then - buildFile="$mountPoint/etc/nixos/default.nix" - elif [[ -d $buildFile ]]; then - buildFile="$buildFile/default.nix" - fi -elif [[ -n $flake ]]; then +fi + +# Find configuration files if not already found +if [[ -n "${requestedBuildMethods["flake"]}" ]]; then if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then flake="${BASH_REMATCH[1]}" flakeAttr="${BASH_REMATCH[2]}" @@ -156,19 +210,28 @@ elif [[ -n $flake ]]; then exit 1 fi flakeAttr="nixosConfigurations.\"$flakeAttr\"" +elif [[ -n "${requestedBuildMethods["by-attrset"]}" && -z $buildFile ]]; then + findByAttrset +elif [[ -n "${requestedBuildMethods["module"]}" && -z $NIXOS_CONFIG ]]; then + findModule + + if [[ ${NIXOS_CONFIG:0:1} != / ]]; then + echo "$0: \$NIXOS_CONFIG is not an absolute path" + exit 1 + fi fi # Resolve the flake. -if [[ -n $flake ]]; then +if [[ -n "${requestedBuildMethods["flake"]}" ]]; then flake=$(nix "${flakeFlags[@]}" flake metadata --json "${extraBuildFlags[@]}" "${lockFlags[@]}" -- "$flake" | jq -r .url) fi -if [[ ! -e $NIXOS_CONFIG && -z $system && -z $flake && -n $buildingAttribute ]]; then +if [[ ! -e $NIXOS_CONFIG && -n "${requestedBuildMethods["module"]}" ]]; then echo "configuration file $NIXOS_CONFIG doesn't exist" exit 1 fi -if [[ ! -z $buildingAttribute && -e $buildFile && -z $system ]]; then +if [[ ! -e $buildFile && -n "${requestedBuildMethods["by-attrset"]}" ]]; then echo "configuration file $buildFile doesn't exist" exit 1 fi @@ -202,12 +265,12 @@ fi # Build the system configuration in the target filesystem. if [[ -z $system ]]; then outLink="$tmpdir/system" - if [[ -z $flake && -n $buildingAttribute ]]; then + if [[ -n "${requestedBuildMethods["module"]}" ]]; then echo "building the configuration in $NIXOS_CONFIG..." nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \ --extra-substituters "$sub" \ '' -A system -I "nixos-config=$NIXOS_CONFIG" "${verbosity[@]}" - elif [[ -z $buildingAttribute ]]; then + elif [[ -n "${requestedBuildMethods["by-attrset"]}" ]]; then if [[ -n $attr ]]; then echo "building the configuration in $buildFile and attribute $attr..." else @@ -216,7 +279,7 @@ if [[ -z $system ]]; then nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \ --extra-substituters "$sub" \ "$buildFile" -A "${attr:+$attr.}config.system.build.toplevel" "${verbosity[@]}" - else + else # [[ -n "${requestedBuildMethods["flake"]}" ]] echo "building the flake in $flake..." nix "${flakeFlags[@]}" build "$flake#$flakeAttr.config.system.build.toplevel" \ --store "$mountPoint" --extra-substituters "$sub" "${verbosity[@]}" \ diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd b/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd index b2298ce10639..d4f83ac9d803 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd +++ b/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd @@ -28,11 +28,12 @@ _nixos-rebuild_ \[--verbose] [--quiet] [--max-jobs MAX_JOBS] [--cores CORES] [-- # DESCRIPTION This command updates the system so that it corresponds to the configuration -specified in /etc/nixos/configuration.nix, /etc/nixos/flake.nix or the file and -attribute specified by the *--file* and/or *--attr* options. Thus, every +specified in /etc/nixos/configuration.nix, /etc/nixos/flake.nix, +/etc/nixos/system.nix or the file and attribute specified by the *--file*, +*--attr* or *--flake* options. Thus, every time you modify the configuration or any other NixOS module, you must run *nixos-rebuild* to make the changes take effect. It builds the new system in -/nix/store, runs its activation script, and stop and (re)starts any system +/nix/store, runs its activation script, stops and (re)starts any system services if needed. Please note that user services need to be started manually as they aren't detected by the activation script at the moment. @@ -281,20 +282,21 @@ It must be one of the following: Implies *--sudo*. *--file* _path_, *-f* _path_ - Enable and build the NixOS system from the specified file. The file must + Build the NixOS system from the specified file. The file must evaluate to an attribute set, and it must contain a valid NixOS configuration at attribute _attrPath_. This is useful for building a NixOS system from a nix file that is not a flake or a NixOS configuration module. Attribute set a with valid NixOS configuration can be made using _nixos_ function in nixpkgs or importing and calling nixos/lib/eval-config.nix from nixpkgs. If specified without *--attr* - option, builds the configuration from the top-level attribute of the + option, builds the configuration from the top-level attribute set of the file. *--attr* _attrPath_, *-A* _attrPath_ - Enable and build the NixOS system from nix file and use the specified - attribute path from file specified by the *--file* option. If specified - without *--file* option, uses _default.nix_ in current directory. + Build the NixOS system from a nix file and use the specified + attribute path from the file specified by the *--file* option. + If specified without *--file* option, uses _system.nix_ in current directory, + the system-wide __ file, or finally, /etc/nixos/system.nix. *--flake* _flake-uri[#name]_, *-F* _flake-uri[#name]_ Build the NixOS system from the specified flake. It defaults to the @@ -355,6 +357,11 @@ NIX_SUDOOPTS # FILES +/etc/nixos/system.nix + If this file exists, then *nixos-rebuild* will use it as if the + *--file* option was given. This allows to build a self-contained + system configuration, without requiring ‘nixos’ channel. + /etc/nixos/flake.nix If this file exists, then *nixos-rebuild* will use it as if the *--flake* option was given. This file may be a symlink to a diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/models.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/models.py index 7fb37e8f97a9..ad28e368c2f5 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/models.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/models.py @@ -7,6 +7,7 @@ from enum import Enum from pathlib import Path from typing import Any, ClassVar, Self, TypedDict, override +from . import nix from .process import Remote, run_wrapper from .utils import Args @@ -58,9 +59,31 @@ class BuildAttr: @classmethod def from_arg(cls, attr: str | None, file: str | None) -> Self: - if not (attr or file): - return cls("", None) - return cls(Path(file or "default.nix"), attr) + # We use, in this order: + # 1. the --file argument (can be a directory, implying /system.nix) + # 2. system.nix in the cwd, but only if --attr is used + # 3. the Nix path entry + # 4. /etc/nixos/system.nix + # 5. the Nix path entry (uses configuration.nix) + + if file: + fpath = Path(file) + if fpath.is_dir() and (fpath / "system.nix").exists(): + return cls(fpath / "system.nix", attr) + # Backward compatibility + elif fpath.is_dir() and (fpath / "default.nix").exists(): + return cls(fpath / "default.nix", attr) + return cls(fpath, attr) + elif attr and Path("system.nix").exists(): + return cls(Path("system.nix"), attr) + elif attr and Path("default.nix").exists(): + # Backward compatibility + return cls(Path("default.nix"), attr) + elif nix.find_file("nixos-system"): + return cls("", attr) + elif Path("/etc/nixos/system.nix").exists(): + return cls(Path("/etc/nixos/system.nix"), attr) + return cls("", attr) @dataclass(frozen=True) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py index 6699cc1778e1..95ebb3c9c0c2 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py @@ -270,8 +270,8 @@ 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)], - stdout=PIPE, check=False, + capture_output=True, ) if r.returncode: return None @@ -670,6 +670,8 @@ def set_profile( remote=target_host, sudo=sudo, ) + + def switch_to_configuration( path_to_config: Path, action: Literal[Action.SWITCH, Action.BOOT, Action.TEST, Action.DRY_ACTIVATE], diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py index 7f51c09d7497..456a41f46eac 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py @@ -187,12 +187,18 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None: nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv", "--no-reexec"]) - assert mock_run.call_count == 7 + assert mock_run.call_count == 8 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + capture_output=True, + check=False, + **DEFAULT_RUN_KWARGS, + ), call( ["nix-instantiate", "--find-file", "nixpkgs", "-vvv"], - stdout=PIPE, + capture_output=True, check=False, **DEFAULT_RUN_KWARGS, ), @@ -210,7 +216,7 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None: call( [ "nix-build", - "", + "", "--attr", "config.system.build.toplevel", "--no-out-link", @@ -278,9 +284,15 @@ def test_execute_nix_build(mock_run: Mock, tmp_path: Path) -> None: ] ) - assert mock_run.call_count == 1 + assert mock_run.call_count == 2 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -308,6 +320,8 @@ def test_execute_nix_build_vm(mock_run: Mock, tmp_path: Path) -> None: def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: if args[0] == "nix-build": return CompletedProcess([], 0, str(config_path)) + elif args[0] == "nix-instantiate": + return CompletedProcess([], 1) else: return CompletedProcess([], 0) @@ -326,9 +340,15 @@ def test_execute_nix_build_vm(mock_run: Mock, tmp_path: Path) -> None: ] ) - assert mock_run.call_count == 1 + assert mock_run.call_count == 2 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix-build", @@ -343,7 +363,7 @@ def test_execute_nix_build_vm(mock_run: Mock, tmp_path: Path) -> None: check=True, stdout=PIPE, **DEFAULT_RUN_KWARGS, - ) + ), ] ) @@ -363,6 +383,8 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None: ) elif args[0] == "nix": return CompletedProcess([], 0, str(config_path)) + elif args[0] == "nix-instantiate": + return CompletedProcess([], 1) else: return CompletedProcess([], 0) @@ -379,9 +401,15 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None: ] ) - assert mock_run.call_count == 3 + assert mock_run.call_count == 4 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -440,6 +468,8 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None: def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: if args[0] == "nix": return CompletedProcess([], 0, str(config_path)) + elif args[0] == "nix-instantiate": + return CompletedProcess([], 1) else: return CompletedProcess([], 0) @@ -462,9 +492,15 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None: ] ) - assert mock_run.call_count == 4 + assert mock_run.call_count == 5 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -572,9 +608,15 @@ def test_execute_nix_switch_build_target_host( ] ) - assert mock_run.call_count == 11 + assert mock_run.call_count == 12 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix-instantiate", @@ -586,7 +628,7 @@ def test_execute_nix_switch_build_target_host( "nixpkgs=$HOME/.nix-defexpr/channels/pinned_nixpkgs", ], check=False, - stdout=PIPE, + capture_output=True, **DEFAULT_RUN_KWARGS, ), call( @@ -777,6 +819,8 @@ def test_execute_nix_switch_flake_target_host( def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: if args[0] == "nix": return CompletedProcess([], 0, str(config_path)) + elif args[0] == "nix-instantiate": + return CompletedProcess([], 1) else: return CompletedProcess([], 0) @@ -795,9 +839,15 @@ def test_execute_nix_switch_flake_target_host( ] ) - assert mock_run.call_count == 5 + assert mock_run.call_count == 6 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -896,6 +946,8 @@ def test_execute_nix_switch_flake_build_host( return CompletedProcess([], 0, str(config_path)) elif args[0] == "ssh" and "nix" in args: return CompletedProcess([], 0, str(config_path)) + elif args[0] == "nix-instantiate": + return CompletedProcess([], 1) else: return CompletedProcess([], 0) @@ -913,9 +965,15 @@ def test_execute_nix_switch_flake_build_host( ] ) - assert mock_run.call_count == 7 + assert mock_run.call_count == 8 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + check=False, + capture_output=True, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -1001,7 +1059,9 @@ def test_execute_switch_rollback(mock_run: Mock, tmp_path: Path) -> None: (nixpkgs_path / ".git").mkdir(parents=True) def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: - if args[0] == "nix-instantiate": + if args[0] == "nix-instantiate" and "nixos-system" in args: + return CompletedProcess([], 1) + elif args[0] == "nix-instantiate": return CompletedProcess([], 0, str(nixpkgs_path)) elif args[0] == "git": return CompletedProcess([], 0, "") @@ -1023,13 +1083,19 @@ def test_execute_switch_rollback(mock_run: Mock, tmp_path: Path) -> None: ] ) - assert mock_run.call_count == 5 + assert mock_run.call_count == 6 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + capture_output=True, + check=False, + **DEFAULT_RUN_KWARGS, + ), call( ["nix-instantiate", "--find-file", "nixpkgs"], check=False, - stdout=PIPE, + capture_output=True, **DEFAULT_RUN_KWARGS, ), call( @@ -1077,15 +1143,23 @@ def test_execute_build(mock_run: Mock, tmp_path: Path) -> None: config_path = tmp_path / "test" config_path.touch() mock_run.side_effect = [ + # nix-instantiate --find-file nixos-system + CompletedProcess([], 1), # nixos_build_flake CompletedProcess([], 0, str(config_path)), ] nr.execute(["nixos-rebuild", "build", "--no-flake", "--no-reexec"]) - assert mock_run.call_count == 1 + assert mock_run.call_count == 2 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + capture_output=True, + check=False, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix-build", @@ -1096,7 +1170,7 @@ def test_execute_build(mock_run: Mock, tmp_path: Path) -> None: check=True, stdout=PIPE, **DEFAULT_RUN_KWARGS, - ) + ), ] ) @@ -1108,6 +1182,8 @@ def test_execute_build_dry_run_build_and_target_remote( config_path = tmp_path / "test" config_path.touch() mock_run.side_effect = [ + # nix-instantiate --find-file nixos-system + CompletedProcess([], 1), CompletedProcess([], 0, str(config_path)), CompletedProcess([], 0), CompletedProcess([], 0, str(config_path)), @@ -1126,9 +1202,15 @@ def test_execute_build_dry_run_build_and_target_remote( ] ) - assert mock_run.call_count == 3 + assert mock_run.call_count == 4 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + capture_output=True, + check=False, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -1181,6 +1263,8 @@ def test_execute_test_flake(mock_run: Mock, tmp_path: Path) -> None: def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]: if args[0] == "nix": return CompletedProcess([], 0, str(config_path)) + elif args[0] == "nix-instantiate": + return CompletedProcess([], 1) elif args[0] == "test": return CompletedProcess([], 1) else: @@ -1192,9 +1276,15 @@ def test_execute_test_flake(mock_run: Mock, tmp_path: Path) -> None: ["nixos-rebuild", "test", "--flake", "github:user/repo#hostname", "--no-reexec"] ) - assert mock_run.call_count == 3 + assert mock_run.call_count == 4 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + capture_output=True, + check=False, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix", @@ -1242,6 +1332,8 @@ def test_execute_test_rollback( 2084 2024-11-07 23:54:17 (current) """), ) + elif args[0] == "nix-instantiate" and "nixos-system" in args: + return CompletedProcess([], 1) elif args[0] == "test": return CompletedProcess([], 1) else: @@ -1253,7 +1345,7 @@ def test_execute_test_rollback( ["nixos-rebuild", "test", "--rollback", "--profile-name", "foo", "--no-reexec"] ) - assert mock_run.call_count == 3 + assert mock_run.call_count == 4 mock_run.assert_has_calls( [ call( @@ -1296,7 +1388,7 @@ def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None: config_path = tmp_path / "test-system" config_path.mkdir() - mock_run.return_value = CompletedProcess([], 0) + mock_run.return_value = CompletedProcess([], 0, stdout="") nr.execute( [ @@ -1309,9 +1401,15 @@ def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None: ) # --store-path skips build and write_version_suffix, so only activation calls - assert mock_run.call_count == 3 + assert mock_run.call_count == 4 mock_run.assert_has_calls( [ + call( + ["nix-instantiate", "--find-file", "nixos-system"], + capture_output=True, + check=False, + **DEFAULT_RUN_KWARGS, + ), call( [ "nix-env", @@ -1359,7 +1457,7 @@ def test_execute_switch_store_path_target_host( config_path = tmp_path / "test-system" config_path.mkdir() - mock_run.return_value = CompletedProcess([], 0) + mock_run.return_value = CompletedProcess([], 0, stdout="") nr.execute( [ @@ -1375,7 +1473,7 @@ def test_execute_switch_store_path_target_host( ) # --store-path skips build and write_version_suffix, so only copy/activation calls - assert mock_run.call_count == 5 + assert mock_run.call_count == 6 mock_run.assert_has_calls( [ call( diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_models.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_models.py index 21c957b9d821..64ffce686976 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_models.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_models.py @@ -5,19 +5,57 @@ from unittest.mock import Mock, patch from pytest import MonkeyPatch import nixos_rebuild.models as m +import nixos_rebuild.nix as n from .helpers import get_qualified_name -def test_build_attr_from_arg() -> None: - assert m.BuildAttr.from_arg(None, None) == m.BuildAttr("", None) - assert m.BuildAttr.from_arg("attr", None) == m.BuildAttr( - Path("default.nix"), "attr" - ) +def test_build_attr_from_arg(tmp_path: Path) -> None: assert m.BuildAttr.from_arg("attr", "file.nix") == m.BuildAttr( Path("file.nix"), "attr" ) - assert m.BuildAttr.from_arg(None, "file.nix") == m.BuildAttr(Path("file.nix"), None) + + with patch( + # system.nix exists + "pathlib.Path.exists", + autospec=True, + side_effect=[True], + ): + assert m.BuildAttr.from_arg("attr", None) == m.BuildAttr( + Path("system.nix"), "attr" + ) + + with patch( + # is defined + get_qualified_name(n.find_file), + autospec=True, + return_value=Path("/some/file.nix"), + ): + assert m.BuildAttr.from_arg("attr", None) == m.BuildAttr( + "", "attr" + ) + + with ( + # not defined + patch(get_qualified_name(n.find_file), autospec=True, return_value=None), + # system.nix does not exist, but /etc/nixos/system.nix does + patch( + "pathlib.Path.exists", + autospec=True, + side_effect=[True], + ), + ): + assert m.BuildAttr.from_arg(None, None) == m.BuildAttr( + Path("/etc/nixos/system.nix"), None + ) + + with patch( + # not defined + get_qualified_name(n.find_file), + autospec=True, + return_value=None, + ): + assert m.BuildAttr.from_arg(None, None) == m.BuildAttr("", None) def test_build_attr_to_attr() -> None: diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py index 529073e62464..6bd823b1d48d 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from subprocess import CompletedProcess from unittest.mock import ANY, Mock, call, patch from pytest import MonkeyPatch @@ -12,8 +13,13 @@ from .helpers import get_qualified_name @patch.dict(os.environ, {}, clear=True) @patch("os.execve", autospec=True) +@patch(get_qualified_name(n.nix.run_wrapper, n.nix), autospec=True) @patch(get_qualified_name(s.nix.build), autospec=True) -def test_reexec(mock_build: Mock, mock_execve: Mock, monkeypatch: MonkeyPatch) -> None: +def test_reexec( + mock_build: Mock, mock_run: Mock, mock_execve: Mock, monkeypatch: MonkeyPatch +) -> None: + mock_run.return_value = CompletedProcess([], 0, stdout="") + monkeypatch.setattr(s, "EXECUTABLE", "nixos-rebuild-ng") argv = ["/path/bin/nixos-rebuild-ng", "switch", "--no-flake"] args, _ = n.parse_args(argv)