feat: implement ABI-breaking provider derivation for runtime dependencies

This commit is contained in:
2026-06-16 08:19:34 -05:00
parent d47cc5eb91
commit 86905c59d3
3 changed files with 106 additions and 2 deletions
+13 -2
View File
@@ -22,6 +22,15 @@ pub(crate) fn build_env_rootfs(rootfs: &Path) -> String {
.into_owned() .into_owned()
} }
fn derive_built_against_for_package(
mut pkg_spec: package::PackageSpec,
db_path: &Path,
) -> Result<package::PackageSpec> {
pkg_spec.package.built_against =
deps::derive_built_against_for_runtime_deps(&pkg_spec, db_path)?;
Ok(pkg_spec)
}
pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> { pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> {
let BuildArgs { let BuildArgs {
rootfs_args, rootfs_args,
@@ -366,13 +375,14 @@ pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> {
let arch = cross_prefix.as_deref().unwrap_or(std::env::consts::ARCH); let arch = cross_prefix.as_deref().unwrap_or(std::env::consts::ARCH);
let mut created_files = Vec::new(); let mut created_files = Vec::new();
let staged_outputs = if !lib32_only { let mut staged_outputs = if !lib32_only {
staged_output_specs(&pkg_spec, &destdir)? staged_output_specs(&pkg_spec, &destdir)?
} else { } else {
Vec::new() Vec::new()
}; };
if !lib32_only { if !lib32_only {
for (spec_for_out, out_destdir) in &staged_outputs { for (spec_for_out, out_destdir) in &mut staged_outputs {
*spec_for_out = derive_built_against_for_package(spec_for_out.clone(), &db_path)?;
let packager = package::Packager::new( let packager = package::Packager::new(
spec_for_out.clone(), spec_for_out.clone(),
out_destdir.clone(), out_destdir.clone(),
@@ -395,6 +405,7 @@ pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> {
!no_flags, !no_flags,
lib32_only, lib32_only,
)? { )? {
let lib32_spec = derive_built_against_for_package(lib32_spec, &db_path)?;
let packager = let packager =
package::Packager::new(lib32_spec.clone(), lib32_destdir.clone(), config.clone()); package::Packager::new(lib32_spec.clone(), lib32_destdir.clone(), config.clone());
let pkg_file = packager.create_package(Path::new("."), arch)?; let pkg_file = packager.create_package(Path::new("."), arch)?;
+48
View File
@@ -1124,6 +1124,54 @@ pub(crate) fn get_dependency_version(db_path: &Path, name: &str) -> Result<Optio
Ok(version) Ok(version)
} }
/// Return the concrete installed ABI-breaking package satisfying a dependency name.
///
/// Dependency aliases are matched through the package name, stable `real_name`,
/// `provides`, and `replaces` metadata. The returned value is always the
/// concrete installed `packages.name` so packages built against renamed streams
/// can remember the ABI-carrying package they actually used.
pub(crate) fn get_abi_breaking_provider_for_dependency(
db_path: &Path,
name: &str,
) -> Result<Option<String>> {
if !db_path.exists() {
return Ok(None);
}
let conn = Connection::open(db_path)?;
init_db(&conn)?;
let provider = conn
.query_row(
"SELECT p.name
FROM packages p
WHERE p.abi_breaking = 1
AND (
p.name = ?1
OR p.real_name = ?1
OR EXISTS (
SELECT 1 FROM provides pr
WHERE pr.package_id = p.id AND pr.provides_name = ?1
)
OR EXISTS (
SELECT 1 FROM replaces r
WHERE r.package_id = p.id AND r.replaces_name = ?1
)
)
ORDER BY
CASE
WHEN p.name = ?1 THEN 0
WHEN p.real_name = ?1 THEN 1
ELSE 2
END,
p.name
LIMIT 1",
params![name],
|row| row.get(0),
)
.optional()?;
Ok(provider)
}
/// Find the installed package that owns a filesystem path from the local DB. /// Find the installed package that owns a filesystem path from the local DB.
pub fn owns_path(db_path: &Path, path: &Path) -> Result<Option<String>> { pub fn owns_path(db_path: &Path, path: &Path) -> Result<Option<String>> {
if !db_path.exists() { if !db_path.exists() {
+45
View File
@@ -230,6 +230,27 @@ pub(crate) fn declared_test_deps(spec: &PackageSpec, outputs: RequestedOutputs)
deps deps
} }
/// Derive concrete ABI-breaking provider packages for an output's runtime deps.
///
/// This is intentionally computed from the installed database after dependency
/// installation, not from static package spec metadata. For a dependency such
/// as `libressl`, this records the currently installed concrete ABI package
/// name, for example `libressl43`.
pub(crate) fn derive_built_against_for_runtime_deps(
spec: &PackageSpec,
db_path: &Path,
) -> Result<Vec<String>> {
let mut built_against = Vec::new();
for dep in &spec.dependencies.runtime {
if let Some(provider) =
db::get_abi_breaking_provider_for_dependency(db_path, dep_name(dep))?
{
push_unique(&mut built_against, provider);
}
}
Ok(built_against)
}
/// Check if all build dependencies are satisfied for the selected outputs. /// Check if all build dependencies are satisfied for the selected outputs.
pub(crate) fn check_build_deps_for_outputs( pub(crate) fn check_build_deps_for_outputs(
spec: &PackageSpec, spec: &PackageSpec,
@@ -692,4 +713,28 @@ mod tests {
assert!(is_dep_satisfied_in_db("libressl", &db_path).unwrap()); assert!(is_dep_satisfied_in_db("libressl", &db_path).unwrap());
assert!(is_dep_satisfied_in_db("libressl>=4.3.0", &db_path).unwrap()); assert!(is_dep_satisfied_in_db("libressl>=4.3.0", &db_path).unwrap());
} }
#[test]
fn test_derive_built_against_uses_installed_abi_breaking_provider() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("packages.db");
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(destdir.join("usr/lib")).unwrap();
std::fs::write(destdir.join("usr/lib/libssl.so"), "ssl").unwrap();
let mut provider = test_spec_with_build(BuildType::Custom, None, &[]);
provider.package.name = "libressl43".into();
provider.package.real_name = Some("libressl".into());
provider.package.version = "4.3.2".into();
provider.package.abi_breaking = true;
crate::db::register_package(&db_path, &provider, &destdir).unwrap();
let mut consumer = test_spec_with_build(BuildType::Custom, None, &[]);
consumer.package.name = "libssh2".into();
consumer.dependencies.runtime = vec!["zlib".into(), "libressl>=4.3.0".into()];
let built_against = derive_built_against_for_runtime_deps(&consumer, &db_path).unwrap();
assert_eq!(built_against, vec!["libressl43".to_string()]);
}
} }