cargo_zigbuild/zig/
cli_config.rs1use std::collections::BTreeMap;
17use std::env;
18
19use anyhow::{Context, Result};
20use cargo_config2::Flags;
21
22#[derive(Debug, Clone, Default, PartialEq)]
31pub struct CliConfig {
32 build_rustflags: Option<Flags>,
33 target_rustflags: BTreeMap<String, Flags>,
34}
35
36impl CliConfig {
37 pub fn parse(config_args: &[String]) -> Result<Self> {
44 let mut parsed = Self::default();
45 for arg in config_args {
46 let config: cargo_config2::de::Config = if arg.ends_with(".toml") {
49 cargo_config2::de::Config::load_file(arg)?
50 } else {
51 toml::from_str(arg)
52 .with_context(|| format!("failed to parse --config argument `{arg}`"))?
53 };
54 if let Some(flags) = &config.build.rustflags {
55 append_de_flags(parsed.build_rustflags.get_or_insert_default(), flags);
56 }
57 for (key, target_config) in &config.target {
58 if key.starts_with("cfg(") {
59 continue;
60 }
61 if let Some(flags) = &target_config.rustflags {
62 append_de_flags(
63 parsed.target_rustflags.entry(key.clone()).or_default(),
64 flags,
65 );
66 }
67 }
68 }
69 Ok(parsed)
70 }
71
72 pub fn rustflags(
87 &self,
88 cargo_config: &cargo_config2::Config,
89 rust_target: &str,
90 ) -> Result<Option<Flags>> {
91 let resolved = cargo_config.rustflags(rust_target)?;
92 if env::var_os("CARGO_ENCODED_RUSTFLAGS").is_some() || env::var_os("RUSTFLAGS").is_some() {
93 return Ok(resolved);
95 }
96 Ok(self.overlay(rust_target, resolved, cargo_config.build.rustflags.clone()))
97 }
98
99 fn overlay(
108 &self,
109 rust_target: &str,
110 resolved: Option<Flags>,
111 build_flags: Option<Flags>,
112 ) -> Option<Flags> {
113 let from_build_tier = resolved == build_flags;
114 if let Some(cli_target) = self.target_rustflags.get(rust_target) {
115 let mut flags = if from_build_tier {
119 Flags::default()
120 } else {
121 resolved.unwrap_or_default()
122 };
123 flags.flags.extend(cli_target.flags.iter().cloned());
124 Some(flags)
125 } else if let Some(cli_build) = &self.build_rustflags {
126 if from_build_tier {
127 let mut flags = resolved.unwrap_or_default();
128 flags.flags.extend(cli_build.flags.iter().cloned());
129 Some(flags)
130 } else {
131 resolved
134 }
135 } else {
136 resolved
137 }
138 }
139}
140
141fn append_de_flags(flags: &mut Flags, de_flags: &cargo_config2::de::Flags) {
142 flags
143 .flags
144 .extend(de_flags.flags.iter().map(|value| value.val.clone()));
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use std::io::Write;
151
152 const TARGET: &str = "x86_64-unknown-linux-gnu";
153
154 fn flags(s: &str) -> Flags {
155 Flags::from_space_separated(s)
156 }
157
158 #[test]
159 fn test_parse_target_rustflags_array() {
160 let config = CliConfig::parse(&[format!(
161 "target.{TARGET}.rustflags=['-C','target-cpu=x86-64-v4']"
162 )])
163 .unwrap();
164 assert_eq!(
165 config.target_rustflags[TARGET].flags,
166 vec!["-C", "target-cpu=x86-64-v4"]
167 );
168 assert!(config.build_rustflags.is_none());
169 }
170
171 #[test]
172 fn test_parse_target_rustflags_string() {
173 let config = CliConfig::parse(&[format!(
174 "target.{TARGET}.rustflags='-C target-cpu=x86-64-v4'"
175 )])
176 .unwrap();
177 assert_eq!(
178 config.target_rustflags[TARGET].flags,
179 vec!["-C", "target-cpu=x86-64-v4"]
180 );
181 }
182
183 #[test]
184 fn test_parse_build_rustflags() {
185 let config =
186 CliConfig::parse(&["build.rustflags=['-Ctarget-cpu=neoverse-n1']".to_string()])
187 .unwrap();
188 assert_eq!(
189 config.build_rustflags.unwrap().flags,
190 vec!["-Ctarget-cpu=neoverse-n1"]
191 );
192 }
193
194 #[test]
195 fn test_parse_multiple_args_join_left_to_right() {
196 let config = CliConfig::parse(&[
197 format!("target.{TARGET}.rustflags=['-Ctarget-cpu=x86-64-v2']"),
198 format!("target.{TARGET}.rustflags=['-Ctarget-cpu=x86-64-v4']"),
199 ])
200 .unwrap();
201 assert_eq!(
203 config.target_rustflags[TARGET].flags,
204 vec!["-Ctarget-cpu=x86-64-v2", "-Ctarget-cpu=x86-64-v4"]
205 );
206 }
207
208 #[test]
209 fn test_parse_config_file() {
210 let mut file = tempfile::Builder::new().suffix(".toml").tempfile().unwrap();
211 writeln!(
212 file,
213 "[target.{TARGET}]\nrustflags = ['-C', 'target-cpu=x86-64-v3']"
214 )
215 .unwrap();
216 let config = CliConfig::parse(&[file.path().to_str().unwrap().to_string()]).unwrap();
217 assert_eq!(
218 config.target_rustflags[TARGET].flags,
219 vec!["-C", "target-cpu=x86-64-v3"]
220 );
221 }
222
223 #[test]
224 fn test_parse_ignores_unrelated_and_cfg_keys() {
225 let config = CliConfig::parse(&[
226 "net.git-fetch-with-cli=true".to_string(),
227 "profile.release.lto=true".to_string(),
228 "target.'cfg(target_arch = \"x86_64\")'.rustflags=['-Ctarget-cpu=x86-64-v4']"
229 .to_string(),
230 ])
231 .unwrap();
232 assert_eq!(config, CliConfig::default());
233 }
234
235 #[test]
236 fn test_overlay_cli_target_replaces_build_tier() {
237 let config = CliConfig::parse(&[format!(
238 "target.{TARGET}.rustflags=['-Ctarget-cpu=x86-64-v4']"
239 )])
240 .unwrap();
241 let result = config.overlay(
244 TARGET,
245 Some(flags("-Ctarget-cpu=x86-64-v2")),
246 Some(flags("-Ctarget-cpu=x86-64-v2")),
247 );
248 assert_eq!(result.unwrap().flags, vec!["-Ctarget-cpu=x86-64-v4"]);
249 }
250
251 #[test]
252 fn test_overlay_cli_target_joins_file_target_tier() {
253 let config = CliConfig::parse(&[format!(
254 "target.{TARGET}.rustflags=['-Ctarget-cpu=x86-64-v4']"
255 )])
256 .unwrap();
257 let result = config.overlay(TARGET, Some(flags("-Ctarget-cpu=x86-64-v2")), None);
260 assert_eq!(
261 result.unwrap().flags,
262 vec!["-Ctarget-cpu=x86-64-v2", "-Ctarget-cpu=x86-64-v4"]
263 );
264 }
265
266 #[test]
267 fn test_overlay_cli_target_other_triple_is_ignored() {
268 let config = CliConfig::parse(&[
269 "target.aarch64-unknown-linux-gnu.rustflags=['-Ctarget-cpu=neoverse-n1']".to_string(),
270 ])
271 .unwrap();
272 let result = config.overlay(TARGET, None, None);
273 assert_eq!(result, None);
274 }
275
276 #[test]
277 fn test_overlay_cli_build_joins_build_tier() {
278 let config =
279 CliConfig::parse(&["build.rustflags=['-Ctarget-cpu=x86-64-v4']".to_string()]).unwrap();
280 let result = config.overlay(
281 TARGET,
282 Some(flags("-Ctarget-cpu=x86-64-v2")),
283 Some(flags("-Ctarget-cpu=x86-64-v2")),
284 );
285 assert_eq!(
286 result.unwrap().flags,
287 vec!["-Ctarget-cpu=x86-64-v2", "-Ctarget-cpu=x86-64-v4"]
288 );
289 }
290
291 #[test]
292 fn test_overlay_cli_build_ignored_when_target_tier_present() {
293 let config =
294 CliConfig::parse(&["build.rustflags=['-Ctarget-cpu=x86-64-v4']".to_string()]).unwrap();
295 let result = config.overlay(TARGET, Some(flags("-Ctarget-cpu=x86-64-v2")), None);
298 assert_eq!(result.unwrap().flags, vec!["-Ctarget-cpu=x86-64-v2"]);
299 }
300
301 #[test]
302 fn test_overlay_no_cli_flags_keeps_resolved() {
303 let config = CliConfig::default();
304 let result = config.overlay(TARGET, Some(flags("-Ctarget-cpu=x86-64-v2")), None);
305 assert_eq!(result.unwrap().flags, vec!["-Ctarget-cpu=x86-64-v2"]);
306 }
307}