Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae5b0042e4 | ||
|
|
5b0cc08d8f |
+27
-23
@@ -1,10 +1,12 @@
|
|||||||
# Quirks configuration for package-specific workarounds
|
# Quirks configuration for package-specific workarounds
|
||||||
# This file defines package-specific quirks that are applied during pull and deb operations
|
# 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
|
# `pull` and `deb` hold one entry per scope: several entries can carry
|
||||||
# series, otherwise only the listed ones. Packaging workarounds should
|
# different `series` lists, and every matching entry applies in file
|
||||||
# carry the series they were verified against so they can be dropped once
|
# order. Entries can be scoped with `series`: an empty list applies to
|
||||||
# the upstream packaging catches up.
|
# 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:
|
quirks:
|
||||||
|
|
||||||
@@ -15,29 +17,31 @@ quirks:
|
|||||||
# until the control is fixed upstream.
|
# until the control is fixed upstream.
|
||||||
linux:
|
linux:
|
||||||
deb:
|
deb:
|
||||||
series: [resolute]
|
- series: [resolute]
|
||||||
dependencies:
|
dependencies:
|
||||||
replace:
|
replace:
|
||||||
llvm-21-dev: llvm-21-dev:native <!stage1>
|
llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||||
linux-riscv:
|
linux-riscv:
|
||||||
deb:
|
deb:
|
||||||
series: [resolute]
|
- series: [resolute]
|
||||||
dependencies:
|
dependencies:
|
||||||
replace:
|
replace:
|
||||||
llvm-21-dev: llvm-21-dev:native <!stage1>
|
llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||||
|
|
||||||
# Add more packages and their quirks as needed
|
# Add more packages and their quirks as needed
|
||||||
# example-package:
|
# example-package:
|
||||||
# pull:
|
# pull:
|
||||||
# method: archive
|
# - series: [noble]
|
||||||
|
# package_directory:
|
||||||
|
# - linux-main
|
||||||
# deb:
|
# deb:
|
||||||
# series: [noble]
|
# - series: [resolute]
|
||||||
# dependencies:
|
# dependencies:
|
||||||
# replace:
|
# replace:
|
||||||
# old-dep: new-dep (>= 2) [linux-any]
|
# llvm-21-dev: llvm-21-dev:native <!stage1>
|
||||||
# inject:
|
# - series: [stonking]
|
||||||
# - missing-dep
|
# dependencies:
|
||||||
# drop:
|
# replace:
|
||||||
# - broken-dep
|
# llvm-22-dev: llvm-22-dev:native <!stage1>
|
||||||
# parameters:
|
# parameters:
|
||||||
# key: value
|
# key: value
|
||||||
|
|||||||
+48
-14
@@ -24,33 +24,31 @@ impl ContextDriver for LocalDriver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_temp_dir(&self) -> io::Result<String> {
|
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()
|
let base_timestamp = SystemTime::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs();
|
.as_millis();
|
||||||
|
|
||||||
let mut attempt = 0;
|
let mut attempt = 0;
|
||||||
loop {
|
loop {
|
||||||
let work_dir_name = if attempt == 0 {
|
let work_dir_name = if attempt == 0 {
|
||||||
format!("pkh-{}", base_timestamp)
|
format!("pkh-{base_timestamp}")
|
||||||
} else {
|
} else {
|
||||||
format!("pkh-{}-{}", base_timestamp, attempt)
|
format!("pkh-{base_timestamp}-{attempt}")
|
||||||
};
|
};
|
||||||
|
|
||||||
let temp_dir_path = std::env::temp_dir().join(&work_dir_name);
|
let temp_dir_path = std::env::temp_dir().join(&work_dir_name);
|
||||||
|
|
||||||
// Check if directory already exists
|
match std::fs::create_dir(&temp_dir_path) {
|
||||||
if temp_dir_path.exists() {
|
Ok(()) => return Ok(temp_dir_path.to_string_lossy().to_string()),
|
||||||
attempt += 1;
|
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => attempt += 1,
|
||||||
continue;
|
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(())
|
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> {
|
fn create_temp_dir(&self) -> io::Result<String> {
|
||||||
// Create a temporary directory inside the chroot with unique naming
|
// 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()
|
let base_timestamp = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs();
|
.as_millis();
|
||||||
|
|
||||||
let mut attempt = 0;
|
let mut attempt = 0;
|
||||||
loop {
|
loop {
|
||||||
let work_dir_name = if attempt == 0 {
|
let work_dir_name = if attempt == 0 {
|
||||||
format!("pkh-build-{}", base_timestamp)
|
format!("pkh-build-{base_timestamp}")
|
||||||
} else {
|
} 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);
|
let host_path = Path::new(&self.path).join("tmp").join(&work_dir_name);
|
||||||
|
|
||||||
// Check if directory already exists
|
match std::fs::create_dir(&host_path) {
|
||||||
if host_path.exists() {
|
Ok(()) => {
|
||||||
attempt += 1;
|
debug!(
|
||||||
continue;
|
"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 the path as it appears inside the chroot
|
||||||
return Ok(work_dir_inside_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());
|
return Err("Could not install essential packages for the build".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the actual package directory
|
||||||
// Find the actual package directory
|
// Find the actual package directory
|
||||||
let package_dir =
|
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
|
let package_dir_str = package_dir
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or("Invalid package directory path")?;
|
.ok_or("Invalid package directory path")?;
|
||||||
|
|||||||
+10
-4
@@ -337,10 +337,11 @@ pub(crate) fn find_package_directory(
|
|||||||
parent_dir: &Path,
|
parent_dir: &Path,
|
||||||
package: &str,
|
package: &str,
|
||||||
version: &str,
|
version: &str,
|
||||||
|
series: &str,
|
||||||
ctx: &context::Context,
|
ctx: &context::Context,
|
||||||
) -> Result<PathBuf, Box<dyn Error>> {
|
) -> Result<PathBuf, Box<dyn Error>> {
|
||||||
// Check quirks first for custom package directories
|
// 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 {
|
for custom_dir in custom_dirs {
|
||||||
let package_dir = parent_dir.join(&custom_dir);
|
let package_dir = parent_dir.join(&custom_dir);
|
||||||
if ctx.exists(&package_dir)? && ctx.exists(&package_dir.join("debian"))? {
|
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());
|
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap());
|
||||||
|
|
||||||
// Change directory to the package directory
|
// Change directory to the package directory
|
||||||
let cwd =
|
let cwd = crate::deb::find_package_directory(
|
||||||
crate::deb::find_package_directory(cwd, package, &package_info.stanza.version, &ctx)
|
cwd,
|
||||||
.expect("Cannot find package directory");
|
package,
|
||||||
|
&package_info.stanza.version,
|
||||||
|
series,
|
||||||
|
&ctx,
|
||||||
|
)
|
||||||
|
.expect("Cannot find package directory");
|
||||||
log::debug!("Package directory: {}", cwd.display());
|
log::debug!("Package directory: {}", cwd.display());
|
||||||
|
|
||||||
log::info!("Starting binary package build...");
|
log::info!("Starting binary package build...");
|
||||||
|
|||||||
+55
-36
@@ -57,15 +57,19 @@ pub struct OperationQuirks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Quirks for a specific package
|
/// 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)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct PackageQuirks {
|
pub struct PackageQuirks {
|
||||||
/// Quirks to apply during pull operation
|
/// Quirks to apply during pull operation
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub pull: Option<OperationQuirks>,
|
pub pull: Vec<OperationQuirks>,
|
||||||
|
|
||||||
/// Quirks to apply during deb operation
|
/// Quirks to apply during deb operation
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub deb: Option<OperationQuirks>,
|
pub deb: Vec<OperationQuirks>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Top-level quirks configuration
|
/// 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
|
/// 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
|
/// # Arguments
|
||||||
/// * `package` - The package name
|
/// * `package` - The package name
|
||||||
/// * `series` - The distribution series (e.g. "resolute")
|
/// * `series` - The distribution series (e.g. "resolute")
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `Option<DependencyQuirks>` - The rules, or None when the package has
|
/// * `Vec<DependencyQuirks>` - The matching rules, empty when the package
|
||||||
/// no deb entry or the entry does not apply to the series
|
/// has no deb entry or none applies to the series
|
||||||
pub fn get_deb_dependency_quirks(package: &str, series: &str) -> Option<DependencyQuirks> {
|
pub fn get_deb_dependency_quirks(package: &str, series: &str) -> Vec<DependencyQuirks> {
|
||||||
let quirks = get_package_quirks(&QUIRKS_DATA, package)?;
|
let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) else {
|
||||||
let deb = quirks.deb.as_ref()?;
|
return Vec::new();
|
||||||
if entry_applies_to_series(deb, series) {
|
};
|
||||||
deb.dependencies.clone()
|
quirks
|
||||||
} else {
|
.deb
|
||||||
None
|
.iter()
|
||||||
}
|
.filter(|deb| entry_applies_to_series(deb, series))
|
||||||
|
.filter_map(|deb| deb.dependencies.clone())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get package directories from quirks configuration
|
/// Get package directories from quirks configuration
|
||||||
///
|
///
|
||||||
/// This function returns the list of custom package directories to try
|
/// 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
|
/// # Arguments
|
||||||
/// * `package` - The package name
|
/// * `package` - The package name
|
||||||
|
/// * `series` - The distribution series (e.g. "resolute")
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `Vec<String>` - List of package directories to try, or empty vector if none
|
/// * `Vec<String>` - List of package directories to try, or empty vector if none
|
||||||
pub fn get_package_directories(package: &str) -> Vec<String> {
|
pub fn get_package_directories(package: &str, series: &str) -> Vec<String> {
|
||||||
if let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) {
|
let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) else {
|
||||||
// Check deb quirks first, then pull quirks
|
return Vec::new();
|
||||||
if let Some(deb_quirks) = &quirks.deb
|
};
|
||||||
&& !deb_quirks.package_directory.is_empty()
|
|
||||||
|
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();
|
directories.extend(pull.package_directory.iter().cloned());
|
||||||
}
|
|
||||||
if let Some(pull_quirks) = &quirks.pull
|
|
||||||
&& !pull_quirks.package_directory.is_empty()
|
|
||||||
{
|
|
||||||
return pull_quirks.package_directory.clone();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
directories
|
||||||
Vec::new()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the dependency quirks of `package` in `series` to parsed
|
/// Apply the dependency quirks of `package` in `series` to parsed
|
||||||
@@ -160,10 +179,10 @@ pub fn apply_dependency_quirks(
|
|||||||
clauses: &mut Vec<Vec<PkgRelation>>,
|
clauses: &mut Vec<Vec<PkgRelation>>,
|
||||||
opts: &ParseOpts,
|
opts: &ParseOpts,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let Some(deps) = get_deb_dependency_quirks(package, series) else {
|
for deps in get_deb_dependency_quirks(package, series) {
|
||||||
return Ok(());
|
apply_rules(clauses, &deps, opts)?;
|
||||||
};
|
}
|
||||||
apply_rules(clauses, &deps, opts)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply one set of dependency rules to parsed clauses.
|
/// Apply one set of dependency rules to parsed clauses.
|
||||||
@@ -236,8 +255,8 @@ mod tests {
|
|||||||
fn test_unknown_package_has_no_quirks() {
|
fn test_unknown_package_has_no_quirks() {
|
||||||
// A package absent from quirks.yml has no dependency rules nor
|
// A package absent from quirks.yml has no dependency rules nor
|
||||||
// custom directories, and must not panic
|
// custom directories, and must not panic
|
||||||
assert!(get_deb_dependency_quirks("not-in-quirks", "resolute").is_none());
|
assert!(get_deb_dependency_quirks("not-in-quirks", "resolute").is_empty());
|
||||||
assert!(get_package_directories("not-in-quirks").is_empty());
|
assert!(get_package_directories("not-in-quirks", "resolute").is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The linux dependency quirks are scoped to the series they were
|
/// The linux dependency quirks are scoped to the series they were
|
||||||
@@ -245,13 +264,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn linux_dependency_quirks_are_series_scoped() {
|
fn linux_dependency_quirks_are_series_scoped() {
|
||||||
for package in ["linux", "linux-riscv"] {
|
for package in ["linux", "linux-riscv"] {
|
||||||
let deps =
|
let rules = get_deb_dependency_quirks(package, "resolute");
|
||||||
get_deb_dependency_quirks(package, "resolute").expect("the resolute entry applies");
|
assert_eq!(rules.len(), 1, "the resolute entry applies");
|
||||||
assert_eq!(
|
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>")
|
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