1use crate::Result;
2use crate::manifest::{DevenvRuntime, NixRuntime, Runtime};
3use cuenv_hooks::{Hook, capture_source_environment};
4use std::collections::HashMap;
5use std::path::Path;
6
7const RUNTIME_ENV_TIMEOUT_SECONDS: u64 = 600;
8
9pub async fn resolve_runtime_environment(
18 project_root: &Path,
19 runtime: Option<&Runtime>,
20) -> Result<HashMap<String, String>> {
21 match runtime {
22 Some(Runtime::Nix(nix_runtime)) => {
23 resolve_nix_runtime_environment(project_root, nix_runtime).await
24 }
25 Some(Runtime::Devenv(devenv_runtime)) => {
26 resolve_devenv_runtime_environment(project_root, devenv_runtime).await
27 }
28 _ => Ok(HashMap::new()),
29 }
30}
31
32async fn resolve_nix_runtime_environment(
33 project_root: &Path,
34 runtime: &NixRuntime,
35) -> Result<HashMap<String, String>> {
36 let hook = Hook {
37 order: 10,
38 propagate: false,
39 command: "nix".to_string(),
40 args: nix_print_dev_env_args(runtime),
41 dir: Some(project_root.to_string_lossy().to_string()),
42 inputs: vec!["flake.nix".to_string(), "flake.lock".to_string()],
43 source: Some(true),
44 };
45
46 capture_source_environment(hook, &HashMap::new(), RUNTIME_ENV_TIMEOUT_SECONDS)
47 .await
48 .map_err(|e| {
49 crate::Error::configuration(format!("Failed to acquire Nix runtime environment: {e}"))
50 })
51}
52
53async fn resolve_devenv_runtime_environment(
54 project_root: &Path,
55 runtime: &DevenvRuntime,
56) -> Result<HashMap<String, String>> {
57 let devenv_dir = if runtime.path.is_empty() || runtime.path == "." {
58 project_root.to_path_buf()
59 } else {
60 project_root.join(&runtime.path)
61 };
62
63 let devenv_cmd = resolve_devenv_command().await?;
64
65 let hook = Hook {
66 order: 10,
67 propagate: false,
68 command: devenv_cmd,
69 args: vec!["print-dev-env".to_string()],
70 dir: Some(devenv_dir.to_string_lossy().to_string()),
71 inputs: vec!["devenv.nix".to_string(), "devenv.lock".to_string()],
72 source: Some(true),
73 };
74
75 capture_source_environment(hook, &HashMap::new(), RUNTIME_ENV_TIMEOUT_SECONDS)
76 .await
77 .map_err(|e| {
78 crate::Error::configuration(format!(
79 "Failed to acquire devenv runtime environment: {e}"
80 ))
81 })
82}
83
84async fn resolve_devenv_command() -> Result<String> {
89 if tokio::process::Command::new("devenv")
90 .arg("version")
91 .output()
92 .await
93 .map(|o| o.status.success())
94 .unwrap_or(false)
95 {
96 return Ok("devenv".to_string());
97 }
98
99 tracing::info!("devenv not found, installing via nix profile install");
100 let output = tokio::process::Command::new("nix")
101 .args([
102 "--extra-experimental-features",
103 "nix-command flakes",
104 "profile",
105 "install",
106 "nixpkgs#devenv",
107 ])
108 .output()
109 .await
110 .map_err(|e| crate::Error::configuration(format!("Failed to install devenv: {e}")))?;
111
112 if !output.status.success() {
113 let stderr = String::from_utf8_lossy(&output.stderr);
114 return Err(crate::Error::configuration(format!(
115 "Failed to install devenv: {stderr}"
116 )));
117 }
118
119 if let Ok(home) = std::env::var("HOME") {
121 let devenv_path = format!("{home}/.nix-profile/bin/devenv");
122 if std::path::Path::new(&devenv_path).exists() {
123 return Ok(devenv_path);
124 }
125 }
126
127 Ok("devenv".to_string())
128}
129
130fn nix_print_dev_env_args(runtime: &NixRuntime) -> Vec<String> {
131 let mut args = vec![
132 "--extra-experimental-features".to_string(),
133 "nix-command flakes".to_string(),
134 "print-dev-env".to_string(),
135 ];
136 args.push(nix_runtime_target(runtime));
137 args
138}
139
140fn nix_runtime_target(runtime: &NixRuntime) -> String {
141 match &runtime.output {
142 Some(output) => format!("{}#{}", runtime.flake, output),
143 None => runtime.flake.clone(),
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn nix_runtime_defaults_to_local_flake() {
153 let runtime = NixRuntime::default();
154
155 assert_eq!(
156 nix_print_dev_env_args(&runtime),
157 vec![
158 "--extra-experimental-features",
159 "nix-command flakes",
160 "print-dev-env",
161 ".",
162 ]
163 );
164 }
165
166 #[test]
167 fn devenv_runtime_from_cue_defaults_to_current_dir() {
168 let runtime: Runtime = serde_json::from_str(r#"{"type":"devenv"}"#).unwrap();
170 match runtime {
171 Runtime::Devenv(devenv) => assert_eq!(devenv.path, "."),
172 _ => panic!("Expected Devenv runtime"),
173 }
174 }
175
176 #[test]
177 fn nix_runtime_uses_explicit_output_target() {
178 let runtime = NixRuntime {
179 flake: "github:example/project".to_string(),
180 output: Some("devShells.x86_64-linux.ci".to_string()),
181 };
182
183 assert_eq!(
184 nix_print_dev_env_args(&runtime),
185 vec![
186 "--extra-experimental-features",
187 "nix-command flakes",
188 "print-dev-env",
189 "github:example/project#devShells.x86_64-linux.ci",
190 ]
191 );
192 }
193}