Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -796,8 +796,9 @@ let
inherit elemType lazy placeholder;
};
in
{
elemType,
args@{
elemType ? throw "Internal error: the implementation of attrsWith must refer to getElemType, not elemType.",
getElemType ? _name: elemType,
lazy ? false,
placeholder ? "name",
}:
Expand All @@ -817,6 +818,7 @@ let
(
name: defs:
let
elemType = getElemType name;
merged = mergeDefinitions (loc ++ [ name ]) elemType defs;
# mergedValue will trigger an appropriate error when accessed
in
Expand All @@ -831,7 +833,7 @@ let
loc: defs:
mapAttrs (n: v: v.value) (
filterAttrs (n: v: v ? value) (
zipAttrsWith (name: defs: (mergeDefinitions (loc ++ [ name ]) elemType (defs)).optionalValue)
zipAttrsWith (name: defs: (mergeDefinitions (loc ++ [ name ]) (getElemType name) (defs)).optionalValue)
# Push down position info.
(pushPositions defs)
)
Expand All @@ -840,23 +842,29 @@ let
emptyValue = {
value = { };
};
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]);
getSubModules = elemType.getSubModules;
getSubOptions = prefix: (getElemType "<${placeholder}>").getSubOptions (prefix ++ [ "<${placeholder}>" ]);
getSubModules = if args?elemType then elemType.getSubModules else null;
substSubModules =
m:
attrsWith {
elemType = elemType.substSubModules m;
getElemType = name: (getElemType name).substSubModules m;
inherit lazy placeholder;
};
functor =
(elemTypeFunctor "attrsWith" {
inherit elemType lazy placeholder;
})
// {
# Custom type merging required because of the "placeholder" attribute
inherit binOp;
};
nestedTypes.elemType = elemType;
if args ? elemType
then
elemTypeFunctor "attrsWith" { inherit elemType lazy placeholder; }
// {
# Custom type merging required because of the "placeholder" attribute
inherit binOp;
}
else
defaultFunctor "attrsWith" // {
type = payload: types.attrsWith payload;
payload = args // { inherit lazy placeholder; };
};
nestedTypes = builtins.intersectAttrs { elemType = null; } args;
};

