feat: update depot version to 0.11.0 and add support for Perl build type

This commit is contained in:
2026-03-08 17:45:03 -05:00
parent 93f1b13179
commit be0288440a
10 changed files with 486 additions and 13 deletions
Generated
+1 -1
View File
@@ -382,7 +382,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
[[package]]
name = "depot"
version = "0.10.1"
version = "0.11.0"
dependencies = [
"anyhow",
"ar",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "depot"
version = "0.10.1"
version = "0.11.0"
edition = "2024"
[lints.rust]
+1 -1
View File
@@ -1,6 +1,6 @@
project(
'depot',
version: '0.10.1',
version: '0.11.0',
meson_version: '>=0.60.0',
)
+11 -7
View File
@@ -362,7 +362,7 @@ fn num_cpus() -> usize {
.unwrap_or(1)
}
fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result<std::path::PathBuf> {
pub(crate) fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result<std::path::PathBuf> {
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<Vec<PathBuf>> {
pub(crate) fn resolve_make_dirs(
build_dir: &Path,
dirs: &[String],
field_name: &str,
) -> Result<Vec<PathBuf>> {
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<String> {
pub(crate) fn phase_targets(single: &str, many: &[String], default: Option<&str>) -> Vec<String> {
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<St
targets
}
fn make_exec_supports_make_assignments(make_exec: &str) -> 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 {
+2
View File
@@ -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),
+432
View File
@@ -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")
);
}
}
+1 -1
View File
@@ -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
)
}
+1 -1
View File
@@ -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
)
}
+5 -1
View File
@@ -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<PackageSpec> {
BuildType::Autotools,
BuildType::CMake,
BuildType::Meson,
BuildType::Perl,
BuildType::Makefile,
BuildType::Python,
BuildType::Rust,
@@ -528,12 +530,13 @@ pub fn create_interactive() -> Result<PackageSpec> {
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<String> {
BuildType::Autotools => "autotools",
BuildType::CMake => "cmake",
BuildType::Meson => "meson",
BuildType::Perl => "perl",
BuildType::Custom => "custom",
BuildType::Python => "python",
BuildType::Rust => "rust",
+31
View File
@@ -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,