1use std::{
2 collections::HashMap,
3 ffi::{OsStr, OsString},
4 path::{Path, PathBuf},
5 process::Command,
6};
7
8use crate::{Error, Result};
9
10enum NixTarget {
11 Function(OsString),
12 Flake(String),
13 Expr(String),
14}
15
16impl Default for NixTarget {
17 fn default() -> Self {
18 Self::Function(OsString::from("default.nix"))
19 }
20}
21
22pub struct Config {
24 target: NixTarget,
25 arg_exprs: Vec<(String, String)>,
26 arg_strs: Vec<(String, String)>,
27 impure: bool,
28}
29
30impl Default for Config {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36#[derive(Debug, serde::Deserialize)]
38pub struct Derivation {
39 #[serde(alias = "drvPath")]
40 pub drv_path: PathBuf,
42 pub outputs: HashMap<String, PathBuf>,
46}
47
48impl Derivation {
49 pub fn out(&self) -> Option<&PathBuf> {
50 self.outputs.get("out")
51 }
52}
53
54impl Config {
55 pub fn new() -> Self {
59 Self {
60 target: NixTarget::default(),
61 arg_exprs: vec![],
62 arg_strs: vec![],
63 impure: false,
64 }
65 }
66
67 pub fn arg_expr(&mut self, name: &str, value: &str) -> &mut Self {
74 self.arg_exprs.push((name.to_owned(), value.to_owned()));
75 self
76 }
77
78 pub fn arg_str(&mut self, name: &str, value: &str) -> &mut Self {
85 self.arg_strs.push((name.to_owned(), value.to_owned()));
86 self
87 }
88
89 pub fn target_file(&mut self, filename: impl AsRef<OsStr>) -> &mut Self {
96 self.target = NixTarget::Function(filename.as_ref().to_owned());
97 self
98 }
99
100 pub fn target_flake(&mut self, flake: &str) -> &mut Self {
107 self.target = NixTarget::Flake(flake.to_owned());
108 self
109 }
110
111 pub fn target_expr(&mut self, expr: &str) -> &mut Self {
118 self.target = NixTarget::Expr(expr.to_owned());
119 self
120 }
121
122 pub fn impure(&mut self, impure: bool) -> &mut Self {
126 self.impure = impure;
127 self
128 }
129
130 #[must_use]
132 pub fn build(&self) -> Result<Vec<Derivation>> {
133 let nix = crate::is_nix_available().ok_or(Error::NixNotAvailable)?;
134
135 let cwd = std::env::current_dir().unwrap();
136 let mut cmd = Command::new(nix);
137 cmd.current_dir(&cwd);
138 cmd.arg("build");
139
140 cmd.args(&["--no-link", "--json"]);
141
142 match &self.target {
143 NixTarget::Function(file) => {
144 cmd.args(&[OsStr::new("-f"), &file]);
145
146 println!(
148 "cargo:rerun-if-changed={}",
149 AsRef::<Path>::as_ref(file).display()
150 );
151 }
152 NixTarget::Flake(installable) => {
153 cmd.arg(installable);
154
155 if let Some(Ok(local_flake)) = cwd
157 .to_string_lossy()
158 .split_once('#')
159 .map(|(path, _)| path)
160 .map(std::fs::canonicalize)
161 {
162 println!(
164 "cargo:rerun-if-changed={}",
165 local_flake.join("flake.lock").display()
166 );
167 }
168 }
169 NixTarget::Expr(expr) => {
170 cmd.args(["--expr", expr.as_str()]);
171 }
172 }
173
174 for (key, val) in &self.arg_exprs {
175 cmd.args(&["--arg", &key, &val]);
176 }
177
178 for (key, val) in &self.arg_strs {
179 cmd.args(&["--argstr", &key, &val]);
180 }
181
182 if self.impure {
183 cmd.arg("--impure");
184 }
185
186 cmd.arg("-L");
188
189 cmd.args(&["--experimental-features", "nix-command flakes"]);
191
192 let output = cmd.output().map_err(|_| Error::BuildFailed)?;
193
194 if !output.status.success() {
195 return Err(Error::BuildFailed);
196 }
197
198 serde_json::from_slice(&output.stdout).map_err(|_| Error::UnknownOutput)
199 }
200}