# TODO: deprecate this in the future:
Expand Down
2 changes: 1 addition & 1 deletion nixos/lib/qemu-common.nix
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ rec {
powerpc64-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
riscv32-linux = "${qemuPkg}/bin/qemu-system-riscv32 -machine virt";
riscv64-linux = "${qemuPkg}/bin/qemu-system-riscv64 -machine virt";
x86_64-darwin = "${qemuPkg}/bin/qemu-system-x86_64 -machine accel=kvm:tcg -cpu max";
x86_64-darwin = "${qemuPkg}/bin/qemu-system-x86_64";
};
otherHostGuestMatrix = {
aarch64-darwin = {
Expand Down
23 changes: 23 additions & 0 deletions nixos/lib/testing/machine/options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{ lib, ... }:

let
inherit (lib) mkOption types;
in
{
options = {
osType = mkOption {
type = types.optionType;
defaultText = lib.literalMD ''
NixOS as an option type
'';
};
moduleForName = mkOption {
type = types.functionTo types.deferredModule;
default = { };
description = ''
A function that receives the machine name and returns a module that may use the machine name.
This is added to all nodes.
'';
};
};
}
27 changes: 25 additions & 2 deletions nixos/lib/testing/nodes.nix
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ let
# TODO (lib): Dedup with run.nix, add to lib/options.nix
mkOneUp = opt: f: lib.mkOverride (opt.highestPrio - 1) (f opt.value);

machineType = types.submoduleWith {
modules = [
./machine/options.nix
{
osType = lib.mkOptionDefault baseOS.type;
}
];
};

in

{
Expand Down Expand Up @@ -107,14 +116,18 @@ in
};
};

node.type = mkOption {
node.type_ = mkOption {
type = types.raw;
default = baseOS.type;
internal = true;
description = "Deprecated. Use `machines` instead.";
};

nodes = mkOption {
type = types.lazyAttrsOf config.node.type;
type = types.attrsWith { getElemType = name:
if config.machines ? ${name}
then config.machines.${name}.osType
else config.node.type_; };
visible = "shallow";
description = ''
An attribute set of NixOS configuration modules.
Expand All @@ -127,6 +140,16 @@ in
'';
};

machines = mkOption {
type = types.lazyAttrsOf machineType;
description = ''
An attribute set of machine configurations.

This generalizes the [`nodes`](#test-opt-nodes) option to allow for more than just NixOS nodes, e.g. Docker containers or VMs with other operating systems.
'';
default = { };
};

defaults = mkOption {
description = ''
NixOS configuration that is applied to all [{option}`nodes`](#test-opt-nodes).
Expand Down
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ in
[[ 143 = $(cat $failed/testBuildFailure.exit) ]]
touch $out
'';
multi-os = runTest ./nixos-test-driver/multi-os.nix;
};

# NixOS vm tests and non-vm unit tests
Expand Down
79 changes: 79 additions & 0 deletions nixos/tests/nixos-test-driver/multi-os.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{ lib, hostPkgs, ... }:

let

nixThePlanet = builtins.getFlake "github:MatthewCroughan/NixThePlanet/c9d159dc2e0049e7b250a767a3a01def52c3412b";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we should use builtins.getFlake in Nixpkgs, especially to a repository that contains a bunch of legally dubious stuff. (But I assume this is just a proof‐of‐concept and the intention was to pull the relevant functionality in‐tree.)


# no specific commit in particular
nix-darwin = builtins.getFlake "github:nix-darwin/nix-darwin/e04a388232d9a6ba56967ce5b53a8a6f713cdfcf";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if the correct place of this test framework would be nix-darwin itself? Afaik all the module system of the test framework should be accessible from there as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was thinking to move things when it works, and the refactoring is done.
Refactoring will be important, because when the code is split out, it will need a reasonably clean interface to attach to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect some parts should live in NixThePlanet and some parts will make more sense to be put into nix-darwin where they can be shared between both x86 and ARM VMs

Copy link
Member

@Eveeifyeve Eveeifyeve Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an open issue about nix-darwin refractor to allow this to happen without flakes?


guestDarwin =
let
inherit (hostPkgs.stdenv) hostPlatform;
in
if hostPlatform.isDarwin then
hostPlatform.system
else
let
hostToGuest = {
"x86_64-linux" = "x86_64-darwin";
"aarch64-linux" = "aarch64-darwin";
};

supportedHosts = lib.concatStringsSep ", " (lib.attrNames hostToGuest);

message = "NixOS Test: don't know which VM guest system to pair with VM host system: ${hostPlatform.system}. Perhaps you intended to run the tests on a Darwin host, or one of the following systems that may run NixOS tests: ${supportedHosts}";
in
hostToGuest.${hostPlatform.system} or (throw message);

defaultDarwinModule = { config, name, ... }:
{
_file = "${__curPos.file}:${toString (__curPos.line - 1)}";
imports = [ ./multi-os/qemu-vm.nix ];
virtualisation.host.pkgs = hostPkgs;
};

darwinBaseEval = nix-darwin.lib.darwinSystem {
system = guestDarwin;
modules = [ defaultDarwinModule ];
};

in

{
name = "nixos-test-driver-multi-os";
passthru = {
/** NixThePlanet flake, for use in repl */
inherit nixThePlanet;
/** nix-darwin flake, for use in repl */
inherit nix-darwin;
};
node.specialArgs = {
inherit nixThePlanet;
};
nodes = {
# A NixOS VM
rose = { };
# A macOS VM
daisy = {
system.stateVersion = 6;
virtualisation.diskImage = "${nixThePlanet.packages.${hostPkgs.stdenv.hostPlatform.system}.macos-ventura-image}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be an FOD?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an installation process into a disk image, and it's not byte for byte reproducible, so we can't slap a hash on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ventura is going to be EOL by the release of Nixpkgs 25.11.

virtualisation.qemu.options = [
"-drive" "if=pflash,format=raw,readonly=on,file=${nixThePlanet.legacyPackages.${hostPkgs.stdenv.hostPlatform.system}.osx-kvm}/OVMF_CODE.fd"
"-drive" "if=pflash,format=raw,readonly=on,file=${nixThePlanet.legacyPackages.${hostPkgs.stdenv.hostPlatform.system}.osx-kvm}/OVMF_VARS-1920x1080.fd"
"-drive" "id=OpenCoreBoot,if=virtio,snapshot=on,readonly=on,format=qcow2,file=${nixThePlanet.legacyPackages.${hostPkgs.stdenv.hostPlatform.system}.osx-kvm}/OpenCore/OpenCore.qcow2"
Comment on lines +62 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenCore is, of course, open, and we have from‐source builds of EDK II and OVMF. It would be unfortunate to consume it in the form of opaque binary blobs from the somewhat messy OSX-KVM repository that contains things like decompiled macOS code. (Again only relevant for x86_64-darwin anyway, though.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just opened a wip PR for OpenCore. Builds only x86_64-linux for now, but I intend to also make it buildable on Darwin too.

#479419


];
};
};
machines = {
daisy = {
osType = darwinBaseEval.type;

};
};
testScript = ''
# start_all();
daisy.succeed("uname -s | tee /dev/stderr | grep '^Darwin$'");
'';
}
Loading
Loading