feat: add repo planning/binary repo support and Meson build integration
- add repos.toml source/binary repo configuration with arch overrides and priorities - add dependency planner/execution plan flow with interactive provider selection and --yes support - expand binary repo DB metadata (sha512, dependencies, file lists) and repo owns/search helpers - add rootfs-scoped fd locking and minisign detached signing/verification helpers - add Meson build/test support for the project with builddir-local CARGO_HOME/CARGO_TARGET_DIR - install contrib example configs into sysconfdir (/etc/depot.d layout) via Meson - improve builder env handling (sanitized PATH, tool env passthrough, CXXFLAGS support) - support += appends in package specs and add build.flags.cxxflags parsing - prompt for git credentials when private repo auth is required - update docs/examples and dependency setup (system libs, zstdmt, tempfile pin)
This commit is contained in:
+185
-1
@@ -58,12 +58,17 @@ impl PackageSpec {
|
||||
|
||||
let content = fs::read_to_string(&abs_path)
|
||||
.with_context(|| format!("Failed to read package spec: {}", abs_path.display()))?;
|
||||
let mut spec: PackageSpec = toml::from_str(&content)
|
||||
let (base_content, appends) =
|
||||
preprocess_spec_toml_appends(&content).with_context(|| {
|
||||
format!("Failed to preprocess package spec: {}", abs_path.display())
|
||||
})?;
|
||||
let mut spec: PackageSpec = toml::from_str(&base_content)
|
||||
.with_context(|| format!("Failed to parse package spec: {}", abs_path.display()))?;
|
||||
spec.spec_dir = abs_path
|
||||
.parent()
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
spec.apply_spec_appends(&appends)?;
|
||||
|
||||
// Require at least one source (remote or manual)
|
||||
if spec.source.is_empty() && spec.manual_sources.is_empty() {
|
||||
@@ -74,6 +79,28 @@ impl PackageSpec {
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
fn apply_spec_appends(
|
||||
&mut self,
|
||||
appends: &std::collections::HashMap<String, Vec<toml::Value>>,
|
||||
) -> Result<()> {
|
||||
for (key, values) in appends {
|
||||
if let Some(subkey) = key.strip_prefix("build.flags.") {
|
||||
self.apply_append(subkey, values);
|
||||
continue;
|
||||
}
|
||||
if let Some(subkey) = key.strip_prefix("flags.") {
|
||||
self.apply_append(subkey, values);
|
||||
continue;
|
||||
}
|
||||
if !key.contains('.') {
|
||||
self.apply_append(key, values);
|
||||
continue;
|
||||
}
|
||||
anyhow::bail!("Unsupported '+=' key in package spec: {}", key);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_manual_sources(&self) -> Result<()> {
|
||||
for (idx, manual) in self.manual_sources.iter().enumerate() {
|
||||
let has_file = manual
|
||||
@@ -206,6 +233,17 @@ impl PackageSpec {
|
||||
self.build.flags.cflags = vec![s.to_string()];
|
||||
}
|
||||
}
|
||||
"cxxflags" => {
|
||||
if let Some(arr) = v.as_array() {
|
||||
self.build.flags.cxxflags = arr
|
||||
.iter()
|
||||
.filter_map(|x| x.as_str())
|
||||
.map(String::from)
|
||||
.collect();
|
||||
} else if let Some(s) = v.as_str() {
|
||||
self.build.flags.cxxflags = vec![s.to_string()];
|
||||
}
|
||||
}
|
||||
"ldflags" => {
|
||||
if let Some(arr) = v.as_array() {
|
||||
self.build.flags.ldflags = arr
|
||||
@@ -485,6 +523,18 @@ impl PackageSpec {
|
||||
}
|
||||
}
|
||||
}
|
||||
"cxxflags" => {
|
||||
for v in values {
|
||||
if let Some(arr) = v.as_array() {
|
||||
self.build
|
||||
.flags
|
||||
.cxxflags
|
||||
.extend(arr.iter().filter_map(|x| x.as_str()).map(String::from));
|
||||
} else if let Some(s) = v.as_str() {
|
||||
self.build.flags.cxxflags.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
"ldflags" => {
|
||||
for v in values {
|
||||
if let Some(arr) = v.as_array() {
|
||||
@@ -815,6 +865,74 @@ impl PackageSpec {
|
||||
}
|
||||
}
|
||||
|
||||
fn preprocess_spec_toml_appends(
|
||||
input: &str,
|
||||
) -> Result<(String, std::collections::HashMap<String, Vec<toml::Value>>)> {
|
||||
let mut base_text = String::new();
|
||||
let mut appends = std::collections::HashMap::new();
|
||||
let mut current_table: Option<String> = None;
|
||||
let mut in_array_table = false;
|
||||
|
||||
for line in input.lines() {
|
||||
let trimmed = line.trim();
|
||||
|
||||
if trimmed.starts_with("[[") && trimmed.ends_with("]]") && trimmed.len() >= 4 {
|
||||
current_table = Some(trimmed[2..trimmed.len() - 2].trim().to_string());
|
||||
in_array_table = true;
|
||||
base_text.push_str(line);
|
||||
base_text.push('\n');
|
||||
continue;
|
||||
}
|
||||
if trimmed.starts_with('[') && trimmed.ends_with(']') && trimmed.len() >= 2 {
|
||||
current_table = Some(trimmed[1..trimmed.len() - 1].trim().to_string());
|
||||
in_array_table = false;
|
||||
base_text.push_str(line);
|
||||
base_text.push('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||
base_text.push_str(line);
|
||||
base_text.push('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(plus_idx) = trimmed.find("+=") {
|
||||
if in_array_table {
|
||||
anyhow::bail!(
|
||||
"'+=' is not supported inside array-of-table sections ({})",
|
||||
current_table.as_deref().unwrap_or("")
|
||||
);
|
||||
}
|
||||
let key = trimmed[..plus_idx].trim();
|
||||
let val_str = trimmed[plus_idx + 2..].trim();
|
||||
let val: toml::Value = toml::from_str::<toml::Value>(&format!("v = {}", val_str))
|
||||
.context("Failed to parse append value")?
|
||||
.get("v")
|
||||
.cloned()
|
||||
.unwrap();
|
||||
|
||||
let full_key = if key.contains('.') {
|
||||
key.to_string()
|
||||
} else if let Some(table) = current_table.as_deref() {
|
||||
format!("{}.{}", table, key)
|
||||
} else {
|
||||
key.to_string()
|
||||
};
|
||||
|
||||
appends.entry(full_key).or_insert_with(Vec::new).push(val);
|
||||
// Preserve line numbering for parser diagnostics.
|
||||
base_text.push('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
base_text.push_str(line);
|
||||
base_text.push('\n');
|
||||
}
|
||||
|
||||
Ok((base_text, appends))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod spec_tests {
|
||||
use super::*;
|
||||
@@ -1223,6 +1341,7 @@ type = "custom"
|
||||
[flags]
|
||||
cc = "my-cc"
|
||||
cflags = ["-O2"]
|
||||
cxxflags = ["-O2", "-pipe"]
|
||||
passthrough_env = ["RUSTFLAGS"]
|
||||
make_vars = ["V=1"]
|
||||
make_dirs = ["lib"]
|
||||
@@ -1242,6 +1361,10 @@ post_configure = ["echo configured"]
|
||||
"build.flags.cflags".to_string(),
|
||||
vec![toml::Value::String("-g".to_string())],
|
||||
);
|
||||
config.appends.insert(
|
||||
"build.flags.cxxflags".to_string(),
|
||||
vec![toml::Value::String("-stdlib=libc++".to_string())],
|
||||
);
|
||||
config.appends.insert(
|
||||
"build.flags.rustflags".to_string(),
|
||||
vec![toml::Value::Array(vec![
|
||||
@@ -1301,6 +1424,14 @@ post_configure = ["echo configured"]
|
||||
assert_eq!(spec.build.flags.cc, "my-cc");
|
||||
assert!(spec.build.flags.cflags.contains(&"-O2".to_string()));
|
||||
assert!(spec.build.flags.cflags.contains(&"-g".to_string()));
|
||||
assert!(spec.build.flags.cxxflags.contains(&"-O2".to_string()));
|
||||
assert!(spec.build.flags.cxxflags.contains(&"-pipe".to_string()));
|
||||
assert!(
|
||||
spec.build
|
||||
.flags
|
||||
.cxxflags
|
||||
.contains(&"-stdlib=libc++".to_string())
|
||||
);
|
||||
assert!(spec.build.flags.rustflags.contains(&"-C".to_string()));
|
||||
assert!(
|
||||
spec.build
|
||||
@@ -1656,6 +1787,51 @@ keep = ["etc/locale.gen", "etc/resolv.conf"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_build_flags_appends_from_spec_file() {
|
||||
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 = "custom"
|
||||
|
||||
[build.flags]
|
||||
cxxflags = ["-O2"]
|
||||
cxxflags += [ "-Wno-gnu-statement-expression-from-macro-expansion" ]
|
||||
ldflags += "-Wl,--as-needed"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let spec = PackageSpec::from_file(&path).unwrap();
|
||||
assert_eq!(
|
||||
spec.build.flags.cxxflags,
|
||||
vec![
|
||||
"-O2".to_string(),
|
||||
"-Wno-gnu-statement-expression-from-macro-expansion".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
spec.build.flags.ldflags,
|
||||
vec!["-Wl,--as-needed".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_passthrough_env_from_spec() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
@@ -1829,8 +2005,12 @@ cbuild = "x86_64-pc-linux-gnu"
|
||||
.unwrap(),
|
||||
package_overrides: toml::Value::Table(toml::map::Map::new()),
|
||||
appends: std::collections::HashMap::new(),
|
||||
repo_settings: crate::config::RepoSettings::default(),
|
||||
source_repos: std::collections::BTreeMap::new(),
|
||||
binary_repos: std::collections::BTreeMap::new(),
|
||||
mirrors: std::collections::HashMap::new(),
|
||||
repo_clone_dir: PathBuf::from("/tmp"),
|
||||
package_cache_dir: PathBuf::from("/tmp"),
|
||||
};
|
||||
|
||||
spec.apply_config(&config);
|
||||
@@ -2124,6 +2304,9 @@ pub enum BuildType {
|
||||
pub struct BuildFlags {
|
||||
#[serde(default, deserialize_with = "deserialize_string_or_array")]
|
||||
pub cflags: Vec<String>,
|
||||
/// Extra flags exported to `CXXFLAGS`.
|
||||
#[serde(default, deserialize_with = "deserialize_string_or_array")]
|
||||
pub cxxflags: Vec<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_string_or_array")]
|
||||
pub ldflags: Vec<String>,
|
||||
/// Keep existing files and install package-provided replacement as `<path>.depotnew`.
|
||||
@@ -2328,6 +2511,7 @@ impl Default for BuildFlags {
|
||||
fn default() -> Self {
|
||||
BuildFlags {
|
||||
cflags: Vec::new(),
|
||||
cxxflags: Vec::new(),
|
||||
ldflags: Vec::new(),
|
||||
keep: Vec::new(),
|
||||
no_flags: false,
|
||||
|
||||
Reference in New Issue
Block a user