diff --git a/Cargo.lock b/Cargo.lock index d179111..40777b8 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,7 +451,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "depot" -version = "0.35.1" +version = "0.36.0" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index 322e94f..37c9218 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "depot" -version = "0.35.1" +version = "0.36.0" edition = "2024" [lints.rust] diff --git a/meson.build b/meson.build index a718562..0e0ecf9 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'depot', - version: '0.35.1', + version: '0.36.0', meson_version: '>=0.60.0', default_options: ['buildtype=release'], ) diff --git a/src/builder/autotools.rs b/src/builder/autotools.rs index f5efca4..22ef485 100755 --- a/src/builder/autotools.rs +++ b/src/builder/autotools.rs @@ -1,5 +1,6 @@ //! GNU Autotools build system (configure && make && make install) +use crate::builder::BuildHelperContext; use crate::builder::state::{BuildStep, StateTracker}; use crate::cross::CrossConfig; use crate::fakeroot; @@ -501,6 +502,202 @@ pub(crate) fn ensure_host_build( .with_context(|| format!("Failed to resolve host build dir: {}", build_dir.display())) } +pub(crate) fn run_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + build_dir: Option<&Path>, + cross: Option<&CrossConfig>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let source_dir = source_dir + .map(Path::to_path_buf) + .unwrap_or(std::env::current_dir().context("Failed to determine current directory")?); + let build_dir = build_dir.map(Path::to_path_buf).unwrap_or_else(|| { + flags + .build_dir + .as_ref() + .map(|dir| source_dir.join(dir)) + .unwrap_or_else(|| source_dir.clone()) + }); + fs::create_dir_all(&build_dir) + .with_context(|| format!("Failed to create build directory: {}", build_dir.display()))?; + + let mut helper_env = env_vars.to_vec(); + let cc = if let Some(cc_cfg) = cross { + cc_cfg.cc.clone() + } else { + helper_env + .iter() + .find(|(key, _)| key == "CC") + .map(|(_, value)| value.clone()) + .unwrap_or_else(|| flags.cc.clone()) + }; + + if let Some(cflags_str) = helper_env + .iter() + .find(|(key, _)| key == "CFLAGS") + .map(|(_, value)| value.clone()) + .filter(|value| !value.trim().is_empty()) + { + let expanded = expand_shell_commands(&cflags_str, &cc)?; + crate::builder::set_env_var(&mut helper_env, "CFLAGS", expanded); + } + + let configure_path = helper_configure_path(context, &source_dir); + let mut configure_cmd = Command::new(&configure_path); + configure_cmd.current_dir(&build_dir); + crate::builder::prepare_tool_command(&mut configure_cmd, &helper_env); + + let help_text = configure_help_text(&configure_path, &build_dir, &helper_env); + configure_cmd.arg(format!("--prefix={}", flags.prefix)); + for default_dir_arg in default_configure_install_dirs(&flags, help_text.as_deref()) { + configure_cmd.arg(default_dir_arg); + } + + let supports_host = + configure_supports_option(help_text.as_deref(), "--host", &flags.configure_file); + let supports_build = + configure_supports_option(help_text.as_deref(), "--build", &flags.configure_file); + + let requested_host = if let Some(cc_cfg) = cross { + Some(cc_cfg.host_triple().to_string()) + } else if !flags.chost.is_empty() { + Some(flags.chost.clone()) + } else { + None + } + .map(|host| { + if flags.lib32_variant { + lib32_host_triple(&host) + } else { + host + } + }); + + let requested_build = if cross.is_some() { + CrossConfig::build_triple().ok() + } else if !flags.cbuild.is_empty() { + Some(flags.cbuild.clone()) + } else { + None + }; + + if let Some(host) = requested_host { + if supports_host { + configure_cmd.arg(format!("--host={host}")); + } else { + crate::log_info!(" configure does not support --host; skipping {}", host); + } + } + if let Some(build) = requested_build { + if supports_build { + configure_cmd.arg(format!("--build={build}")); + } else { + crate::log_info!(" configure does not support --build; skipping {}", build); + } + } + + for arg in &flags.configure { + configure_cmd.arg(expand_with_envs(&context.expand_vars(arg), &helper_env)); + } + for arg in crate::builder::static_build_args_for(crate::package::BuildType::Autotools, &flags)? + { + add_auto_configure_arg_if_supported(&mut configure_cmd, help_text.as_deref(), &arg); + } + for arg in extra_args { + add_auto_configure_arg_if_supported( + &mut configure_cmd, + help_text.as_deref(), + &expand_with_envs(&context.expand_vars(arg), &helper_env), + ); + } + + let status = crate::interrupts::command_status(&mut configure_cmd) + .with_context(|| format!("Failed to run helper configure in {}", build_dir.display()))?; + if !status.success() { + anyhow::bail!("configure failed with status: {}", status); + } + + Ok(()) +} + +pub(crate) fn run_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let source_dir = helper_source_dir(); + let build_dir = build_dir.map(Path::to_path_buf).unwrap_or_else(|| { + flags + .build_dir + .as_ref() + .map(|dir| source_dir.join(dir)) + .unwrap_or_else(|| source_dir.clone()) + }); + let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for autotools_install")?; + let make_exec = resolve_make_exec(&flags.make_exec); + let install_dirs = resolve_make_dirs( + &build_dir, + &flags.make_install_dirs, + "build.flags.make_install_dirs", + )?; + let install_targets = phase_targets( + &flags.make_install_target, + &flags.make_install_targets, + Some("install"), + ); + + for install_dir in install_dirs { + let mut install_cmd = fakeroot::wrap_install_command(make_exec, Path::new(&destdir)); + install_cmd.current_dir(&install_dir); + if make_exec_supports_make_assignments(make_exec) + && !has_make_variable_override(&flags.make_install_vars, "DESTDIR") + { + install_cmd.arg(format!("DESTDIR={destdir}")); + } + add_make_variable_overrides_if_supported( + &mut install_cmd, + make_exec, + &flags.make_install_vars, + "install", + )?; + for install_target in &install_targets { + install_cmd.arg(install_target); + } + for arg in extra_args { + install_cmd.arg(context.expand_vars(arg)); + } + + let mut install_env = env_vars.to_vec(); + crate::builder::set_env_var(&mut install_env, "DESTDIR", destdir.clone()); + crate::builder::prepare_tool_command(&mut install_cmd, &install_env); + + let status = crate::interrupts::command_status(&mut install_cmd).with_context(|| { + format!( + "Failed to run helper {} {} in {}", + make_exec, + install_targets.join(" "), + install_dir.display() + ) + })?; + if !status.success() { + anyhow::bail!( + "{} {} failed with status: {} (dir: {})", + make_exec, + install_targets.join(" "), + status, + install_dir.display() + ); + } + } + + Ok(()) +} + fn num_cpus() -> usize { std::thread::available_parallelism() .map(|n| n.get()) @@ -544,6 +741,22 @@ fn resolve_configure_path(spec: &PackageSpec, actual_src: &Path) -> PathBuf { } } +fn helper_configure_path(context: &BuildHelperContext, actual_src: &Path) -> PathBuf { + let flags = context.build_flags(); + let configured = context.expand_vars(&flags.configure_file); + let trimmed = configured.trim(); + if trimmed.is_empty() { + return actual_src.join("configure"); + } + + let path = Path::new(trimmed); + if path.is_absolute() { + path.to_path_buf() + } else { + actual_src.join(path) + } +} + fn configure_help_text( configure_path: &Path, build_dir: &Path, @@ -823,6 +1036,12 @@ fn nonempty_trimmed(value: &str) -> Option<&str> { (!trimmed.is_empty()).then_some(trimmed) } +fn helper_source_dir() -> PathBuf { + std::env::var(crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV) + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))) +} + pub(crate) fn resolve_make_exec(configured: &str) -> &str { nonempty_trimmed(configured).unwrap_or("make") } diff --git a/src/builder/cmake.rs b/src/builder/cmake.rs index 6649447..4cd9fdf 100755 --- a/src/builder/cmake.rs +++ b/src/builder/cmake.rs @@ -1,5 +1,6 @@ //! CMake build system +use crate::builder::BuildHelperContext; use crate::builder::state::{BuildStep, StateTracker}; use crate::cross::CrossConfig; use crate::fakeroot; @@ -359,6 +360,139 @@ pub(crate) fn ensure_host_build( .with_context(|| format!("Failed to resolve host build dir: {}", build_dir.display())) } +pub(crate) fn run_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + build_dir: Option<&Path>, + cross: Option<&CrossConfig>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let source_dir = source_dir + .map(Path::to_path_buf) + .unwrap_or(std::env::current_dir().context("Failed to determine current directory")?); + let build_dir = build_dir.map(Path::to_path_buf).unwrap_or_else(|| { + flags + .build_dir + .as_ref() + .map(|dir| source_dir.join(dir)) + .unwrap_or_else(|| source_dir.join("build")) + }); + let prefix = effective_cmake_install_prefix(&flags); + + fs::create_dir_all(&build_dir) + .with_context(|| format!("Failed to create build directory: {}", build_dir.display()))?; + + let toolchain_file = if let Some(cc_cfg) = cross { + Some(cc_cfg.generate_cmake_toolchain(&build_dir)?) + } else { + None + }; + + let mut cmake_cmd = Command::new("cmake"); + cmake_cmd.current_dir(&build_dir); + cmake_cmd.arg("-S").arg(&source_dir); + cmake_cmd.arg("-B").arg(&build_dir); + cmake_cmd.arg(format!("-DCMAKE_INSTALL_PREFIX={prefix}")); + cmake_cmd.arg("-DCMAKE_BUILD_TYPE=Release"); + for arg in cmake_install_dir_args(&flags, prefix) { + cmake_cmd.arg(arg); + } + for arg in cmake_lib32_target_args(&flags, cross) { + cmake_cmd.arg(arg); + } + if let Some(toolchain_file) = &toolchain_file { + cmake_cmd.arg(format!( + "-DCMAKE_TOOLCHAIN_FILE={}", + toolchain_file.display() + )); + } + + let make_exec_override = flags.make_exec.trim(); + if !make_exec_override.is_empty() { + if !cmake_configure_flags_specify_generator(&flags.configure) + && let Some(generator) = cmake_generator_for_make_exec(make_exec_override) + { + cmake_cmd.arg("-G").arg(generator); + } + if !cmake_configure_flags_set_make_program(&flags.configure) { + cmake_cmd.arg(format!("-DCMAKE_MAKE_PROGRAM={make_exec_override}")); + } + } + + for flag in &flags.configure { + cmake_cmd.arg(expand_with_envs(&context.expand_vars(flag), env_vars)); + } + for arg in crate::builder::static_build_args_for(crate::package::BuildType::CMake, &flags)? { + cmake_cmd.arg(arg); + } + for arg in extra_args { + cmake_cmd.arg(expand_with_envs(&context.expand_vars(arg), env_vars)); + } + + crate::builder::prepare_tool_command(&mut cmake_cmd, &env_vars.to_vec()); + + let status = crate::interrupts::command_status(&mut cmake_cmd) + .context("Failed to run helper cmake configure")?; + if !status.success() { + anyhow::bail!("cmake configure failed"); + } + + Ok(()) +} + +pub(crate) fn run_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let source_dir = helper_source_dir(); + let build_dir = build_dir.map(Path::to_path_buf).unwrap_or_else(|| { + flags + .build_dir + .as_ref() + .map(|dir| source_dir.join(dir)) + .unwrap_or_else(|| source_dir.join("build")) + }); + let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for cmake_install")?; + let install_targets = phase_targets(&flags.make_install_target, &flags.make_install_targets); + + let mut install_cmd = fakeroot::wrap_install_command("cmake", Path::new(&destdir)); + if install_targets.is_empty() { + install_cmd.arg("--install").arg(&build_dir); + } else { + install_cmd.arg("--build").arg(&build_dir); + install_cmd.arg("--target"); + for target in &install_targets { + install_cmd.arg(target); + } + } + for arg in extra_args { + install_cmd.arg(context.expand_vars(arg)); + } + + let mut install_env = env_vars.to_vec(); + crate::builder::set_env_var(&mut install_env, "DESTDIR", destdir); + crate::builder::prepare_tool_command(&mut install_cmd, &install_env); + + let status = crate::interrupts::command_status(&mut install_cmd) + .context("Failed to run helper cmake install")?; + if !status.success() { + if install_targets.is_empty() { + anyhow::bail!("cmake install failed"); + } + anyhow::bail!( + "cmake install target(s) '{}' failed", + install_targets.join(" ") + ); + } + + Ok(()) +} + /// Expand environment variables in a string (e.g., $DEPOT_SYSROOT) fn expand_env_vars(input: &str) -> String { let mut result = input.to_string(); @@ -580,6 +714,12 @@ fn num_cpus() -> usize { .unwrap_or(1) } +fn helper_source_dir() -> PathBuf { + std::env::var(crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV) + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))) +} + /// Resolve `source_subdir` with multiple fallbacks: /// - empty -> use `src_dir` /// - absolute path -> use if exists diff --git a/src/builder/custom.rs b/src/builder/custom.rs index 3e4c270..e1880e2 100755 --- a/src/builder/custom.rs +++ b/src/builder/custom.rs @@ -51,6 +51,8 @@ pub fn build( let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags); let shell_helpers = crate::shell_helpers::ShellHelpers::new(&install_destdir)?; shell_helpers.apply_to_env_vars(&mut env_vars); + crate::builder::apply_build_helper_context_env(&mut env_vars, spec)?; + crate::builder::apply_build_helper_dirs_env(&mut env_vars, Some(src_dir), Some(&build_dir)); // For custom builds, look for a build.sh script in the source directory let build_script = src_dir.join("build.sh"); @@ -280,19 +282,27 @@ fn build_function_mode_install_command( "DEPOT_OUTPUT_NAME='{q_name}'; DEPOT_OUTPUT_DESTDIR='{q_dest}'; DESTDIR=\"$DEPOT_OUTPUT_DESTDIR\"; export DEPOT_OUTPUT_NAME DEPOT_OUTPUT_DESTDIR DESTDIR\n" )); wrapper.push_str("depot_output_installed=0\n"); - wrapper.push_str(&format!( - "if depot_has_function depot_install_{fn_suffix}; then depot_install_{fn_suffix}; depot_output_installed=1;\n" - )); - wrapper.push_str(&format!( - "elif depot_has_function install_{fn_suffix}; then install_{fn_suffix}; depot_output_installed=1;\n" - )); if out.name == *primary { - wrapper - .push_str("elif depot_has_function depot_install; then depot_install; depot_output_installed=1;\n"); + wrapper.push_str( + "if depot_has_function depot_install; then depot_install; depot_output_installed=1;\n", + ); wrapper.push_str( "elif depot_has_function install; then install; depot_output_installed=1;\n", ); + wrapper.push_str(&format!( + "elif depot_has_function depot_install_{fn_suffix}; then depot_install_{fn_suffix}; depot_output_installed=1;\n" + )); + wrapper.push_str(&format!( + "elif depot_has_function install_{fn_suffix}; then install_{fn_suffix}; depot_output_installed=1;\n" + )); wrapper.push_str("elif [ \"$depot_build_ran\" = 1 ]; then depot_output_installed=1;\n"); + } else { + wrapper.push_str(&format!( + "if depot_has_function depot_install_{fn_suffix}; then depot_install_{fn_suffix}; depot_output_installed=1;\n" + )); + wrapper.push_str(&format!( + "elif depot_has_function install_{fn_suffix}; then install_{fn_suffix}; depot_output_installed=1;\n" + )); } wrapper.push_str("fi\n"); wrapper.push_str("if [ \"$depot_output_installed\" != 1 ]; then\n"); diff --git a/src/builder/meson.rs b/src/builder/meson.rs index aeafa73..0a217fd 100755 --- a/src/builder/meson.rs +++ b/src/builder/meson.rs @@ -1,5 +1,6 @@ //! Meson build system +use crate::builder::BuildHelperContext; use crate::builder::state::{BuildStep, StateTracker}; use crate::cross::CrossConfig; use crate::fakeroot; @@ -267,12 +268,106 @@ pub(crate) fn ensure_host_build( .with_context(|| format!("Failed to resolve host build dir: {}", build_dir.display())) } +pub(crate) fn run_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + build_dir: Option<&Path>, + cross: Option<&CrossConfig>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let source_dir = source_dir + .map(Path::to_path_buf) + .unwrap_or(std::env::current_dir().context("Failed to determine current directory")?); + let build_dir = build_dir + .map(Path::to_path_buf) + .unwrap_or_else(|| resolve_build_dir(&source_dir, &flags)); + + fs::create_dir_all(&build_dir) + .with_context(|| format!("Failed to create build directory: {}", build_dir.display()))?; + + let mut helper_env = env_vars.to_vec(); + configure_pkg_config_env(&mut helper_env, &flags, cross); + + let cross_file = if let Some(cc_cfg) = cross { + Some(cc_cfg.generate_meson_cross_file(&build_dir)?) + } else if flags.lib32_variant { + Some(generate_lib32_meson_cross_file(&flags, &build_dir)?) + } else { + None + }; + + let mut meson_cmd = Command::new("meson"); + meson_cmd.current_dir(&source_dir); + meson_cmd.arg("setup"); + meson_cmd.arg(&build_dir); + + for arg in meson_setup_args(&flags, cross_file.as_deref(), &helper_env) { + meson_cmd.arg(arg); + } + for arg in crate::builder::static_build_args_for(crate::package::BuildType::Meson, &flags)? { + meson_cmd.arg(arg); + } + for arg in extra_args { + meson_cmd.arg(context.expand_vars(arg)); + } + + crate::builder::prepare_tool_command(&mut meson_cmd, &helper_env); + + let status = crate::interrupts::command_status(&mut meson_cmd) + .context("Failed to run helper meson setup")?; + if !status.success() { + bail!("meson setup failed"); + } + + Ok(()) +} + +pub(crate) fn run_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let build_dir = build_dir + .map(Path::to_path_buf) + .unwrap_or_else(|| resolve_build_dir(&helper_source_dir(), &flags)); + let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for meson_install")?; + + let mut install_cmd = fakeroot::wrap_install_command("meson", Path::new(&destdir)); + install_cmd.arg("install"); + install_cmd.arg("-C").arg(&build_dir); + for arg in extra_args { + install_cmd.arg(context.expand_vars(arg)); + } + + let mut install_env = env_vars.to_vec(); + crate::builder::set_env_var(&mut install_env, "DESTDIR", destdir); + crate::builder::prepare_tool_command(&mut install_cmd, &install_env); + + let status = crate::interrupts::command_status(&mut install_cmd) + .context("Failed to run helper meson install")?; + if !status.success() { + bail!("meson install failed"); + } + + Ok(()) +} + fn num_cpus() -> usize { std::thread::available_parallelism() .map(|n| n.get()) .unwrap_or(1) } +fn helper_source_dir() -> PathBuf { + std::env::var(crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV) + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))) +} + fn resolve_build_dir(actual_src: &Path, flags: &crate::package::BuildFlags) -> PathBuf { if let Some(dir) = flags .build_dir diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 3537ab0..3330f34 100755 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -22,6 +22,9 @@ use walkdir::WalkDir; pub type EnvVars = Vec<(String, String)>; pub(crate) const DEPOT_BUILD_HOST_DIR_ENV: &str = "DEPOT_BUILD_HOST_DIR"; +pub(crate) const DEPOT_BUILD_HELPER_CONTEXT_ENV: &str = "DEPOT_BUILD_HELPER_CONTEXT"; +pub(crate) const DEPOT_BUILD_HELPER_SOURCE_DIR_ENV: &str = "DEPOT_BUILD_HELPER_SOURCE_DIR"; +pub(crate) const DEPOT_BUILD_HELPER_BUILD_DIR_ENV: &str = "DEPOT_BUILD_HELPER_BUILD_DIR"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TargetBuildKind { @@ -45,6 +48,154 @@ pub(crate) struct InstallDirs { pub infodir: String, } +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct BuildHelperContext { + pub package_name: String, + pub package_version: String, + pub spec_dir: PathBuf, + pub flags: BuildFlags, + pub lib32_variant: bool, + pub host_build_dir: Option, +} + +impl BuildHelperContext { + pub(crate) fn from_spec(spec: &PackageSpec) -> Self { + Self { + package_name: spec.package.name.clone(), + package_version: spec.package.version.clone(), + spec_dir: spec.spec_dir.clone(), + flags: spec.build.flags.clone(), + lib32_variant: spec.build.flags.lib32_variant, + host_build_dir: spec.build.flags.host_build_dir.clone(), + } + } + + pub(crate) fn expand_vars(&self, input: &str) -> String { + let specdir = self.spec_dir.to_string_lossy(); + input + .replace("$name", &self.package_name) + .replace("$version", &self.package_version) + .replace("$specdir", &specdir) + .replace("$DEPOT_SPECDIR", &specdir) + } + + pub(crate) fn build_flags(&self) -> BuildFlags { + let mut flags = self.flags.clone(); + flags.lib32_variant = self.lib32_variant; + flags.host_build_dir = self.host_build_dir.clone(); + flags + } +} + +pub(crate) fn apply_build_helper_context_env( + env_vars: &mut EnvVars, + spec: &PackageSpec, +) -> Result<()> { + let encoded = toml::to_string(&BuildHelperContext::from_spec(spec)) + .context("Failed to serialize build helper context")?; + set_env_var(env_vars, DEPOT_BUILD_HELPER_CONTEXT_ENV, encoded); + Ok(()) +} + +pub(crate) fn apply_build_helper_dirs_env( + env_vars: &mut EnvVars, + source_dir: Option<&Path>, + build_dir: Option<&Path>, +) { + if let Some(source_dir) = source_dir { + set_env_var( + env_vars, + DEPOT_BUILD_HELPER_SOURCE_DIR_ENV, + source_dir.to_string_lossy().into_owned(), + ); + } + if let Some(build_dir) = build_dir { + set_env_var( + env_vars, + DEPOT_BUILD_HELPER_BUILD_DIR_ENV, + build_dir.to_string_lossy().into_owned(), + ); + } +} + +pub(crate) fn run_autotools_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + build_dir: Option<&Path>, + cross: Option<&CrossConfig>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + autotools::run_helper_configure(context, source_dir, build_dir, cross, env_vars, extra_args) +} + +pub(crate) fn run_autotools_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + autotools::run_helper_install(context, build_dir, env_vars, extra_args) +} + +pub(crate) fn run_cmake_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + build_dir: Option<&Path>, + cross: Option<&CrossConfig>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + cmake::run_helper_configure(context, source_dir, build_dir, cross, env_vars, extra_args) +} + +pub(crate) fn run_cmake_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + cmake::run_helper_install(context, build_dir, env_vars, extra_args) +} + +pub(crate) fn run_meson_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + build_dir: Option<&Path>, + cross: Option<&CrossConfig>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + meson::run_helper_configure(context, source_dir, build_dir, cross, env_vars, extra_args) +} + +pub(crate) fn run_meson_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + meson::run_helper_install(context, build_dir, env_vars, extra_args) +} + +pub(crate) fn run_perl_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + perl::run_helper_configure(context, source_dir, env_vars, extra_args) +} + +pub(crate) fn run_perl_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &EnvVars, + extra_args: &[String], +) -> Result<()> { + perl::run_helper_install(context, build_dir, env_vars, extra_args) +} + pub fn set_env_var(env_vars: &mut EnvVars, key: &str, value: impl Into) { let value = value.into(); if let Some((_, existing)) = env_vars.iter_mut().find(|(k, _)| k == key) { @@ -1353,6 +1504,17 @@ mod tests { assert_eq!(lib32_dirs.libexecdir, "/usr/lib32"); } + #[test] + fn test_build_helper_context_restores_runtime_build_flags() { + let mut spec = mk_spec(Vec::new(), Vec::new()); + spec.build.flags.lib32_variant = true; + spec.build.flags.host_build_dir = Some("/tmp/build-host".into()); + + let restored = BuildHelperContext::from_spec(&spec).build_flags(); + assert!(restored.lib32_variant); + assert_eq!(restored.host_build_dir.as_deref(), Some("/tmp/build-host")); + } + #[test] fn test_install_dirs_respect_explicit_overrides_and_derived_defaults() { let dirs = install_dirs(&BuildFlags { diff --git a/src/builder/perl.rs b/src/builder/perl.rs index 7f2490d..4ce797c 100644 --- a/src/builder/perl.rs +++ b/src/builder/perl.rs @@ -1,5 +1,6 @@ //! Perl MakeMaker build system (`perl Makefile.PL && make && make test && make install`) +use crate::builder::BuildHelperContext; use crate::builder::autotools; use crate::builder::state::{BuildStep, StateTracker}; use crate::cross::CrossConfig; @@ -261,6 +262,124 @@ pub fn build( Ok(()) } +pub(crate) fn run_helper_configure( + context: &BuildHelperContext, + source_dir: Option<&Path>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let source_dir = source_dir + .map(Path::to_path_buf) + .unwrap_or(std::env::current_dir().context("Failed to determine current directory")?); + let configure_script = helper_configure_script(context, &source_dir); + let mut helper_env = env_vars.to_vec(); + crate::builder::set_env_var(&mut helper_env, "PERL_MM_USE_DEFAULT", "1"); + if !flags.rootfs.is_empty() && flags.rootfs != "/" { + crate::builder::set_env_var( + &mut helper_env, + "PKG_CONFIG_SYSROOT_DIR", + flags.rootfs.clone(), + ); + } + + let mut configure_cmd = + Command::new(resolved_command_path("perl").unwrap_or_else(|| PathBuf::from("perl"))); + configure_cmd.current_dir(&source_dir); + 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(context.expand_vars(arg)); + } + for arg in crate::builder::static_build_args_for(crate::package::BuildType::Perl, &flags)? { + configure_cmd.arg(arg); + } + for arg in extra_args { + configure_cmd.arg(context.expand_vars(arg)); + } + crate::builder::prepare_tool_command(&mut configure_cmd, &helper_env); + + let status = command_status_with_sh_fallback(&mut configure_cmd) + .context("Failed to run helper perl configure")?; + if !status.success() { + anyhow::bail!("perl Makefile.PL failed with status: {}", status); + } + + Ok(()) +} + +pub(crate) fn run_helper_install( + context: &BuildHelperContext, + build_dir: Option<&Path>, + env_vars: &[(String, String)], + extra_args: &[String], +) -> Result<()> { + let flags = context.build_flags(); + let build_dir = build_dir + .map(Path::to_path_buf) + .unwrap_or_else(helper_source_dir); + let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for perl_install")?; + let make_exec = autotools::resolve_make_exec(&flags.make_exec); + let install_dirs = autotools::resolve_make_dirs( + &build_dir, + &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 { + let mut install_cmd = fakeroot::wrap_install_command(make_exec, Path::new(&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}")); + } + 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); + } + for arg in extra_args { + install_cmd.arg(context.expand_vars(arg)); + } + + let mut install_env = env_vars.to_vec(); + crate::builder::set_env_var(&mut install_env, "DESTDIR", destdir.clone()); + crate::builder::prepare_tool_command(&mut install_cmd, &install_env); + + let status = command_status_with_sh_fallback(&mut install_cmd).with_context(|| { + format!( + "Failed to run helper {} {} in {}", + make_exec, + install_targets.join(" "), + install_dir.display() + ) + })?; + if !status.success() { + anyhow::bail!( + "{} {} failed with status: {} (dir: {})", + make_exec, + install_targets.join(" "), + status, + install_dir.display() + ); + } + } + + 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(); @@ -276,6 +395,22 @@ fn resolve_perl_configure_script(spec: &PackageSpec, actual_src: &Path) -> PathB } } +fn helper_configure_script(context: &BuildHelperContext, actual_src: &Path) -> PathBuf { + let flags = context.build_flags(); + let configured = context.expand_vars(&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 command_status_with_sh_fallback(cmd: &mut Command) -> std::io::Result { match crate::interrupts::command_status(cmd) { Ok(status) => Ok(status), @@ -346,6 +481,12 @@ fn has_assignment_prefix(args: &[String], name: &str) -> bool { }) } +fn helper_source_dir() -> PathBuf { + std::env::var(crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV) + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/cli.rs b/src/cli.rs index b489586..e10eafd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -316,6 +316,46 @@ pub enum InternalCommands { }, #[command(hide = true)] Clone { repo: String, dest: Option }, + #[command(hide = true)] + AutotoolsConfigure { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + AutotoolsInstall { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + CmakeConfigure { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + CmakeInstall { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + MesonConfigure { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + MesonInstall { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + PerlConfigure { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, + #[command(hide = true)] + PerlInstall { + #[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)] + args: Vec, + }, } #[derive(Debug, Clone, Args)] diff --git a/src/commands/misc/internal.rs b/src/commands/misc/internal.rs index b4e27a3..106848d 100644 --- a/src/commands/misc/internal.rs +++ b/src/commands/misc/internal.rs @@ -1,4 +1,6 @@ use super::*; +use crate::builder::BuildHelperContext; +use std::path::{Path, PathBuf}; fn current_process_env_vars() -> Vec<(String, String)> { const ALLOWED_ENV_VARS: &[&str] = &[ @@ -13,6 +15,10 @@ fn current_process_env_vars() -> Vec<(String, String)> { "CFLAGS", "CXX", "CXXFLAGS", + crate::builder::DEPOT_BUILD_HELPER_BUILD_DIR_ENV, + crate::builder::DEPOT_BUILD_HELPER_CONTEXT_ENV, + crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV, + crate::builder::DEPOT_BUILD_HOST_DIR_ENV, "DEPOT_ROOTFS", "DEPOT_SPECDIR", "DESTDIR", @@ -41,6 +47,42 @@ fn current_process_env_vars() -> Vec<(String, String)> { .collect() } +fn current_build_helper_context() -> Result { + let raw = std::env::var(crate::builder::DEPOT_BUILD_HELPER_CONTEXT_ENV).with_context(|| { + format!( + "{} must be set for internal build helpers", + crate::builder::DEPOT_BUILD_HELPER_CONTEXT_ENV + ) + })?; + toml::from_str(&raw).context("Failed to parse build helper context") +} + +fn current_helper_source_dir() -> Option { + std::env::var(crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV) + .ok() + .map(PathBuf::from) +} + +fn current_helper_build_dir() -> Option { + std::env::var(crate::builder::DEPOT_BUILD_HELPER_BUILD_DIR_ENV) + .ok() + .map(PathBuf::from) +} + +fn current_cross_config() -> Result> { + let Some(prefix) = std::env::var("CROSS_PREFIX") + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + else { + return Ok(None); + }; + + crate::cross::CrossConfig::from_prefix(&prefix) + .map(Some) + .with_context(|| format!("Failed to resolve cross-compilation tools for {}", prefix)) +} + pub(crate) fn run_internal_command(command: InternalCommands) -> Result<()> { match command { InternalCommands::PythonBuild { @@ -101,5 +143,219 @@ pub(crate) fn run_internal_command(command: InternalCommands) -> Result<()> { &[], ) } + InternalCommands::AutotoolsConfigure { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + let cross = current_cross_config()?; + crate::builder::run_autotools_helper_configure( + &context, + current_helper_source_dir().as_deref(), + current_helper_build_dir().as_deref(), + cross.as_ref(), + &env_vars, + &args, + ) + } + InternalCommands::AutotoolsInstall { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + crate::builder::run_autotools_helper_install( + &context, + current_helper_build_dir().as_deref(), + &env_vars, + &args, + ) + } + InternalCommands::CmakeConfigure { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + let cross = current_cross_config()?; + crate::builder::run_cmake_helper_configure( + &context, + current_helper_source_dir().as_deref(), + current_helper_build_dir().as_deref(), + cross.as_ref(), + &env_vars, + &args, + ) + } + InternalCommands::CmakeInstall { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + crate::builder::run_cmake_helper_install( + &context, + current_helper_build_dir().as_deref(), + &env_vars, + &args, + ) + } + InternalCommands::MesonConfigure { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + let cross = current_cross_config()?; + crate::builder::run_meson_helper_configure( + &context, + current_helper_source_dir().as_deref(), + current_helper_build_dir().as_deref(), + cross.as_ref(), + &env_vars, + &args, + ) + } + InternalCommands::MesonInstall { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + crate::builder::run_meson_helper_install( + &context, + current_helper_build_dir().as_deref(), + &env_vars, + &args, + ) + } + InternalCommands::PerlConfigure { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + crate::builder::run_perl_helper_configure( + &context, + current_helper_source_dir().as_deref(), + &env_vars, + &args, + ) + } + InternalCommands::PerlInstall { args } => { + let env_vars = current_process_env_vars(); + let context = current_build_helper_context()?; + crate::builder::run_perl_helper_install( + &context, + current_helper_build_dir().as_deref(), + &env_vars, + &args, + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::package::BuildFlags; + use crate::test_support::TestEnv; + use std::fs; + use std::os::unix::fs::PermissionsExt; + use tempfile::tempdir; + + fn write_executable(path: &Path, contents: &str) -> Result<()> { + fs::write(path, contents).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(()) + } + + fn helper_context(flags: BuildFlags, spec_dir: &Path) -> BuildHelperContext { + BuildHelperContext { + package_name: "demo".into(), + package_version: "1.0".into(), + spec_dir: spec_dir.to_path_buf(), + flags, + lib32_variant: false, + host_build_dir: None, + } + } + + #[test] + fn meson_configure_uses_build_helper_context_defaults() -> Result<()> { + let source = tempdir()?; + let tools = tempdir()?; + let log = tools.path().join("meson.log"); + let flags = BuildFlags { + prefix: "/opt/demo".into(), + build_dir: Some("builddir".into()), + ..BuildFlags::default() + }; + + write_executable( + &tools.path().join("meson"), + &format!("#!/bin/sh\nprintf '%s\\n' \"$@\" > '{}'\n", log.display()), + )?; + + let mut env = TestEnv::new(); + env.set_var("PATH", tools.path()); + env.set_var( + crate::builder::DEPOT_BUILD_HELPER_CONTEXT_ENV, + toml::to_string(&helper_context(flags, source.path()))?, + ); + env.set_var( + crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV, + source.path(), + ); + + run_internal_command(InternalCommands::MesonConfigure { + args: vec!["-Dfeature=enabled".into()], + })?; + + let output = fs::read_to_string(&log)?; + assert!(output.contains("setup")); + assert!(output.contains("--prefix=/opt/demo")); + assert!(output.contains("--buildtype=release")); + assert!(output.contains("-Dfeature=enabled")); + assert!(output.contains(source.path().join("builddir").to_string_lossy().as_ref())); + Ok(()) + } + + #[test] + fn cmake_install_uses_fakeroot_and_destdir() -> Result<()> { + let source = tempdir()?; + let build_dir = source.path().join("build"); + let tools = tempdir()?; + let fakeroot_log = tools.path().join("fakeroot.log"); + let cmake_log = tools.path().join("cmake.log"); + let destdir = source.path().join("dest"); + fs::create_dir_all(&build_dir)?; + fs::create_dir_all(&destdir)?; + + write_executable( + &tools.path().join("fakeroot"), + &format!( + "#!/bin/sh\nprintf '%s\\n' \"$@\" > '{}'\n[ \"$1\" = \"--\" ]\nshift\nexec \"$@\"\n", + fakeroot_log.display() + ), + )?; + write_executable( + &tools.path().join("cmake"), + &format!( + "#!/bin/sh\n{{ printf 'DESTDIR=%s\\n' \"$DESTDIR\"; printf '%s\\n' \"$@\"; }} > '{}'\n", + cmake_log.display() + ), + )?; + + let mut env = TestEnv::new(); + env.set_var("PATH", tools.path()); + env.set_var("DESTDIR", &destdir); + env.set_var( + crate::builder::DEPOT_BUILD_HELPER_CONTEXT_ENV, + toml::to_string(&helper_context(BuildFlags::default(), source.path()))?, + ); + env.set_var( + crate::builder::DEPOT_BUILD_HELPER_SOURCE_DIR_ENV, + source.path(), + ); + env.set_var(crate::builder::DEPOT_BUILD_HELPER_BUILD_DIR_ENV, &build_dir); + + run_internal_command(InternalCommands::CmakeInstall { + args: vec!["--component".into(), "runtime".into()], + })?; + + let fakeroot_output = fs::read_to_string(&fakeroot_log)?; + assert!(fakeroot_output.contains("--")); + assert!(fakeroot_output.contains("cmake")); + + let cmake_output = fs::read_to_string(&cmake_log)?; + assert!(cmake_output.contains(&format!("DESTDIR={}", destdir.display()))); + assert!(cmake_output.contains("--install")); + assert!(cmake_output.contains(build_dir.to_string_lossy().as_ref())); + assert!(cmake_output.contains("--component")); + assert!(cmake_output.contains("runtime")); + Ok(()) } } diff --git a/src/commands/tests.rs b/src/commands/tests.rs index f2d9b29..e5904a5 100644 --- a/src/commands/tests.rs +++ b/src/commands/tests.rs @@ -6,10 +6,16 @@ use crate::cli::{ use crate::test_support::TestEnv; use git2::{Oid, Repository}; use std::path::Path; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard}; static ASSUME_YES_TEST_LOCK: Mutex<()> = Mutex::new(()); +fn assume_yes_test_lock() -> MutexGuard<'static, ()> { + ASSUME_YES_TEST_LOCK + .lock() + .unwrap_or_else(|err| err.into_inner()) +} + fn rootfs_args(rootfs: impl Into) -> RootfsArgs { RootfsArgs { rootfs: rootfs.into(), @@ -189,6 +195,16 @@ fn register_installed_test_package( Ok(()) } +fn register_required_development_package_if_configured( + config: &config::Config, + rootfs: &Path, +) -> Result<()> { + if let Some(package_name) = builder::requested_development_package() { + register_installed_test_package(config, rootfs, &package_name, "1.0.0")?; + } + Ok(()) +} + fn write_test_repo_spec(path: &Path, name: &str, version: &str) -> Result<()> { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; @@ -2216,7 +2232,7 @@ file = "missing.patch" #[test] fn build_command_checks_manual_sources_before_dependency_resolution() -> Result<()> { - let _guard = ASSUME_YES_TEST_LOCK.lock().expect("assume-yes test lock"); + let _guard = assume_yes_test_lock(); let temp = tempfile::tempdir().context("Failed to create temp dir")?; let rootfs = temp.path().join("rootfs"); let spec_dir = temp.path().join("packages").join("demo"); @@ -2534,7 +2550,7 @@ fn build_type_runs_automatic_tests_matches_builder_behavior() { #[test] fn requested_test_deps_prompt_can_disable_tests() -> Result<()> { - let _guard = ASSUME_YES_TEST_LOCK.lock().expect("assume-yes test lock"); + let _guard = assume_yes_test_lock(); let mut spec = test_package_spec(package::BuildType::Meson, None, &[]); spec.dependencies.test = vec!["pytest".into()]; @@ -2553,7 +2569,7 @@ fn requested_test_deps_prompt_can_disable_tests() -> Result<()> { #[test] fn requested_test_deps_prompt_is_ignored_for_non_automatic_test_builders() -> Result<()> { - let _guard = ASSUME_YES_TEST_LOCK.lock().expect("assume-yes test lock"); + let _guard = assume_yes_test_lock(); let mut spec = test_package_spec(package::BuildType::Custom, None, &[]); spec.dependencies.test = vec!["pytest".into()]; @@ -2572,7 +2588,7 @@ fn requested_test_deps_prompt_is_ignored_for_non_automatic_test_builders() -> Re #[test] fn requested_test_deps_prompt_is_ignored_for_multilib_builds() -> Result<()> { - let _guard = ASSUME_YES_TEST_LOCK.lock().expect("assume-yes test lock"); + let _guard = assume_yes_test_lock(); let mut spec = test_package_spec(package::BuildType::Meson, None, &[]); spec.build.flags.build_32 = true; spec.dependencies.test = vec!["pytest".into()]; @@ -2777,7 +2793,7 @@ fn live_rootfs_child_install_batches_group_consecutive_binary_steps() -> Result< #[test] fn build_command_requires_install_deps_flag_for_missing_dependencies() -> Result<()> { - let _guard = ASSUME_YES_TEST_LOCK.lock().expect("assume-yes test lock"); + let _guard = assume_yes_test_lock(); let temp = tempfile::tempdir().context("Failed to create temp dir")?; let rootfs = temp.path().join("rootfs"); let repo_root = temp.path().join("packages"); @@ -2841,6 +2857,9 @@ optional = [] ) .with_context(|| format!("Failed to write {}", dep_spec.display()))?; + let config = config::Config::for_rootfs(&rootfs); + register_required_development_package_if_configured(&config, &rootfs)?; + let result = run(Cli { command: Commands::Build(BuildArgs { rootfs_args: rootfs_args(rootfs.clone()), diff --git a/src/shell_helpers.rs b/src/shell_helpers.rs index c36c3e9..06c6ef0 100644 --- a/src/shell_helpers.rs +++ b/src/shell_helpers.rs @@ -15,6 +15,14 @@ const DEPOT_SUBDESTDIR_HELPER_ENV: &str = "DEPOT_SUBDESTDIR_HELPER"; const DEPOT_PYTHON_BUILD_HELPER_ENV: &str = "DEPOT_PYTHON_BUILD_HELPER"; const DEPOT_PYTHON_INSTALL_HELPER_ENV: &str = "DEPOT_PYTHON_INSTALL_HELPER"; const DEPOT_CLONE_HELPER_ENV: &str = "DEPOT_CLONE_HELPER"; +const DEPOT_AUTOTOOLS_CONFIGURE_HELPER_ENV: &str = "DEPOT_AUTOTOOLS_CONFIGURE_HELPER"; +const DEPOT_AUTOTOOLS_INSTALL_HELPER_ENV: &str = "DEPOT_AUTOTOOLS_INSTALL_HELPER"; +const DEPOT_CMAKE_CONFIGURE_HELPER_ENV: &str = "DEPOT_CMAKE_CONFIGURE_HELPER"; +const DEPOT_CMAKE_INSTALL_HELPER_ENV: &str = "DEPOT_CMAKE_INSTALL_HELPER"; +const DEPOT_MESON_CONFIGURE_HELPER_ENV: &str = "DEPOT_MESON_CONFIGURE_HELPER"; +const DEPOT_MESON_INSTALL_HELPER_ENV: &str = "DEPOT_MESON_INSTALL_HELPER"; +const DEPOT_PERL_CONFIGURE_HELPER_ENV: &str = "DEPOT_PERL_CONFIGURE_HELPER"; +const DEPOT_PERL_INSTALL_HELPER_ENV: &str = "DEPOT_PERL_INSTALL_HELPER"; const DEPOT_EXECUTABLE_ENV: &str = "DEPOT_EXECUTABLE"; /// Ephemeral helper command directory to prepend to PATH while running scripts. @@ -28,6 +36,14 @@ pub struct ShellHelpers { python_build_path: PathBuf, python_install_path: PathBuf, clone_path: PathBuf, + autotools_configure_path: PathBuf, + autotools_install_path: PathBuf, + cmake_configure_path: PathBuf, + cmake_install_path: PathBuf, + meson_configure_path: PathBuf, + meson_install_path: PathBuf, + perl_configure_path: PathBuf, + perl_install_path: PathBuf, } impl ShellHelpers { @@ -49,99 +65,43 @@ impl ShellHelpers { .context("Failed to locate depot executable for shell helpers")?; let haul_path = bin_dir.join("haul"); - fs::write(&haul_path, HAUL_SCRIPT).with_context(|| { - format!( - "Failed to write shell helper command: {}", - haul_path.display() - ) - })?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&haul_path) - .with_context(|| format!("Failed to stat helper: {}", haul_path.display()))? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&haul_path, perms) - .with_context(|| format!("Failed to chmod helper: {}", haul_path.display()))?; - } + write_helper_script(&haul_path, HAUL_SCRIPT)?; let subdestdir_path = bin_dir.join("subdestdir"); - fs::write(&subdestdir_path, SUBDESTDIR_SCRIPT).with_context(|| { - format!( - "Failed to write shell helper command: {}", - subdestdir_path.display() - ) - })?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&subdestdir_path) - .with_context(|| format!("Failed to stat helper: {}", subdestdir_path.display()))? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&subdestdir_path, perms).with_context(|| { - format!("Failed to chmod helper: {}", subdestdir_path.display()) - })?; - } + write_helper_script(&subdestdir_path, SUBDESTDIR_SCRIPT)?; let python_build_path = bin_dir.join("python_build"); - fs::write(&python_build_path, PYTHON_BUILD_SCRIPT).with_context(|| { - format!( - "Failed to write shell helper command: {}", - python_build_path.display() - ) - })?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&python_build_path) - .with_context(|| format!("Failed to stat helper: {}", python_build_path.display()))? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&python_build_path, perms).with_context(|| { - format!("Failed to chmod helper: {}", python_build_path.display()) - })?; - } + write_helper_script(&python_build_path, PYTHON_BUILD_SCRIPT)?; let python_install_path = bin_dir.join("python_install"); - fs::write(&python_install_path, PYTHON_INSTALL_SCRIPT).with_context(|| { - format!( - "Failed to write shell helper command: {}", - python_install_path.display() - ) - })?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&python_install_path) - .with_context(|| { - format!("Failed to stat helper: {}", python_install_path.display()) - })? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&python_install_path, perms).with_context(|| { - format!("Failed to chmod helper: {}", python_install_path.display()) - })?; - } + write_helper_script(&python_install_path, PYTHON_INSTALL_SCRIPT)?; let clone_path = bin_dir.join("depot_clone"); - fs::write(&clone_path, CLONE_SCRIPT).with_context(|| { - format!( - "Failed to write shell helper command: {}", - clone_path.display() - ) - })?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&clone_path) - .with_context(|| format!("Failed to stat helper: {}", clone_path.display()))? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&clone_path, perms) - .with_context(|| format!("Failed to chmod helper: {}", clone_path.display()))?; - } + write_helper_script(&clone_path, CLONE_SCRIPT)?; + + let autotools_configure_path = bin_dir.join("autotools_configure"); + write_helper_script(&autotools_configure_path, AUTOTOOLS_CONFIGURE_SCRIPT)?; + + let autotools_install_path = bin_dir.join("autotools_install"); + write_helper_script(&autotools_install_path, AUTOTOOLS_INSTALL_SCRIPT)?; + + let cmake_configure_path = bin_dir.join("cmake_configure"); + write_helper_script(&cmake_configure_path, CMAKE_CONFIGURE_SCRIPT)?; + + let cmake_install_path = bin_dir.join("cmake_install"); + write_helper_script(&cmake_install_path, CMAKE_INSTALL_SCRIPT)?; + + let meson_configure_path = bin_dir.join("meson_configure"); + write_helper_script(&meson_configure_path, MESON_CONFIGURE_SCRIPT)?; + + let meson_install_path = bin_dir.join("meson_install"); + write_helper_script(&meson_install_path, MESON_INSTALL_SCRIPT)?; + + let perl_configure_path = bin_dir.join("perl_configure"); + write_helper_script(&perl_configure_path, PERL_CONFIGURE_SCRIPT)?; + + let perl_install_path = bin_dir.join("perl_install"); + write_helper_script(&perl_install_path, PERL_INSTALL_SCRIPT)?; let path_value = crate::runtime_env::prepend_helper_to_safe_path(&bin_dir); @@ -155,6 +115,14 @@ impl ShellHelpers { python_build_path, python_install_path, clone_path, + autotools_configure_path, + autotools_install_path, + cmake_configure_path, + cmake_install_path, + meson_configure_path, + meson_install_path, + perl_configure_path, + perl_install_path, }) } @@ -192,6 +160,46 @@ impl ShellHelpers { DEPOT_CLONE_HELPER_ENV, self.clone_path.to_string_lossy().into_owned(), ); + set_env_var( + env_vars, + DEPOT_AUTOTOOLS_CONFIGURE_HELPER_ENV, + self.autotools_configure_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_AUTOTOOLS_INSTALL_HELPER_ENV, + self.autotools_install_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_CMAKE_CONFIGURE_HELPER_ENV, + self.cmake_configure_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_CMAKE_INSTALL_HELPER_ENV, + self.cmake_install_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_MESON_CONFIGURE_HELPER_ENV, + self.meson_configure_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_MESON_INSTALL_HELPER_ENV, + self.meson_install_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_PERL_CONFIGURE_HELPER_ENV, + self.perl_configure_path.to_string_lossy().into_owned(), + ); + set_env_var( + env_vars, + DEPOT_PERL_INSTALL_HELPER_ENV, + self.perl_install_path.to_string_lossy().into_owned(), + ); set_env_var( env_vars, DEPOT_EXECUTABLE_ENV, @@ -204,10 +212,26 @@ impl ShellHelpers { /// through `/bin/sh`, avoiding direct execution from mounts that may be `noexec`. pub fn wrap_shell_command(command: &str) -> String { format!( - "haul() {{ /bin/sh \"${{{DEPOT_HAUL_HELPER_ENV}:?}}\" \"$@\"; }}\nsubdestdir() {{ /bin/sh \"${{{DEPOT_SUBDESTDIR_HELPER_ENV}:?}}\" \"$@\"; }}\npython_build() {{ /bin/sh \"${{{DEPOT_PYTHON_BUILD_HELPER_ENV}:?}}\" \"$@\"; }}\npython_install() {{ /bin/sh \"${{{DEPOT_PYTHON_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\ndepot_clone() {{ /bin/sh \"${{{DEPOT_CLONE_HELPER_ENV}:?}}\" \"$@\"; }}\n{command}" + "haul() {{ /bin/sh \"${{{DEPOT_HAUL_HELPER_ENV}:?}}\" \"$@\"; }}\nsubdestdir() {{ /bin/sh \"${{{DEPOT_SUBDESTDIR_HELPER_ENV}:?}}\" \"$@\"; }}\npython_build() {{ /bin/sh \"${{{DEPOT_PYTHON_BUILD_HELPER_ENV}:?}}\" \"$@\"; }}\npython_install() {{ /bin/sh \"${{{DEPOT_PYTHON_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\ndepot_clone() {{ /bin/sh \"${{{DEPOT_CLONE_HELPER_ENV}:?}}\" \"$@\"; }}\nautotools_configure() {{ /bin/sh \"${{{DEPOT_AUTOTOOLS_CONFIGURE_HELPER_ENV}:?}}\" \"$@\"; }}\nautotools_install() {{ /bin/sh \"${{{DEPOT_AUTOTOOLS_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\ncmake_configure() {{ /bin/sh \"${{{DEPOT_CMAKE_CONFIGURE_HELPER_ENV}:?}}\" \"$@\"; }}\ncmake_install() {{ /bin/sh \"${{{DEPOT_CMAKE_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\nmeson_configure() {{ /bin/sh \"${{{DEPOT_MESON_CONFIGURE_HELPER_ENV}:?}}\" \"$@\"; }}\nmeson_install() {{ /bin/sh \"${{{DEPOT_MESON_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\nperl_configure() {{ /bin/sh \"${{{DEPOT_PERL_CONFIGURE_HELPER_ENV}:?}}\" \"$@\"; }}\nperl_install() {{ /bin/sh \"${{{DEPOT_PERL_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\n{command}" ) } +fn write_helper_script(path: &Path, content: &str) -> Result<()> { + fs::write(path, content) + .with_context(|| format!("Failed to write shell helper command: {}", path.display()))?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(path) + .with_context(|| format!("Failed to stat helper: {}", path.display()))? + .permissions(); + perms.set_mode(0o755); + fs::set_permissions(path, perms) + .with_context(|| format!("Failed to chmod helper: {}", path.display()))?; + } + Ok(()) +} + /// Convert a package name into a safe shell identifier suffix. pub fn shell_ident_suffix(pkg_name: &str) -> String { let mut out = String::with_capacity(pkg_name.len().max(1)); @@ -474,6 +498,114 @@ fail() { exec "$DEPOT_EXECUTABLE" internal clone "$@" "#; +const AUTOTOOLS_CONFIGURE_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "autotools_configure: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" + +exec "$DEPOT_EXECUTABLE" internal autotools-configure "$@" +"#; + +const AUTOTOOLS_INSTALL_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "autotools_install: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" +[ "${DESTDIR:-}" != "" ] || fail "DESTDIR is not set" + +exec "$DEPOT_EXECUTABLE" internal autotools-install "$@" +"#; + +const CMAKE_CONFIGURE_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "cmake_configure: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" + +exec "$DEPOT_EXECUTABLE" internal cmake-configure "$@" +"#; + +const CMAKE_INSTALL_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "cmake_install: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" +[ "${DESTDIR:-}" != "" ] || fail "DESTDIR is not set" + +exec "$DEPOT_EXECUTABLE" internal cmake-install "$@" +"#; + +const MESON_CONFIGURE_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "meson_configure: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" + +exec "$DEPOT_EXECUTABLE" internal meson-configure "$@" +"#; + +const MESON_INSTALL_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "meson_install: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" +[ "${DESTDIR:-}" != "" ] || fail "DESTDIR is not set" + +exec "$DEPOT_EXECUTABLE" internal meson-install "$@" +"#; + +const PERL_CONFIGURE_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "perl_configure: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" + +exec "$DEPOT_EXECUTABLE" internal perl-configure "$@" +"#; + +const PERL_INSTALL_SCRIPT: &str = r#"#!/bin/sh +set -eu + +fail() { + echo "perl_install: $*" >&2 + exit 1 +} + +[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set" +[ "${DESTDIR:-}" != "" ] || fail "DESTDIR is not set" + +exec "$DEPOT_EXECUTABLE" internal perl-install "$@" +"#; + #[cfg(test)] mod tests { use super::{INTERNAL_DEPOT_DIR, ShellHelpers, shell_ident_suffix, wrap_shell_command}; @@ -557,15 +689,57 @@ mod tests { .any(|(key, _)| key == "DEPOT_PYTHON_INSTALL_HELPER") ); assert!(envs.iter().any(|(key, _)| key == "DEPOT_CLONE_HELPER")); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_AUTOTOOLS_CONFIGURE_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_AUTOTOOLS_INSTALL_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_CMAKE_CONFIGURE_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_CMAKE_INSTALL_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_MESON_CONFIGURE_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_MESON_INSTALL_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_PERL_CONFIGURE_HELPER") + ); + assert!( + envs.iter() + .any(|(key, _)| key == "DEPOT_PERL_INSTALL_HELPER") + ); assert!(envs.iter().any(|(key, _)| key == "DEPOT_EXECUTABLE")); } #[test] fn wrap_shell_command_exposes_python_helpers() { - let wrapped = wrap_shell_command("python_build\npython_install\ndepot_clone foo"); + let wrapped = wrap_shell_command( + "python_build\npython_install\ndepot_clone foo\nmeson_configure\ncmake_install", + ); assert!(wrapped.contains("python_build()")); assert!(wrapped.contains("python_install()")); assert!(wrapped.contains("depot_clone()")); + assert!(wrapped.contains("autotools_configure()")); + assert!(wrapped.contains("autotools_install()")); + assert!(wrapped.contains("cmake_configure()")); + assert!(wrapped.contains("cmake_install()")); + assert!(wrapped.contains("meson_configure()")); + assert!(wrapped.contains("meson_install()")); + assert!(wrapped.contains("perl_configure()")); + assert!(wrapped.contains("perl_install()")); } #[test] diff --git a/src/source/hooks.rs b/src/source/hooks.rs index a1b992a..077b1c5 100755 --- a/src/source/hooks.rs +++ b/src/source/hooks.rs @@ -13,9 +13,12 @@ use crate::builder::state::{BuildStep, StateTracker}; fn hook_env_vars( spec: &PackageSpec, shell_helpers: &crate::shell_helpers::ShellHelpers, + source_dir: &Path, ) -> Result { let mut env_vars = crate::builder::standard_build_env(spec, None, true, true); shell_helpers.apply_to_env_vars(&mut env_vars); + crate::builder::apply_build_helper_context_env(&mut env_vars, spec)?; + crate::builder::apply_build_helper_dirs_env(&mut env_vars, Some(source_dir), None); Ok(env_vars) } @@ -96,7 +99,7 @@ fn run_post_extract_commands(spec: &PackageSpec, source: &Source, src_dir: &Path ); 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)?; + let env_vars = hook_env_vars(spec, &shell_helpers, src_dir)?; for cmd in &source.post_extract { let cmd_str = spec.expand_vars(cmd); @@ -132,7 +135,7 @@ 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)?; + let mut env_vars = hook_env_vars(spec, &shell_helpers, src_dir)?; crate::builder::set_env_var( &mut env_vars, "DESTDIR", @@ -168,7 +171,7 @@ 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)?; + let mut env_vars = hook_env_vars(spec, &shell_helpers, src_dir)?; crate::builder::set_env_var( &mut env_vars, "DESTDIR", @@ -208,7 +211,7 @@ pub fn run_post_install_commands_in_dir( 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)?; + let mut env_vars = hook_env_vars(spec, &shell_helpers, work_dir)?; crate::builder::set_env_var( &mut env_vars, "DESTDIR",