diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 50dd73f..5673957 100755 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -78,6 +78,13 @@ pub fn standard_build_env( if !flags.prefix.is_empty() { set_env_var(&mut env_vars, "PREFIX", flags.prefix.clone()); } + if !flags.makeflags.trim().is_empty() { + set_env_var( + &mut env_vars, + "MAKEFLAGS", + flags.makeflags.trim().to_string(), + ); + } set_env_var(&mut env_vars, "DEPOT_ROOTFS", flags.rootfs.clone()); set_env_var( @@ -417,6 +424,19 @@ mod tests { ); } + #[test] + fn test_standard_build_env_exports_makeflags() { + let mut spec = mk_spec(Vec::new(), Vec::new()); + spec.build.flags.makeflags = "-j12 --output-sync=target".to_string(); + + let env = standard_build_env(&spec, None, true, true); + assert!( + env.iter() + .any(|(k, v)| k == "MAKEFLAGS" && v == "-j12 --output-sync=target"), + "expected MAKEFLAGS to be exported from build flags" + ); + } + #[test] fn test_standard_build_env_exports_passthrough_env() { let mut spec = mk_spec(Vec::new(), Vec::new()); diff --git a/src/package/interactive.rs b/src/package/interactive.rs index 79da7b4..4e7f508 100644 --- a/src/package/interactive.rs +++ b/src/package/interactive.rs @@ -972,6 +972,12 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result { Value::String(spec.build.flags.carch.clone()), ); } + if !spec.build.flags.makeflags.is_empty() { + flags_tbl.insert( + "makeflags".into(), + Value::String(spec.build.flags.makeflags.clone()), + ); + } if !spec.build.flags.make_vars.is_empty() { flags_tbl.insert( "make_vars".into(), @@ -1475,6 +1481,7 @@ mod tests { flags.no_delete_static = true; flags.no_compress_man = true; flags.skip_tests = true; + flags.makeflags = "-j12 --output-sync=target".into(); flags.make_vars = vec!["V=1".into()]; flags.make_dirs = vec!["lib".into(), "libelf".into()]; flags.make_test_vars = vec!["TESTS=unit".into()]; @@ -1533,6 +1540,7 @@ mod tests { assert!(toml.contains("no_delete_static = true")); assert!(toml.contains("no_compress_man = true")); assert!(toml.contains("skip_tests = true")); + assert!(toml.contains("makeflags = \"-j12 --output-sync=target\"")); assert!(toml.contains("make_vars = [")); assert!(toml.contains("make_dirs = [")); assert!(toml.contains("make_test_vars = [")); diff --git a/src/package/spec.rs b/src/package/spec.rs index 4958451..e65d9cf 100755 --- a/src/package/spec.rs +++ b/src/package/spec.rs @@ -346,6 +346,19 @@ impl PackageSpec { self.build.flags.carch = s.to_string(); } } + "makeflags" | "make_flags" | "make-flags" => { + if let Some(s) = v.as_str() { + self.build.flags.makeflags = s.to_string(); + } else if let Some(arr) = v.as_array() { + self.build.flags.makeflags = arr + .iter() + .filter_map(|x| x.as_str()) + .map(str::trim) + .filter(|x| !x.is_empty()) + .collect::>() + .join(" "); + } + } "make_vars" | "make-vars" | "make_build_vars" | "make-build-vars" => { if let Some(arr) = v.as_array() { self.build.flags.make_vars = arr @@ -860,6 +873,22 @@ impl PackageSpec { self.build.flags.carch = s.to_string(); } } + "makeflags" | "make_flags" | "make-flags" | "MAKEFLAGS" => { + for v in values { + if let Some(arr) = v.as_array() { + let joined = arr + .iter() + .filter_map(|x| x.as_str()) + .map(str::trim) + .filter(|x| !x.is_empty()) + .collect::>() + .join(" "); + append_whitespace_separated(&mut self.build.flags.makeflags, &joined); + } else if let Some(s) = v.as_str() { + append_whitespace_separated(&mut self.build.flags.makeflags, s); + } + } + } "make_vars" | "make-vars" | "make_build_vars" | "make-build-vars" => { for v in values { if let Some(arr) = v.as_array() { @@ -1652,6 +1681,7 @@ cc = "my-cc" cflags = ["-O2"] cxxflags = ["-O2", "-pipe"] passthrough_env = ["RUSTFLAGS"] +makeflags = "-j8" make_vars = ["V=1"] make_dirs = ["lib"] make_test_dirs = ["tests"] @@ -1709,6 +1739,10 @@ post_configure = ["echo configured"] "build.flags.make_test_vars".to_string(), vec![toml::Value::String("TESTS=smoke".to_string())], ); + config.appends.insert( + "build.flags.makeflags".to_string(), + vec![toml::Value::String("--output-sync=target".to_string())], + ); config.appends.insert( "build.flags.make_dirs".to_string(), vec![toml::Value::String("libelf".to_string())], @@ -1782,6 +1816,7 @@ post_configure = ["echo configured"] .passthrough_env .contains(&"CARGO_HOME".to_string()) ); + assert_eq!(spec.build.flags.makeflags, "-j8 --output-sync=target"); assert!(spec.build.flags.make_vars.contains(&"V=1".to_string())); assert!(spec.build.flags.make_dirs.contains(&"lib".to_string())); assert!(spec.build.flags.make_dirs.contains(&"libelf".to_string())); @@ -2378,6 +2413,39 @@ make_install_dirs = ["lib", "apps"] ); } + #[test] + fn parse_makeflags_from_spec() { + let tmp = tempfile::tempdir().unwrap(); + let path = tmp.path().join("pkg.toml"); + + std::fs::write( + &path, + r#" +[package] +name = "foo" +version = "1.0" +description = "d" +homepage = "h" +license = "MIT" + +[source] +url = "https://example.com/foo.tar.gz" +sha256 = "skip" +extract_dir = "foo" + +[build] +type = "autotools" + +[build.flags] +MAKEFLAGS = ["-j12", "--output-sync=target"] +"#, + ) + .unwrap(); + + let spec = PackageSpec::from_file(&path).unwrap(); + assert_eq!(spec.build.flags.makeflags, "-j12 --output-sync=target"); + } + #[test] fn test_chost_cbuild_overrides() { let mut spec = mk_spec("foo", "1.0"); @@ -2857,6 +2925,15 @@ pub struct BuildFlags { /// CPU architecture short name (CARCH equivalent), e.g. "x86_64", "aarch64" #[serde(default = "default_carch")] pub carch: String, + /// MAKEFLAGS environment variable passed to build commands. + #[serde( + default, + alias = "make-flags", + alias = "make_flags", + alias = "MAKEFLAGS", + deserialize_with = "deserialize_string_or_array_joined" + )] + pub makeflags: String, /// Variable overrides passed directly to `make` (compile step), e.g. ["V=1", "CC=clang"]. #[serde( default, @@ -3022,6 +3099,7 @@ impl Default for BuildFlags { chost: String::new(), cbuild: String::new(), carch: default_carch(), + makeflags: String::new(), make_vars: Vec::new(), make_exec: String::new(), make_target: String::new(), @@ -3089,6 +3167,31 @@ where } } +fn deserialize_string_or_array_joined<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrArray { + String(String), + Array(Vec), + } + + match Option::::deserialize(deserializer)? { + Some(StringOrArray::String(s)) => Ok(s), + Some(StringOrArray::Array(a)) => Ok(a + .iter() + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect::>() + .join(" ")), + None => Ok(String::new()), + } +} + fn deserialize_boolish<'de, D>(deserializer: D) -> std::result::Result where D: Deserializer<'de>, @@ -3127,6 +3230,19 @@ fn toml_value_as_boolish(value: &toml::Value) -> Option { }) } +fn append_whitespace_separated(dst: &mut String, value: &str) { + let trimmed = value.trim(); + if trimmed.is_empty() { + return; + } + if dst.is_empty() { + dst.push_str(trimmed); + } else { + dst.push(' '); + dst.push_str(trimmed); + } +} + fn default_cc() -> String { // Prefer clang if available (supports -print-resource-dir and other useful flags) if std::process::Command::new("which")