From d434e79a76f25a656004d8b75b658123df3faab9 Mon Sep 17 00:00:00 2001 From: SFG545 Date: Thu, 12 Mar 2026 00:48:30 -0500 Subject: [PATCH] feat: update depot version to 0.17.1 and enhance command execution with improved error handling and environment variable management --- Cargo.lock | 2 +- Cargo.toml | 2 +- meson.build | 2 +- src/commands.rs | 328 +++++++++++++++++++++++++++++++++++++++---- src/shell_helpers.rs | 12 -- src/source/hooks.rs | 120 ++++++++++++---- 6 files changed, 393 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b852120..d0a62be 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "depot" -version = "0.17.0" +version = "0.17.1" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index b32fbc6..0431cdb 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "depot" -version = "0.17.0" +version = "0.17.1" edition = "2024" [lints.rust] diff --git a/meson.build b/meson.build index 9447238..4c54006 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'depot', - version: '0.17.0', + version: '0.17.1', meson_version: '>=0.60.0', default_options: ['buildtype=release'], ) diff --git a/src/commands.rs b/src/commands.rs index 95f9e0e..fef5695 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -165,7 +165,10 @@ fn command_status_with_sh_fallback( ) -> std::io::Result { match cmd.status() { Ok(status) => Ok(status), - Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => { + Err(err) + if err.kind() == std::io::ErrorKind::PermissionDenied + || err.raw_os_error() == Some(26) => + { let program = cmd.get_program(); let contents = fs::read(program); let is_script = contents.ok().is_some_and(|bytes| bytes.starts_with(b"#!")); @@ -261,7 +264,43 @@ fn parse_keep_list(metadata: &toml::Value) -> Vec { } fn current_process_env_vars() -> Vec<(String, String)> { - std::env::vars().collect() + const ALLOWED_ENV_VARS: &[&str] = &[ + "AR", + "CARCH", + "CBUILD", + "CC", + "CHOST", + "CPP", + "CROSS_COMPILE", + "CROSS_PREFIX", + "CFLAGS", + "CXX", + "CXXFLAGS", + "DEPOT_ROOTFS", + "DEPOT_SPECDIR", + "DESTDIR", + "LD", + "LDFLAGS", + "LTOFLAGS", + "MAKEFLAGS", + "NM", + "PREFIX", + "PYTHONDONTWRITEBYTECODE", + "PYTHONNOUSERSITE", + "RANLIB", + "RUSTFLAGS", + "SETUPTOOLS_USE_DISTUTILS", + "STRIP", + ]; + + ALLOWED_ENV_VARS + .iter() + .filter_map(|key| { + std::env::var(key) + .ok() + .map(|value| ((*key).to_string(), value)) + }) + .collect() } fn run_internal_command(command: InternalCommands) -> Result<()> { @@ -594,6 +633,28 @@ fn maybe_disable_tests_for_missing_deps( Ok(()) } +fn maybe_prompt_to_skip_tests_for_missing_requested_deps( + pkg_spec: &mut package::PackageSpec, + missing_test: &[String], + reason: &str, +) -> Result { + if pkg_spec.build.flags.skip_tests + || !build_type_runs_automatic_tests(pkg_spec) + || missing_test.is_empty() + { + return Ok(false); + } + + ui::warn(format!("{reason}: {}", missing_test.join(", "))); + if ui::prompt_yes_no("Continue without tests?", false)? { + pkg_spec.build.flags.skip_tests = true; + ui::warn("Tests will be skipped for this build."); + return Ok(true); + } + + 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() } @@ -3380,27 +3441,33 @@ fn run_direct_install_request( })?; let db_path = config.installed_db_path(options.rootfs); - if staging_dir.is_none() - && (options.no_deps || !should_install_test_deps(&pkg_spec, options.install_test_deps)) - { - maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path)?; + 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 !missing_test.is_empty() + && !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies are missing", + )? + { + anyhow::bail!("Missing test dependencies: {}", missing_test.join(", ")); + } + } else if options.no_deps || !should_install_test_deps(&pkg_spec, options.install_test_deps) + { + maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path)?; + } } // Check dependencies and prompt for auto-install if needed if !options.no_deps { deps::print_dep_status(&pkg_spec, &db_path)?; - // Collect all missing dependencies (build + runtime) - let missing = merge_missing_dependencies( + let missing_required = merge_missing_dependencies( deps::check_build_deps(&pkg_spec, &db_path)?, deps::check_runtime_deps(&pkg_spec, &db_path)?, ); - let missing = if should_install_test_deps(&pkg_spec, options.install_test_deps) { - merge_missing_dependencies(missing, deps::check_test_deps(&pkg_spec, &db_path)?) - } else { - missing - }; - if !missing.is_empty() { + if !missing_required.is_empty() { // Check for dependency cycles via DEPOT_DEPCHAIN env var let dep_chain = std::env::var("DEPOT_DEPCHAIN").unwrap_or_default(); let chain_set: std::collections::HashSet<&str> = @@ -3414,8 +3481,11 @@ fn run_direct_install_request( ); } - ui::warn(format!("Missing dependencies: {}", missing.join(", "))); - if ui::prompt_package_action("dependency installation", &missing, true)? { + ui::warn(format!( + "Missing dependencies: {}", + missing_required.join(", ") + )); + if ui::prompt_package_action("dependency installation", &missing_required, true)? { // Build package index for fast lookups let pkg_index = index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone())); @@ -3428,7 +3498,7 @@ fn run_direct_install_request( }; let mut dep_spec_paths = Vec::new(); - for dep in missing { + for dep in missing_required { // Use package index for O(1) lookup let candidate = pkg_index.find(&dep); @@ -3465,7 +3535,79 @@ fn run_direct_install_request( 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) { - deps::require_test_deps(&pkg_spec, &db_path)?; + let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if !missing_test.is_empty() { + let pkg_index = + index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone())); + let mut dep_spec_paths = Vec::new(); + let mut unavailable_test = Vec::new(); + for dep in &missing_test { + if let Some(dep_spec_path) = pkg_index.find(dep) { + dep_spec_paths.push(dep_spec_path); + } else { + unavailable_test.push(dep.clone()); + } + } + + if !unavailable_test.is_empty() + && !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &unavailable_test, + "Requested test dependencies could not be resolved", + )? + { + anyhow::bail!("Missing test dependencies: {}", unavailable_test.join(", ")); + } + + if !pkg_spec.build.flags.skip_tests && !dep_spec_paths.is_empty() { + ui::warn(format!( + "Missing test dependencies: {}", + missing_test.join(", ") + )); + if ui::prompt_package_action("dependency installation", &missing_test, true)? { + ui::info(format!( + "Installing test dependencies: {}", + install_request_display(&dep_spec_paths) + )); + let exe = + std::env::current_exe().context("Failed to locate depot executable")?; + run_install_command_with_program( + &exe, + &dep_spec_paths, + options.rootfs, + ChildInstallCommandOptions { + no_deps: options.no_deps, + assume_yes: false, + no_flags: options.no_flags, + cross_prefix: options.cross_prefix, + clean: options.clean, + install_test_deps: options.install_test_deps, + install_context: None, + dep_chain: None, + }, + )?; + } else if !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies were not installed", + )? { + anyhow::bail!("Aborted"); + } + } + } + } + + if should_install_test_deps(&pkg_spec, options.install_test_deps) { + let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if !missing_test.is_empty() + && !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies are still missing", + )? + { + deps::require_test_deps(&pkg_spec, &db_path)?; + } } } @@ -3717,24 +3859,33 @@ 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) { + 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 !missing_test.is_empty() + && !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies are missing", + )? + { + 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)?; } // Check build dependencies if !cli.no_deps { deps::print_dep_status(&pkg_spec, &db_path)?; - let missing = merge_missing_dependencies( + let missing_required = merge_missing_dependencies( deps::check_build_deps(&pkg_spec, &db_path)?, deps::check_runtime_deps(&pkg_spec, &db_path)?, ); - let missing = if should_install_test_deps(&pkg_spec, install_test_deps) { - merge_missing_dependencies(missing, deps::check_test_deps(&pkg_spec, &db_path)?) - } else { - missing - }; - if !missing.is_empty() { - ui::warn(format!("Missing dependencies: {}", missing.join(", "))); + if !missing_required.is_empty() { + ui::warn(format!( + "Missing dependencies: {}", + missing_required.join(", ") + )); let local_sibling_root = spec_path .parent() .and_then(|p| p.parent()) @@ -3742,7 +3893,7 @@ pub fn run(cli: Cli) -> Result<()> { let dep_plan = planner::build_dependency_install_plan( &config, &cli.rootfs, - &missing, + &missing_required, planner::PlannerOptions { assume_yes: cli.yes, prefer_binary: config.repo_settings.prefer_binary, @@ -3751,7 +3902,11 @@ pub fn run(cli: Cli) -> Result<()> { }, )?; print_plan_summary(&dep_plan); - if !ui::prompt_package_action("dependency installation", &missing, true)? { + if !ui::prompt_package_action( + "dependency installation", + &missing_required, + true, + )? { anyhow::bail!("Aborted"); } if cli.dry_run { @@ -3775,7 +3930,84 @@ pub fn run(cli: Cli) -> Result<()> { 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) { - deps::require_test_deps(&pkg_spec, &db_path)?; + let missing_test = deps::check_test_deps(&pkg_spec, &db_path)?; + if !missing_test.is_empty() { + let local_sibling_root = spec_path + .parent() + .and_then(|p| p.parent()) + .map(Path::to_path_buf); + let dep_plan = match planner::build_dependency_install_plan( + &config, + &cli.rootfs, + &missing_test, + planner::PlannerOptions { + assume_yes: cli.yes, + prefer_binary: config.repo_settings.prefer_binary, + local_sibling_root, + include_test_deps: install_test_deps, + }, + ) { + Ok(plan) => plan, + Err(err) + if maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies could not be resolved", + )? => + { + planner::ExecutionPlan { steps: Vec::new() } + } + Err(err) => return Err(err), + }; + + if !pkg_spec.build.flags.skip_tests && !dep_plan.steps.is_empty() { + ui::warn(format!( + "Missing test dependencies: {}", + missing_test.join(", ") + )); + print_plan_summary(&dep_plan); + if !ui::prompt_package_action( + "dependency installation", + &missing_test, + true, + )? { + if !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies were not installed", + )? { + anyhow::bail!("Aborted"); + } + } else if !cli.dry_run { + execute_install_plan_with_child_commands( + &dep_plan, + &cli.rootfs, + &config, + InstallPlanExecutionOptions { + no_flags: cli.no_flags, + cross_prefix: cli.cross_prefix.as_deref(), + clean: cli.clean, + dry_run: cli.dry_run, + confirm_installation: 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 !missing_test.is_empty() + && !maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut pkg_spec, + &missing_test, + "Requested test dependencies are still missing", + )? + { + deps::require_test_deps(&pkg_spec, &db_path)?; + } + } + } } } else if cli.dry_run { ui::info("Dry run enabled, stopping before build."); @@ -5594,6 +5826,42 @@ optional = [] ))); } + #[test] + fn requested_test_deps_prompt_can_disable_tests() -> Result<()> { + let mut spec = test_package_spec(package::BuildType::Meson, None, &[]); + spec.dependencies.test = vec!["pytest".into()]; + + ui::set_assume_yes(true); + let prompted = maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut spec, + &["pytest".into()], + "Requested test dependencies are missing", + )?; + ui::set_assume_yes(false); + + assert!(prompted); + assert!(spec.build.flags.skip_tests); + Ok(()) + } + + #[test] + fn requested_test_deps_prompt_is_ignored_for_non_automatic_test_builders() -> Result<()> { + let mut spec = test_package_spec(package::BuildType::Custom, None, &[]); + spec.dependencies.test = vec!["pytest".into()]; + + ui::set_assume_yes(true); + let prompted = maybe_prompt_to_skip_tests_for_missing_requested_deps( + &mut spec, + &["pytest".into()], + "Requested test dependencies are missing", + )?; + ui::set_assume_yes(false); + + assert!(!prompted); + assert!(!spec.build.flags.skip_tests); + Ok(()) + } + #[test] fn rootfs_is_system_root_detects_live_rootfs() { assert!(rootfs_is_system_root(Path::new("/"))); diff --git a/src/shell_helpers.rs b/src/shell_helpers.rs index 8337ea0..82f3824 100644 --- a/src/shell_helpers.rs +++ b/src/shell_helpers.rs @@ -172,18 +172,6 @@ impl ShellHelpers { self.depot_executable.to_string_lossy().into_owned(), ); } - - /// Apply helper-related variables directly to a `std::process::Command`. - pub fn apply_to_command(&self, cmd: &mut std::process::Command) { - cmd.env("PATH", &self.path_value) - .env("DEPOT_OUTPUTS_DIR", &self.outputs_dir) - .env("DEPOT_INTERNAL_DIR", INTERNAL_DEPOT_DIR) - .env(DEPOT_HAUL_HELPER_ENV, &self.haul_path) - .env(DEPOT_SUBDESTDIR_HELPER_ENV, &self.subdestdir_path) - .env(DEPOT_PYTHON_BUILD_HELPER_ENV, &self.python_build_path) - .env(DEPOT_PYTHON_INSTALL_HELPER_ENV, &self.python_install_path) - .env(DEPOT_EXECUTABLE_ENV, &self.depot_executable); - } } /// Wrap a shell command with helper functions that invoke the helper scripts diff --git a/src/source/hooks.rs b/src/source/hooks.rs index 99c3d24..93aef43 100755 --- a/src/source/hooks.rs +++ b/src/source/hooks.rs @@ -10,6 +10,15 @@ use std::process::Command; use crate::builder::state::{BuildStep, StateTracker}; +fn hook_env_vars( + spec: &PackageSpec, + shell_helpers: &crate::shell_helpers::ShellHelpers, +) -> crate::builder::EnvVars { + let mut env_vars = crate::builder::standard_build_env(spec, None, true, true); + shell_helpers.apply_to_env_vars(&mut env_vars); + env_vars +} + /// Apply patches and run `post_extract` commands in the extracted source tree. pub fn post_extract( spec: &PackageSpec, @@ -85,23 +94,27 @@ fn run_post_extract_commands(spec: &PackageSpec, source: &Source, src_dir: &Path "Running {} post-extract command(s)...", source.post_extract.len() ); + let helper_root = tempfile::tempdir().context("Failed to create post-extract helper root")?; + let shell_helpers = crate::shell_helpers::ShellHelpers::new(helper_root.path())?; + let env_vars = hook_env_vars(spec, &shell_helpers); for cmd in &source.post_extract { - let cmd = spec.expand_vars(cmd); - crate::log_info!(" post_extract: {}", cmd); + let cmd_str = spec.expand_vars(cmd); + crate::log_info!(" post_extract: {}", cmd_str); + let wrapped_cmd = crate::shell_helpers::wrap_shell_command(&cmd_str); // Use a shell for convenience; this is a package manager, so specs are trusted input. - let status = Command::new("sh") - .current_dir(src_dir) - .env("DEPOT_SPECDIR", &spec.spec_dir) - .env("PATH", crate::runtime_env::safe_script_path()) + let mut shell_cmd = Command::new("sh"); + shell_cmd.current_dir(src_dir); + crate::builder::prepare_command(&mut shell_cmd, &env_vars); + let status = shell_cmd .arg("-c") - .arg(&cmd) + .arg(&wrapped_cmd) .status() - .with_context(|| format!("Failed to run post_extract command: {}", cmd))?; + .with_context(|| format!("Failed to run post_extract command: {}", cmd_str))?; if !status.success() { - bail!("post_extract command failed: {}", cmd); + bail!("post_extract command failed: {}", cmd_str); } } @@ -121,6 +134,12 @@ pub fn run_post_configure_commands( crate::log_info!("Running {} post-configure command(s)...", commands.len()); let shell_helpers = crate::shell_helpers::ShellHelpers::new(destdir)?; + let mut env_vars = hook_env_vars(spec, &shell_helpers); + crate::builder::set_env_var( + &mut env_vars, + "DESTDIR", + destdir.to_string_lossy().into_owned(), + ); for cmd in commands { let cmd_str = spec.expand_vars(cmd); @@ -129,13 +148,8 @@ pub fn run_post_configure_commands( let mut shell_cmd = Command::new("sh"); shell_cmd.current_dir(src_dir); - shell_helpers.apply_to_command(&mut shell_cmd); + crate::builder::prepare_command(&mut shell_cmd, &env_vars); let status = shell_cmd - .env("DEPOT_SPECDIR", &spec.spec_dir) - .env("DESTDIR", destdir) - .env("DEPOT_ROOTFS", &spec.build.flags.rootfs) - .env("CC", &spec.build.flags.cc) - .env("AR", &spec.build.flags.ar) .arg("-c") .arg(&wrapped_cmd) .status() @@ -158,6 +172,12 @@ pub fn run_post_compile_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P crate::log_info!("Running {} post-compile command(s)...", commands.len()); let shell_helpers = crate::shell_helpers::ShellHelpers::new(destdir)?; + let mut env_vars = hook_env_vars(spec, &shell_helpers); + crate::builder::set_env_var( + &mut env_vars, + "DESTDIR", + destdir.to_string_lossy().into_owned(), + ); for cmd in commands { let cmd_str = spec.expand_vars(cmd); @@ -166,13 +186,8 @@ pub fn run_post_compile_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P let mut shell_cmd = Command::new("sh"); shell_cmd.current_dir(src_dir); - shell_helpers.apply_to_command(&mut shell_cmd); + crate::builder::prepare_command(&mut shell_cmd, &env_vars); let status = shell_cmd - .env("DEPOT_SPECDIR", &spec.spec_dir) - .env("DESTDIR", destdir) - .env("DEPOT_ROOTFS", &spec.build.flags.rootfs) - .env("CC", &spec.build.flags.cc) - .env("AR", &spec.build.flags.ar) .arg("-c") .arg(&wrapped_cmd) .status() @@ -195,6 +210,12 @@ pub fn run_post_install_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P crate::log_info!("Running {} post-install command(s)...", commands.len()); let shell_helpers = crate::shell_helpers::ShellHelpers::new(destdir)?; + let mut env_vars = hook_env_vars(spec, &shell_helpers); + crate::builder::set_env_var( + &mut env_vars, + "DESTDIR", + destdir.to_string_lossy().into_owned(), + ); for cmd in commands { let cmd_str = spec.expand_vars(cmd); @@ -203,13 +224,8 @@ pub fn run_post_install_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P let mut shell_cmd = Command::new("sh"); shell_cmd.current_dir(src_dir); - shell_helpers.apply_to_command(&mut shell_cmd); + crate::builder::prepare_command(&mut shell_cmd, &env_vars); let status = shell_cmd - .env("DEPOT_SPECDIR", &spec.spec_dir) - .env("DESTDIR", destdir) - .env("DEPOT_ROOTFS", &spec.build.flags.rootfs) - .env("CC", &spec.build.flags.cc) - .env("AR", &spec.build.flags.ar) .arg("-c") .arg(&wrapped_cmd) .status() @@ -291,7 +307,7 @@ fn download(url: &str, dest: &Path) -> Result<()> { #[cfg(test)] mod tests { - use super::{resolve_patch_path, run_post_install_commands}; + use super::{resolve_patch_path, run_post_extract_commands, run_post_install_commands}; use crate::package::{ Alternatives, Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec, Source, }; @@ -386,4 +402,52 @@ mod tests { .exists() ); } + + #[test] + fn post_extract_commands_can_call_python_build_helper() { + let tmp = tempfile::tempdir().unwrap(); + let spec_dir = tmp.path().join("spec"); + let src_dir = tmp.path().join("src"); + std::fs::create_dir_all(&spec_dir).unwrap(); + std::fs::create_dir_all(&src_dir).unwrap(); + + let fake_depot = tmp.path().join("fake-depot"); + let log_path = tmp.path().join("python-build.log"); + std::fs::write( + &fake_depot, + format!( + "#!/bin/sh\nset -eu\nprintf '%s\\n' \"$@\" > '{}'\n", + log_path.display() + ), + ) + .unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(&fake_depot).unwrap().permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&fake_depot, perms).unwrap(); + } + + let spec = dummy_spec(&spec_dir); + let source = Source { + url: "https://example.com/foo.tar.gz".into(), + sha256: "skip".into(), + extract_dir: "foo".into(), + patches: Vec::new(), + post_extract: vec![format!( + "DEPOT_EXECUTABLE='{}' python_build --src-dir . --dist-dir dist", + fake_depot.display() + )], + cherry_pick: Vec::new(), + }; + + run_post_extract_commands(&spec, &source, &src_dir).unwrap(); + + let logged = std::fs::read_to_string(&log_path).unwrap(); + assert!(logged.contains("internal")); + assert!(logged.contains("python-build")); + assert!(logged.contains("--src-dir")); + assert!(logged.contains("--dist-dir")); + } }