diff --git a/src/builder/autotools.rs b/src/builder/autotools.rs index 90925ce..b848223 100755 --- a/src/builder/autotools.rs +++ b/src/builder/autotools.rs @@ -1245,6 +1245,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1278,6 +1279,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1652,6 +1654,7 @@ foo: bar description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1686,6 +1689,7 @@ foo: bar description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1722,6 +1726,7 @@ foo: bar description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/bin.rs b/src/builder/bin.rs index de70c57..6862c91 100644 --- a/src/builder/bin.rs +++ b/src/builder/bin.rs @@ -49,6 +49,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/cmake.rs b/src/builder/cmake.rs index 47d5d1a..9f9a1b9 100755 --- a/src/builder/cmake.rs +++ b/src/builder/cmake.rs @@ -1204,6 +1204,7 @@ mod tests { description: "".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/custom.rs b/src/builder/custom.rs index c6f9537..788a251 100755 --- a/src/builder/custom.rs +++ b/src/builder/custom.rs @@ -412,6 +412,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -555,6 +556,7 @@ depot_install_dev_pkg() { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }); @@ -660,6 +662,7 @@ depot_install() { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }); diff --git a/src/builder/makefile.rs b/src/builder/makefile.rs index 977e0f4..fc000f5 100644 --- a/src/builder/makefile.rs +++ b/src/builder/makefile.rs @@ -150,6 +150,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/meson.rs b/src/builder/meson.rs index 663fb92..a34d2a1 100755 --- a/src/builder/meson.rs +++ b/src/builder/meson.rs @@ -1050,6 +1050,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 986121c..cad7af8 100755 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -1149,6 +1149,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/perl.rs b/src/builder/perl.rs index e3b5983..2b5ddd6 100644 --- a/src/builder/perl.rs +++ b/src/builder/perl.rs @@ -517,6 +517,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/python.rs b/src/builder/python.rs index eb30444..5b6f192 100644 --- a/src/builder/python.rs +++ b/src/builder/python.rs @@ -791,6 +791,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/builder/rust.rs b/src/builder/rust.rs index b0995a8..a115559 100755 --- a/src/builder/rust.rs +++ b/src/builder/rust.rs @@ -239,6 +239,7 @@ mod tests { description: String::new(), homepage: String::new(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/commands.rs b/src/commands.rs index eb48a0f..929960a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -603,6 +603,19 @@ fn parse_dependency_list(metadata: &toml::Value, kind: &str) -> Vec { .unwrap_or_default() } +fn parse_metadata_string_list(metadata: &toml::Value, key: &str) -> Vec { + metadata + .get(key) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str()) + .map(String::from) + .collect() + }) + .unwrap_or_default() +} + fn parse_keep_list(metadata: &toml::Value) -> Vec { if let Some(s) = metadata.get("keep").and_then(|v| v.as_str()) { return vec![s.to_string()]; diff --git a/src/commands/build_cmd/support.rs b/src/commands/build_cmd/support.rs index 410c918..5480724 100644 --- a/src/commands/build_cmd/support.rs +++ b/src/commands/build_cmd/support.rs @@ -386,6 +386,7 @@ mod tests { description: "d".into(), homepage: "https://example.test".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/commands/install_cmd/archive.rs b/src/commands/install_cmd/archive.rs index 3adfec7..019738c 100644 --- a/src/commands/install_cmd/archive.rs +++ b/src/commands/install_cmd/archive.rs @@ -35,6 +35,7 @@ fn package_spec_from_archive_metadata(metadata: &toml::Value) -> package::Packag .get("abi_breaking") .and_then(|v| v.as_bool()) .unwrap_or(false), + built_against: super::super::parse_metadata_string_list(metadata, "built_against"), license: super::super::parse_licenses_from_toml(metadata), }, packages: Vec::new(), @@ -118,6 +119,7 @@ pub(crate) fn package_spec_from_repo_record( description: record.description.clone().unwrap_or_default(), homepage: record.homepage.clone().unwrap_or_default(), abi_breaking: record.abi_breaking, + built_against: record.built_against.clone(), license: parse_license_list_from_repo(&record.license), }, packages: Vec::new(), diff --git a/src/commands/tests.rs b/src/commands/tests.rs index 9130bcb..15a390e 100644 --- a/src/commands/tests.rs +++ b/src/commands/tests.rs @@ -133,6 +133,7 @@ fn test_binary_repo_record(name: &str, filename: &str) -> db::repo::BinaryRepoPa version: "1.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: filename.into(), size: 1, @@ -173,6 +174,7 @@ fn test_package_spec( description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -436,6 +438,7 @@ fn binary_install_path_uses_repo_record_metadata_without_archive_metadata() -> R version: "1.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: archive_path .file_name() @@ -734,6 +737,7 @@ fn binary_archive_install_preserves_setuid_permissions() -> Result<()> { version: "1.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: archive_path .file_name() @@ -851,6 +855,7 @@ fn binary_archive_install_honors_replaces_from_metadata() -> Result<()> { description: "diffutils".into(), homepage: "https://example.test/diffutils".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["GPL-3.0-or-later".into()], }, packages: Vec::new(), @@ -955,6 +960,7 @@ fn plan_staged_install_reads_updates_from_rootfs_installed_db() -> Result<()> { description: "Base filesystem".into(), homepage: "https://example.test".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["Unlicense".into()], }, packages: Vec::new(), @@ -997,6 +1003,7 @@ fn renamed_abi_updates_keep_versioned_shared_libraries() -> Result<()> { description: "libxml2 2.14".into(), homepage: "https://example.test/libxml2".into(), abi_breaking: true, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1032,6 +1039,7 @@ fn renamed_abi_updates_keep_versioned_shared_libraries() -> Result<()> { description: "libxml2 2.15".into(), homepage: "https://example.test/libxml2".into(), abi_breaking: true, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1142,6 +1150,7 @@ fn collect_update_candidates_matches_renamed_packages_by_real_name() -> Result<( description: "icu78".into(), homepage: "https://example.test/icu".into(), abi_breaking: true, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1219,6 +1228,7 @@ fn update_candidate_prefers_binary_when_versions_match_and_config_does() { version: "1.0.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, }; let source_spec = package::PackageSpec { @@ -1230,6 +1240,7 @@ fn update_candidate_prefers_binary_when_versions_match_and_config_does() { description: "test".into(), homepage: "https://example.test".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1273,6 +1284,7 @@ fn update_candidate_prefers_binary_when_versions_match_and_config_does() { version: "1.1.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: "pkg-1.1.0-1-x86_64.depot.pkg.tar.zst".into(), size: 1, @@ -1312,6 +1324,7 @@ fn select_update_candidate_uses_newer_timestamp_when_versions_match() { version: "1.0.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: Some(100), }; let source_spec = package::PackageSpec { @@ -1323,6 +1336,7 @@ fn select_update_candidate_uses_newer_timestamp_when_versions_match() { description: "test".into(), homepage: "https://example.test".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1371,6 +1385,7 @@ fn select_update_candidate_prefers_replacement_candidate() { version: "4.9.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: Some(100), }; @@ -1383,6 +1398,7 @@ fn select_update_candidate_prefers_replacement_candidate() { description: "findutils".into(), homepage: "https://example.test/findutils".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1407,6 +1423,7 @@ fn select_update_candidate_prefers_replacement_candidate() { description: "busybox".into(), homepage: "https://example.test/busybox".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["GPL-2.0-only".into()], }, packages: Vec::new(), @@ -1481,6 +1498,7 @@ fn install_planned_packages_to_rootfs_runs_post_hooks_after_batch_install() -> R description: "findutils".into(), homepage: "https://example.test/findutils".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1510,6 +1528,7 @@ fn install_planned_packages_to_rootfs_runs_post_hooks_after_batch_install() -> R description: "alpha".into(), homepage: "https://example.test/alpha".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1543,6 +1562,7 @@ fn install_planned_packages_to_rootfs_runs_post_hooks_after_batch_install() -> R description: "busybox".into(), homepage: "https://example.test/busybox".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["GPL-2.0-only".into()], }, packages: Vec::new(), @@ -1635,6 +1655,7 @@ fn collect_update_candidates_skips_source_when_prefer_binary_is_enabled() -> Res description: "pkg".into(), homepage: "https://example.test".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1806,6 +1827,7 @@ fn collect_missing_update_dependencies_skips_planned_provides_and_installed_deps description: "glibc".into(), homepage: "https://example.test".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/db/mod.rs b/src/db/mod.rs index 1421f35..97a1207 100755 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -41,6 +41,8 @@ pub struct InstalledPackageRecord { pub revision: u32, /// Whether renamed updates should retain versioned shared libraries. pub abi_breaking: bool, + /// Concrete package names this package was built against for ABI-sensitive dependencies. + pub built_against: Vec, /// Package completion timestamp if known. pub completed_at: Option, } @@ -140,8 +142,8 @@ pub fn register_package_with_replacement( // Insert/update package without changing its primary key (UPSERT keeps the existing row). tx.execute( - "INSERT INTO packages (name, real_name, version, revision, description, homepage, license, abi_breaking, completed_at) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) + "INSERT INTO packages (name, real_name, version, revision, description, homepage, license, abi_breaking, built_against, completed_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) ON CONFLICT(name) DO UPDATE SET real_name=excluded.real_name, version=excluded.version, @@ -150,6 +152,7 @@ pub fn register_package_with_replacement( homepage=excluded.homepage, license=excluded.license, abi_breaking=excluded.abi_breaking, + built_against=excluded.built_against, completed_at=excluded.completed_at", params![ spec.package.name, @@ -160,6 +163,7 @@ pub fn register_package_with_replacement( spec.package.homepage, format_licenses(&spec.package.license), spec.package.abi_breaking, + format_built_against(&spec.package.built_against), completed_at, ], )?; @@ -575,6 +579,7 @@ fn init_db(conn: &Connection) -> Result<()> { homepage TEXT, license TEXT, abi_breaking INTEGER NOT NULL DEFAULT 0, + built_against TEXT NOT NULL DEFAULT '', completed_at INTEGER ); @@ -632,6 +637,7 @@ fn init_db(conn: &Connection) -> Result<()> { ensure_packages_completed_at_column(conn)?; ensure_packages_real_name_column(conn)?; ensure_packages_abi_breaking_column(conn)?; + ensure_packages_built_against_column(conn)?; Ok(()) } @@ -692,6 +698,39 @@ fn ensure_packages_abi_breaking_column(conn: &Connection) -> Result<()> { Ok(()) } +fn ensure_packages_built_against_column(conn: &Connection) -> Result<()> { + let has_built_against: bool = conn + .query_row( + "SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'built_against'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .context("Failed to inspect installed package DB schema")?; + if !has_built_against { + conn.execute( + "ALTER TABLE packages ADD COLUMN built_against TEXT NOT NULL DEFAULT ''", + [], + ) + .context("Failed to add built_against column to installed package DB")?; + } + Ok(()) +} + +fn format_built_against(packages: &[String]) -> String { + packages.join("\n") +} + +fn parse_built_against(raw: &str) -> Vec { + raw.lines() + .map(str::trim) + .filter(|name| !name.is_empty()) + .map(String::from) + .collect() +} + fn delete_package_rows(conn: &Connection, pkg_id: i64) -> Result<()> { conn.execute("DELETE FROM files WHERE package_id = ?1", params![pkg_id])?; conn.execute( @@ -877,18 +916,20 @@ pub fn list_installed_package_records(db_path: &Path) -> Result(3)? as u32, abi_breaking: row.get(4)?, - completed_at: row.get(5)?, + built_against: parse_built_against(&built_against), + completed_at: row.get(6)?, }) })?; Ok(rows.filter_map(|row| row.ok()).collect()) @@ -1133,6 +1174,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1565,6 +1607,23 @@ mod tests { ); } + #[test] + fn register_package_records_built_against_metadata() { + let tmp = tempfile::tempdir().unwrap(); + let db_path = tmp.path().join("packages.db"); + let dest = tmp.path().join("dest"); + std::fs::create_dir_all(&dest).unwrap(); + std::fs::write(dest.join("payload"), "x").unwrap(); + + let mut spec = mk_spec("app", "1.0"); + spec.package.built_against = vec!["icu78".into()]; + register_package(&db_path, &spec, &dest).unwrap(); + + let records = list_installed_package_records(&db_path).unwrap(); + assert_eq!(records.len(), 1); + assert_eq!(records[0].built_against, vec!["icu78".to_string()]); + } + #[test] fn installed_group_helpers_round_trip_membership() { let tmp = tempfile::tempdir().unwrap(); diff --git a/src/db/repo.rs b/src/db/repo.rs index a2788f4..800d786 100644 --- a/src/db/repo.rs +++ b/src/db/repo.rs @@ -32,6 +32,31 @@ fn parse_license_text(metadata: &toml::Value) -> Option { None } +fn parse_string_array_metadata(metadata: &toml::Value, key: &str) -> Vec { + metadata + .get(key) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str()) + .map(String::from) + .collect() + }) + .unwrap_or_default() +} + +fn format_built_against(packages: &[String]) -> String { + packages.join("\n") +} + +fn parse_built_against(raw: &str) -> Vec { + raw.lines() + .map(str::trim) + .filter(|name| !name.is_empty()) + .map(String::from) + .collect() +} + pub struct RepoManager { pub repo_dir: PathBuf, } @@ -76,6 +101,7 @@ struct IndexedPackage { version: String, revision: u32, abi_breaking: bool, + built_against: Vec, completed_at: Option, description: Option, homepage: Option, @@ -116,6 +142,7 @@ pub struct BinaryRepoPackageRecord { pub version: String, pub revision: u32, pub abi_breaking: bool, + pub built_against: Vec, pub completed_at: Option, pub filename: String, pub size: u64, @@ -301,6 +328,7 @@ impl RepoManager { version TEXT NOT NULL, revision INTEGER NOT NULL, abi_breaking INTEGER NOT NULL DEFAULT 0, + built_against TEXT NOT NULL DEFAULT '', completed_at INTEGER, description TEXT, homepage TEXT, @@ -378,6 +406,7 @@ impl RepoManager { let mut version = String::new(); let mut revision = 1; let mut abi_breaking = false; + let mut built_against = Vec::new(); let mut completed_at = path_modified_unix_timestamp(pkg_path)?; let mut description = None; let mut homepage = None; @@ -427,6 +456,7 @@ impl RepoManager { .get("abi_breaking") .and_then(|v| v.as_bool()) .unwrap_or(false); + built_against = parse_string_array_metadata(&metadata, "built_against"); completed_at = metadata_time::parse_completed_at_value(&metadata).or(completed_at); description = metadata @@ -532,6 +562,7 @@ impl RepoManager { version, revision, abi_breaking, + built_against, completed_at, description, homepage, @@ -557,6 +588,7 @@ impl RepoManager { version, revision, abi_breaking, + built_against, completed_at, description, homepage, @@ -576,14 +608,15 @@ impl RepoManager { // Insert into database conn.execute( - "INSERT INTO packages (name, real_name, version, revision, abi_breaking, completed_at, description, homepage, license, filename, size, sha256, sha512) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)", + "INSERT INTO packages (name, real_name, version, revision, abi_breaking, built_against, completed_at, description, homepage, license, filename, size, sha256, sha512) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)", params![ name, real_name, version, revision as i64, abi_breaking, + format_built_against(&built_against), completed_at, description, homepage, @@ -1675,6 +1708,19 @@ fn query_package_runtime_deps(conn: &Connection, package_id: i64) -> Result Result> { + if !repo_packages_have_built_against(conn)? { + return Ok(Vec::new()); + } + + let raw: String = conn.query_row( + "SELECT built_against FROM packages WHERE id = ?1", + params![package_id], + |row| row.get(0), + )?; + Ok(parse_built_against(&raw)) +} + fn query_package_optional_deps(conn: &Connection, package_id: i64) -> Result> { let has_dependencies_table: bool = conn .query_row( @@ -1740,6 +1786,11 @@ fn find_cached_binary_repo_packages( } else { "0" }; + let built_against_expr = if repo_packages_have_built_against(&conn)? { + "p.built_against" + } else { + "''" + }; let sql = format!( "SELECT p.id, @@ -1748,6 +1799,7 @@ fn find_cached_binary_repo_packages( p.version, p.revision, {abi_breaking_expr}, + {built_against_expr}, {completed_at_expr}, p.filename, p.size, @@ -1758,6 +1810,7 @@ fn find_cached_binary_repo_packages( p.license FROM packages p WHERE lower(p.name) = lower(?1) + OR lower({real_name_expr}) = lower(?1) OR EXISTS ( SELECT 1 FROM replaces rp WHERE rp.package_id = p.id @@ -1776,6 +1829,7 @@ fn find_cached_binary_repo_packages( AND lower(rp.name) = lower(?1) ) THEN 0 WHEN lower(p.name) = lower(?1) THEN 1 + WHEN lower({real_name_expr}) = lower(?1) THEN 2 ELSE 2 END, p.name ASC" @@ -1793,14 +1847,15 @@ fn find_cached_binary_repo_packages( version: row.get(3)?, revision: row.get::<_, i64>(4)? as u32, abi_breaking: row.get(5)?, - completed_at: row.get(6)?, - filename: row.get(7)?, - size: row.get::<_, i64>(8)? as u64, - sha256: row.get(9)?, - sha512: row.get(10)?, - description: row.get(11)?, - homepage: row.get(12)?, - license: row.get(13)?, + built_against: parse_built_against(&row.get::<_, String>(6)?), + completed_at: row.get(7)?, + filename: row.get(8)?, + size: row.get::<_, i64>(9)? as u64, + sha256: row.get(10)?, + sha512: row.get(11)?, + description: row.get(12)?, + homepage: row.get(13)?, + license: row.get(14)?, provides: Vec::new(), conflicts: Vec::new(), replaces: Vec::new(), @@ -1818,6 +1873,7 @@ fn find_cached_binary_repo_packages( rec.conflicts = query_package_conflicts(&conn, package_id)?; rec.replaces = query_package_replaces(&conn, package_id)?; rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?; + rec.built_against = query_package_built_against(&conn, package_id)?; rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?; rec.groups = query_package_groups(&conn, package_id)?; out.push(rec); @@ -1862,6 +1918,11 @@ fn find_cached_binary_repo_packages_by_group( } else { "0" }; + let built_against_expr = if repo_packages_have_built_against(&conn)? { + "p.built_against" + } else { + "''" + }; let sql = format!( "SELECT p.id, @@ -1870,6 +1931,7 @@ fn find_cached_binary_repo_packages_by_group( p.version, p.revision, {abi_breaking_expr}, + {built_against_expr}, {completed_at_expr}, p.filename, p.size, @@ -1899,14 +1961,15 @@ fn find_cached_binary_repo_packages_by_group( version: row.get(3)?, revision: row.get::<_, i64>(4)? as u32, abi_breaking: row.get(5)?, - completed_at: row.get(6)?, - filename: row.get(7)?, - size: row.get::<_, i64>(8)? as u64, - sha256: row.get(9)?, - sha512: row.get(10)?, - description: row.get(11)?, - homepage: row.get(12)?, - license: row.get(13)?, + built_against: parse_built_against(&row.get::<_, String>(6)?), + completed_at: row.get(7)?, + filename: row.get(8)?, + size: row.get::<_, i64>(9)? as u64, + sha256: row.get(10)?, + sha512: row.get(11)?, + description: row.get(12)?, + homepage: row.get(13)?, + license: row.get(14)?, provides: Vec::new(), conflicts: Vec::new(), replaces: Vec::new(), @@ -1924,6 +1987,7 @@ fn find_cached_binary_repo_packages_by_group( rec.conflicts = query_package_conflicts(&conn, package_id)?; rec.replaces = query_package_replaces(&conn, package_id)?; rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?; + rec.built_against = query_package_built_against(&conn, package_id)?; rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?; rec.groups = query_package_groups(&conn, package_id)?; out.push(rec); @@ -1953,6 +2017,11 @@ fn list_cached_binary_repo_packages( } else { "0" }; + let built_against_expr = if repo_packages_have_built_against(&conn)? { + "p.built_against" + } else { + "''" + }; let sql = format!( "SELECT p.id, @@ -1961,6 +2030,7 @@ fn list_cached_binary_repo_packages( p.version, p.revision, {abi_breaking_expr}, + {built_against_expr}, {completed_at_expr}, p.filename, p.size, @@ -1985,14 +2055,15 @@ fn list_cached_binary_repo_packages( version: row.get(3)?, revision: row.get::<_, i64>(4)? as u32, abi_breaking: row.get(5)?, - completed_at: row.get(6)?, - filename: row.get(7)?, - size: row.get::<_, i64>(8)? as u64, - sha256: row.get(9)?, - sha512: row.get(10)?, - description: row.get(11)?, - homepage: row.get(12)?, - license: row.get(13)?, + built_against: parse_built_against(&row.get::<_, String>(6)?), + completed_at: row.get(7)?, + filename: row.get(8)?, + size: row.get::<_, i64>(9)? as u64, + sha256: row.get(10)?, + sha512: row.get(11)?, + description: row.get(12)?, + homepage: row.get(13)?, + license: row.get(14)?, provides: Vec::new(), conflicts: Vec::new(), replaces: Vec::new(), @@ -2010,6 +2081,7 @@ fn list_cached_binary_repo_packages( rec.conflicts = query_package_conflicts(&conn, package_id)?; rec.replaces = query_package_replaces(&conn, package_id)?; rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?; + rec.built_against = query_package_built_against(&conn, package_id)?; rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?; rec.groups = query_package_groups(&conn, package_id)?; out.push(rec); @@ -2053,6 +2125,18 @@ fn repo_packages_have_abi_breaking(conn: &Connection) -> Result { .context("Failed to inspect binary repo DB schema") } +fn repo_packages_have_built_against(conn: &Connection) -> Result { + conn.query_row( + "SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'built_against'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .context("Failed to inspect binary repo DB schema") +} + fn path_modified_unix_timestamp(path: &Path) -> Result> { let metadata = fs::metadata(path) .with_context(|| format!("Failed to read metadata for {}", path.display()))?; @@ -2655,6 +2739,7 @@ real_name = "icu" version = "1.0" revision = 1 abi_breaking = true +built_against = ["icu78"] description = "test description" homepage = "https://example.com" license = "MIT" @@ -2692,6 +2777,7 @@ optional = [] String, i64, i64, + String, Option, Option, Option, @@ -2699,9 +2785,21 @@ optional = [] String, ); - let (name, real_name, version, revision, abi_breaking, desc, home, lic, sha256, sha512): PackageRow = conn + let ( + name, + real_name, + version, + revision, + abi_breaking, + built_against, + desc, + home, + lic, + sha256, + sha512, + ): PackageRow = conn .query_row( - "SELECT name, real_name, version, revision, abi_breaking, description, homepage, license, sha256, sha512 FROM packages", + "SELECT name, real_name, version, revision, abi_breaking, built_against, description, homepage, license, sha256, sha512 FROM packages", [], |r| { Ok(( @@ -2715,6 +2813,7 @@ optional = [] r.get(7)?, r.get(8)?, r.get(9)?, + r.get(10)?, )) }, ) @@ -2725,6 +2824,7 @@ optional = [] assert_eq!(version, "1.0"); assert_eq!(revision, 1); assert_eq!(abi_breaking, 1); + assert_eq!(built_against, "icu78"); assert_eq!(desc, Some("test description".to_string())); assert_eq!(home, Some("https://example.com".to_string())); assert_eq!(lic, Some("MIT".to_string())); @@ -2965,6 +3065,46 @@ revision = 1 assert_eq!(rec.filename, "foo-1.0-1.depot.pkg.tar.zst"); } + #[test] + fn test_find_cached_binary_repo_packages_matches_real_name_and_built_against() { + let tmp = tempfile::tempdir().unwrap(); + let db_path = tmp.path().join("repo.db"); + let mut conn = Connection::open(&db_path).unwrap(); + let manager = RepoManager::new(tmp.path().to_path_buf()); + manager.init_repo_schema(&mut conn).unwrap(); + + conn.execute( + "INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, description, homepage, license, filename, size, sha256, sha512) + VALUES (1, 'icu78', 'icu', '78.1', 1, 0, '', NULL, NULL, NULL, 'icu78.pkg', 10, 'aa', 'bb')", + [], + ) + .unwrap(); + conn.execute( + "INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, description, homepage, license, filename, size, sha256, sha512) + VALUES (2, 'app', NULL, '1.0', 1, 0, 'icu78', NULL, NULL, NULL, 'app.pkg', 10, 'cc', 'dd')", + [], + ) + .unwrap(); + conn.execute( + "INSERT INTO dependencies (package_id, kind, name) VALUES (2, 'runtime', 'icu')", + [], + ) + .unwrap(); + drop(conn); + + let icu_matches = find_cached_binary_repo_packages("repo", &db_path, "icu").unwrap(); + assert_eq!(icu_matches.len(), 1); + assert_eq!(icu_matches[0].name, "icu78"); + assert_eq!(icu_matches[0].real_name.as_deref(), Some("icu")); + + let app = find_cached_binary_repo_packages("repo", &db_path, "app") + .unwrap() + .pop() + .unwrap(); + assert_eq!(app.runtime_dependencies, vec!["icu".to_string()]); + assert_eq!(app.built_against, vec!["icu78".to_string()]); + } + #[test] fn test_verify_binary_package_record_checksums_accepts_valid_hashes() { use sha2::{Digest, Sha256, Sha512}; @@ -2991,6 +3131,7 @@ revision = 1 version: "1.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: "pkg.depot.pkg.tar.zst".into(), size: 7, @@ -3031,6 +3172,7 @@ revision = 1 version: "1.0".into(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: filename.to_string(), size: payload.len() as u64, diff --git a/src/deps.rs b/src/deps.rs index ced54d4..11f725f 100755 --- a/src/deps.rs +++ b/src/deps.rs @@ -398,6 +398,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -475,6 +476,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -525,6 +527,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -575,6 +578,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: vec![crate::package::PackageInfo { @@ -585,6 +589,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }], alternatives: Default::default(), diff --git a/src/install/scripts.rs b/src/install/scripts.rs index a40ed08..ab998f0 100644 --- a/src/install/scripts.rs +++ b/src/install/scripts.rs @@ -1567,6 +1567,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/package/interactive.rs b/src/package/interactive.rs index 5d3fb0b..048e90b 100644 --- a/src/package/interactive.rs +++ b/src/package/interactive.rs @@ -692,6 +692,7 @@ pub fn create_interactive() -> Result { description, homepage, abi_breaking: false, + built_against: Vec::new(), license, }, packages: Vec::new(), @@ -1757,6 +1758,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1802,6 +1804,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: vec![PackageInfo { @@ -1812,6 +1815,7 @@ mod tests { description: "dev files".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }], alternatives: Alternatives::default(), @@ -1850,6 +1854,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into(), "Apache-2.0".into()], }, packages: Vec::new(), @@ -1938,6 +1943,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -2019,6 +2025,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -2057,6 +2064,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -2125,6 +2133,7 @@ mod tests { description: "A test".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -2207,6 +2216,7 @@ mod tests { description: "Meta package".into(), homepage: "".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -2249,6 +2259,7 @@ mod tests { description: "keyring".into(), homepage: "https://www.vertexlinux.net".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/package/packager.rs b/src/package/packager.rs index f6c346f..3ff21b6 100644 --- a/src/package/packager.rs +++ b/src/package/packager.rs @@ -184,6 +184,19 @@ impl Packager { "abi_breaking".to_string(), toml::Value::Boolean(self.spec.package.abi_breaking), ); + if !self.spec.package.built_against.is_empty() { + map.insert( + "built_against".to_string(), + toml::Value::Array( + self.spec + .package + .built_against + .iter() + .map(|s| toml::Value::String(s.clone())) + .collect(), + ), + ); + } map.insert( "license".to_string(), license_value(&self.spec.package.license), @@ -357,6 +370,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -528,6 +542,7 @@ mod tests { let mut packager = mk_packager(dest.to_path_buf()); packager.spec.package.real_name = Some("icu".into()); packager.spec.package.abi_breaking = true; + packager.spec.package.built_against = vec!["icu78".into()]; packager.generate_metadata_toml().unwrap(); let meta_path = dest.join(".metadata.toml"); @@ -538,6 +553,11 @@ mod tests { val.get("abi_breaking").and_then(|v| v.as_bool()), Some(true) ); + let built_against = val + .get("built_against") + .and_then(|v| v.as_array()) + .expect("built_against should be an array"); + assert_eq!(built_against[0].as_str(), Some("icu78")); } #[test] diff --git a/src/package/spec/model.rs b/src/package/spec/model.rs index 88c33a1..f091709 100644 --- a/src/package/spec/model.rs +++ b/src/package/spec/model.rs @@ -17,6 +17,13 @@ pub struct PackageInfo { /// When true, renamed updates retain versioned shared libraries from the old package. #[serde(default, alias = "abi-breaking")] pub abi_breaking: bool, + /// Concrete package names this package was built against for ABI-sensitive dependencies. + #[serde( + default, + alias = "built-against", + deserialize_with = "deserialize_string_or_array" + )] + pub built_against: Vec, #[serde( deserialize_with = "deserialize_licenses", serialize_with = "serialize_licenses" diff --git a/src/package/spec/tests.rs b/src/package/spec/tests.rs index bc30189..d8b795c 100644 --- a/src/package/spec/tests.rs +++ b/src/package/spec/tests.rs @@ -40,6 +40,37 @@ type = "custom" assert_eq!(spec.spec_dir, tmp.path()); } +#[test] +fn parse_package_built_against_metadata() { + 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" +built-against = ["icu78"] + +[source] +url = "https://example.com/foo.tar.gz" +sha256 = "skip" +extract_dir = "foo" + +[build] +type = "custom" +"#, + ) + .unwrap(); + + let spec = PackageSpec::from_file(&path).unwrap(); + assert_eq!(spec.package.built_against, vec!["icu78".to_string()]); +} + #[test] fn parse_source_array() { let tmp = tempfile::tempdir().unwrap(); @@ -2358,6 +2389,7 @@ fn mk_spec(name: &str, version: &str) -> PackageSpec { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/package/starbuild.rs b/src/package/starbuild.rs index cbb951f..a985887 100644 --- a/src/package/starbuild.rs +++ b/src/package/starbuild.rs @@ -472,6 +472,7 @@ impl ParsedStarbuild { description: primary_description, homepage: homepage.clone(), abi_breaking: false, + built_against: Vec::new(), license: self .output_licenses .get(self.main_package_name()) @@ -497,6 +498,7 @@ impl ParsedStarbuild { .unwrap_or_else(|| primary.description.clone()), homepage: homepage.clone(), abi_breaking: false, + built_against: Vec::new(), license: self .output_licenses .get(pkg_name) diff --git a/src/planner.rs b/src/planner.rs index ee7e8d9..0359cba 100644 --- a/src/planner.rs +++ b/src/planner.rs @@ -284,9 +284,24 @@ impl<'a> Resolver<'a> { requester_spec_path: Option<&Path>, requested_by: String, ) -> Result> { - if deps::is_dep_satisfied_in_db(dep, &self.db_path)? { + self.resolve_dep_node_with_preferred(dep, requester_spec_path, requested_by, &[]) + } + + fn resolve_dep_node_with_preferred( + &mut self, + dep: &str, + requester_spec_path: Option<&Path>, + requested_by: String, + preferred_packages: &[String], + ) -> Result> { + if preferred_packages.is_empty() && deps::is_dep_satisfied_in_db(dep, &self.db_path)? { return Ok(None); } + for preferred in preferred_packages { + if deps::is_dep_satisfied_in_db(preferred, &self.db_path)? { + return Ok(None); + } + } let dep_name = deps::dep_name(dep); let candidates = self.collect_candidates(dep_name, requester_spec_path)?; @@ -304,7 +319,7 @@ impl<'a> Resolver<'a> { ); } - let chosen = self.choose_candidate(dep, &candidates)?; + let chosen = self.choose_candidate(dep, &candidates, preferred_packages)?; let idx = match chosen.kind { CandidateKind::Source { ref path, @@ -345,9 +360,18 @@ impl<'a> Resolver<'a> { self.by_package.insert(record.name.clone(), idx); for dep in &record.runtime_dependencies { - if let Some(dep_idx) = - self.resolve_dep_node(dep, None, format!("{} needs {}", record.name, dep))? - { + let preferred = self.preferred_built_against_packages(dep, &record.built_against)?; + let dep_label = if preferred.is_empty() { + dep.clone() + } else { + format!("{} (built against {})", dep, preferred.join(", ")) + }; + if let Some(dep_idx) = self.resolve_dep_node_with_preferred( + dep, + None, + format!("{} needs {}", record.name, dep_label), + &preferred, + )? { self.add_dependency_edge(dep_idx, idx, &record.name)?; } } @@ -356,11 +380,50 @@ impl<'a> Resolver<'a> { Ok(idx) } - fn choose_candidate(&self, dep: &str, candidates: &[Candidate]) -> Result { + fn preferred_built_against_packages( + &mut self, + dep: &str, + built_against: &[String], + ) -> Result> { + if built_against.is_empty() { + return Ok(Vec::new()); + } + + let dep_name = deps::dep_name(dep); + let candidates = self.collect_candidates(dep_name, None)?; + let mut preferred = Vec::new(); + for built in built_against { + let built_name = deps::dep_name(built); + if candidates + .iter() + .any(|candidate| candidate.package.eq_ignore_ascii_case(built_name)) + { + push_unique(&mut preferred, built.to_string()); + } + } + Ok(preferred) + } + + fn choose_candidate( + &self, + dep: &str, + candidates: &[Candidate], + preferred_packages: &[String], + ) -> Result { let mut sorted = prune_replacement_fallback_candidates(dedupe_candidate_packages( sort_candidates(candidates, self.opts.prefer_binary), )); + for preferred in preferred_packages { + let preferred_name = deps::dep_name(preferred); + if let Some(position) = sorted + .iter() + .position(|candidate| candidate.package.eq_ignore_ascii_case(preferred_name)) + { + return Ok(sorted.remove(position)); + } + } + if sorted.len() == 1 { return Ok(sorted.remove(0)); } @@ -457,7 +520,12 @@ impl<'a> Resolver<'a> { ) { Ok(records) => { for rec in records { - let match_kind = if rec.name.eq_ignore_ascii_case(dep_name) { + let match_kind = if rec.name.eq_ignore_ascii_case(dep_name) + || rec + .real_name + .as_deref() + .is_some_and(|name| name.eq_ignore_ascii_case(dep_name)) + { MatchKind::Exact } else if rec .replaces @@ -862,7 +930,7 @@ mod tests { dedupe_candidate_packages, prune_replacement_fallback_candidates, sort_candidates, source_deps_for_install, }; - use crate::config::Config; + use crate::config::{BinaryRepo, Config}; use crate::db; use crate::package::{ Alternatives, Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec, Source, @@ -881,6 +949,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: vec![PackageInfo { @@ -891,6 +960,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }], alternatives: Alternatives::default(), @@ -958,6 +1028,7 @@ mod tests { version: "1.0.0".to_string(), revision: 1, abi_breaking: false, + built_against: Vec::new(), completed_at: None, filename: format!("{name}-1.0.0-1-x86_64.depot.pkg.tar.zst"), size: 1024, @@ -1269,6 +1340,121 @@ version = "4.3.2" )); } + fn write_compressed_repo_db(db_path: &Path, zst_path: &Path) { + let mut input = fs::File::open(db_path).unwrap(); + let output = fs::File::create(zst_path).unwrap(); + let mut encoder = zstd::stream::write::Encoder::new(output, 3).unwrap(); + std::io::copy(&mut input, &mut encoder).unwrap(); + encoder.finish().unwrap(); + } + + #[test] + fn binary_plan_uses_built_against_concrete_dependency() { + let rootfs = tempfile::tempdir().unwrap(); + let repo_dir = tempfile::tempdir().unwrap(); + let cache_dir = tempfile::tempdir().unwrap(); + let mut config = Config::for_rootfs(rootfs.path()); + config.package_cache_dir = cache_dir.path().to_path_buf(); + config.repo_settings.prefer_binary = true; + config.binary_repos.insert( + "core".into(), + BinaryRepo { + url: url::Url::from_directory_path(repo_dir.path()) + .expect("file URL") + .to_string(), + allow_unsigned: true, + ..BinaryRepo::default() + }, + ); + + let installed_dest = rootfs.path().join("installed"); + fs::create_dir_all(&installed_dest).unwrap(); + let mut installed = mk_installed_spec("icu79", "79.1"); + installed.package.real_name = Some("icu".into()); + db::register_package( + &config.installed_db_path(rootfs.path()), + &installed, + &installed_dest, + ) + .unwrap(); + + let db_path = repo_dir.path().join("repo.db"); + let conn = rusqlite::Connection::open(&db_path).unwrap(); + conn.execute_batch( + " + CREATE TABLE packages ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + real_name TEXT, + version TEXT NOT NULL, + revision INTEGER NOT NULL, + abi_breaking INTEGER NOT NULL DEFAULT 0, + built_against TEXT NOT NULL DEFAULT '', + completed_at INTEGER, + description TEXT, + homepage TEXT, + license TEXT, + filename TEXT NOT NULL, + size INTEGER NOT NULL, + sha256 TEXT NOT NULL, + sha512 TEXT NOT NULL + ); + CREATE TABLE provides (package_id INTEGER, name TEXT NOT NULL); + CREATE TABLE replaces (package_id INTEGER, name TEXT NOT NULL); + CREATE TABLE conflicts (package_id INTEGER, name TEXT NOT NULL); + CREATE TABLE dependencies (package_id INTEGER, kind TEXT NOT NULL, name TEXT NOT NULL); + CREATE TABLE groups (package_id INTEGER, name TEXT NOT NULL); + ", + ) + .unwrap(); + conn.execute( + "INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, completed_at, description, homepage, license, filename, size, sha256, sha512) + VALUES (1, 'app', NULL, '1.0', 1, 0, 'icu78', NULL, NULL, NULL, NULL, 'app.pkg', 1, 'aa', 'bb')", + [], + ) + .unwrap(); + conn.execute( + "INSERT INTO dependencies (package_id, kind, name) VALUES (1, 'runtime', 'icu')", + [], + ) + .unwrap(); + conn.execute( + "INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, completed_at, description, homepage, license, filename, size, sha256, sha512) + VALUES (2, 'icu78', 'icu', '78.1', 1, 0, '', NULL, NULL, NULL, NULL, 'icu78.pkg', 1, 'cc', 'dd')", + [], + ) + .unwrap(); + conn.execute( + "INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, completed_at, description, homepage, license, filename, size, sha256, sha512) + VALUES (3, 'icu79', 'icu', '79.1', 1, 0, '', NULL, NULL, NULL, NULL, 'icu79.pkg', 1, 'ee', 'ff')", + [], + ) + .unwrap(); + drop(conn); + write_compressed_repo_db(&db_path, &repo_dir.path().join("repo.db.zst")); + fs::remove_file(&db_path).unwrap(); + + let plan = super::build_install_plan( + &config, + rootfs.path(), + super::InstallTarget::PackageName("app".into()), + PlannerOptions { + assume_yes: true, + prefer_binary: true, + local_sibling_root: None, + include_test_deps: false, + lib32_only_requested_specs: false, + }, + ) + .unwrap(); + + let actionable: Vec<_> = plan + .actionable_steps() + .map(|step| step.package.as_str()) + .collect(); + assert_eq!(actionable, vec!["icu78", "app"]); + } + #[test] fn add_dependency_edge_skips_active_binary_cycle_back_edge() { let rootfs = tempfile::tempdir().unwrap(); diff --git a/src/source/extractor.rs b/src/source/extractor.rs index ec9b264..0db5bed 100755 --- a/src/source/extractor.rs +++ b/src/source/extractor.rs @@ -712,6 +712,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/source/hooks.rs b/src/source/hooks.rs index 077b1c5..820142c 100755 --- a/src/source/hooks.rs +++ b/src/source/hooks.rs @@ -329,6 +329,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/source/mod.rs b/src/source/mod.rs index 1e2960a..b477396 100755 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -758,6 +758,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -817,6 +818,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -886,6 +888,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), diff --git a/src/staging/mod.rs b/src/staging/mod.rs index 453e13a..acdaba5 100755 --- a/src/staging/mod.rs +++ b/src/staging/mod.rs @@ -1753,6 +1753,7 @@ mod tests { description: "d".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }, packages: Vec::new(), @@ -1859,6 +1860,7 @@ mod tests { description: "dev".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }); spec.build.flags.split_docs = true; @@ -1930,6 +1932,7 @@ mod tests { description: "dev".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["MIT".into()], }); spec.packages.push(PackageInfo { @@ -1940,6 +1943,7 @@ mod tests { description: "extras".into(), homepage: "h".into(), abi_breaking: false, + built_against: Vec::new(), license: vec!["Apache-2.0".into()], });