From af0571c33baf28aa88512aa9ce3a3fc5900affcc Mon Sep 17 00:00:00 2001 From: SFG545 Date: Mon, 16 Feb 2026 23:24:26 -0600 Subject: [PATCH] feat: Enhance package specification to support case-insensitive flags and add C++ compiler configuration --- src/builder/cmake.rs | 134 +++++++++++++++++++++++++++++++++++++------ src/package/spec.rs | 22 ++++++- 2 files changed, 138 insertions(+), 18 deletions(-) diff --git a/src/builder/cmake.rs b/src/builder/cmake.rs index c55e531..3852e02 100755 --- a/src/builder/cmake.rs +++ b/src/builder/cmake.rs @@ -17,19 +17,8 @@ pub fn build( let flags = &spec.build.flags; // Determine actual source directory (support source_subdir) - let actual_src = if !flags.source_subdir.is_empty() { - src_dir.join(&flags.source_subdir) - } else { - src_dir.to_path_buf() - }; + let actual_src = resolve_actual_src(spec, src_dir)?; - if !actual_src.exists() { - anyhow::bail!( - "Source directory not found: {} (source_subdir: {})", - actual_src.display(), - flags.source_subdir - ); - } let build_dir = actual_src.join("build"); @@ -70,6 +59,10 @@ pub fn build( } env_vars.push(("CC", cc)); + // Ensure CXX is exported for configure flags that reference $CXX + // prefer cross-config CXX when cross-compiling (handled earlier), otherwise use flags.cxx + env_vars.push(("CXX", flags.cxx.clone())); + // Export rootfs for build scripts env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone())); @@ -117,10 +110,11 @@ pub fn build( cmake_cmd.arg(format!("-DCMAKE_TOOLCHAIN_FILE={}", tf.display())); } - // Add custom configure flags from spec (supports cross-compilation overrides) + // Add custom configure flags from spec (supports cross-compilation overrides). + // Expand using env-vars we will supply to the child process first so patterns + // like `$CXX` (coming from flags.cxx) are substituted. for flag in &flags.configure { - // Expand environment variables in the flag - let expanded = expand_env_vars(flag); + let expanded = expand_with_envs(flag, &env_vars); cmake_cmd.arg(&expanded); } @@ -196,7 +190,7 @@ pub fn build( /// Expand environment variables in a string (e.g., $DEPOT_SYSROOT) fn expand_env_vars(input: &str) -> String { let mut result = input.to_string(); - // Simple expansion for $VAR and ${VAR} patterns + // Simple expansion for $VAR and ${VAR} patterns using process environment only for (key, value) in std::env::vars() { result = result.replace(&format!("${}", key), &value); result = result.replace(&format!("${{{}}}", key), &value); @@ -204,16 +198,73 @@ fn expand_env_vars(input: &str) -> String { result } +/// Expand using a provided set of env vars (used to expand flags before spawning child). +fn expand_with_envs(input: &str, envs: &Vec<(&str, String)>) -> String { + let mut result = input.to_string(); + for (k, v) in envs { + result = result.replace(&format!("${}", k), v); + result = result.replace(&format!("${{{}}}", k), v); + } + expand_env_vars(&result) +} + fn num_cpus() -> usize { std::thread::available_parallelism() .map(|n| n.get()) .unwrap_or(1) } +/// Resolve `source_subdir` with multiple fallbacks: +/// - empty -> use `src_dir` +/// - absolute path -> use if exists +/// - `src_dir/` -> use if exists +/// - `spec.spec_dir/` -> use if exists (supports `../llvm`) +/// - bare relative path (cwd) +fn resolve_actual_src(spec: &crate::package::PackageSpec, src_dir: &Path) -> anyhow::Result { + let flags = &spec.build.flags; + if flags.source_subdir.is_empty() { + return Ok(src_dir.to_path_buf()); + } + + let candidate = std::path::Path::new(&flags.source_subdir); + // 1) absolute path -> use directly + if candidate.is_absolute() { + if candidate.exists() { + return Ok(candidate.to_path_buf()); + } + anyhow::bail!("Source directory not found: {} (source_subdir: {})", candidate.display(), flags.source_subdir); + } + + // 2) src_dir/ + let under_src = src_dir.join(&flags.source_subdir); + if under_src.exists() { + return Ok(under_src); + } + + // 3) spec_dir/ (useful for ../llvm relative to spec) + let spec_path = spec.spec_dir.join(&flags.source_subdir); + if spec_path.exists() { + return Ok(spec_path); + } + + // 4) bare relative path (relative to CWD) + if candidate.exists() { + return Ok(candidate.to_path_buf()); + } + + // fallback error + anyhow::bail!( + "Source directory not found: {} (tried src_dir, spec_dir, and absolute path)", + flags.source_subdir + ); +} + #[cfg(test)] mod tests { use super::*; - + use crate::package::{Build, BuildFlags, BuildType, PackageInfo, PackageSpec}; + use tempfile::tempdir; + #[test] fn test_expand_env_vars_replaces_vars() { // Set a test env var @@ -224,9 +275,58 @@ mod tests { assert_eq!(out, "bar and bar"); } + #[test] + fn test_expand_with_envs_prefers_provided_envs() { + let envs = vec![("CXX", "my-cxx".to_string()), ("CC", "my-cc".to_string())]; + let s = "-DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=${CXX} -DROOT=$HOME"; + let out = expand_with_envs(s, &envs); + assert!(out.contains("my-cc")); + assert!(out.contains("my-cxx")); + // $HOME should be expanded from process env (may be present) + } + #[test] fn test_num_cpus_at_least_one() { let n = num_cpus(); assert!(n >= 1); } + + #[test] + fn resolve_actual_src_prefers_srcdir_then_specdir_and_handles_absolute() { + let tmp = tempdir().unwrap(); + let src_root = tmp.path().join("srcroot"); + let spec_dir = tmp.path().join("specdir"); + let external = tmp.path().join("external"); + std::fs::create_dir_all(&src_root.join("sub")).unwrap(); + // create directories for candidates + std::fs::create_dir_all(&spec_dir.join("../llvm")).unwrap(); + std::fs::create_dir_all(&external).unwrap(); + + let spec = PackageSpec { + package: PackageInfo { name: "x".into(), version: "1.0".into(), revision: 1, description: "".into(), homepage: "".into(), license: "MIT".into() }, + packages: Vec::new(), + alternatives: Default::default(), + manual_sources: Vec::new(), + source: Vec::new(), + build: Build { build_type: BuildType::CMake, flags: BuildFlags { source_subdir: "sub".into(), ..BuildFlags::default() } }, + dependencies: Default::default(), + spec_dir: spec_dir.clone(), + }; + + // case: relative path under src_dir + let p = resolve_actual_src(&spec, &src_root).unwrap(); + assert!(p.ends_with("sub")); + + // case: ../llvm should resolve relative to spec_dir + let mut spec2 = spec.clone(); + spec2.build.flags.source_subdir = "../llvm".into(); + let p2 = resolve_actual_src(&spec2, &src_root).unwrap(); + assert!(p2.ends_with("llvm")); + + // case: absolute path + let mut spec3 = spec.clone(); + spec3.build.flags.source_subdir = external.to_string_lossy().into_owned(); + let p3 = resolve_actual_src(&spec3, &src_root).unwrap(); + assert_eq!(p3, external); + } } diff --git a/src/package/spec.rs b/src/package/spec.rs index 377bf34..f6c3af2 100755 --- a/src/package/spec.rs +++ b/src/package/spec.rs @@ -103,7 +103,8 @@ impl PackageSpec { fn apply_flags_table(&mut self, table: &toml::map::Map) { for (k, v) in table { - match k.as_str() { + // match case-insensitively for common keys (allow CXX/Cc etc.) + match k.to_lowercase().as_str() { "cflags" => { if let Some(arr) = v.as_array() { self.build.flags.cflags = arr @@ -131,6 +132,11 @@ impl PackageSpec { self.build.flags.cc = s.to_string(); } } + "cxx" => { + if let Some(s) = v.as_str() { + self.build.flags.cxx = s.to_string(); + } + } "ar" => { if let Some(s) = v.as_str() { self.build.flags.ar = s.to_string(); @@ -674,6 +680,9 @@ pub struct BuildFlags { /// C compiler #[serde(default = "default_cc")] pub cc: String, + /// C++ compiler + #[serde(default = "default_cxx")] + pub cxx: String, /// Archiver #[serde(default = "default_ar")] pub ar: String, @@ -749,6 +758,7 @@ impl Default for BuildFlags { ldflags: Vec::new(), configure: Vec::new(), cc: default_cc(), + cxx: default_cxx(), ar: default_ar(), libc: String::new(), rootfs: default_rootfs(), @@ -829,6 +839,16 @@ fn default_carch() -> String { std::env::consts::ARCH.to_string() } +fn default_cxx() -> String { + // Infer a sensible C++ compiler name from default_cc() + let cc = default_cc(); + if cc.contains("clang") { + "clang++".to_string() + } else { + "g++".to_string() + } +} + /// Package dependencies #[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] pub struct Dependencies {