Skip to content

Commit 8190bca

Browse files
authored
Merge pull request #2085 from cachix/container-test
devenv: support containers on macOS
2 parents c57bded + ede4c48 commit 8190bca

File tree

7 files changed

+189
-141
lines changed

7 files changed

+189
-141
lines changed

devenv/build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fn main() {
2+
println!(
3+
"cargo:rustc-env=TARGET_ARCH={}",
4+
std::env::var("CARGO_CFG_TARGET_ARCH").unwrap()
5+
);
6+
println!(
7+
"cargo:rustc-env=TARGET_OS={}",
8+
std::env::var("CARGO_CFG_TARGET_OS").unwrap()
9+
);
10+
}

devenv/src/devenv.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -465,12 +465,6 @@ impl Devenv {
465465
fields(devenv.user_message = format!("Building {name} container"))
466466
)]
467467
pub async fn container_build(&mut self, name: &str) -> Result<String> {
468-
if cfg!(target_os = "macos") {
469-
bail!(
470-
"Containers are not supported on macOS yet: https://github.com/cachix/devenv/issues/430"
471-
);
472-
}
473-
474468
// This container name is passed to the flake as an argument and tells the module system
475469
// that we're 1. building a container 2. which container we're building.
476470
self.container_name = Some(name.to_string());
@@ -480,10 +474,23 @@ impl Devenv {
480474
let gc_root = self
481475
.devenv_dot_gc
482476
.join(format!("container-{sanitized_name}-derivation"));
477+
let host_arch = env!("TARGET_ARCH");
478+
let host_os = env!("TARGET_OS");
479+
let target_system = if host_os == "macos" {
480+
match host_arch {
481+
"aarch64" => "aarch64-linux",
482+
"x86_64" => "x86_64-linux",
483+
_ => bail!("Unsupported container architecture for macOS: {host_arch}"),
484+
}
485+
} else {
486+
&self.global_options.system
487+
};
483488
let paths = self
484489
.nix
485490
.build(
486-
&[&format!("devenv.containers.{name}.derivation")],
491+
&[&format!(
492+
"devenv.perSystem.{target_system}.config.containers.{name}.derivation"
493+
)],
487494
None,
488495
Some(&gc_root),
489496
)
@@ -509,7 +516,7 @@ impl Devenv {
509516
let paths = self
510517
.nix
511518
.build(
512-
&[&format!("devenv.containers.{name}.copyScript")],
519+
&[&format!("devenv.config.containers.{name}.copyScript")],
513520
None,
514521
Some(&gc_root),
515522
)
@@ -564,7 +571,7 @@ impl Devenv {
564571
let paths = self
565572
.nix
566573
.build(
567-
&[&format!("devenv.containers.{name}.dockerRun")],
574+
&[&format!("devenv.config.containers.{name}.dockerRun")],
568575
None,
569576
Some(&gc_root),
570577
)
@@ -722,7 +729,7 @@ impl Devenv {
722729
let value = self
723730
.has_processes
724731
.get_or_try_init(|| async {
725-
let processes = self.nix.eval(&["devenv.processes"]).await?;
732+
let processes = self.nix.eval(&["devenv.config.processes"]).await?;
726733
Ok::<bool, miette::Report>(processes.trim() != "{}")
727734
})
728735
.await?;
@@ -734,7 +741,7 @@ impl Devenv {
734741
let span = info_span!("load_tasks", devenv.user_message = "Evaluating tasks");
735742
let gc_root = self.devenv_dot_gc.join("task-config");
736743
self.nix
737-
.build(&["devenv.task.config"], None, Some(&gc_root))
744+
.build(&["devenv.config.task.config"], None, Some(&gc_root))
738745
.instrument(span)
739746
.await?
740747
};
@@ -900,7 +907,7 @@ impl Devenv {
900907
let gc_root = self.devenv_dot_gc.join("test");
901908
let test_script = self
902909
.nix
903-
.build(&["devenv.test"], None, Some(&gc_root))
910+
.build(&["devenv.config.test"], None, Some(&gc_root))
904911
.instrument(span)
905912
.await?;
906913
test_script[0].to_string_lossy().to_string()
@@ -975,7 +982,7 @@ impl Devenv {
975982
flatten_object(&format!("{}.{}", prefix, k), v)
976983
})
977984
.collect(),
978-
_ => vec![format!("devenv.{}", prefix)],
985+
_ => vec![format!("devenv.config.{}", prefix)],
979986
}
980987
}
981988
flatten_object(key, value)
@@ -984,7 +991,7 @@ impl Devenv {
984991
} else {
985992
attributes
986993
.iter()
987-
.map(|attr| format!("devenv.{}", attr))
994+
.map(|attr| format!("devenv.config.{}", attr))
988995
.collect()
989996
};
990997
let paths = self

devenv/src/flake.tmpl.nix

Lines changed: 149 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -19,136 +19,163 @@
1919
if builtins.pathExists (devenv_dotfile_path + "/devenv.json")
2020
then builtins.fromJSON (builtins.readFile (devenv_dotfile_path + "/devenv.json"))
2121
else { };
22-
getOverlays = inputName: inputAttrs:
23-
map
24-
(overlay:
25-
let
26-
input = inputs.${inputName} or (throw "No such input `${inputName}` while trying to configure overlays.");
27-
in
28-
input.overlays.${overlay} or (throw "Input `${inputName}` has no overlay called `${overlay}`. Supported overlays: ${nixpkgs.lib.concatStringsSep ", " (builtins.attrNames input.overlays)}"))
29-
inputAttrs.overlays or [ ];
30-
overlays = nixpkgs.lib.flatten (nixpkgs.lib.mapAttrsToList getOverlays (devenv.inputs or { }));
31-
permittedUnfreePackages = devenv.nixpkgs.per-platform."${system}".permittedUnfreePackages or devenv.nixpkgs.permittedUnfreePackages or [ ];
32-
pkgs = import nixpkgs {
33-
inherit overlays system;
34-
config = {
35-
allowUnfree = devenv.nixpkgs.per-platform."${system}".allowUnfree or devenv.nixpkgs.allowUnfree or devenv.allowUnfree or false;
36-
allowBroken = devenv.nixpkgs.per-platform."${system}".allowBroken or devenv.nixpkgs.allowBroken or devenv.allowBroken or false;
37-
cudaSupport = devenv.nixpkgs.per-platform."${system}".cudaSupport or devenv.nixpkgs.cudaSupport or false;
38-
cudaCapabilities = devenv.nixpkgs.per-platform."${system}".cudaCapabilities or devenv.nixpkgs.cudaCapabilities or [ ];
39-
permittedInsecurePackages = devenv.nixpkgs.per-platform."${system}".permittedInsecurePackages or devenv.nixpkgs.permittedInsecurePackages or devenv.permittedInsecurePackages or [ ];
40-
allowUnfreePredicate = if (permittedUnfreePackages != [ ]) then (pkg: builtins.elem (nixpkgs.lib.getName pkg) permittedUnfreePackages) else (_: false);
41-
};
42-
};
43-
lib = pkgs.lib;
44-
importModule = path:
45-
if lib.hasPrefix "./" path
46-
then if lib.hasSuffix ".nix" path
47-
then ./. + (builtins.substring 1 255 path)
48-
else ./. + (builtins.substring 1 255 path) + "/devenv.nix"
49-
else if lib.hasPrefix "../" path
50-
then throw "devenv: ../ is not supported for imports"
51-
else
52-
let
53-
paths = lib.splitString "/" path;
54-
name = builtins.head paths;
55-
input = inputs.${name} or (throw "Unknown input ${name}");
56-
subpath = "/${lib.concatStringsSep "/" (builtins.tail paths)}";
57-
devenvpath = "${input}" + subpath;
58-
devenvdefaultpath = devenvpath + "/devenv.nix";
59-
in
60-
if lib.hasSuffix ".nix" devenvpath
61-
then devenvpath
62-
else if builtins.pathExists devenvdefaultpath
63-
then devenvdefaultpath
64-
else throw (devenvdefaultpath + " file does not exist for input ${name}.");
65-
project = pkgs.lib.evalModules {
66-
specialArgs = inputs // { inherit inputs; };
67-
modules = [
68-
({ config, ... }: {
69-
_module.args.pkgs = pkgs.appendOverlays (config.overlays or [ ]);
70-
})
71-
(inputs.devenv.modules + /top-level.nix)
72-
{
73-
devenv.cliVersion = version;
74-
devenv.root = devenv_root;
75-
devenv.dotfile = devenv_dotfile;
76-
}
77-
({ options, ... }: {
78-
config.devenv = lib.mkMerge [
79-
(pkgs.lib.optionalAttrs (builtins.hasAttr "tmpdir" options.devenv) {
80-
tmpdir = devenv_tmpdir;
81-
})
82-
(pkgs.lib.optionalAttrs (builtins.hasAttr "isTesting" options.devenv) {
83-
isTesting = devenv_istesting;
22+
23+
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
24+
25+
# Function to create devenv configuration for a specific system
26+
mkDevenvForSystem = targetSystem:
27+
let
28+
getOverlays = inputName: inputAttrs:
29+
map
30+
(overlay:
31+
let
32+
input = inputs.${inputName} or (throw "No such input `${inputName}` while trying to configure overlays.");
33+
in
34+
input.overlays.${overlay} or (throw "Input `${inputName}` has no overlay called `${overlay}`. Supported overlays: ${nixpkgs.lib.concatStringsSep ", " (builtins.attrNames input.overlays)}"))
35+
inputAttrs.overlays or [ ];
36+
overlays = nixpkgs.lib.flatten (nixpkgs.lib.mapAttrsToList getOverlays (devenv.inputs or { }));
37+
permittedUnfreePackages = devenv.nixpkgs.per-platform."${targetSystem}".permittedUnfreePackages or devenv.nixpkgs.permittedUnfreePackages or [ ];
38+
pkgs = import nixpkgs {
39+
system = targetSystem;
40+
config = {
41+
allowUnfree = devenv.nixpkgs.per-platform."${targetSystem}".allowUnfree or devenv.nixpkgs.allowUnfree or devenv.allowUnfree or false;
42+
allowBroken = devenv.nixpkgs.per-platform."${targetSystem}".allowBroken or devenv.nixpkgs.allowBroken or devenv.allowBroken or false;
43+
cudaSupport = devenv.nixpkgs.per-platform."${targetSystem}".cudaSupport or devenv.nixpkgs.cudaSupport or false;
44+
cudaCapabilities = devenv.nixpkgs.per-platform."${targetSystem}".cudaCapabilities or devenv.nixpkgs.cudaCapabilities or [ ];
45+
permittedInsecurePackages = devenv.nixpkgs.per-platform."${targetSystem}".permittedInsecurePackages or devenv.nixpkgs.permittedInsecurePackages or devenv.permittedInsecurePackages or [ ];
46+
allowUnfreePredicate = if (permittedUnfreePackages != [ ]) then (pkg: builtins.elem (nixpkgs.lib.getName pkg) permittedUnfreePackages) else (_: false);
47+
};
48+
inherit overlays;
49+
};
50+
lib = pkgs.lib;
51+
importModule = path:
52+
if lib.hasPrefix "./" path
53+
then if lib.hasSuffix ".nix" path
54+
then ./. + (builtins.substring 1 255 path)
55+
else ./. + (builtins.substring 1 255 path) + "/devenv.nix"
56+
else if lib.hasPrefix "../" path
57+
then throw "devenv: ../ is not supported for imports"
58+
else
59+
let
60+
paths = lib.splitString "/" path;
61+
name = builtins.head paths;
62+
input = inputs.${name} or (throw "Unknown input ${name}");
63+
subpath = "/${lib.concatStringsSep "/" (builtins.tail paths)}";
64+
devenvpath = "${input}" + subpath;
65+
devenvdefaultpath = devenvpath + "/devenv.nix";
66+
in
67+
if lib.hasSuffix ".nix" devenvpath
68+
then devenvpath
69+
else if builtins.pathExists devenvdefaultpath
70+
then devenvdefaultpath
71+
else throw (devenvdefaultpath + " file does not exist for input ${name}.");
72+
project = pkgs.lib.evalModules {
73+
specialArgs = inputs // { inherit inputs; };
74+
modules = [
75+
({ config, ... }: {
76+
_module.args.pkgs = pkgs.appendOverlays (config.overlays or [ ]);
8477
})
85-
(pkgs.lib.optionalAttrs (builtins.hasAttr "runtime" options.devenv) {
86-
runtime = devenv_runtime;
78+
(inputs.devenv.modules + /top-level.nix)
79+
{
80+
devenv.cliVersion = version;
81+
devenv.root = devenv_root;
82+
devenv.dotfile = devenv_dotfile;
83+
}
84+
({ options, ... }: {
85+
config.devenv = lib.mkMerge [
86+
(pkgs.lib.optionalAttrs (builtins.hasAttr "tmpdir" options.devenv) {
87+
tmpdir = devenv_tmpdir;
88+
})
89+
(pkgs.lib.optionalAttrs (builtins.hasAttr "isTesting" options.devenv) {
90+
isTesting = devenv_istesting;
91+
})
92+
(pkgs.lib.optionalAttrs (builtins.hasAttr "runtime" options.devenv) {
93+
runtime = devenv_runtime;
94+
})
95+
(pkgs.lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) {
96+
direnvrcLatestVersion = devenv_direnvrc_latest_version;
97+
})
98+
];
8799
})
88-
(pkgs.lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) {
89-
direnvrcLatestVersion = devenv_direnvrc_latest_version;
100+
(pkgs.lib.optionalAttrs (container_name != null) {
101+
container.isBuilding = pkgs.lib.mkForce true;
102+
containers.${container_name}.isBuilding = true;
90103
})
104+
] ++ (map importModule (devenv.imports or [ ])) ++ [
105+
(if builtins.pathExists ./devenv.nix then ./devenv.nix else { })
106+
(devenv.devenv or { })
107+
(if builtins.pathExists ./devenv.local.nix then ./devenv.local.nix else { })
108+
(if builtins.pathExists (devenv_dotfile_path + "/cli-options.nix") then import (devenv_dotfile_path + "/cli-options.nix") else { })
91109
];
92-
})
93-
(pkgs.lib.optionalAttrs (container_name != null) {
94-
container.isBuilding = pkgs.lib.mkForce true;
95-
containers.${container_name}.isBuilding = true;
96-
})
97-
] ++ (map importModule (devenv.imports or [ ])) ++ [
98-
(if builtins.pathExists ./devenv.nix then ./devenv.nix else { })
99-
(devenv.devenv or { })
100-
(if builtins.pathExists ./devenv.local.nix then ./devenv.local.nix else { })
101-
(if builtins.pathExists (devenv_dotfile_path + "/cli-options.nix") then import (devenv_dotfile_path + "/cli-options.nix") else { })
102-
];
103-
};
104-
config = project.config;
110+
};
111+
config = project.config;
105112

106-
options = pkgs.nixosOptionsDoc {
107-
options = builtins.removeAttrs project.options [ "_module" ];
108-
warningsAreErrors = false;
109-
# Unpack Nix types, e.g. literalExpression, mDoc.
110-
transformOptions =
111-
let isDocType = v: builtins.elem v [ "literalDocBook" "literalExpression" "literalMD" "mdDoc" ];
112-
in lib.attrsets.mapAttrs (_: v:
113-
if v ? _type && isDocType v._type then
114-
v.text
115-
else if v ? _type && v._type == "derivation" then
116-
v.name
117-
else
118-
v
119-
);
120-
};
113+
options = pkgs.nixosOptionsDoc {
114+
options = builtins.removeAttrs project.options [ "_module" ];
115+
warningsAreErrors = false;
116+
# Unpack Nix types, e.g. literalExpression, mDoc.
117+
transformOptions =
118+
let isDocType = v: builtins.elem v [ "literalDocBook" "literalExpression" "literalMD" "mdDoc" ];
119+
in lib.attrsets.mapAttrs (_: v:
120+
if v ? _type && isDocType v._type then
121+
v.text
122+
else if v ? _type && v._type == "derivation" then
123+
v.name
124+
else
125+
v
126+
);
127+
};
121128

122-
# Recursively search for outputs in the config.
123-
# This is used when not building a specific output by attrpath.
124-
build = options: config:
125-
lib.concatMapAttrs
126-
(name: option:
127-
if lib.isOption option then
128-
let typeName = option.type.name or "";
129-
in
130-
if builtins.elem typeName [ "output" "outputOf" ] then
131-
{ ${name} = config.${name}; }
132-
else { }
133-
else
134-
let v = build option config.${name};
135-
in if v != { } then {
136-
${name} = v;
137-
} else { }
138-
)
139-
options;
129+
# Recursively search for outputs in the config.
130+
# This is used when not building a specific output by attrpath.
131+
build = options: config:
132+
lib.concatMapAttrs
133+
(name: option:
134+
if lib.isOption option then
135+
let typeName = option.type.name or "";
136+
in
137+
if builtins.elem typeName [ "output" "outputOf" ] then
138+
{ ${name} = config.${name}; }
139+
else { }
140+
else if builtins.isAttrs option && !lib.isDerivation option then
141+
let v = build option config.${name};
142+
in if v != { } then {
143+
${name} = v;
144+
} else { }
145+
else { }
146+
)
147+
options;
148+
in
149+
{
150+
inherit config options build;
151+
shell = config.shell;
152+
packages = {
153+
optionsJSON = options.optionsJSON;
154+
# deprecated
155+
inherit (config) info procfileScript procfileEnv procfile;
156+
ci = config.ciDerivation;
157+
};
158+
};
140159

141-
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
160+
# Generate per-system devenv configurations
161+
perSystem = nixpkgs.lib.genAttrs systems mkDevenvForSystem;
162+
163+
# Default devenv for the current system
164+
currentSystemDevenv = perSystem.${system};
142165
in
143166
{
144-
devShell = lib.genAttrs systems (system: config.shell);
145-
packages = lib.genAttrs systems (system: {
146-
optionsJSON = options.optionsJSON;
147-
# deprecated
148-
inherit (config) info procfileScript procfileEnv procfile;
149-
ci = config.ciDerivation;
150-
});
151-
devenv = config;
152-
build = build project.options project.config;
167+
devShell = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.shell);
168+
packages = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.packages);
169+
170+
# Per-system devenv configurations
171+
devenv = {
172+
# Default devenv for the current system
173+
inherit (currentSystemDevenv) config options build shell packages;
174+
# Per-system devenv configurations
175+
inherit perSystem;
176+
};
177+
178+
# Legacy build output
179+
build = currentSystemDevenv.build currentSystemDevenv.options currentSystemDevenv.config;
153180
};
154181
}

0 commit comments

Comments
 (0)