feat: Enhance package specification to support case-insensitive flags and add C++ compiler configuration
This commit is contained in:
+117
-17
@@ -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/<sub>` -> use if exists
|
||||
/// - `spec.spec_dir/<sub>` -> use if exists (supports `../llvm`)
|
||||
/// - bare relative path (cwd)
|
||||
fn resolve_actual_src(spec: &crate::package::PackageSpec, src_dir: &Path) -> anyhow::Result<std::path::PathBuf> {
|
||||
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/<source_subdir>
|
||||
let under_src = src_dir.join(&flags.source_subdir);
|
||||
if under_src.exists() {
|
||||
return Ok(under_src);
|
||||
}
|
||||
|
||||
// 3) spec_dir/<source_subdir> (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);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -103,7 +103,8 @@ impl PackageSpec {
|
||||
|
||||
fn apply_flags_table(&mut self, table: &toml::map::Map<String, toml::Value>) {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user