diff --git a/Cargo.lock b/Cargo.lock index c73353c..51d2bba 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "depot" -version = "0.18.1" +version = "0.19.0" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index 3b9d45d..8c876f8 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "depot" -version = "0.18.1" +version = "0.19.0" edition = "2024" [lints.rust] diff --git a/meson.build b/meson.build index 1a9689a..400c2c8 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'depot', - version: '0.18.1', + version: '0.19.0', meson_version: '>=0.60.0', default_options: ['buildtype=release'], ) diff --git a/src/commands.rs b/src/commands.rs index cb4f2af..abdbb11 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -91,6 +91,7 @@ struct ChildInstallCommandOptions<'a> { no_flags: bool, cross_prefix: Option<&'a str>, clean: bool, + lib32_only: bool, install_test_deps: bool, install_context: Option<&'a str>, dep_chain: Option<&'a str>, @@ -131,6 +132,9 @@ fn run_install_command_with_program( if options.clean { cmd.arg("--clean"); } + if options.lib32_only { + cmd.arg("--lib32-only"); + } if options.install_test_deps { cmd.arg("--test-deps"); } @@ -214,6 +218,7 @@ fn run_child_install_command( no_flags: options.no_flags, cross_prefix: options.cross_prefix, clean: options.clean, + lib32_only: false, install_test_deps: options.install_test_deps, install_context: None, dep_chain: None, @@ -381,6 +386,7 @@ fn package_spec_from_archive_metadata(metadata: &toml::Value) -> package::Packag runtime: parse_dependency_list(metadata, "runtime"), test: Vec::new(), optional: parse_dependency_list(metadata, "optional"), + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), @@ -454,6 +460,7 @@ fn package_spec_from_repo_record( runtime: record.runtime_dependencies.clone(), test: Vec::new(), optional: record.optional_dependencies.clone(), + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), @@ -613,15 +620,16 @@ fn build_type_runs_automatic_tests(spec: &package::PackageSpec) -> bool { fn maybe_disable_tests_for_missing_deps( pkg_spec: &mut package::PackageSpec, db_path: &Path, + requested_outputs: deps::RequestedOutputs, ) -> Result<()> { if pkg_spec.build.flags.skip_tests || !build_type_runs_automatic_tests(pkg_spec) - || pkg_spec.dependencies.test.is_empty() + || deps::declared_test_deps(pkg_spec, requested_outputs).is_empty() { return Ok(()); } - let missing_test = deps::check_test_deps(pkg_spec, db_path)?; + let missing_test = deps::check_test_deps_for_outputs(pkg_spec, db_path, requested_outputs)?; if !missing_test.is_empty() { ui::warn(format!( "Missing test dependencies: {}. Tests will be skipped.", @@ -655,8 +663,24 @@ fn maybe_prompt_to_skip_tests_for_missing_requested_deps( Ok(false) } -fn should_install_test_deps(pkg_spec: &package::PackageSpec, install_test_deps: bool) -> bool { - install_test_deps && !pkg_spec.build.flags.skip_tests && !pkg_spec.dependencies.test.is_empty() +fn requested_outputs(pkg_spec: &package::PackageSpec, lib32_only: bool) -> deps::RequestedOutputs { + if lib32_only { + deps::RequestedOutputs::Lib32Only + } else if pkg_spec.build.flags.build_32 { + deps::RequestedOutputs::PrimaryAndLib32 + } else { + deps::RequestedOutputs::PrimaryOnly + } +} + +fn should_install_test_deps( + pkg_spec: &package::PackageSpec, + install_test_deps: bool, + requested_outputs: deps::RequestedOutputs, +) -> bool { + install_test_deps + && !pkg_spec.build.flags.skip_tests + && !deps::declared_test_deps(pkg_spec, requested_outputs).is_empty() } fn clean_build_workspace(config: &config::Config) -> Result<()> { @@ -817,9 +841,12 @@ fn make_lib32_build_spec(base: &package::PackageSpec) -> package::PackageSpec { fn make_lib32_package_spec(base: &package::PackageSpec) -> package::PackageSpec { let mut spec = base.clone(); - spec.package.name = lib32_package_name(&base.package.name); + let lib32_name = lib32_package_name(&base.package.name); + spec.package.name = lib32_name.clone(); // The lib32 pass currently emits a single companion package from /usr/lib32. spec.packages.clear(); + spec.alternatives = base.alternatives_for_output(&lib32_name); + spec.dependencies = base.dependencies_for_output(&lib32_name); spec } @@ -2312,6 +2339,7 @@ fn run_update_install_command( no_flags: options.no_flags, cross_prefix: options.cross_prefix, clean: options.clean, + lib32_only: false, install_test_deps: options.install_test_deps, install_context: Some(INSTALL_CONTEXT_UPDATE), dep_chain: None, @@ -2406,6 +2434,7 @@ fn run_update_command( prefer_binary: config.repo_settings.prefer_binary, local_sibling_root: None, include_test_deps: options.install_test_deps, + lib32_only_requested_specs: false, }, )?; print_plan_summary(&dep_plan); @@ -2425,6 +2454,7 @@ fn run_update_command( clean: options.clean, dry_run: false, confirm_installation: false, + lib32_only_requested_specs: false, install_test_deps: options.install_test_deps, }, )?; @@ -2901,9 +2931,21 @@ struct InstallPlanExecutionOptions<'a> { clean: bool, dry_run: bool, confirm_installation: bool, + lib32_only_requested_specs: bool, install_test_deps: bool, } +fn step_requests_only_lib32( + step: &planner::PlannedStep, + options: &InstallPlanExecutionOptions<'_>, +) -> bool { + options.lib32_only_requested_specs + && step + .requested_by + .iter() + .any(|reason| reason.starts_with("requested ")) +} + fn execute_install_plan_with_child_commands( plan: &planner::ExecutionPlan, rootfs: &Path, @@ -2946,10 +2988,11 @@ fn execute_install_plan_with_child_commands( let mut spec = package::PackageSpec::from_file(path) .with_context(|| format!("Failed to parse spec {}", path.display()))?; spec.apply_config(config); + let lib32_only = step_requests_only_lib32(step, &options); conflict_subjects.extend(install_conflict_subjects_for_spec( &spec, - true, - spec.build.flags.build_32, + !lib32_only, + spec.build.flags.build_32 || lib32_only, )); } planner::PlanOrigin::Binary { record, .. } => { @@ -2966,29 +3009,40 @@ fn execute_install_plan_with_child_commands( } if should_delegate_live_rootfs_installs(rootfs) { - let mut install_requests = Vec::new(); for step in actionable_steps { - match &step.origin { - planner::PlanOrigin::Source { path, .. } => { - install_requests.push(path.clone()); - } + let install_request = match &step.origin { + planner::PlanOrigin::Source { path, .. } => path.clone(), planner::PlanOrigin::Binary { repo_name, record } => { let repo_cfg = config.binary_repos.get(repo_name).with_context(|| { format!("Binary repo '{}' not found in config", repo_name) })?; - let archive_path = db::repo::fetch_binary_package_archive( + db::repo::fetch_binary_package_archive( repo_name, repo_cfg, rootfs, record, &config.package_cache_dir, - )?; - install_requests.push(archive_path); + )? } - planner::PlanOrigin::Installed => {} - } + planner::PlanOrigin::Installed => continue, + }; + run_install_command_with_program( + &std::env::current_exe().context("Failed to locate depot executable")?, + &[install_request], + rootfs, + ChildInstallCommandOptions { + no_deps: true, + assume_yes: true, + no_flags: options.no_flags, + cross_prefix: options.cross_prefix, + clean: options.clean, + lib32_only: step_requests_only_lib32(step, &options), + install_test_deps: options.install_test_deps, + install_context: None, + dep_chain: None, + }, + )?; } - run_child_install_command(&install_requests, rootfs, options)?; return Ok(()); } @@ -3197,6 +3251,9 @@ fn execute_install_plan_with_child_commands( if options.clean { cmd.arg("--clean"); } + if step_requests_only_lib32(step, &options) { + cmd.arg("--lib32-only"); + } cmd.arg("install").arg(path); let status = cmd @@ -3466,6 +3523,8 @@ fn run_direct_install_request( )); } + let requested_outputs = requested_outputs(&pkg_spec, options.lib32_only); + let mut conflict_subjects = install_conflict_subjects_for_spec( &pkg_spec, !options.lib32_only, @@ -3506,8 +3565,11 @@ fn run_direct_install_request( let db_path = config.installed_db_path(options.rootfs); if staging_dir.is_none() { - if options.no_deps && should_install_test_deps(&pkg_spec, options.install_test_deps) { - let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if options.no_deps + && should_install_test_deps(&pkg_spec, options.install_test_deps, requested_outputs) + { + let missing_test = + deps::check_test_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; if !missing_test.is_empty() && !maybe_prompt_to_skip_tests_for_missing_requested_deps( &mut pkg_spec, @@ -3517,19 +3579,20 @@ fn run_direct_install_request( { anyhow::bail!("Missing test dependencies: {}", missing_test.join(", ")); } - } else if options.no_deps || !should_install_test_deps(&pkg_spec, options.install_test_deps) + } else if options.no_deps + || !should_install_test_deps(&pkg_spec, options.install_test_deps, requested_outputs) { - maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path)?; + maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path, requested_outputs)?; } } // Check dependencies and prompt for auto-install if needed if !options.no_deps { - deps::print_dep_status(&pkg_spec, &db_path)?; + deps::print_dep_status_for_outputs(&pkg_spec, &db_path, requested_outputs)?; let missing_required = merge_missing_dependencies( - deps::check_build_deps(&pkg_spec, &db_path)?, - deps::check_runtime_deps(&pkg_spec, &db_path)?, + deps::check_build_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?, + deps::check_runtime_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?, ); if !missing_required.is_empty() { // Check for dependency cycles via DEPOT_DEPCHAIN env var @@ -3587,6 +3650,7 @@ fn run_direct_install_request( no_flags: options.no_flags, cross_prefix: options.cross_prefix, clean: options.clean, + lib32_only: false, install_test_deps: options.install_test_deps, install_context: None, dep_chain: Some(&new_chain), @@ -3596,10 +3660,11 @@ fn run_direct_install_request( } // Enforce required dependencies before building/installing. - deps::require_build_deps(&pkg_spec, &db_path)?; - deps::require_runtime_deps(&pkg_spec, &db_path)?; - if should_install_test_deps(&pkg_spec, options.install_test_deps) { - let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + deps::require_build_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; + deps::require_runtime_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; + if should_install_test_deps(&pkg_spec, options.install_test_deps, requested_outputs) { + let missing_test = + deps::check_test_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; if !missing_test.is_empty() { let pkg_index = index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone())); @@ -3645,6 +3710,7 @@ fn run_direct_install_request( no_flags: options.no_flags, cross_prefix: options.cross_prefix, clean: options.clean, + lib32_only: false, install_test_deps: options.install_test_deps, install_context: None, dep_chain: None, @@ -3661,8 +3727,9 @@ fn run_direct_install_request( } } - if should_install_test_deps(&pkg_spec, options.install_test_deps) { - let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if should_install_test_deps(&pkg_spec, options.install_test_deps, requested_outputs) { + let missing_test = + deps::check_test_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; if !missing_test.is_empty() && !maybe_prompt_to_skip_tests_for_missing_requested_deps( &mut pkg_spec, @@ -3670,7 +3737,7 @@ fn run_direct_install_request( "Requested test dependencies are still missing", )? { - deps::require_test_deps(&pkg_spec, &db_path)?; + deps::require_test_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; } } } @@ -3817,6 +3884,7 @@ pub fn run(cli: Cli) -> Result<()> { prefer_binary: config.repo_settings.prefer_binary, local_sibling_root: shared_local_sibling_root(&planned_spec_paths), include_test_deps: install_test_deps, + lib32_only_requested_specs: cli.lib32_only, }; let plan = if planned_targets.len() == 1 { planner::build_install_plan( @@ -3844,6 +3912,7 @@ pub fn run(cli: Cli) -> Result<()> { clean: cli.clean, dry_run: cli.dry_run, confirm_installation: true, + lib32_only_requested_specs: cli.lib32_only, install_test_deps, }, )?; @@ -3913,6 +3982,7 @@ pub fn run(cli: Cli) -> Result<()> { // Apply system overrides pkg_spec.apply_config(&config); + let requested_outputs = requested_outputs(&pkg_spec, cli.lib32_only); // Ensure database directory exists std::fs::create_dir_all(&config.db_dir).with_context(|| { @@ -3923,8 +3993,11 @@ pub fn run(cli: Cli) -> Result<()> { })?; let db_path = config.installed_db_path(&cli.rootfs); - if cli.no_deps && should_install_test_deps(&pkg_spec, install_test_deps) { - let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if cli.no_deps + && should_install_test_deps(&pkg_spec, install_test_deps, requested_outputs) + { + let missing_test = + deps::check_test_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; if !missing_test.is_empty() && !maybe_prompt_to_skip_tests_for_missing_requested_deps( &mut pkg_spec, @@ -3934,16 +4007,18 @@ pub fn run(cli: Cli) -> Result<()> { { anyhow::bail!("Missing test dependencies: {}", missing_test.join(", ")); } - } else if cli.no_deps || !should_install_test_deps(&pkg_spec, install_test_deps) { - maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path)?; + } else if cli.no_deps + || !should_install_test_deps(&pkg_spec, install_test_deps, requested_outputs) + { + maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path, requested_outputs)?; } // Check build dependencies if !cli.no_deps { - deps::print_dep_status(&pkg_spec, &db_path)?; + deps::print_dep_status_for_outputs(&pkg_spec, &db_path, requested_outputs)?; let missing_required = merge_missing_dependencies( - deps::check_build_deps(&pkg_spec, &db_path)?, - deps::check_runtime_deps(&pkg_spec, &db_path)?, + deps::check_build_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?, + deps::check_runtime_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?, ); if !missing_required.is_empty() { ui::warn(format!( @@ -3963,6 +4038,7 @@ pub fn run(cli: Cli) -> Result<()> { prefer_binary: config.repo_settings.prefer_binary, local_sibling_root, include_test_deps: install_test_deps, + lib32_only_requested_specs: false, }, )?; print_plan_summary(&dep_plan); @@ -3987,14 +4063,16 @@ pub fn run(cli: Cli) -> Result<()> { clean: cli.clean, dry_run: cli.dry_run, confirm_installation: false, + lib32_only_requested_specs: false, install_test_deps, }, )?; } - deps::require_build_deps(&pkg_spec, &db_path)?; - deps::require_runtime_deps(&pkg_spec, &db_path)?; - if should_install_test_deps(&pkg_spec, install_test_deps) { - let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + deps::require_build_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; + deps::require_runtime_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; + if should_install_test_deps(&pkg_spec, install_test_deps, requested_outputs) { + let missing_test = + deps::check_test_deps_for_outputs(&pkg_spec, &db_path, requested_outputs)?; if !missing_test.is_empty() { let local_sibling_root = spec_path .parent() @@ -4009,6 +4087,7 @@ pub fn run(cli: Cli) -> Result<()> { prefer_binary: config.repo_settings.prefer_binary, local_sibling_root, include_test_deps: install_test_deps, + lib32_only_requested_specs: false, }, ) { Ok(plan) => plan, @@ -4053,14 +4132,20 @@ pub fn run(cli: Cli) -> Result<()> { clean: cli.clean, dry_run: cli.dry_run, confirm_installation: false, + lib32_only_requested_specs: false, install_test_deps, }, )?; } } - if should_install_test_deps(&pkg_spec, install_test_deps) { - let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if should_install_test_deps(&pkg_spec, install_test_deps, requested_outputs) + { + let missing_test = deps::check_test_deps_for_outputs( + &pkg_spec, + &db_path, + requested_outputs, + )?; if !missing_test.is_empty() && !maybe_prompt_to_skip_tests_for_missing_requested_deps( &mut pkg_spec, @@ -4068,7 +4153,11 @@ pub fn run(cli: Cli) -> Result<()> { "Requested test dependencies are still missing", )? { - deps::require_test_deps(&pkg_spec, &db_path)?; + deps::require_test_deps_for_outputs( + &pkg_spec, + &db_path, + requested_outputs, + )?; } } } @@ -4217,6 +4306,7 @@ pub fn run(cli: Cli) -> Result<()> { clean: cli.clean, dry_run: cli.dry_run, confirm_installation: false, + lib32_only_requested_specs: false, install_test_deps, }, )?; @@ -5798,6 +5888,7 @@ optional = [] no_flags: true, cross_prefix: Some("x86_64-linux-musl"), clean: true, + lib32_only: false, install_test_deps: true, install_context: None, dep_chain: Some("parent"), @@ -5825,6 +5916,46 @@ optional = [] Ok(()) } + #[test] + #[cfg(unix)] + fn child_install_command_includes_lib32_only_flag_when_requested() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let temp = tempfile::tempdir().context("Failed to create temp dir")?; + let script_path = temp.path().join("capture-lib32-child-install.sh"); + let args_path = temp.path().join("args.txt"); + let script = format!( + "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"{}\"\n", + args_path.display() + ); + fs::write(&script_path, script) + .with_context(|| format!("Failed to write {}", script_path.display()))?; + fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)) + .with_context(|| format!("Failed to chmod {}", script_path.display()))?; + + run_install_command_with_program( + &script_path, + &[PathBuf::from("/tmp/pkg.toml")], + Path::new("/"), + ChildInstallCommandOptions { + no_deps: true, + assume_yes: true, + no_flags: false, + cross_prefix: None, + clean: false, + lib32_only: true, + install_test_deps: false, + install_context: None, + dep_chain: None, + }, + )?; + + let captured_args = fs::read_to_string(&args_path) + .with_context(|| format!("Failed to read {}", args_path.display()))?; + assert!(captured_args.lines().any(|line| line == "--lib32-only")); + Ok(()) + } + #[test] #[cfg(unix)] fn child_install_command_propagates_install_context_env() -> Result<()> { @@ -5853,6 +5984,7 @@ optional = [] no_flags: false, cross_prefix: None, clean: false, + lib32_only: false, install_test_deps: false, install_context: Some(INSTALL_CONTEXT_UPDATE), dep_chain: None, @@ -5995,4 +6127,23 @@ optional = [] vec!["-O2=>-O3", "-fno-rtti=>-fno-exceptions"] ); } + + #[test] + fn make_lib32_package_spec_uses_lib32_dependency_override() { + let mut base = test_package_spec(package::BuildType::Custom, None, &[]); + base.dependencies.runtime = vec!["zlib".into()]; + base.dependencies.lib32 = Some(package::DependencyGroup { + build: vec!["gcc-multilib".into()], + runtime: vec!["lib32-zlib".into()], + test: Vec::new(), + optional: vec!["lib32-gtk-doc".into()], + }); + + let lib32 = make_lib32_package_spec(&base); + + assert_eq!(lib32.package.name, "lib32-pkg"); + assert_eq!(lib32.dependencies.build, vec!["gcc-multilib"]); + assert_eq!(lib32.dependencies.runtime, vec!["lib32-zlib"]); + assert_eq!(lib32.dependencies.optional, vec!["lib32-gtk-doc"]); + } } diff --git a/src/cross.rs b/src/cross.rs index 4a7fd94..5e52f82 100755 --- a/src/cross.rs +++ b/src/cross.rs @@ -102,7 +102,7 @@ impl CrossConfig { let content = format!( r#"# CMake toolchain file for cross-compilation -# Generated by nyapm for target: {} +# Generated by depot for target: {} set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR {}) @@ -140,7 +140,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) let content = format!( r#"# Meson cross file for cross-compilation -# Generated by nyapm for target: {} +# Generated by depot for target: {} [binaries] c = '{}' diff --git a/src/deps.rs b/src/deps.rs index 9b083ea..14322f7 100755 --- a/src/deps.rs +++ b/src/deps.rs @@ -12,6 +12,7 @@ use crate::db; use crate::package::{BuildType, PackageSpec}; use crate::ui; use anyhow::Result; +use std::collections::HashSet; use std::path::Path; /// Version comparison operator @@ -32,6 +33,13 @@ struct ParsedDep<'a> { op: VersionOp, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum RequestedOutputs { + PrimaryOnly, + PrimaryAndLib32, + Lib32Only, +} + /// Parse a dependency string into name, version, and operator fn parse_dep(dep: &str) -> ParsedDep<'_> { // Try operators in order of specificity (>= before >, etc.) @@ -157,18 +165,85 @@ pub fn is_dep_satisfied_in_db(dep: &str, db_path: &Path) -> Result { is_dep_satisfied(dep, &installed, &provides, db_path) } -/// Check if all build dependencies are satisfied -pub fn check_build_deps(spec: &PackageSpec, db_path: &Path) -> Result> { +fn push_unique(v: &mut Vec, item: String) { + if !v.contains(&item) { + v.push(item); + } +} + +fn requested_dependency_sets( + spec: &PackageSpec, + outputs: RequestedOutputs, +) -> Vec { + match outputs { + RequestedOutputs::PrimaryOnly => vec![spec.dependencies.primary_dependencies()], + RequestedOutputs::PrimaryAndLib32 => { + vec![ + spec.dependencies.primary_dependencies(), + spec.lib32_dependencies(), + ] + } + RequestedOutputs::Lib32Only => vec![spec.lib32_dependencies()], + } +} + +fn requested_local_provides(spec: &PackageSpec, outputs: RequestedOutputs) -> HashSet { + match outputs { + RequestedOutputs::PrimaryOnly => spec.local_dependency_provides_for_selection(true, false), + RequestedOutputs::PrimaryAndLib32 => { + spec.local_dependency_provides_for_selection(true, true) + } + RequestedOutputs::Lib32Only => spec.local_dependency_provides_for_selection(false, true), + } +} + +fn collect_build_deps(spec: &PackageSpec, outputs: RequestedOutputs) -> Vec { + let mut deps = Vec::new(); + for dep_set in requested_dependency_sets(spec, outputs) { + for dep in dep_set.build { + push_unique(&mut deps, dep); + } + } + deps +} + +fn collect_runtime_deps(spec: &PackageSpec, outputs: RequestedOutputs) -> Vec { + let mut deps = Vec::new(); + for dep_set in requested_dependency_sets(spec, outputs) { + for dep in dep_set.runtime { + push_unique(&mut deps, dep); + } + } + deps +} + +pub(crate) fn declared_test_deps(spec: &PackageSpec, outputs: RequestedOutputs) -> Vec { + let mut deps = Vec::new(); + for dep_set in requested_dependency_sets(spec, outputs) { + for dep in dep_set.test { + push_unique(&mut deps, dep); + } + } + deps +} + +/// Check if all build dependencies are satisfied for the selected outputs. +pub(crate) fn check_build_deps_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result> { let mut missing = Vec::new(); + let build_deps = collect_build_deps(spec, outputs); if !db_path.exists() { - return Ok(spec.dependencies.build.clone()); + return Ok(build_deps); } let installed = db::get_installed_packages(db_path)?; let provides = db::get_all_provides(db_path)?; - for dep in &spec.dependencies.build { + for dep in &build_deps { if !is_dep_satisfied(dep, &installed, &provides, db_path)? { missing.push(dep.clone()); } @@ -177,13 +252,18 @@ pub fn check_build_deps(spec: &PackageSpec, db_path: &Path) -> Result Result> { +/// Check if all runtime dependencies are satisfied for the selected outputs. +pub(crate) fn check_runtime_deps_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result> { let mut missing = Vec::new(); - let local_provides = spec.local_dependency_provides(); + let runtime_deps = collect_runtime_deps(spec, outputs); + let local_provides = requested_local_provides(spec, outputs); if !db_path.exists() { - for dep in &spec.dependencies.runtime { + for dep in &runtime_deps { if !local_provides.contains(dep_name(dep)) { missing.push(dep.clone()); } @@ -194,7 +274,7 @@ pub fn check_runtime_deps(spec: &PackageSpec, db_path: &Path) -> Result Result Result> { +/// Check if all test dependencies are satisfied for the selected outputs. +pub(crate) fn check_test_deps_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result> { let mut missing = Vec::new(); + let test_deps = declared_test_deps(spec, outputs); if !db_path.exists() { - return Ok(spec.dependencies.test.clone()); + return Ok(test_deps); } let installed = db::get_installed_packages(db_path)?; let provides = db::get_all_provides(db_path)?; - for dep in &spec.dependencies.test { + for dep in &test_deps { if !is_dep_satisfied(dep, &installed, &provides, db_path)? { missing.push(dep.clone()); } @@ -228,59 +313,109 @@ pub fn check_test_deps(spec: &PackageSpec, db_path: &Path) -> Result /// Print dependency status pub fn print_dep_status(spec: &PackageSpec, db_path: &Path) -> Result<()> { - let missing_build = check_build_deps(spec, db_path)?; - let missing_runtime = check_runtime_deps(spec, db_path)?; - let missing_test = check_test_deps(spec, db_path)?; + print_dep_status_for_outputs(spec, db_path, RequestedOutputs::PrimaryOnly) +} - if !spec.dependencies.build.is_empty() { - ui::info(format!( - "Build dependencies: {}", - spec.dependencies.build.join(", ") - )); - if !missing_build.is_empty() { - ui::warn(format!("Build deps missing: {}", missing_build.join(", "))); - } +fn print_named_dep_status(label: &str, deps: &[String], missing: &[String], warn_on_missing: bool) { + if deps.is_empty() { + return; } - if !spec.dependencies.runtime.is_empty() { - ui::info(format!( - "Runtime dependencies: {}", - spec.dependencies.runtime.join(", ") - )); - if !missing_runtime.is_empty() { - ui::warn(format!( - "Runtime deps missing: {}", - missing_runtime.join(", ") + ui::info(format!("{label}: {}", deps.join(", "))); + if warn_on_missing && !missing.is_empty() { + ui::warn(format!("{label} missing: {}", missing.join(", "))); + } +} + +/// Print dependency status for the selected outputs. +pub(crate) fn print_dep_status_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result<()> { + let primary = spec.dependencies.primary_dependencies(); + let lib32 = spec.lib32_dependencies(); + + if matches!( + outputs, + RequestedOutputs::PrimaryOnly | RequestedOutputs::PrimaryAndLib32 + ) { + let missing_build = + check_build_deps_for_outputs(spec, db_path, RequestedOutputs::PrimaryOnly)?; + let missing_runtime = + check_runtime_deps_for_outputs(spec, db_path, RequestedOutputs::PrimaryOnly)?; + let missing_test = + check_test_deps_for_outputs(spec, db_path, RequestedOutputs::PrimaryOnly)?; + + print_named_dep_status("Build dependencies", &primary.build, &missing_build, true); + print_named_dep_status( + "Runtime dependencies", + &primary.runtime, + &missing_runtime, + true, + ); + print_named_dep_status( + "Test dependencies", + &primary.test, + &missing_test, + !spec.build.flags.skip_tests && build_type_runs_automatic_tests(spec), + ); + if !primary.optional.is_empty() { + ui::info(format!( + "Optional dependencies: {}", + primary.optional.join(", ") )); } } - if !spec.dependencies.test.is_empty() { - ui::info(format!( - "Test dependencies: {}", - spec.dependencies.test.join(", ") - )); - if !spec.build.flags.skip_tests - && build_type_runs_automatic_tests(spec) - && !missing_test.is_empty() - { - ui::warn(format!("Test deps missing: {}", missing_test.join(", "))); - } - } + if matches!( + outputs, + RequestedOutputs::PrimaryAndLib32 | RequestedOutputs::Lib32Only + ) { + let missing_build = + check_build_deps_for_outputs(spec, db_path, RequestedOutputs::Lib32Only)?; + let missing_runtime = + check_runtime_deps_for_outputs(spec, db_path, RequestedOutputs::Lib32Only)?; + let missing_test = check_test_deps_for_outputs(spec, db_path, RequestedOutputs::Lib32Only)?; - if !spec.dependencies.optional.is_empty() { - ui::info(format!( - "Optional dependencies: {}", - spec.dependencies.optional.join(", ") - )); + if outputs == RequestedOutputs::Lib32Only || lib32 != primary { + print_named_dep_status( + "Lib32 build dependencies", + &lib32.build, + &missing_build, + true, + ); + print_named_dep_status( + "Lib32 runtime dependencies", + &lib32.runtime, + &missing_runtime, + true, + ); + print_named_dep_status( + "Lib32 test dependencies", + &lib32.test, + &missing_test, + !spec.build.flags.skip_tests && build_type_runs_automatic_tests(spec), + ); + if !lib32.optional.is_empty() { + ui::info(format!( + "Lib32 optional dependencies: {}", + lib32.optional.join(", ") + )); + } + } } Ok(()) } -/// Verify all build dependencies are installed, error if not -pub fn require_build_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> { - let missing = check_build_deps(spec, db_path)?; +/// Verify all build dependencies are installed for the selected outputs. +pub(crate) fn require_build_deps_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result<()> { + let missing = check_build_deps_for_outputs(spec, db_path, outputs)?; if !missing.is_empty() { anyhow::bail!( @@ -292,9 +427,13 @@ pub fn require_build_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> { Ok(()) } -/// Verify all runtime dependencies are installed, error if not. -pub fn require_runtime_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> { - let missing = check_runtime_deps(spec, db_path)?; +/// Verify all runtime dependencies are installed for the selected outputs. +pub(crate) fn require_runtime_deps_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result<()> { + let missing = check_runtime_deps_for_outputs(spec, db_path, outputs)?; if !missing.is_empty() { anyhow::bail!( @@ -306,9 +445,13 @@ pub fn require_runtime_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> { Ok(()) } -/// Verify all test dependencies are installed, error if not. -pub fn require_test_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> { - let missing = check_test_deps(spec, db_path)?; +/// Verify all test dependencies are installed for the selected outputs. +pub(crate) fn require_test_deps_for_outputs( + spec: &PackageSpec, + db_path: &Path, + outputs: RequestedOutputs, +) -> Result<()> { + let missing = check_test_deps_for_outputs(spec, db_path, outputs)?; if !missing.is_empty() { anyhow::bail!( @@ -441,13 +584,19 @@ mod tests { runtime: Vec::new(), test: vec!["bats".into(), "python".into()], optional: Vec::new(), + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), spec_dir: std::path::PathBuf::from("."), }; - let missing = check_test_deps(&spec, Path::new("/definitely/not/a/real/db")).unwrap(); + let missing = check_test_deps_for_outputs( + &spec, + Path::new("/definitely/not/a/real/db"), + RequestedOutputs::PrimaryOnly, + ) + .unwrap(); assert_eq!(missing, vec!["bats".to_string(), "python".to_string()]); } @@ -482,14 +631,19 @@ mod tests { runtime: vec!["python".into()], test: Vec::new(), optional: Vec::new(), + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), spec_dir: std::path::PathBuf::from("."), }; - let err = require_runtime_deps(&spec, Path::new("/definitely/not/a/real/db")) - .expect_err("runtime deps should be required"); + let err = require_runtime_deps_for_outputs( + &spec, + Path::new("/definitely/not/a/real/db"), + RequestedOutputs::PrimaryOnly, + ) + .expect_err("runtime deps should be required"); assert!(err.to_string().contains("Missing runtime dependencies")); } @@ -531,6 +685,7 @@ mod tests { runtime: vec!["foo-libs".into(), "libfoo".into(), "python".into()], test: Vec::new(), optional: Vec::new(), + lib32: None, }, package_alternatives: std::collections::BTreeMap::from([( "foo-libs".to_string(), @@ -544,10 +699,34 @@ mod tests { spec_dir: std::path::PathBuf::from("."), }; - let missing = check_runtime_deps(&spec, Path::new("/definitely/not/a/real/db")).unwrap(); + let missing = check_runtime_deps_for_outputs( + &spec, + Path::new("/definitely/not/a/real/db"), + RequestedOutputs::PrimaryOnly, + ) + .unwrap(); assert_eq!(missing, vec!["python".to_string()]); } + #[test] + fn test_check_runtime_deps_for_lib32_only_does_not_treat_primary_output_as_local() { + let mut spec = test_spec_with_build(BuildType::Custom, None, &[]); + spec.dependencies.lib32 = Some(crate::package::DependencyGroup { + build: Vec::new(), + runtime: vec!["foo".into()], + test: Vec::new(), + optional: Vec::new(), + }); + + let missing = check_runtime_deps_for_outputs( + &spec, + Path::new("/definitely/not/a/real/db"), + RequestedOutputs::Lib32Only, + ) + .unwrap(); + assert_eq!(missing, vec!["foo".to_string()]); + } + #[test] fn test_build_type_runs_automatic_tests_matches_builder_behavior() { assert!(build_type_runs_automatic_tests(&test_spec_with_build( diff --git a/src/install/scripts.rs b/src/install/scripts.rs index b7dc971..a4ad5fd 100644 --- a/src/install/scripts.rs +++ b/src/install/scripts.rs @@ -115,6 +115,11 @@ impl Hook { let compact = self.canonical_name().replace('_', ""); [compact.clone(), format!("{}.sh", compact)] } + + fn legacy_root_lib32_candidate_names(self) -> [String; 2] { + let compact = self.canonical_name().replace('_', ""); + [format!("lib32-{compact}"), format!("lib32-{compact}.sh")] + } } #[derive(Debug, Clone)] @@ -154,7 +159,7 @@ pub fn stage_scripts_from_spec_dir(spec: &PackageSpec, destdir: &Path) -> Result ); } - let legacy_hooks = collect_legacy_root_hooks(&spec.spec_dir)?; + let legacy_hooks = collect_legacy_root_hooks(&spec.spec_dir, &spec.package.name)?; if !has_scripts_dir && legacy_hooks.is_empty() { return Ok(false); } @@ -749,48 +754,82 @@ fn is_lib32_package(pkg_name: &str) -> bool { pkg_name.starts_with("lib32-") } -fn collect_legacy_root_hooks(spec_dir: &Path) -> Result> { - let mut found = Vec::new(); - - for hook in ALL_HOOKS { - let mut hook_matches = Vec::new(); - for candidate in hook.legacy_root_candidate_names() { - let path = spec_dir.join(candidate); - let metadata = match path.symlink_metadata() { - Ok(meta) => meta, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue, - Err(e) => { - return Err(e).with_context(|| { - format!("Failed to inspect legacy hook script: {}", path.display()) - }); - } - }; - - let file_type = metadata.file_type(); - if !file_type.is_file() && !file_type.is_symlink() { - bail!( - "Legacy lifecycle hook candidate exists but is not a file: {}", - path.display() - ); +fn collect_legacy_root_hook_candidates( + spec_dir: &Path, + hook_label: &str, + candidates: I, +) -> Result> +where + I: IntoIterator, +{ + let mut matches = Vec::new(); + for candidate in candidates { + let path = spec_dir.join(&candidate); + let metadata = match path.symlink_metadata() { + Ok(meta) => meta, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue, + Err(e) => { + return Err(e).with_context(|| { + format!("Failed to inspect legacy hook script: {}", path.display()) + }); } + }; - hook_matches.push(path); - } - - if hook_matches.len() > 1 { - let names = hook_matches - .iter() - .map(|p| p.display().to_string()) - .collect::>() - .join(", "); + let file_type = metadata.file_type(); + if !file_type.is_file() && !file_type.is_symlink() { bail!( - "Ambiguous legacy lifecycle hook '{}': multiple script candidates found: {}", - hook.canonical_name(), - names + "Legacy lifecycle hook candidate exists but is not a file: {}", + path.display() ); } - if let Some(path) = hook_matches.into_iter().next() { + matches.push(path); + } + + if matches.len() > 1 { + let names = matches + .iter() + .map(|p| p.display().to_string()) + .collect::>() + .join(", "); + bail!( + "Ambiguous legacy lifecycle hook '{}': multiple script candidates found: {}", + hook_label, + names + ); + } + + Ok(matches.into_iter().next()) +} + +fn collect_legacy_root_hooks(spec_dir: &Path, pkg_name: &str) -> Result> { + let mut found = Vec::new(); + + for hook in ALL_HOOKS { + let matched_path = if is_lib32_package(pkg_name) { + let lib32_label = format!("{} (lib32)", hook.canonical_name()); + if let Some(path) = collect_legacy_root_hook_candidates( + spec_dir, + &lib32_label, + hook.legacy_root_lib32_candidate_names(), + )? { + Some(path) + } else { + collect_legacy_root_hook_candidates( + spec_dir, + hook.canonical_name(), + hook.legacy_root_candidate_names(), + )? + } + } else { + collect_legacy_root_hook_candidates( + spec_dir, + hook.canonical_name(), + hook.legacy_root_candidate_names(), + )? + }; + + if let Some(path) = matched_path { found.push((hook, path)); } } @@ -1282,4 +1321,47 @@ mod tests { assert_ne!(mode & 0o111, 0); } } + + #[test] + fn stage_scripts_from_spec_dir_stages_lib32_prefixed_legacy_root_hook() { + let tmp = tempfile::tempdir().unwrap(); + let spec_dir = tmp.path().join("spec"); + let destdir = tmp.path().join("dest"); + std::fs::create_dir_all(&spec_dir).unwrap(); + std::fs::create_dir_all(&destdir).unwrap(); + + std::fs::write(spec_dir.join("lib32-postinstall.sh"), "echo lib32-post").unwrap(); + + let mut spec = mk_spec(&spec_dir); + spec.package.name = "lib32-foo".into(); + let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap(); + assert!(staged); + assert!(destdir.join("scripts/post_install").exists()); + #[cfg(unix)] + { + let mode = std::fs::metadata(destdir.join("scripts/post_install")) + .unwrap() + .permissions() + .mode(); + assert_ne!(mode & 0o111, 0); + } + } + + #[test] + fn stage_scripts_from_spec_dir_lib32_falls_back_to_generic_legacy_root_hook() { + let tmp = tempfile::tempdir().unwrap(); + let spec_dir = tmp.path().join("spec"); + let destdir = tmp.path().join("dest"); + std::fs::create_dir_all(&spec_dir).unwrap(); + std::fs::create_dir_all(&destdir).unwrap(); + + // No lib32-prefixed hook; the generic one should be staged for lib32 packages. + std::fs::write(spec_dir.join("postinstall.sh"), "echo fallback").unwrap(); + + let mut spec = mk_spec(&spec_dir); + spec.package.name = "lib32-foo".into(); + let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap(); + assert!(staged); + assert!(destdir.join("scripts/post_install").exists()); + } } diff --git a/src/package/interactive.rs b/src/package/interactive.rs index 982682b..57df7f0 100644 --- a/src/package/interactive.rs +++ b/src/package/interactive.rs @@ -690,6 +690,7 @@ pub fn create_interactive() -> Result { runtime: runtime_deps, test: test_deps, optional: optional_deps, + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), @@ -1776,6 +1777,7 @@ mod tests { runtime: vec![], test: vec!["python".into(), "bats".into()], optional: vec!["gtk-doc".into()], + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), @@ -1884,6 +1886,7 @@ mod tests { runtime: vec!["foo".into(), "bar".into()], test: Vec::new(), optional: Vec::new(), + lib32: None, }, package_alternatives: Default::default(), package_dependencies: Default::default(), diff --git a/src/package/spec.rs b/src/package/spec.rs index 0bd3880..5826f42 100755 --- a/src/package/spec.rs +++ b/src/package/spec.rs @@ -225,6 +225,14 @@ impl PackageSpec { /// /// If no per-output override exists, returns the top-level dependencies. pub fn dependencies_for_output(&self, pkg_name: &str) -> Dependencies { + if pkg_name == self.lib32_package_name() { + return self + .package_dependencies + .get(pkg_name) + .cloned() + .unwrap_or_else(|| self.lib32_dependencies()); + } + if let Some(parent_output) = self.docs_parent_output_name(pkg_name) { return self .package_dependencies @@ -240,16 +248,40 @@ impl PackageSpec { self.package_dependencies .get(pkg_name) .cloned() - .unwrap_or_else(|| self.dependencies.clone()) + .unwrap_or_else(|| self.dependencies.primary_dependencies()) } - /// Return all package names/provided features produced by this spec. - /// - /// This includes all output package names and per-output `provides` entries. - pub fn local_dependency_provides(&self) -> HashSet { + /// Return the generated lib32 companion package name for this spec. + pub fn lib32_package_name(&self) -> String { + format!("lib32-{}", self.package.name) + } + + /// Return the effective dependency set used by the generated lib32 companion package. + pub fn lib32_dependencies(&self) -> Dependencies { + self.dependencies + .lib32_dependencies() + .unwrap_or_else(|| self.dependencies.primary_dependencies()) + } + + /// Return local package names/provided features for the selected output set. + pub fn local_dependency_provides_for_selection( + &self, + include_primary_outputs: bool, + include_lib32_output: bool, + ) -> HashSet { let mut names = HashSet::new(); - for output in self.outputs() { - let output_name = output.name.clone(); + if include_primary_outputs { + for output in self.outputs() { + let output_name = output.name.clone(); + names.insert(output_name.clone()); + let alternatives = self.alternatives_for_output(&output_name); + for provided in alternatives.provides { + names.insert(provided); + } + } + } + if include_lib32_output { + let output_name = self.lib32_package_name(); names.insert(output_name.clone()); let alternatives = self.alternatives_for_output(&output_name); for provided in alternatives.provides { @@ -1796,6 +1828,59 @@ runtime = ["llvm-libgcc", "zstd"] ); } + #[test] + fn parse_lib32_dependencies_override() { + let tmp = tempfile::tempdir().unwrap(); + let path = tmp.path().join("pkg.toml"); + + std::fs::write( + &path, + r#" +[package] +name = "llvm" +version = "1.0" +description = "d" +homepage = "h" +license = "MIT" + +[source] +url = "https://example.com/llvm.tar.gz" +sha256 = "skip" +extract_dir = "llvm" + +[build] +type = "custom" + +[dependencies] +runtime = ["base"] + +[dependencies.lib32] +build = ["gcc-multilib"] +runtime = ["lib32-zlib"] +test = ["bats"] +"#, + ) + .unwrap(); + + let spec = PackageSpec::from_file(&path).unwrap(); + assert_eq!( + spec.dependencies_for_output("llvm").runtime, + vec!["base".to_string()] + ); + assert_eq!( + spec.dependencies_for_output("lib32-llvm").build, + vec!["gcc-multilib".to_string()] + ); + assert_eq!( + spec.dependencies_for_output("lib32-llvm").runtime, + vec!["lib32-zlib".to_string()] + ); + assert_eq!( + spec.dependencies_for_output("lib32-llvm").test, + vec!["bats".to_string()] + ); + } + #[test] fn parse_package_alternatives_overrides() { let tmp = tempfile::tempdir().unwrap(); @@ -4355,9 +4440,9 @@ fn default_cxx() -> String { } } -/// Package dependencies -#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] -pub struct Dependencies { +/// Nested dependency override group for a specific output variant. +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +pub struct DependencyGroup { /// Dependencies required for building packages. #[serde(default)] pub build: Vec, @@ -4371,3 +4456,53 @@ pub struct Dependencies { #[serde(default)] pub optional: Vec, } + +impl DependencyGroup { + fn to_dependencies(&self) -> Dependencies { + Dependencies { + build: self.build.clone(), + runtime: self.runtime.clone(), + test: self.test.clone(), + optional: self.optional.clone(), + lib32: None, + } + } +} + +/// Package dependencies +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +pub struct Dependencies { + /// Dependencies required for building packages. + #[serde(default)] + pub build: Vec, + /// Dependencies required at runtime. + #[serde(default)] + pub runtime: Vec, + /// Dependencies required to run package test suites. + #[serde(default)] + pub test: Vec, + /// Optional runtime integrations that enhance functionality when installed. + #[serde(default)] + pub optional: Vec, + /// Optional dependency overrides used only for the generated `lib32-*` companion package. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lib32: Option, +} + +impl Dependencies { + /// Return the top-level dependency set without any nested output-specific overrides. + pub fn primary_dependencies(&self) -> Dependencies { + Dependencies { + build: self.build.clone(), + runtime: self.runtime.clone(), + test: self.test.clone(), + optional: self.optional.clone(), + lib32: None, + } + } + + /// Return the optional lib32-specific dependency override set. + pub fn lib32_dependencies(&self) -> Option { + self.lib32.as_ref().map(DependencyGroup::to_dependencies) + } +} diff --git a/src/planner.rs b/src/planner.rs index 3b11f9d..8f85ae6 100644 --- a/src/planner.rs +++ b/src/planner.rs @@ -105,6 +105,7 @@ pub(crate) struct PlannerOptions { pub prefer_binary: bool, pub local_sibling_root: Option, pub include_test_deps: bool, + pub lib32_only_requested_specs: bool, } #[derive(Debug, Clone)] @@ -267,11 +268,13 @@ impl<'a> Resolver<'a> { requested_by: String, ) -> Result { let include_test_deps = self.opts.include_test_deps; + let lib32_only = + self.opts.lib32_only_requested_specs && requested_by.starts_with("requested "); let (package_name, deps_needed) = { let spec = self.load_spec(path)?; ( spec.package.name.clone(), - source_deps_for_install(spec, include_test_deps), + source_deps_for_install(spec, include_test_deps, lib32_only), ) }; if let Some(&idx) = self.by_package.get(&package_name) { @@ -613,31 +616,57 @@ impl<'a> Resolver<'a> { } } -fn source_deps_for_install(spec: &PackageSpec, include_test_deps: bool) -> Vec { +fn source_deps_for_install( + spec: &PackageSpec, + include_test_deps: bool, + lib32_only: bool, +) -> Vec { let mut deps_all = Vec::new(); - let local_provides = spec.local_dependency_provides(); - if !spec.is_metapackage() { + let include_lib32 = lib32_only || spec.build.flags.build_32; + let local_provides = spec.local_dependency_provides_for_selection(!lib32_only, include_lib32); + if !lib32_only && !spec.is_metapackage() { for dep in &spec.dependencies.build { push_unique(&mut deps_all, dep.clone()); } } - for dep in &spec.dependencies.runtime { - if !local_provides.contains(deps::dep_name(dep)) { + if include_lib32 && !spec.is_metapackage() { + for dep in &spec.lib32_dependencies().build { push_unique(&mut deps_all, dep.clone()); } } + if !lib32_only { + for dep in &spec.dependencies.runtime { + if !local_provides.contains(deps::dep_name(dep)) { + push_unique(&mut deps_all, dep.clone()); + } + } - for out in spec.outputs() { - let deps = spec.dependencies_for_output(&out.name); - for dep in deps.runtime { - if !local_provides.contains(deps::dep_name(&dep)) { - push_unique(&mut deps_all, dep); + for out in spec.outputs() { + let deps = spec.dependencies_for_output(&out.name); + for dep in deps.runtime { + if !local_provides.contains(deps::dep_name(&dep)) { + push_unique(&mut deps_all, dep); + } + } + } + } + if include_lib32 { + for dep in &spec.lib32_dependencies().runtime { + if !local_provides.contains(deps::dep_name(dep)) { + push_unique(&mut deps_all, dep.clone()); } } } if include_test_deps && !spec.build.flags.skip_tests { - for dep in &spec.dependencies.test { - push_unique(&mut deps_all, dep.clone()); + if !lib32_only { + for dep in &spec.dependencies.test { + push_unique(&mut deps_all, dep.clone()); + } + } + if include_lib32 { + for dep in &spec.lib32_dependencies().test { + push_unique(&mut deps_all, dep.clone()); + } } } deps_all @@ -809,6 +838,7 @@ mod tests { runtime: vec!["foo-libs".into(), "zlib".into()], test: vec!["bats".into()], optional: vec!["docs-viewer".into()], + lib32: None, }, package_alternatives: BTreeMap::from([( "foo-libs".into(), @@ -825,6 +855,7 @@ mod tests { runtime: vec!["foo-libs".into(), "libfoo".into(), "openssl".into()], test: Vec::new(), optional: Vec::new(), + lib32: None, }, )]), spec_dir: PathBuf::from("."), @@ -888,7 +919,7 @@ mod tests { #[test] fn source_deps_for_install_excludes_local_runtime_outputs_and_provides() { let spec = mk_spec(); - let deps = source_deps_for_install(&spec, false); + let deps = source_deps_for_install(&spec, false, false); assert!(deps.contains(&"make".to_string())); assert!(deps.contains(&"zlib".to_string())); assert!(deps.contains(&"openssl".to_string())); @@ -899,17 +930,36 @@ mod tests { #[test] fn source_deps_for_install_does_not_include_test_deps() { let spec = mk_spec(); - let deps = source_deps_for_install(&spec, false); + let deps = source_deps_for_install(&spec, false, false); assert!(!deps.contains(&"bats".to_string())); } #[test] fn source_deps_for_install_includes_test_deps_when_enabled() { let spec = mk_spec(); - let deps = source_deps_for_install(&spec, true); + let deps = source_deps_for_install(&spec, true, false); assert!(deps.contains(&"bats".to_string())); } + #[test] + fn source_deps_for_install_uses_lib32_only_dependencies_when_requested() { + let mut spec = mk_spec(); + spec.dependencies.lib32 = Some(crate::package::DependencyGroup { + build: vec!["gcc-multilib".into()], + runtime: vec!["lib32-zlib".into()], + test: vec!["lib32-bats".into()], + optional: Vec::new(), + }); + + let deps = source_deps_for_install(&spec, true, true); + assert!(deps.contains(&"gcc-multilib".to_string())); + assert!(deps.contains(&"lib32-zlib".to_string())); + assert!(deps.contains(&"lib32-bats".to_string())); + assert!(!deps.contains(&"make".to_string())); + assert!(!deps.contains(&"zlib".to_string())); + assert!(!deps.contains(&"bats".to_string())); + } + #[test] fn candidate_dedup_keeps_highest_priority_origin_for_same_package() { let candidates = vec![ @@ -957,6 +1007,7 @@ mod tests { prefer_binary: true, local_sibling_root: None, include_test_deps: false, + lib32_only_requested_specs: false, }, ) .unwrap();