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;
|
let flags = &spec.build.flags;
|
||||||
|
|
||||||
// Determine actual source directory (support source_subdir)
|
// Determine actual source directory (support source_subdir)
|
||||||
let actual_src = if !flags.source_subdir.is_empty() {
|
let actual_src = resolve_actual_src(spec, src_dir)?;
|
||||||
src_dir.join(&flags.source_subdir)
|
|
||||||
} else {
|
|
||||||
src_dir.to_path_buf()
|
|
||||||
};
|
|
||||||
|
|
||||||
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");
|
let build_dir = actual_src.join("build");
|
||||||
|
|
||||||
@@ -70,6 +59,10 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
env_vars.push(("CC", cc));
|
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
|
// Export rootfs for build scripts
|
||||||
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
||||||
|
|
||||||
@@ -117,10 +110,11 @@ pub fn build(
|
|||||||
cmake_cmd.arg(format!("-DCMAKE_TOOLCHAIN_FILE={}", tf.display()));
|
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 {
|
for flag in &flags.configure {
|
||||||
// Expand environment variables in the flag
|
let expanded = expand_with_envs(flag, &env_vars);
|
||||||
let expanded = expand_env_vars(flag);
|
|
||||||
cmake_cmd.arg(&expanded);
|
cmake_cmd.arg(&expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +190,7 @@ pub fn build(
|
|||||||
/// Expand environment variables in a string (e.g., $DEPOT_SYSROOT)
|
/// Expand environment variables in a string (e.g., $DEPOT_SYSROOT)
|
||||||
fn expand_env_vars(input: &str) -> String {
|
fn expand_env_vars(input: &str) -> String {
|
||||||
let mut result = input.to_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() {
|
for (key, value) in std::env::vars() {
|
||||||
result = result.replace(&format!("${}", key), &value);
|
result = result.replace(&format!("${}", key), &value);
|
||||||
result = result.replace(&format!("${{{}}}", key), &value);
|
result = result.replace(&format!("${{{}}}", key), &value);
|
||||||
@@ -204,16 +198,73 @@ fn expand_env_vars(input: &str) -> String {
|
|||||||
result
|
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 {
|
fn num_cpus() -> usize {
|
||||||
std::thread::available_parallelism()
|
std::thread::available_parallelism()
|
||||||
.map(|n| n.get())
|
.map(|n| n.get())
|
||||||
.unwrap_or(1)
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::package::{Build, BuildFlags, BuildType, PackageInfo, PackageSpec};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand_env_vars_replaces_vars() {
|
fn test_expand_env_vars_replaces_vars() {
|
||||||
// Set a test env var
|
// Set a test env var
|
||||||
@@ -224,9 +275,58 @@ mod tests {
|
|||||||
assert_eq!(out, "bar and bar");
|
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]
|
#[test]
|
||||||
fn test_num_cpus_at_least_one() {
|
fn test_num_cpus_at_least_one() {
|
||||||
let n = num_cpus();
|
let n = num_cpus();
|
||||||
assert!(n >= 1);
|
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>) {
|
fn apply_flags_table(&mut self, table: &toml::map::Map<String, toml::Value>) {
|
||||||
for (k, v) in table {
|
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" => {
|
"cflags" => {
|
||||||
if let Some(arr) = v.as_array() {
|
if let Some(arr) = v.as_array() {
|
||||||
self.build.flags.cflags = arr
|
self.build.flags.cflags = arr
|
||||||
@@ -131,6 +132,11 @@ impl PackageSpec {
|
|||||||
self.build.flags.cc = s.to_string();
|
self.build.flags.cc = s.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"cxx" => {
|
||||||
|
if let Some(s) = v.as_str() {
|
||||||
|
self.build.flags.cxx = s.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
"ar" => {
|
"ar" => {
|
||||||
if let Some(s) = v.as_str() {
|
if let Some(s) = v.as_str() {
|
||||||
self.build.flags.ar = s.to_string();
|
self.build.flags.ar = s.to_string();
|
||||||
@@ -674,6 +680,9 @@ pub struct BuildFlags {
|
|||||||
/// C compiler
|
/// C compiler
|
||||||
#[serde(default = "default_cc")]
|
#[serde(default = "default_cc")]
|
||||||
pub cc: String,
|
pub cc: String,
|
||||||
|
/// C++ compiler
|
||||||
|
#[serde(default = "default_cxx")]
|
||||||
|
pub cxx: String,
|
||||||
/// Archiver
|
/// Archiver
|
||||||
#[serde(default = "default_ar")]
|
#[serde(default = "default_ar")]
|
||||||
pub ar: String,
|
pub ar: String,
|
||||||
@@ -749,6 +758,7 @@ impl Default for BuildFlags {
|
|||||||
ldflags: Vec::new(),
|
ldflags: Vec::new(),
|
||||||
configure: Vec::new(),
|
configure: Vec::new(),
|
||||||
cc: default_cc(),
|
cc: default_cc(),
|
||||||
|
cxx: default_cxx(),
|
||||||
ar: default_ar(),
|
ar: default_ar(),
|
||||||
libc: String::new(),
|
libc: String::new(),
|
||||||
rootfs: default_rootfs(),
|
rootfs: default_rootfs(),
|
||||||
@@ -829,6 +839,16 @@ fn default_carch() -> String {
|
|||||||
std::env::consts::ARCH.to_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
|
/// Package dependencies
|
||||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct Dependencies {
|
pub struct Dependencies {
|
||||||
|
|||||||
Reference in New Issue
Block a user