From be0288440a23da999d528ddd7ea7da9fe67269e7 Mon Sep 17 00:00:00 2001 From: SFG545 Date: Sun, 8 Mar 2026 17:45:03 -0500 Subject: [PATCH] feat: update depot version to 0.11.0 and add support for Perl build type --- Cargo.lock | 2 +- Cargo.toml | 2 +- meson.build | 2 +- src/builder/autotools.rs | 18 +- src/builder/mod.rs | 2 + src/builder/perl.rs | 432 +++++++++++++++++++++++++++++++++++++ src/commands.rs | 2 +- src/deps.rs | 2 +- src/package/interactive.rs | 6 +- src/package/spec.rs | 31 +++ 10 files changed, 486 insertions(+), 13 deletions(-) create mode 100644 src/builder/perl.rs diff --git a/Cargo.lock b/Cargo.lock index 72640f3..b294e8a 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,7 +382,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "depot" -version = "0.10.1" +version = "0.11.0" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index 7472f78..6eabbd5 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "depot" -version = "0.10.1" +version = "0.11.0" edition = "2024" [lints.rust] diff --git a/meson.build b/meson.build index 2db8be7..fd2618a 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'depot', - version: '0.10.1', + version: '0.11.0', meson_version: '>=0.60.0', ) diff --git a/src/builder/autotools.rs b/src/builder/autotools.rs index 7bc1bed..09bd951 100755 --- a/src/builder/autotools.rs +++ b/src/builder/autotools.rs @@ -362,7 +362,7 @@ fn num_cpus() -> usize { .unwrap_or(1) } -fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result { +pub(crate) fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result { let flags = &spec.build.flags; let source_subdir = spec.expand_vars(&flags.source_subdir); @@ -496,7 +496,11 @@ fn maybe_find_autotools_test_target( find_autotools_test_target(build_dir) } -fn resolve_make_dirs(build_dir: &Path, dirs: &[String], field_name: &str) -> Result> { +pub(crate) fn resolve_make_dirs( + build_dir: &Path, + dirs: &[String], + field_name: &str, +) -> Result> { if dirs.is_empty() { return Ok(vec![build_dir.to_path_buf()]); } @@ -590,11 +594,11 @@ fn nonempty_trimmed(value: &str) -> Option<&str> { (!trimmed.is_empty()).then_some(trimmed) } -fn resolve_make_exec(configured: &str) -> &str { +pub(crate) fn resolve_make_exec(configured: &str) -> &str { nonempty_trimmed(configured).unwrap_or("make") } -fn phase_targets(single: &str, many: &[String], default: Option<&str>) -> Vec { +pub(crate) fn phase_targets(single: &str, many: &[String], default: Option<&str>) -> Vec { let mut targets = Vec::new(); if let Some(target) = nonempty_trimmed(single) { targets.push(target.to_string()); @@ -612,7 +616,7 @@ fn phase_targets(single: &str, many: &[String], default: Option<&str>) -> Vec bool { +pub(crate) fn make_exec_supports_make_assignments(make_exec: &str) -> bool { let Some(tool) = Path::new(make_exec) .file_name() .and_then(|name| name.to_str()) @@ -629,7 +633,7 @@ fn make_exec_supports_make_assignments(make_exec: &str) -> bool { || tool.ends_with("make.exe") } -fn add_make_variable_overrides_if_supported( +pub(crate) fn add_make_variable_overrides_if_supported( cmd: &mut Command, make_exec: &str, vars: &[String], @@ -680,7 +684,7 @@ fn add_make_variable_overrides(cmd: &mut Command, vars: &[String], phase: &str) Ok(()) } -fn has_make_variable_override(vars: &[String], name: &str) -> bool { +pub(crate) fn has_make_variable_override(vars: &[String], name: &str) -> bool { vars.iter().any(|raw| { let var = raw.trim(); let Some((lhs, _rhs)) = var.split_once('=') else { diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 1f32588..3345723 100755 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -6,6 +6,7 @@ mod cmake; mod custom; mod makefile; mod meson; +mod perl; mod python; mod rust; pub mod state; @@ -247,6 +248,7 @@ pub fn build( } BuildType::CMake => cmake::build(spec, src_dir, destdir, cross, export_compiler_flags), BuildType::Meson => meson::build(spec, src_dir, destdir, cross, export_compiler_flags), + BuildType::Perl => perl::build(spec, src_dir, destdir, cross, export_compiler_flags), BuildType::Custom => custom::build(spec, src_dir, destdir, cross, export_compiler_flags), BuildType::Python => python::build(spec, src_dir, destdir, cross, export_compiler_flags), BuildType::Rust => rust::build(spec, src_dir, destdir, cross, export_compiler_flags), diff --git a/src/builder/perl.rs b/src/builder/perl.rs new file mode 100644 index 0000000..72158d0 --- /dev/null +++ b/src/builder/perl.rs @@ -0,0 +1,432 @@ +//! Perl MakeMaker build system (`perl Makefile.PL && make && make test && make install`) + +use crate::builder::autotools; +use crate::builder::state::{BuildStep, StateTracker}; +use crate::cross::CrossConfig; +use crate::fakeroot; +use crate::package::PackageSpec; +use crate::source::hooks; +use anyhow::{Context, Result}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +pub fn build( + spec: &PackageSpec, + src_dir: &Path, + destdir: &Path, + cross: Option<&CrossConfig>, + export_compiler_flags: bool, +) -> Result<()> { + let flags = &spec.build.flags; + if flags.build_dir.is_some() { + anyhow::bail!("build.flags.build_dir is not supported for build.type = 'perl'"); + } + + let actual_src = autotools::resolve_actual_src(spec, src_dir)?; + let make_exec = autotools::resolve_make_exec(&flags.make_exec); + let export_compiler_flags = export_compiler_flags && !flags.no_flags; + + fs::create_dir_all(destdir)?; + + let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags); + crate::builder::set_env_var(&mut env_vars, "PERL_MM_USE_DEFAULT", "1"); + if !flags.rootfs.is_empty() && flags.rootfs != "/" { + crate::builder::set_env_var( + &mut env_vars, + "PKG_CONFIG_SYSROOT_DIR", + flags.rootfs.clone(), + ); + } + + let mut state = StateTracker::new_with_namespace( + &actual_src, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; + + if !state.is_done(BuildStep::Configured) { + let configure_script = resolve_perl_configure_script(spec, &actual_src); + crate::log_info!("Running perl {}...", configure_script.display()); + + let mut configure_cmd = Command::new("perl"); + configure_cmd.current_dir(&actual_src); + configure_cmd.arg(&configure_script); + if !has_assignment_prefix(&flags.configure, "INSTALLDIRS") { + configure_cmd.arg("INSTALLDIRS=vendor"); + } + for arg in &flags.configure { + configure_cmd.arg(spec.expand_vars(arg)); + } + crate::builder::prepare_tool_command(&mut configure_cmd, &env_vars); + + let status = configure_cmd.status().with_context(|| { + format!( + "Failed to run perl {} in {}", + configure_script.display(), + actual_src.display() + ) + })?; + if !status.success() { + anyhow::bail!("perl Makefile.PL failed with status: {}", status); + } + + hooks::run_post_configure_commands(spec, &actual_src, destdir)?; + state.mark_done(BuildStep::Configured)?; + } else { + crate::log_info!("Skipping perl configure (already done)"); + } + + if !state.is_done(BuildStep::PostCompileDone) { + let build_dirs = + autotools::resolve_make_dirs(&actual_src, &flags.make_dirs, "build.flags.make_dirs")?; + let build_targets = autotools::phase_targets(&flags.make_target, &flags.make_targets, None); + + for build_dir in build_dirs { + crate::log_info!("Running {} in {}...", make_exec, build_dir.display()); + let mut make_cmd = Command::new(make_exec); + make_cmd.current_dir(&build_dir); + make_cmd.arg("-j").arg( + std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1) + .to_string(), + ); + autotools::add_make_variable_overrides_if_supported( + &mut make_cmd, + make_exec, + &flags.make_vars, + "build", + )?; + for target in &build_targets { + make_cmd.arg(target); + } + crate::builder::prepare_tool_command(&mut make_cmd, &env_vars); + + let status = make_cmd.status().with_context(|| { + format!("Failed to run {} in {}", make_exec, build_dir.display()) + })?; + if !status.success() { + anyhow::bail!( + "{} failed with status: {} (dir: {})", + make_exec, + status, + build_dir.display() + ); + } + } + + if flags.skip_tests { + crate::log_info!("Skipping tests: disabled by build.flags.skip_tests"); + } else { + let test_dirs = autotools::resolve_make_dirs( + &actual_src, + &flags.make_test_dirs, + "build.flags.make_test_dirs", + )?; + let test_targets = autotools::phase_targets( + &flags.make_test_target, + &flags.make_test_targets, + Some("test"), + ); + for test_dir in test_dirs { + crate::log_info!( + "Running {} {} in {}...", + make_exec, + test_targets.join(" "), + test_dir.display() + ); + let mut test_cmd = Command::new(make_exec); + test_cmd.current_dir(&test_dir); + autotools::add_make_variable_overrides_if_supported( + &mut test_cmd, + make_exec, + &flags.make_test_vars, + "test", + )?; + for target in &test_targets { + test_cmd.arg(target); + } + crate::builder::prepare_tool_command(&mut test_cmd, &env_vars); + + let status = test_cmd.status().with_context(|| { + format!( + "Failed to run {} {} in {}", + make_exec, + test_targets.join(" "), + test_dir.display() + ) + })?; + if !status.success() { + anyhow::bail!( + "{} {} failed with status: {} (dir: {})", + make_exec, + test_targets.join(" "), + status, + test_dir.display() + ); + } + } + } + + hooks::run_post_compile_commands(spec, &actual_src, destdir)?; + state.mark_done(BuildStep::PostCompileDone)?; + } else { + crate::log_info!("Skipping perl build and tests (already done)"); + } + + if !state.is_done(BuildStep::PostInstallDone) { + let install_dirs = autotools::resolve_make_dirs( + &actual_src, + &flags.make_install_dirs, + "build.flags.make_install_dirs", + )?; + let install_targets = autotools::phase_targets( + &flags.make_install_target, + &flags.make_install_targets, + Some("install"), + ); + + for install_dir in install_dirs { + crate::log_info!( + "Running {} {}{}...", + make_exec, + install_targets.join(" "), + if fakeroot::is_root() { + "" + } else { + " (with internal fakeroot for build)" + } + ); + let mut install_cmd = fakeroot::wrap_install_command(make_exec, destdir); + install_cmd.current_dir(&install_dir); + if autotools::make_exec_supports_make_assignments(make_exec) + && !autotools::has_make_variable_override(&flags.make_install_vars, "DESTDIR") + { + install_cmd.arg(format!("DESTDIR={}", destdir.to_string_lossy())); + } + autotools::add_make_variable_overrides_if_supported( + &mut install_cmd, + make_exec, + &flags.make_install_vars, + "install", + )?; + for target in &install_targets { + install_cmd.arg(target); + } + + let mut install_env = env_vars.clone(); + install_env.push(( + "DESTDIR".to_string(), + destdir.to_string_lossy().into_owned(), + )); + crate::builder::prepare_tool_command(&mut install_cmd, &install_env); + + let status = install_cmd.status().with_context(|| { + format!( + "Failed to run {} {} for {} in {}", + make_exec, + install_targets.join(" "), + spec.package.name, + install_dir.display() + ) + })?; + if !status.success() { + anyhow::bail!( + "{} {} failed with status: {} (dir: {})", + make_exec, + install_targets.join(" "), + status, + install_dir.display() + ); + } + } + + hooks::run_post_install_commands(spec, &actual_src, destdir)?; + state.mark_done(BuildStep::PostInstallDone)?; + } else { + crate::log_info!("Skipping perl install (already done)"); + } + + Ok(()) +} + +fn resolve_perl_configure_script(spec: &PackageSpec, actual_src: &Path) -> PathBuf { + let configured = spec.expand_vars(&spec.build.flags.configure_file); + let trimmed = configured.trim(); + if trimmed.is_empty() { + return actual_src.join("Makefile.PL"); + } + + let path = Path::new(trimmed); + if path.is_absolute() { + path.to_path_buf() + } else { + actual_src.join(path) + } +} + +fn has_assignment_prefix(args: &[String], name: &str) -> bool { + args.iter().any(|arg| { + let trimmed = arg.trim(); + trimmed == name || trimmed.starts_with(&format!("{name}=")) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo}; + use std::os::unix::fs::PermissionsExt; + use tempfile::tempdir; + + fn mk_spec(name: &str, version: &str) -> PackageSpec { + PackageSpec { + package: PackageInfo { + name: name.into(), + version: version.into(), + revision: 1, + description: "d".into(), + homepage: "h".into(), + license: vec!["MIT".into()], + }, + packages: Vec::new(), + alternatives: Default::default(), + manual_sources: Vec::new(), + source: vec![crate::package::Source { + url: "h".into(), + sha256: "s".into(), + extract_dir: "e".into(), + patches: Vec::new(), + post_extract: Vec::new(), + cherry_pick: Vec::new(), + }], + build: Build { + build_type: BuildType::Perl, + flags: BuildFlags::default(), + }, + dependencies: Dependencies::default(), + package_alternatives: Default::default(), + package_dependencies: Default::default(), + spec_dir: PathBuf::from("."), + } + } + + fn write_executable(path: &Path, content: &str) -> Result<()> { + fs::write(path, content).with_context(|| format!("Failed to write {}", path.display()))?; + let mut perms = fs::metadata(path)?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(path, perms)?; + Ok(()) + } + + #[test] + fn perl_build_runs_makefile_pl_make_test_and_install() -> Result<()> { + let tmp_src = tempdir()?; + let tmp_dest = tempdir()?; + let tmp_tools = tempdir()?; + let src_path = tmp_src.path(); + let dest_path = tmp_dest.path(); + let tools_path = tmp_tools.path(); + + fs::write(src_path.join("Makefile.PL"), "print qq(configure\\n);")?; + + write_executable( + &tools_path.join("perl"), + r#"#!/bin/sh +printf '%s\n' "$@" > perl-args.log +touch Makefile +touch configured.txt +"#, + )?; + write_executable( + &tools_path.join("make"), + r#"#!/bin/sh +printf '%s\n' "$*" >> make-invocations.log +destdir="${DESTDIR:-}" +target="" +for arg in "$@"; do + case "$arg" in + DESTDIR=*) destdir="${arg#DESTDIR=}" ;; + test|install) target="$arg" ;; + esac +done +if [ -z "$target" ]; then + target="build" +fi +case "$target" in + build) + echo built > built.txt + ;; + test) + echo tested > tested.txt + ;; + install) + [ -n "$destdir" ] || exit 11 + mkdir -p "$destdir/usr/lib/perl5/vendor_perl" + cp built.txt "$destdir/usr/lib/perl5/vendor_perl/installed.txt" + ;; +esac +"#, + )?; + write_executable( + &tools_path.join("fakeroot"), + r#"#!/bin/sh +if [ "$1" = "--" ]; then + shift +fi +exec "$@" +"#, + )?; + + let old_path = std::env::var("PATH").unwrap_or_default(); + unsafe { + std::env::set_var("PATH", format!("{}:{}", tools_path.display(), old_path)); + } + + let mut spec = mk_spec("perl-test", "1.0"); + spec.build.flags.make_exec = tools_path.join("make").to_string_lossy().into_owned(); + spec.build.flags.configure = vec!["CCFLAGS=-fPIC".into()]; + + let build_result = build(&spec, src_path, dest_path, None, true); + + unsafe { + std::env::set_var("PATH", old_path); + } + + build_result?; + + let perl_args = fs::read_to_string(src_path.join("perl-args.log"))?; + assert!(perl_args.contains("Makefile.PL")); + assert!(perl_args.contains("INSTALLDIRS=vendor")); + assert!(perl_args.contains("CCFLAGS=-fPIC")); + assert!(src_path.join("built.txt").exists()); + assert!(src_path.join("tested.txt").exists()); + assert!( + dest_path + .join("usr/lib/perl5/vendor_perl/installed.txt") + .exists() + ); + + let state_tracker = StateTracker::new(src_path)?; + assert!(state_tracker.is_done(BuildStep::Configured)); + assert!(state_tracker.is_done(BuildStep::PostCompileDone)); + assert!(state_tracker.is_done(BuildStep::PostInstallDone)); + + Ok(()) + } + + #[test] + fn perl_build_rejects_build_dir() { + let tmp_src = tempdir().unwrap(); + let tmp_dest = tempdir().unwrap(); + let mut spec = mk_spec("perl-test", "1.0"); + spec.build.flags.build_dir = Some("build".into()); + + let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true) + .expect_err("perl build should reject build_dir"); + assert!( + err.to_string() + .contains("build.flags.build_dir is not supported") + ); + } +} diff --git a/src/commands.rs b/src/commands.rs index 0372ecc..4af6ca5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -240,7 +240,7 @@ fn package_spec_from_repo_record( fn build_type_runs_automatic_tests(build_type: package::BuildType) -> bool { matches!( build_type, - package::BuildType::Autotools | package::BuildType::CMake + package::BuildType::Autotools | package::BuildType::CMake | package::BuildType::Perl ) } diff --git a/src/deps.rs b/src/deps.rs index e87a83f..71a2fac 100755 --- a/src/deps.rs +++ b/src/deps.rs @@ -142,7 +142,7 @@ fn is_dep_satisfied( fn build_type_runs_automatic_tests(spec: &PackageSpec) -> bool { matches!( spec.build.build_type, - BuildType::Autotools | BuildType::CMake + BuildType::Autotools | BuildType::CMake | BuildType::Perl ) } diff --git a/src/package/interactive.rs b/src/package/interactive.rs index 3f3ad14..366726c 100644 --- a/src/package/interactive.rs +++ b/src/package/interactive.rs @@ -17,6 +17,7 @@ impl fmt::Display for BuildType { BuildType::Autotools => write!(f, "Autotools"), BuildType::CMake => write!(f, "CMake"), BuildType::Meson => write!(f, "Meson"), + BuildType::Perl => write!(f, "Perl"), BuildType::Custom => write!(f, "Custom"), BuildType::Python => write!(f, "Python"), BuildType::Rust => write!(f, "Rust"), @@ -332,6 +333,7 @@ pub fn create_interactive() -> Result { BuildType::Autotools, BuildType::CMake, BuildType::Meson, + BuildType::Perl, BuildType::Makefile, BuildType::Python, BuildType::Rust, @@ -528,12 +530,13 @@ pub fn create_interactive() -> Result { if matches!( build_type, - BuildType::Autotools | BuildType::CMake | BuildType::Meson + BuildType::Autotools | BuildType::CMake | BuildType::Meson | BuildType::Perl ) { let help = match build_type { BuildType::Autotools => "Examples: --disable-static, --enable-nls, --with-zlib", BuildType::CMake => "Examples: -DENABLE_TESTS=OFF, -DUSE_SYSTEM_LIBS=ON", BuildType::Meson => "Examples: -Dtests=false, -Ddefault_library=static", + BuildType::Perl => "Examples: INSTALLDIRS=vendor, INC=-I/usr/include/foo", _ => "", }; if Confirm::new("Add configure/setup options?") @@ -821,6 +824,7 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result { BuildType::Autotools => "autotools", BuildType::CMake => "cmake", BuildType::Meson => "meson", + BuildType::Perl => "perl", BuildType::Custom => "custom", BuildType::Python => "python", BuildType::Rust => "rust", diff --git a/src/package/spec.rs b/src/package/spec.rs index 72d939c..04acb9e 100755 --- a/src/package/spec.rs +++ b/src/package/spec.rs @@ -1472,6 +1472,36 @@ type = "python" assert!(matches!(spec.build.build_type, BuildType::Python)); } + #[test] + fn parse_perl_build_type() { + 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" + +[source] +url = "https://example.com/foo.tar.gz" +sha256 = "skip" +extract_dir = "foo" + +[build] +type = "perl" +"#, + ) + .unwrap(); + + let spec = PackageSpec::from_file(&path).unwrap(); + assert!(matches!(spec.build.build_type, BuildType::Perl)); + } + #[test] fn parse_python_config_settings_from_spec() { let tmp = tempfile::tempdir().unwrap(); @@ -2927,6 +2957,7 @@ pub enum BuildType { Autotools, CMake, Meson, + Perl, Custom, Python, Rust,