Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae5b0042e4 | ||
|
|
5b0cc08d8f |
+27
-23
@@ -1,10 +1,12 @@
|
||||
# Quirks configuration for package-specific workarounds
|
||||
# This file defines package-specific quirks that are applied during pull and deb operations
|
||||
#
|
||||
# Entries can be scoped with `series`: an empty list applies to every
|
||||
# series, otherwise only the listed ones. Packaging workarounds should
|
||||
# carry the series they were verified against so they can be dropped once
|
||||
# the upstream packaging catches up.
|
||||
# `pull` and `deb` hold one entry per scope: several entries can carry
|
||||
# different `series` lists, and every matching entry applies in file
|
||||
# order. Entries can be scoped with `series`: an empty list applies to
|
||||
# every series, otherwise only the listed ones. Packaging workarounds
|
||||
# should carry the series they were verified against so they can be
|
||||
# dropped once the upstream packaging catches up.
|
||||
|
||||
quirks:
|
||||
|
||||
@@ -15,29 +17,31 @@ quirks:
|
||||
# until the control is fixed upstream.
|
||||
linux:
|
||||
deb:
|
||||
series: [resolute]
|
||||
dependencies:
|
||||
replace:
|
||||
llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||
- series: [resolute]
|
||||
dependencies:
|
||||
replace:
|
||||
llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||
linux-riscv:
|
||||
deb:
|
||||
series: [resolute]
|
||||
dependencies:
|
||||
replace:
|
||||
llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||
- series: [resolute]
|
||||
dependencies:
|
||||
replace:
|
||||
llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||
|
||||
# Add more packages and their quirks as needed
|
||||
# example-package:
|
||||
# pull:
|
||||
# method: archive
|
||||
# - series: [noble]
|
||||
# package_directory:
|
||||
# - linux-main
|
||||
# deb:
|
||||
# series: [noble]
|
||||
# dependencies:
|
||||
# replace:
|
||||
# old-dep: new-dep (>= 2) [linux-any]
|
||||
# inject:
|
||||
# - missing-dep
|
||||
# drop:
|
||||
# - broken-dep
|
||||
# parameters:
|
||||
# key: value
|
||||
# - series: [resolute]
|
||||
# dependencies:
|
||||
# replace:
|
||||
# llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||
# - series: [stonking]
|
||||
# dependencies:
|
||||
# replace:
|
||||
# llvm-22-dev: llvm-22-dev:native <!stage1>
|
||||
# parameters:
|
||||
# key: value
|
||||
|
||||
+48
-14
@@ -24,33 +24,31 @@ impl ContextDriver for LocalDriver {
|
||||
}
|
||||
|
||||
fn create_temp_dir(&self) -> io::Result<String> {
|
||||
// Generate a unique temporary directory name with random string
|
||||
// Sub-second precision and an atomic create: two concurrent
|
||||
// contexts racing on the same name must never share a directory,
|
||||
// so the loser of a create falls through to the next attempt
|
||||
// instead of probing for existence first (a probe-then-create
|
||||
// window loses exactly when two callers arrive together).
|
||||
let base_timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
.as_millis();
|
||||
|
||||
let mut attempt = 0;
|
||||
loop {
|
||||
let work_dir_name = if attempt == 0 {
|
||||
format!("pkh-{}", base_timestamp)
|
||||
format!("pkh-{base_timestamp}")
|
||||
} else {
|
||||
format!("pkh-{}-{}", base_timestamp, attempt)
|
||||
format!("pkh-{base_timestamp}-{attempt}")
|
||||
};
|
||||
|
||||
let temp_dir_path = std::env::temp_dir().join(&work_dir_name);
|
||||
|
||||
// Check if directory already exists
|
||||
if temp_dir_path.exists() {
|
||||
attempt += 1;
|
||||
continue;
|
||||
match std::fs::create_dir(&temp_dir_path) {
|
||||
Ok(()) => return Ok(temp_dir_path.to_string_lossy().to_string()),
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => attempt += 1,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
// Create the directory
|
||||
std::fs::create_dir_all(&temp_dir_path)?;
|
||||
|
||||
// Return the path as a string
|
||||
return Ok(temp_dir_path.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,3 +188,39 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Concurrent callers must never share a temporary directory: the
|
||||
/// create is atomic, so a lost race falls through to the next name
|
||||
/// instead of both callers probing the same free name and unpacking
|
||||
/// into the same directory.
|
||||
#[test]
|
||||
fn create_temp_dir_is_unique_under_concurrency() {
|
||||
const CALLERS: usize = 8;
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let handles: Vec<_> = (0..CALLERS)
|
||||
.map(|_| {
|
||||
let tx = tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let dir = LocalDriver.create_temp_dir().unwrap();
|
||||
tx.send(dir).unwrap();
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
drop(tx);
|
||||
|
||||
let mut names: Vec<String> = rx.iter().collect();
|
||||
names.sort();
|
||||
let unique: std::collections::BTreeSet<&String> = names.iter().collect();
|
||||
assert_eq!(names.len(), unique.len(), "duplicate temp dirs: {names:?}");
|
||||
for name in &unique {
|
||||
std::fs::remove_dir(name).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-17
@@ -296,37 +296,41 @@ impl ContextDriver for UnshareDriver {
|
||||
|
||||
fn create_temp_dir(&self) -> io::Result<String> {
|
||||
// Create a temporary directory inside the chroot with unique naming
|
||||
// Sub-second precision and an atomic create, like the local
|
||||
// driver: concurrent callers racing on the same name must not
|
||||
// share a directory, so an existing target falls through to the
|
||||
// next attempt instead of a probe-then-create window.
|
||||
let base_timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
.as_millis();
|
||||
|
||||
let mut attempt = 0;
|
||||
loop {
|
||||
let work_dir_name = if attempt == 0 {
|
||||
format!("pkh-build-{}", base_timestamp)
|
||||
format!("pkh-build-{base_timestamp}")
|
||||
} else {
|
||||
format!("pkh-build-{}-{}", base_timestamp, attempt)
|
||||
format!("pkh-build-{base_timestamp}-{attempt}")
|
||||
};
|
||||
|
||||
let work_dir_inside_chroot = format!("/tmp/{}", work_dir_name);
|
||||
let work_dir_inside_chroot = format!("/tmp/{work_dir_name}");
|
||||
let host_path = Path::new(&self.path).join("tmp").join(&work_dir_name);
|
||||
|
||||
// Check if directory already exists
|
||||
if host_path.exists() {
|
||||
attempt += 1;
|
||||
continue;
|
||||
match std::fs::create_dir(&host_path) {
|
||||
Ok(()) => {
|
||||
debug!(
|
||||
"Created work directory: {} (host: {})",
|
||||
work_dir_inside_chroot,
|
||||
host_path.display()
|
||||
);
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
|
||||
attempt += 1;
|
||||
continue;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
// Create the directory on the host filesystem
|
||||
std::fs::create_dir_all(&host_path)?;
|
||||
|
||||
debug!(
|
||||
"Created work directory: {} (host: {})",
|
||||
work_dir_inside_chroot,
|
||||
host_path.display()
|
||||
);
|
||||
|
||||
// Return the path as it appears inside the chroot
|
||||
return Ok(work_dir_inside_chroot);
|
||||
}
|
||||
|
||||
+2
-1
@@ -231,9 +231,10 @@ pub async fn build(
|
||||
return Err("Could not install essential packages for the build".into());
|
||||
}
|
||||
|
||||
// Find the actual package directory
|
||||
// Find the actual package directory
|
||||
let package_dir =
|
||||
crate::deb::find_package_directory(Path::new(build_root), package, version, &ctx)?;
|
||||
crate::deb::find_package_directory(Path::new(build_root), package, version, series, &ctx)?;
|
||||
let package_dir_str = package_dir
|
||||
.to_str()
|
||||
.ok_or("Invalid package directory path")?;
|
||||
|
||||
+10
-4
@@ -337,10 +337,11 @@ pub(crate) fn find_package_directory(
|
||||
parent_dir: &Path,
|
||||
package: &str,
|
||||
version: &str,
|
||||
series: &str,
|
||||
ctx: &context::Context,
|
||||
) -> Result<PathBuf, Box<dyn Error>> {
|
||||
// Check quirks first for custom package directories
|
||||
let custom_dirs = crate::quirks::get_package_directories(package);
|
||||
let custom_dirs = crate::quirks::get_package_directories(package, series);
|
||||
for custom_dir in custom_dirs {
|
||||
let package_dir = parent_dir.join(&custom_dir);
|
||||
if ctx.exists(&package_dir)? && ctx.exists(&package_dir.join("debian"))? {
|
||||
@@ -533,9 +534,14 @@ mod tests {
|
||||
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap());
|
||||
|
||||
// Change directory to the package directory
|
||||
let cwd =
|
||||
crate::deb::find_package_directory(cwd, package, &package_info.stanza.version, &ctx)
|
||||
.expect("Cannot find package directory");
|
||||
let cwd = crate::deb::find_package_directory(
|
||||
cwd,
|
||||
package,
|
||||
&package_info.stanza.version,
|
||||
series,
|
||||
&ctx,
|
||||
)
|
||||
.expect("Cannot find package directory");
|
||||
log::debug!("Package directory: {}", cwd.display());
|
||||
|
||||
log::info!("Starting binary package build...");
|
||||
|
||||
+55
-36
@@ -57,15 +57,19 @@ pub struct OperationQuirks {
|
||||
}
|
||||
|
||||
/// Quirks for a specific package
|
||||
///
|
||||
/// `pull` and `deb` hold one entry per scope: an operation can carry
|
||||
/// several entries with different `series` lists; every matching entry
|
||||
/// applies, in file order.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PackageQuirks {
|
||||
/// Quirks to apply during pull operation
|
||||
#[serde(default)]
|
||||
pub pull: Option<OperationQuirks>,
|
||||
pub pull: Vec<OperationQuirks>,
|
||||
|
||||
/// Quirks to apply during deb operation
|
||||
#[serde(default)]
|
||||
pub deb: Option<OperationQuirks>,
|
||||
pub deb: Vec<OperationQuirks>,
|
||||
}
|
||||
|
||||
/// Top-level quirks configuration
|
||||
@@ -102,49 +106,64 @@ fn entry_applies_to_series(quirks: &OperationQuirks, series: &str) -> bool {
|
||||
|
||||
/// Get the build-dependency resolution rules of a package for a series
|
||||
///
|
||||
/// Every deb entry whose series list matches contributes its rules; the
|
||||
/// returned rules apply in file order.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `package` - The package name
|
||||
/// * `series` - The distribution series (e.g. "resolute")
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Option<DependencyQuirks>` - The rules, or None when the package has
|
||||
/// no deb entry or the entry does not apply to the series
|
||||
pub fn get_deb_dependency_quirks(package: &str, series: &str) -> Option<DependencyQuirks> {
|
||||
let quirks = get_package_quirks(&QUIRKS_DATA, package)?;
|
||||
let deb = quirks.deb.as_ref()?;
|
||||
if entry_applies_to_series(deb, series) {
|
||||
deb.dependencies.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
/// * `Vec<DependencyQuirks>` - The matching rules, empty when the package
|
||||
/// has no deb entry or none applies to the series
|
||||
pub fn get_deb_dependency_quirks(package: &str, series: &str) -> Vec<DependencyQuirks> {
|
||||
let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) else {
|
||||
return Vec::new();
|
||||
};
|
||||
quirks
|
||||
.deb
|
||||
.iter()
|
||||
.filter(|deb| entry_applies_to_series(deb, series))
|
||||
.filter_map(|deb| deb.dependencies.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get package directories from quirks configuration
|
||||
///
|
||||
/// This function returns the list of custom package directories to try
|
||||
/// when looking for the package source directory.
|
||||
/// when looking for the package source directory: every matching deb
|
||||
/// entry contributes its directories, falling back to the pull entries
|
||||
/// when no deb entry carries any.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `package` - The package name
|
||||
/// * `series` - The distribution series (e.g. "resolute")
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Vec<String>` - List of package directories to try, or empty vector if none
|
||||
pub fn get_package_directories(package: &str) -> Vec<String> {
|
||||
if let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) {
|
||||
// Check deb quirks first, then pull quirks
|
||||
if let Some(deb_quirks) = &quirks.deb
|
||||
&& !deb_quirks.package_directory.is_empty()
|
||||
pub fn get_package_directories(package: &str, series: &str) -> Vec<String> {
|
||||
let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut directories = Vec::new();
|
||||
for deb in quirks
|
||||
.deb
|
||||
.iter()
|
||||
.filter(|q| entry_applies_to_series(q, series))
|
||||
{
|
||||
directories.extend(deb.package_directory.iter().cloned());
|
||||
}
|
||||
if directories.is_empty() {
|
||||
for pull in quirks
|
||||
.pull
|
||||
.iter()
|
||||
.filter(|q| entry_applies_to_series(q, series))
|
||||
{
|
||||
return deb_quirks.package_directory.clone();
|
||||
}
|
||||
if let Some(pull_quirks) = &quirks.pull
|
||||
&& !pull_quirks.package_directory.is_empty()
|
||||
{
|
||||
return pull_quirks.package_directory.clone();
|
||||
directories.extend(pull.package_directory.iter().cloned());
|
||||
}
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
directories
|
||||
}
|
||||
|
||||
/// Apply the dependency quirks of `package` in `series` to parsed
|
||||
@@ -160,10 +179,10 @@ pub fn apply_dependency_quirks(
|
||||
clauses: &mut Vec<Vec<PkgRelation>>,
|
||||
opts: &ParseOpts,
|
||||
) -> Result<(), String> {
|
||||
let Some(deps) = get_deb_dependency_quirks(package, series) else {
|
||||
return Ok(());
|
||||
};
|
||||
apply_rules(clauses, &deps, opts)
|
||||
for deps in get_deb_dependency_quirks(package, series) {
|
||||
apply_rules(clauses, &deps, opts)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply one set of dependency rules to parsed clauses.
|
||||
@@ -236,8 +255,8 @@ mod tests {
|
||||
fn test_unknown_package_has_no_quirks() {
|
||||
// A package absent from quirks.yml has no dependency rules nor
|
||||
// custom directories, and must not panic
|
||||
assert!(get_deb_dependency_quirks("not-in-quirks", "resolute").is_none());
|
||||
assert!(get_package_directories("not-in-quirks").is_empty());
|
||||
assert!(get_deb_dependency_quirks("not-in-quirks", "resolute").is_empty());
|
||||
assert!(get_package_directories("not-in-quirks", "resolute").is_empty());
|
||||
}
|
||||
|
||||
/// The linux dependency quirks are scoped to the series they were
|
||||
@@ -245,13 +264,13 @@ mod tests {
|
||||
#[test]
|
||||
fn linux_dependency_quirks_are_series_scoped() {
|
||||
for package in ["linux", "linux-riscv"] {
|
||||
let deps =
|
||||
get_deb_dependency_quirks(package, "resolute").expect("the resolute entry applies");
|
||||
let rules = get_deb_dependency_quirks(package, "resolute");
|
||||
assert_eq!(rules.len(), 1, "the resolute entry applies");
|
||||
assert_eq!(
|
||||
deps.replace.get("llvm-21-dev").map(String::as_str),
|
||||
rules[0].replace.get("llvm-21-dev").map(String::as_str),
|
||||
Some("llvm-21-dev:native <!stage1>")
|
||||
);
|
||||
assert!(get_deb_dependency_quirks(package, "noble").is_none());
|
||||
assert!(get_deb_dependency_quirks(package, "noble").is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user