Implement interrupt handling for long-running commands and archive extraction
- Added an `interrupts` module to manage Ctrl-C signals and propagate them to child processes. - Refactored archive extraction functions to check for interrupts and handle them gracefully. - Introduced `copy_interruptibly` function to allow for interruptible copying of data. - Updated various source files to utilize the new interrupt handling functions, ensuring that commands and archive extractions can be interrupted by the user. - Added tests to verify the interrupt functionality during copying and tar extraction. - Introduced a new Meson option for generating HTML CLI documentation. - Enhanced the `PackageSpec` struct to support additional Rust LTO flags.
This commit is contained in:
Generated
+1
-1
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
||||
|
||||
[[package]]
|
||||
name = "depot"
|
||||
version = "0.22.1"
|
||||
version = "0.23.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ar",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "depot"
|
||||
version = "0.22.1"
|
||||
version = "0.23.0"
|
||||
edition = "2024"
|
||||
|
||||
[lints.rust]
|
||||
|
||||
@@ -17,6 +17,8 @@ ldflags = ["-Wl,-O1"]
|
||||
|
||||
# Default LTOFLAGS and automatic injection into CFLAGS/CXXFLAGS/LDFLAGS.
|
||||
ltoflags = ["-flto=auto"]
|
||||
# Rust-specific LTO flags appended only to RUSTFLAGS when use_lto is enabled.
|
||||
#RUSTLTOFLAGS = ["-Clinker-plugin-lto"]
|
||||
use_lto = true
|
||||
|
||||
# CARCH short name (can be overridden per-package)
|
||||
|
||||
+20
-1
@@ -1,6 +1,6 @@
|
||||
project(
|
||||
'depot',
|
||||
version: '0.22.1',
|
||||
version: '0.23.0',
|
||||
meson_version: '>=0.60.0',
|
||||
default_options: ['buildtype=release'],
|
||||
)
|
||||
@@ -10,6 +10,8 @@ fs = import('fs')
|
||||
cargo = find_program('cargo', required: true)
|
||||
find_prog = find_program('find', required: true)
|
||||
sh = find_program('sh', required: true)
|
||||
build_html_doc = get_option('build-html-doc')
|
||||
cli_doc = find_program('cli_doc', required: build_html_doc)
|
||||
|
||||
src_root = meson.project_source_root()
|
||||
build_root = meson.project_build_root()
|
||||
@@ -72,6 +74,23 @@ depot_bin = custom_target(
|
||||
install_dir: get_option('bindir'),
|
||||
)
|
||||
|
||||
if build_html_doc
|
||||
custom_target(
|
||||
'depot-html-doc',
|
||||
input: depot_bin,
|
||||
output: 'depot.html',
|
||||
command: [
|
||||
cli_doc,
|
||||
'--output-filename',
|
||||
'@OUTPUT@',
|
||||
join_paths(build_root, 'depot'),
|
||||
],
|
||||
build_by_default: true,
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()),
|
||||
)
|
||||
endif
|
||||
|
||||
test(
|
||||
'cargo-test',
|
||||
sh,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
option(
|
||||
'build-html-doc',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Generate HTML CLI documentation with cli_doc and install it',
|
||||
)
|
||||
+21
-20
@@ -128,8 +128,7 @@ pub fn build(
|
||||
configure_cmd.arg(expanded);
|
||||
}
|
||||
|
||||
let status = configure_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut configure_cmd)
|
||||
.with_context(|| format!("Failed to run configure in {}", build_dir.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
@@ -164,7 +163,7 @@ pub fn build(
|
||||
|
||||
crate::builder::prepare_tool_command(&mut make_cmd, &env_vars);
|
||||
|
||||
let status = make_cmd.status().with_context(|| {
|
||||
let status = crate::interrupts::command_status(&mut make_cmd).with_context(|| {
|
||||
format!("Failed to run {} in {}", make_exec, make_dir.display())
|
||||
})?;
|
||||
|
||||
@@ -216,14 +215,15 @@ pub fn build(
|
||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||
|
||||
let test_targets_display = test_targets.join(" ");
|
||||
let status = test_cmd.status().with_context(|| {
|
||||
format!(
|
||||
"Failed to run {} {} in {}",
|
||||
make_exec,
|
||||
test_targets_display,
|
||||
test_dir.display()
|
||||
)
|
||||
})?;
|
||||
let status =
|
||||
crate::interrupts::command_status(&mut test_cmd).with_context(|| {
|
||||
format!(
|
||||
"Failed to run {} {} in {}",
|
||||
make_exec,
|
||||
test_targets_display,
|
||||
test_dir.display()
|
||||
)
|
||||
})?;
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
"{} {} failed with status: {} (dir: {})",
|
||||
@@ -344,15 +344,16 @@ pub fn build(
|
||||
));
|
||||
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()
|
||||
)
|
||||
})?;
|
||||
let status =
|
||||
crate::interrupts::command_status(&mut install_cmd).with_context(|| {
|
||||
format!(
|
||||
"Failed to run {} {} for {} in {}",
|
||||
make_exec,
|
||||
install_targets.join(" "),
|
||||
spec.package.name,
|
||||
install_dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
|
||||
+117
-13
@@ -63,6 +63,9 @@ pub fn build(
|
||||
for arg in cmake_install_dir_args(flags) {
|
||||
cmake_cmd.arg(arg);
|
||||
}
|
||||
for arg in cmake_lib32_target_args(flags, cross) {
|
||||
cmake_cmd.arg(arg);
|
||||
}
|
||||
|
||||
// Add toolchain file for cross-compilation
|
||||
if let Some(ref tf) = toolchain_file {
|
||||
@@ -90,7 +93,8 @@ pub fn build(
|
||||
|
||||
crate::builder::prepare_tool_command(&mut cmake_cmd, &env_vars);
|
||||
|
||||
let status = cmake_cmd.status().context("Failed to run cmake")?;
|
||||
let status =
|
||||
crate::interrupts::command_status(&mut cmake_cmd).context("Failed to run cmake")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake configure failed");
|
||||
}
|
||||
@@ -117,8 +121,7 @@ pub fn build(
|
||||
|
||||
crate::builder::prepare_tool_command(&mut build_cmd, &env_vars);
|
||||
|
||||
let status = build_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut build_cmd)
|
||||
.with_context(|| format!("Failed to run cmake build for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake build failed");
|
||||
@@ -139,12 +142,13 @@ pub fn build(
|
||||
}
|
||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||
|
||||
let status = test_cmd.status().with_context(|| {
|
||||
format!(
|
||||
"Failed to run cmake build target(s) '{}' for {}",
|
||||
joined, spec.package.name
|
||||
)
|
||||
})?;
|
||||
let status =
|
||||
crate::interrupts::command_status(&mut test_cmd).with_context(|| {
|
||||
format!(
|
||||
"Failed to run cmake build target(s) '{}' for {}",
|
||||
joined, spec.package.name
|
||||
)
|
||||
})?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake test target(s) '{}' failed", joined);
|
||||
}
|
||||
@@ -157,8 +161,7 @@ pub fn build(
|
||||
test_cmd.arg("--output-on-failure");
|
||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||
|
||||
let status = test_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut test_cmd)
|
||||
.with_context(|| format!("Failed to run ctest for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("ctest failed");
|
||||
@@ -203,8 +206,7 @@ pub fn build(
|
||||
));
|
||||
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
||||
|
||||
let status = install_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut install_cmd)
|
||||
.with_context(|| format!("Failed to run cmake install for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
if !install_targets.is_empty() {
|
||||
@@ -378,6 +380,55 @@ fn cmake_install_dir_args(flags: &crate::package::BuildFlags) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn cmake_lib32_target_args(
|
||||
flags: &crate::package::BuildFlags,
|
||||
cross: Option<&CrossConfig>,
|
||||
) -> Vec<String> {
|
||||
if !flags.lib32_variant {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let target = match lib32_target_triple(flags, cross) {
|
||||
Some(target) => target,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
let arch = crate::cross::target_arch_from_triple(&target);
|
||||
let defaults = [
|
||||
("CMAKE_SYSTEM_PROCESSOR", arch.to_string()),
|
||||
("CMAKE_C_COMPILER_TARGET", target.clone()),
|
||||
("CMAKE_CXX_COMPILER_TARGET", target.clone()),
|
||||
("CMAKE_ASM_COMPILER_TARGET", target),
|
||||
];
|
||||
|
||||
defaults
|
||||
.into_iter()
|
||||
.filter(|(variable, _)| cmake_cache_entry_value(&flags.configure, variable).is_none())
|
||||
.map(|(variable, value)| format!("-D{variable}={value}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn lib32_target_triple(
|
||||
flags: &crate::package::BuildFlags,
|
||||
cross: Option<&CrossConfig>,
|
||||
) -> Option<String> {
|
||||
let host = if let Some(cc_cfg) = cross {
|
||||
Some(cc_cfg.host_triple().to_string())
|
||||
} else if !flags.chost.trim().is_empty() {
|
||||
Some(flags.chost.trim().to_string())
|
||||
} else {
|
||||
let detected = CrossConfig::build_triple();
|
||||
if let Err(err) = &detected {
|
||||
crate::log_warn!(
|
||||
"Failed to detect native build triple for lib32 CMake target flags: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
detected.ok()
|
||||
};
|
||||
|
||||
host.map(|host| crate::cross::lib32_target_triple(&host))
|
||||
}
|
||||
|
||||
fn cmake_install_dir_value(prefix: &str, value: &str) -> String {
|
||||
let trimmed_prefix = prefix.trim();
|
||||
let trimmed_value = value.trim();
|
||||
@@ -610,6 +661,59 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmake_lib32_target_args_include_compiler_target_defaults() {
|
||||
let flags = BuildFlags {
|
||||
lib32_variant: true,
|
||||
chost: "x86_64-sfg-linux-gnu".into(),
|
||||
..BuildFlags::default()
|
||||
};
|
||||
|
||||
let args = cmake_lib32_target_args(&flags, None);
|
||||
assert!(args.iter().any(|a| a == "-DCMAKE_SYSTEM_PROCESSOR=i686"));
|
||||
assert!(
|
||||
args.iter()
|
||||
.any(|a| a == "-DCMAKE_C_COMPILER_TARGET=i686-sfg-linux-gnu")
|
||||
);
|
||||
assert!(
|
||||
args.iter()
|
||||
.any(|a| a == "-DCMAKE_CXX_COMPILER_TARGET=i686-sfg-linux-gnu")
|
||||
);
|
||||
assert!(
|
||||
args.iter()
|
||||
.any(|a| a == "-DCMAKE_ASM_COMPILER_TARGET=i686-sfg-linux-gnu")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmake_lib32_target_args_respect_explicit_overrides() {
|
||||
let flags = BuildFlags {
|
||||
lib32_variant: true,
|
||||
chost: "x86_64-sfg-linux-gnu".into(),
|
||||
configure: vec![
|
||||
"-DCMAKE_C_COMPILER_TARGET=i686-custom-linux-gnu".into(),
|
||||
"-DCMAKE_SYSTEM_PROCESSOR=i686".into(),
|
||||
],
|
||||
..BuildFlags::default()
|
||||
};
|
||||
|
||||
let args = cmake_lib32_target_args(&flags, None);
|
||||
assert!(
|
||||
!args
|
||||
.iter()
|
||||
.any(|a| a.starts_with("-DCMAKE_C_COMPILER_TARGET="))
|
||||
);
|
||||
assert!(
|
||||
!args
|
||||
.iter()
|
||||
.any(|a| a.starts_with("-DCMAKE_SYSTEM_PROCESSOR="))
|
||||
);
|
||||
assert!(
|
||||
args.iter()
|
||||
.any(|a| a == "-DCMAKE_CXX_COMPILER_TARGET=i686-sfg-linux-gnu")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmake_install_dir_args_respect_explicit_user_overrides() {
|
||||
let flags = BuildFlags {
|
||||
|
||||
@@ -131,7 +131,7 @@ pub fn build(
|
||||
crate::builder::prepare_tool_command(&mut cmd, &env_vars);
|
||||
|
||||
// Run the command and include the OS error on spawn failures for clearer diagnostics
|
||||
let status = cmd.status().map_err(|e| {
|
||||
let status = crate::interrupts::command_status(&mut cmd).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to run build script {}: {}",
|
||||
build_script.display(),
|
||||
|
||||
@@ -48,8 +48,7 @@ pub fn build(
|
||||
cmd.current_dir(src_dir);
|
||||
crate::builder::prepare_tool_command(&mut cmd, &env_vars);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut cmd)
|
||||
.with_context(|| format!("Failed to run build command: {}", cmd_str))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Build command failed: {}", cmd_str);
|
||||
@@ -89,8 +88,7 @@ pub fn build(
|
||||
));
|
||||
crate::builder::prepare_tool_command(&mut cmd, &install_env);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut cmd)
|
||||
.with_context(|| format!("Failed to run install command: {}", cmd_str))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Install command failed: {}", cmd_str);
|
||||
|
||||
+154
-8
@@ -29,9 +29,12 @@ pub fn build(
|
||||
// Environment variables
|
||||
let env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
||||
|
||||
// Generate cross file if cross-compiling
|
||||
// Generate cross file if cross-compiling, or when the lib32 variant needs
|
||||
// Meson to treat the build as x86 instead of the native x86_64 host.
|
||||
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
|
||||
};
|
||||
@@ -56,7 +59,8 @@ pub fn build(
|
||||
|
||||
crate::builder::prepare_tool_command(&mut meson_cmd, &env_vars);
|
||||
|
||||
let status = meson_cmd.status().context("Failed to run meson setup")?;
|
||||
let status = crate::interrupts::command_status(&mut meson_cmd)
|
||||
.context("Failed to run meson setup")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson setup failed");
|
||||
}
|
||||
@@ -76,8 +80,7 @@ pub fn build(
|
||||
|
||||
crate::builder::prepare_tool_command(&mut ninja_cmd, &env_vars);
|
||||
|
||||
let status = ninja_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut ninja_cmd)
|
||||
.with_context(|| format!("Failed to run ninja for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("ninja build failed");
|
||||
@@ -105,8 +108,7 @@ pub fn build(
|
||||
|
||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||
|
||||
let status = test_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut test_cmd)
|
||||
.with_context(|| format!("Failed to run meson test for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson test failed");
|
||||
@@ -141,8 +143,7 @@ pub fn build(
|
||||
));
|
||||
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
||||
|
||||
let status = install_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut install_cmd)
|
||||
.with_context(|| format!("Failed to run meson install for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson install failed");
|
||||
@@ -258,6 +259,109 @@ fn meson_setup_args(
|
||||
args
|
||||
}
|
||||
|
||||
fn generate_lib32_meson_cross_file(
|
||||
flags: &crate::package::BuildFlags,
|
||||
build_dir: &Path,
|
||||
) -> Result<PathBuf> {
|
||||
let target = lib32_target_triple(flags);
|
||||
let arch = crate::cross::target_arch_from_triple(&target);
|
||||
let cpu_family = crate::cross::cpu_family_for_arch(arch);
|
||||
let c = meson_binary_value(
|
||||
&compiler_command_with_lib32_target(&flags.cc, &target),
|
||||
"C compiler",
|
||||
)?;
|
||||
let cpp = meson_binary_value(
|
||||
&compiler_command_with_lib32_target(&flags.cxx, &target),
|
||||
"C++ compiler",
|
||||
)?;
|
||||
let ar = meson_binary_value(&command_words(&flags.ar), "archiver")?;
|
||||
|
||||
let mut content = format!(
|
||||
"# Meson cross file for lib32 builds\n# Generated by depot for target: {target}\n\n[binaries]\nc = {c}\ncpp = {cpp}\nar = {ar}\n"
|
||||
);
|
||||
if !flags.ld.trim().is_empty() {
|
||||
let ld = meson_binary_value(&command_words(&flags.ld), "linker")?;
|
||||
content.push_str(&format!("ld = {ld}\n"));
|
||||
}
|
||||
content.push_str(&format!(
|
||||
"\n[host_machine]\nsystem = 'linux'\ncpu_family = '{cpu_family}'\ncpu = '{arch}'\nendian = 'little'\n"
|
||||
));
|
||||
|
||||
fs::create_dir_all(build_dir)?;
|
||||
let cross_path = build_dir.join("lib32-cross-file.ini");
|
||||
fs::write(&cross_path, content)
|
||||
.with_context(|| format!("Failed to write {}", cross_path.display()))?;
|
||||
Ok(cross_path)
|
||||
}
|
||||
|
||||
fn lib32_target_triple(flags: &crate::package::BuildFlags) -> String {
|
||||
let host = if !flags.chost.trim().is_empty() {
|
||||
flags.chost.trim().to_string()
|
||||
} else {
|
||||
match CrossConfig::build_triple() {
|
||||
Ok(triple) => triple,
|
||||
Err(err) => {
|
||||
crate::log_warn!(
|
||||
"Failed to detect native build triple for lib32 Meson target file: {}",
|
||||
err
|
||||
);
|
||||
"x86_64-unknown-linux-gnu".to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
crate::cross::lib32_target_triple(&host)
|
||||
}
|
||||
|
||||
fn compiler_command_with_lib32_target(command: &str, target: &str) -> Vec<String> {
|
||||
let mut parts = command_words(command);
|
||||
if compiler_command_supports_target(&parts) && !compiler_command_has_target(&parts) {
|
||||
parts.push(format!("--target={target}"));
|
||||
}
|
||||
parts
|
||||
}
|
||||
|
||||
fn command_words(command: &str) -> Vec<String> {
|
||||
command
|
||||
.split_whitespace()
|
||||
.filter(|part| !part.is_empty())
|
||||
.map(ToString::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn compiler_command_supports_target(parts: &[String]) -> bool {
|
||||
parts.first().is_some_and(|tool| {
|
||||
Path::new(tool)
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.is_some_and(|name| name.contains("clang"))
|
||||
})
|
||||
}
|
||||
|
||||
fn compiler_command_has_target(parts: &[String]) -> bool {
|
||||
parts.iter().any(|part| {
|
||||
part == "--target"
|
||||
|| part == "-target"
|
||||
|| part.starts_with("--target=")
|
||||
|| part.starts_with("-target=")
|
||||
})
|
||||
}
|
||||
|
||||
fn meson_binary_value(parts: &[String], label: &str) -> Result<String> {
|
||||
if parts.is_empty() {
|
||||
anyhow::bail!("Missing {} command for lib32 Meson cross file", label);
|
||||
}
|
||||
|
||||
let rendered = parts
|
||||
.iter()
|
||||
.map(|part| format!("'{}'", part.replace('\\', "\\\\").replace('\'', "\\'")))
|
||||
.collect::<Vec<_>>();
|
||||
if rendered.len() == 1 {
|
||||
Ok(rendered[0].clone())
|
||||
} else {
|
||||
Ok(format!("[{}]", rendered.join(", ")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand environment variables in a string (e.g., $DEPOT_SYSROOT)
|
||||
fn expand_env_vars(input: &str) -> String {
|
||||
let mut result = input.to_string();
|
||||
@@ -439,6 +543,48 @@ mod tests {
|
||||
assert!(!args.iter().any(|a| a == "-Dcpp_ld=ld.lld"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compiler_command_with_lib32_target_adds_clang_target() {
|
||||
let parts = compiler_command_with_lib32_target("clang -m32", "i686-sfg-linux-gnu");
|
||||
assert_eq!(
|
||||
parts,
|
||||
vec![
|
||||
"clang".to_string(),
|
||||
"-m32".to_string(),
|
||||
"--target=i686-sfg-linux-gnu".to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compiler_command_with_lib32_target_skips_non_clang_compilers() {
|
||||
let parts = compiler_command_with_lib32_target("gcc -m32", "i686-sfg-linux-gnu");
|
||||
assert_eq!(parts, vec!["gcc".to_string(), "-m32".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_lib32_meson_cross_file_sets_x86_host_machine() -> Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let flags = BuildFlags {
|
||||
lib32_variant: true,
|
||||
chost: "x86_64-sfg-linux-gnu".to_string(),
|
||||
cc: "clang -m32".to_string(),
|
||||
cxx: "clang++ -m32".to_string(),
|
||||
ar: "llvm-ar".to_string(),
|
||||
ld: "ld.lld".to_string(),
|
||||
..BuildFlags::default()
|
||||
};
|
||||
|
||||
let path = generate_lib32_meson_cross_file(&flags, tmp.path())?;
|
||||
let content = fs::read_to_string(path)?;
|
||||
assert!(content.contains("Generated by depot for target: i686-sfg-linux-gnu"));
|
||||
assert!(content.contains("c = ['clang', '-m32', '--target=i686-sfg-linux-gnu']"));
|
||||
assert!(content.contains("cpp = ['clang++', '-m32', '--target=i686-sfg-linux-gnu']"));
|
||||
assert!(content.contains("cpu_family = 'x86'"));
|
||||
assert!(content.contains("cpu = 'i686'"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_build_dir_uses_flag() {
|
||||
let flags = BuildFlags {
|
||||
|
||||
+40
-2
@@ -194,12 +194,21 @@ fn compiler_flag_sets(
|
||||
(cflags, cxxflags, ldflags, ltoflags)
|
||||
}
|
||||
|
||||
fn rust_ltoflags(flags: &crate::package::BuildFlags) -> Vec<String> {
|
||||
sanitize_flag_list(flags.rustltoflags.clone(), "build.flags.rustltoflags")
|
||||
}
|
||||
|
||||
pub(crate) fn effective_rustflags(flags: &crate::package::BuildFlags) -> Vec<String> {
|
||||
replaced_flags(
|
||||
let mut rustflags = replaced_flags(
|
||||
&flags.rustflags,
|
||||
&flags.replace_rustflags,
|
||||
"build.flags.rustflags",
|
||||
)
|
||||
);
|
||||
let rust_ltoflags = rust_ltoflags(flags);
|
||||
if flags.use_lto && !rust_ltoflags.is_empty() {
|
||||
rustflags.extend(rust_ltoflags);
|
||||
}
|
||||
rustflags
|
||||
}
|
||||
|
||||
pub fn standard_build_env(
|
||||
@@ -224,6 +233,10 @@ pub fn standard_build_env(
|
||||
if !ltoflags.is_empty() {
|
||||
set_env_var(&mut env_vars, "LTOFLAGS", ltoflags.join(" "));
|
||||
}
|
||||
let rust_ltoflags = rust_ltoflags(flags);
|
||||
if !rust_ltoflags.is_empty() {
|
||||
set_env_var(&mut env_vars, "RUSTLTOFLAGS", rust_ltoflags.join(" "));
|
||||
}
|
||||
|
||||
let ldflags = if !ldflags.is_empty() || !flags.libc.is_empty() {
|
||||
if flags.libc.is_empty() {
|
||||
@@ -717,6 +730,7 @@ mod tests {
|
||||
let mut spec = mk_spec(vec!["-O2"], vec!["-Wl,--as-needed"]);
|
||||
spec.build.flags.cxxflags = vec!["-O2".into()];
|
||||
spec.build.flags.ltoflags = vec!["-flto=auto".into()];
|
||||
spec.build.flags.rustltoflags = vec!["-Clinker-plugin-lto".into()];
|
||||
spec.build.flags.use_lto = false;
|
||||
|
||||
let env = standard_build_env(&spec, None, true, true);
|
||||
@@ -738,6 +752,12 @@ mod tests {
|
||||
.any(|(k, v)| k == "LTOFLAGS" && v == "-flto=auto"),
|
||||
"expected LTOFLAGS variable to be exported even when use_lto is false"
|
||||
);
|
||||
assert!(
|
||||
env.iter()
|
||||
.any(|(k, v)| k == "RUSTLTOFLAGS" && v == "-Clinker-plugin-lto"),
|
||||
"expected RUSTLTOFLAGS variable to be exported even when use_lto is false"
|
||||
);
|
||||
assert_eq!(effective_rustflags(&spec.build.flags), Vec::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -778,6 +798,24 @@ mod tests {
|
||||
assert_eq!(effective_rustflags(&flags), vec!["-C", "opt-level=2"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_effective_rustflags_appends_rustltoflags_when_enabled() {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.rustflags = vec!["-C".into(), "opt-level=3".into()];
|
||||
flags.rustltoflags = vec!["-Clinker-plugin-lto".into(), "-Cembed-bitcode=yes".into()];
|
||||
flags.use_lto = true;
|
||||
|
||||
assert_eq!(
|
||||
effective_rustflags(&flags),
|
||||
vec![
|
||||
"-C",
|
||||
"opt-level=3",
|
||||
"-Clinker-plugin-lto",
|
||||
"-Cembed-bitcode=yes"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_standard_build_env_passthrough_does_not_override_default_vars() {
|
||||
let mut spec = mk_spec(Vec::new(), Vec::new());
|
||||
|
||||
+2
-2
@@ -267,7 +267,7 @@ fn resolve_perl_configure_script(spec: &PackageSpec, actual_src: &Path) -> PathB
|
||||
}
|
||||
|
||||
fn command_status_with_sh_fallback(cmd: &mut Command) -> std::io::Result<std::process::ExitStatus> {
|
||||
match cmd.status() {
|
||||
match crate::interrupts::command_status(cmd) {
|
||||
Ok(status) => Ok(status),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
let Some(script) = resolved_script_path(cmd) else {
|
||||
@@ -296,7 +296,7 @@ fn command_status_with_sh_fallback(cmd: &mut Command) -> std::io::Result<std::pr
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback.status()
|
||||
crate::interrupts::command_status(&mut fallback)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
|
||||
@@ -273,8 +273,7 @@ fn build_wheel_setup_py(
|
||||
.arg(dist_dir);
|
||||
crate::builder::prepare_tool_command(&mut cmd, &env_vars.to_vec());
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut cmd)
|
||||
.with_context(|| format!("Failed to run setup.py in {}", src_dir.display()))?;
|
||||
if !status.success() {
|
||||
bail!("setup.py bdist_wheel failed with status {}", status);
|
||||
@@ -315,25 +314,23 @@ fn build_wheel_pep517(
|
||||
);
|
||||
crate::builder::prepare_command(&mut cmd, &build_env);
|
||||
|
||||
let output = cmd.output().with_context(|| {
|
||||
let status = crate::interrupts::command_status(&mut cmd).with_context(|| {
|
||||
format!(
|
||||
"Failed to run python3 for PEP 517 build in {}",
|
||||
src_dir.display()
|
||||
)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if !status.success() {
|
||||
let requires = if cfg.requires.is_empty() {
|
||||
"(none declared)".to_string()
|
||||
} else {
|
||||
cfg.requires.join(", ")
|
||||
};
|
||||
bail!(
|
||||
"PEP 517 wheel build failed with status {}. Backend: {}. Declared build requirements: {}. stderr: {}",
|
||||
output.status,
|
||||
"PEP 517 wheel build failed with status {}. Backend: {}. Declared build requirements: {}",
|
||||
status,
|
||||
cfg.backend,
|
||||
requires,
|
||||
stderr.trim()
|
||||
requires
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -102,8 +102,7 @@ pub fn build(
|
||||
// Set environment
|
||||
crate::builder::prepare_tool_command(&mut cargo_cmd, &env_vars);
|
||||
|
||||
let status = cargo_cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut cargo_cmd)
|
||||
.with_context(|| format!("Failed to run cargo build for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cargo build failed");
|
||||
|
||||
+214
-25
@@ -9,14 +9,11 @@ use crate::{
|
||||
use anyhow::{Context, Result};
|
||||
use git2::Direction;
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use signal_hook::consts::SIGINT;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::io::IsTerminal;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use url::Url;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@@ -105,6 +102,7 @@ fn should_delegate_live_rootfs_installs(rootfs: &Path) -> bool {
|
||||
const DEPOT_INSTALL_CONTEXT_ENV: &str = "DEPOT_INSTALL_CONTEXT";
|
||||
const INSTALL_CONTEXT_UPDATE: &str = "update";
|
||||
const INSTALL_CONTEXT_PLANNED: &str = "planned";
|
||||
const DEPOT_PACKAGE_NAME: &str = "depot";
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum InstallInvocationContext {
|
||||
@@ -132,6 +130,55 @@ fn install_test_deps_enabled(cli_test_deps: bool, config: &config::Config) -> bo
|
||||
cli_test_deps || config.install_test_deps
|
||||
}
|
||||
|
||||
fn current_argv0() -> String {
|
||||
std::env::args_os()
|
||||
.next()
|
||||
.filter(|arg| !arg.is_empty())
|
||||
.map(|arg| PathBuf::from(arg).display().to_string())
|
||||
.or_else(|| {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.map(|path| path.display().to_string())
|
||||
})
|
||||
.unwrap_or_else(|| DEPOT_PACKAGE_NAME.to_string())
|
||||
}
|
||||
|
||||
fn is_explicit_depot_self_update_request(packages: &[String]) -> bool {
|
||||
packages.len() == 1
|
||||
&& packages
|
||||
.first()
|
||||
.is_some_and(|package| package == DEPOT_PACKAGE_NAME)
|
||||
}
|
||||
|
||||
fn ensure_depot_self_update_not_required(config: &config::Config, rootfs: &Path) -> Result<()> {
|
||||
let db_path = config.installed_db_path(rootfs);
|
||||
if db::get_package_version(&db_path, DEPOT_PACKAGE_NAME)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to query installed package database at {}",
|
||||
db_path.display()
|
||||
)
|
||||
})?
|
||||
.is_none()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let requested = [DEPOT_PACKAGE_NAME.to_string()];
|
||||
let updates = collect_update_candidates(config, rootfs, &requested)
|
||||
.context("Failed to check for pending depot self-update")?;
|
||||
if updates.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"An update for '{}' is available. Run '{} update {}' before continuing.",
|
||||
DEPOT_PACKAGE_NAME,
|
||||
current_argv0(),
|
||||
DEPOT_PACKAGE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
fn maybe_reexec_with_sudo(cli: &Cli) -> Result<bool> {
|
||||
if !should_reexec_with_sudo(cli) {
|
||||
return Ok(false);
|
||||
@@ -235,7 +282,7 @@ fn run_install_command_with_program(
|
||||
fn command_status_with_sh_fallback(
|
||||
cmd: &mut std::process::Command,
|
||||
) -> std::io::Result<std::process::ExitStatus> {
|
||||
match cmd.status() {
|
||||
match crate::interrupts::command_status(cmd) {
|
||||
Ok(status) => Ok(status),
|
||||
Err(err)
|
||||
if err.kind() == std::io::ErrorKind::PermissionDenied
|
||||
@@ -264,7 +311,7 @@ fn command_status_with_sh_fallback(
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback.status()
|
||||
crate::interrupts::command_status(&mut fallback)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
@@ -295,27 +342,21 @@ fn run_child_install_command(
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InterruptWatcher {
|
||||
interrupted: Arc<AtomicBool>,
|
||||
}
|
||||
struct InterruptWatcher;
|
||||
|
||||
impl InterruptWatcher {
|
||||
fn install() -> Result<Self> {
|
||||
let interrupted = Arc::new(AtomicBool::new(false));
|
||||
signal_hook::flag::register(SIGINT, interrupted.clone())
|
||||
.context("Failed to register Ctrl-C handler")?;
|
||||
Ok(Self { interrupted })
|
||||
crate::interrupts::install()?;
|
||||
crate::interrupts::reset();
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn was_interrupted(&self) -> bool {
|
||||
self.interrupted.load(AtomicOrdering::Relaxed)
|
||||
crate::interrupts::was_interrupted()
|
||||
}
|
||||
|
||||
fn check(&self) -> Result<()> {
|
||||
if self.was_interrupted() {
|
||||
anyhow::bail!("Interrupted by Ctrl-C");
|
||||
}
|
||||
Ok(())
|
||||
crate::interrupts::check()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,6 +574,7 @@ fn current_process_env_vars() -> Vec<(String, String)> {
|
||||
"PYTHONNOUSERSITE",
|
||||
"RANLIB",
|
||||
"RUSTFLAGS",
|
||||
"RUSTLTOFLAGS",
|
||||
"SETUPTOOLS_USE_DISTUTILS",
|
||||
"STRIP",
|
||||
];
|
||||
@@ -767,6 +809,7 @@ fn load_package_archive_into_staging(
|
||||
archive_path.display()
|
||||
)
|
||||
})? {
|
||||
crate::interrupts::check()?;
|
||||
let mut entry = entry.with_context(|| {
|
||||
format!(
|
||||
"Failed to read archive entry from {}",
|
||||
@@ -800,7 +843,7 @@ fn load_package_archive_into_staging(
|
||||
.with_context(|| format!("Failed to read zstd stream {}", archive_path.display()))?;
|
||||
let mut archive = tar::Archive::new(zstd_decoder);
|
||||
archive.set_preserve_permissions(true);
|
||||
archive.unpack(&extract_dir).with_context(|| {
|
||||
crate::interrupts::unpack_tar_archive(&mut archive, &extract_dir).with_context(|| {
|
||||
format!(
|
||||
"Failed to extract package archive {} into {}",
|
||||
archive_path.display(),
|
||||
@@ -836,7 +879,7 @@ fn extract_package_archive_to_staging(
|
||||
.with_context(|| format!("Failed to read zstd stream {}", archive_path.display()))?;
|
||||
let mut archive = tar::Archive::new(zstd_decoder);
|
||||
archive.set_preserve_permissions(true);
|
||||
archive.unpack(&extract_dir).with_context(|| {
|
||||
crate::interrupts::unpack_tar_archive(&mut archive, &extract_dir).with_context(|| {
|
||||
format!(
|
||||
"Failed to extract package archive {} into {}",
|
||||
archive_path.display(),
|
||||
@@ -2514,8 +2557,6 @@ fn run_update_command(
|
||||
config: &config::Config,
|
||||
options: UpdateCommandOptions<'_>,
|
||||
) -> Result<()> {
|
||||
sync_source_repositories_for_update(config)?;
|
||||
|
||||
let updates = collect_update_candidates(config, options.rootfs, packages)?;
|
||||
if updates.is_empty() {
|
||||
ui::info("All installed packages are up to date.");
|
||||
@@ -3487,8 +3528,7 @@ fn execute_install_plan_with_child_commands(
|
||||
}
|
||||
cmd.arg("install").arg(path);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
let status = crate::interrupts::command_status(&mut cmd)
|
||||
.context("Failed to spawn planned install step")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Planned install step for '{}' failed", step.package);
|
||||
@@ -4080,6 +4120,8 @@ fn run_direct_install_request(
|
||||
}
|
||||
|
||||
pub fn run(cli: Cli) -> Result<()> {
|
||||
crate::interrupts::install()?;
|
||||
crate::interrupts::reset();
|
||||
ui::set_assume_yes(command_assume_yes(&cli.command));
|
||||
if maybe_reexec_with_sudo(&cli)? {
|
||||
return Ok(());
|
||||
@@ -4126,6 +4168,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
|
||||
// Load configuration early so we can use configured repos/paths.
|
||||
let config = config::Config::for_rootfs(&rootfs);
|
||||
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
||||
let install_test_deps = install_test_deps_enabled(cli_test_deps, &config);
|
||||
let mut planned_targets = Vec::new();
|
||||
let mut planned_spec_paths = Vec::new();
|
||||
@@ -4266,11 +4309,11 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
let dry_run = build_exec_args.dry_run;
|
||||
let cli_lib32_only = lib32_args.lib32_only;
|
||||
warn_if_running_as_root_for_build("build", &rootfs);
|
||||
let config = config::Config::for_rootfs(&rootfs);
|
||||
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
||||
let spec_path = spec.or(spec_pos).context("No spec file provided")?;
|
||||
ui::info(format!("Building package from: {}", spec_path.display()));
|
||||
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
||||
|
||||
let config = config::Config::for_rootfs(&rootfs);
|
||||
let install_test_deps = install_test_deps_enabled(cli_test_deps, &config);
|
||||
let interrupt_watcher = if cleanup_deps {
|
||||
Some(InterruptWatcher::install()?)
|
||||
@@ -4783,6 +4826,10 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
let clean = build_exec_args.clean;
|
||||
let dry_run = build_exec_args.dry_run;
|
||||
let config = config::Config::for_rootfs(&rootfs);
|
||||
sync_source_repositories_for_update(&config)?;
|
||||
if !is_explicit_depot_self_update_request(&packages) {
|
||||
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
||||
}
|
||||
run_update_command(
|
||||
&packages,
|
||||
&config,
|
||||
@@ -5422,6 +5469,57 @@ mod tests {
|
||||
spec_dir: PathBuf::from("."),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_installed_test_package(
|
||||
config: &config::Config,
|
||||
rootfs: &Path,
|
||||
name: &str,
|
||||
version: &str,
|
||||
) -> Result<()> {
|
||||
let mut spec = test_package_spec(package::BuildType::Bin, None, &[]);
|
||||
spec.package.name = name.to_string();
|
||||
spec.package.version = version.to_string();
|
||||
|
||||
let dest = rootfs.join("dest").join(name);
|
||||
fs::create_dir_all(dest.join("usr/bin"))?;
|
||||
fs::write(dest.join("usr/bin").join(name), name)?;
|
||||
db::register_package(&config.installed_db_path(rootfs), &spec, &dest)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_test_repo_spec(path: &Path, name: &str, version: &str) -> Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::write(
|
||||
path,
|
||||
format!(
|
||||
r#"[package]
|
||||
name = "{name}"
|
||||
version = "{version}"
|
||||
revision = 1
|
||||
description = "{name}"
|
||||
homepage = "https://example.test/{name}"
|
||||
license = "MIT"
|
||||
|
||||
[[source]]
|
||||
url = "https://example.test/{name}-{version}.tar.gz"
|
||||
sha256 = "skip"
|
||||
extract_dir = "{name}-{version}"
|
||||
|
||||
[build]
|
||||
type = "custom"
|
||||
|
||||
[dependencies]
|
||||
build = []
|
||||
runtime = []
|
||||
optional = []
|
||||
"#
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("Failed to write {}", path.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
use anyhow::Context;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -6154,6 +6252,97 @@ optional = []
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_depot_self_update_request_requires_only_depot() {
|
||||
assert!(is_explicit_depot_self_update_request(&[
|
||||
DEPOT_PACKAGE_NAME.to_string()
|
||||
]));
|
||||
assert!(!is_explicit_depot_self_update_request(&[]));
|
||||
assert!(!is_explicit_depot_self_update_request(&["pkg".to_string()]));
|
||||
assert!(!is_explicit_depot_self_update_request(&[
|
||||
DEPOT_PACKAGE_NAME.to_string(),
|
||||
"pkg".to_string()
|
||||
]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depot_self_update_check_blocks_when_update_is_available() -> Result<()> {
|
||||
let temp = tempfile::tempdir().context("Failed to create temp dir")?;
|
||||
let rootfs = temp.path().join("rootfs");
|
||||
let repo_clones = temp.path().join("repos");
|
||||
let build_dir = temp.path().join("build");
|
||||
let db_dir = rootfs.join("var/lib/depot");
|
||||
fs::create_dir_all(&db_dir)?;
|
||||
fs::create_dir_all(&repo_clones)?;
|
||||
fs::create_dir_all(&build_dir)?;
|
||||
|
||||
let mut config = config::Config::for_rootfs(&rootfs);
|
||||
config.repo_clone_dir = repo_clones.clone();
|
||||
config.build_dir = build_dir;
|
||||
config.db_dir = db_dir;
|
||||
config.repo_settings.prefer_binary = false;
|
||||
config.binary_repos.clear();
|
||||
config.source_repos.insert(
|
||||
"core".into(),
|
||||
config::SourceRepo {
|
||||
url: "https://example.test/core.git".into(),
|
||||
enabled: true,
|
||||
priority: 0,
|
||||
subdirs: Vec::new(),
|
||||
},
|
||||
);
|
||||
|
||||
register_installed_test_package(&config, &rootfs, DEPOT_PACKAGE_NAME, "1.0.0")?;
|
||||
write_test_repo_spec(
|
||||
&repo_clones.join("core").join("depot.toml"),
|
||||
DEPOT_PACKAGE_NAME,
|
||||
"1.1.0",
|
||||
)?;
|
||||
|
||||
let err = ensure_depot_self_update_not_required(&config, &rootfs)
|
||||
.expect_err("outdated depot should block command execution");
|
||||
assert!(err.to_string().contains("update depot"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depot_self_update_check_allows_when_depot_is_current() -> Result<()> {
|
||||
let temp = tempfile::tempdir().context("Failed to create temp dir")?;
|
||||
let rootfs = temp.path().join("rootfs");
|
||||
let repo_clones = temp.path().join("repos");
|
||||
let build_dir = temp.path().join("build");
|
||||
let db_dir = rootfs.join("var/lib/depot");
|
||||
fs::create_dir_all(&db_dir)?;
|
||||
fs::create_dir_all(&repo_clones)?;
|
||||
fs::create_dir_all(&build_dir)?;
|
||||
|
||||
let mut config = config::Config::for_rootfs(&rootfs);
|
||||
config.repo_clone_dir = repo_clones.clone();
|
||||
config.build_dir = build_dir;
|
||||
config.db_dir = db_dir;
|
||||
config.repo_settings.prefer_binary = false;
|
||||
config.binary_repos.clear();
|
||||
config.source_repos.insert(
|
||||
"core".into(),
|
||||
config::SourceRepo {
|
||||
url: "https://example.test/core.git".into(),
|
||||
enabled: true,
|
||||
priority: 0,
|
||||
subdirs: Vec::new(),
|
||||
},
|
||||
);
|
||||
|
||||
register_installed_test_package(&config, &rootfs, DEPOT_PACKAGE_NAME, "1.1.0")?;
|
||||
write_test_repo_spec(
|
||||
&repo_clones.join("core").join("depot.toml"),
|
||||
DEPOT_PACKAGE_NAME,
|
||||
"1.1.0",
|
||||
)?;
|
||||
|
||||
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_missing_update_dependencies_skips_planned_provides_and_installed_deps() -> Result<()>
|
||||
{
|
||||
|
||||
+59
-22
@@ -119,7 +119,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
"#,
|
||||
self.prefix,
|
||||
self.target_arch(),
|
||||
target_arch_from_triple(&self.prefix),
|
||||
self.cc,
|
||||
self.cxx,
|
||||
self.ar,
|
||||
@@ -169,8 +169,8 @@ endian = 'little'
|
||||
self.objcopy,
|
||||
self.objdump,
|
||||
self.readelf,
|
||||
self.cpu_family(),
|
||||
self.target_arch(),
|
||||
cpu_family_for_arch(target_arch_from_triple(&self.prefix)),
|
||||
target_arch_from_triple(&self.prefix),
|
||||
);
|
||||
|
||||
fs::create_dir_all(build_dir)?;
|
||||
@@ -179,28 +179,65 @@ endian = 'little'
|
||||
|
||||
Ok(cross_path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract target architecture from prefix
|
||||
fn target_arch(&self) -> &str {
|
||||
self.prefix.split('-').next().unwrap_or("unknown")
|
||||
/// Extract the leading architecture component from a target triple.
|
||||
pub fn target_arch_from_triple(triple: &str) -> &str {
|
||||
triple.split('-').next().unwrap_or("unknown")
|
||||
}
|
||||
|
||||
/// Return the Meson CPU family for an architecture token.
|
||||
pub fn cpu_family_for_arch(arch: &str) -> &str {
|
||||
match arch {
|
||||
"x86_64" | "amd64" => "x86_64",
|
||||
"i686" | "i586" | "i486" | "i386" => "x86",
|
||||
"aarch64" | "arm64" => "aarch64",
|
||||
"arm" | "armv7" | "armv7l" => "arm",
|
||||
"riscv64" => "riscv64",
|
||||
"riscv32" => "riscv32",
|
||||
"powerpc64" | "ppc64" => "ppc64",
|
||||
"powerpc" | "ppc" => "ppc",
|
||||
"mips64" => "mips64",
|
||||
"mips" => "mips",
|
||||
_ => arch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get CPU family for Meson
|
||||
fn cpu_family(&self) -> &str {
|
||||
let arch = self.target_arch();
|
||||
match arch {
|
||||
"x86_64" | "amd64" => "x86_64",
|
||||
"i686" | "i586" | "i486" | "i386" => "x86",
|
||||
"aarch64" | "arm64" => "aarch64",
|
||||
"arm" | "armv7" | "armv7l" => "arm",
|
||||
"riscv64" => "riscv64",
|
||||
"riscv32" => "riscv32",
|
||||
"powerpc64" | "ppc64" => "ppc64",
|
||||
"powerpc" | "ppc" => "ppc",
|
||||
"mips64" => "mips64",
|
||||
"mips" => "mips",
|
||||
_ => arch,
|
||||
}
|
||||
/// Derive the lib32 target triple while preserving the explicit x86 variant.
|
||||
pub fn lib32_target_triple(host: &str) -> String {
|
||||
let mut parts = host.splitn(2, '-');
|
||||
let arch = parts.next().unwrap_or(host);
|
||||
let rest = parts.next();
|
||||
let lib32_arch = match arch {
|
||||
"x86_64" | "amd64" => "i686",
|
||||
"i686" | "i586" | "i486" | "i386" => arch,
|
||||
_ => arch,
|
||||
};
|
||||
|
||||
match rest {
|
||||
Some(rest) if !rest.is_empty() => format!("{lib32_arch}-{rest}"),
|
||||
_ => lib32_arch.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::lib32_target_triple;
|
||||
|
||||
#[test]
|
||||
fn lib32_target_triple_preserves_explicit_x86_variant() {
|
||||
assert_eq!(
|
||||
lib32_target_triple("x86_64-sfg-linux-gnu"),
|
||||
"i686-sfg-linux-gnu"
|
||||
);
|
||||
assert_eq!(
|
||||
lib32_target_triple("i686-sfg-linux-gnu"),
|
||||
"i686-sfg-linux-gnu"
|
||||
);
|
||||
assert_eq!(
|
||||
lib32_target_triple("i586-sfg-linux-gnu"),
|
||||
"i586-sfg-linux-gnu"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
use anyhow::{Context, Result, bail};
|
||||
use signal_hook::consts::SIGINT;
|
||||
use std::fs;
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, ExitStatus};
|
||||
use std::sync::Once;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
|
||||
static INSTALL_HANDLER: Once = Once::new();
|
||||
static INSTALL_ERROR: OnceLock<String> = OnceLock::new();
|
||||
|
||||
pub(crate) fn install() -> Result<()> {
|
||||
INSTALL_HANDLER.call_once(|| {
|
||||
if let Err(err) = unsafe {
|
||||
signal_hook::low_level::register(SIGINT, || {
|
||||
INTERRUPTED.store(true, Ordering::Relaxed);
|
||||
})
|
||||
} {
|
||||
let _ = INSTALL_ERROR.set(err.to_string());
|
||||
};
|
||||
});
|
||||
|
||||
if let Some(err) = INSTALL_ERROR.get() {
|
||||
bail!("Failed to register Ctrl-C handler: {}", err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn reset() {
|
||||
INTERRUPTED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub(crate) fn was_interrupted() -> bool {
|
||||
INTERRUPTED.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn check() -> Result<()> {
|
||||
install()?;
|
||||
check_with(was_interrupted)
|
||||
}
|
||||
|
||||
pub(crate) fn command_status(cmd: &mut Command) -> io::Result<ExitStatus> {
|
||||
install().map_err(io::Error::other)?;
|
||||
configure_child_process_group(cmd);
|
||||
let mut child = cmd.spawn()?;
|
||||
wait_for_child_with(&mut child, was_interrupted)
|
||||
}
|
||||
|
||||
pub(crate) fn copy_interruptibly<R: Read, W: Write>(
|
||||
reader: &mut R,
|
||||
writer: &mut W,
|
||||
) -> io::Result<u64> {
|
||||
install().map_err(io::Error::other)?;
|
||||
copy_interruptibly_with(reader, writer, was_interrupted)
|
||||
}
|
||||
|
||||
pub(crate) fn unpack_tar_archive<R: Read>(
|
||||
archive: &mut tar::Archive<R>,
|
||||
dest: &Path,
|
||||
) -> Result<()> {
|
||||
install()?;
|
||||
unpack_tar_archive_with(archive, dest, was_interrupted)
|
||||
}
|
||||
|
||||
fn check_with(interrupted: impl Fn() -> bool) -> Result<()> {
|
||||
if interrupted() {
|
||||
bail!("Interrupted by Ctrl-C");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_interruptibly_with<R: Read, W: Write>(
|
||||
reader: &mut R,
|
||||
writer: &mut W,
|
||||
interrupted: impl Fn() -> bool,
|
||||
) -> io::Result<u64> {
|
||||
let mut copied = 0u64;
|
||||
let mut buf = [0u8; 64 * 1024];
|
||||
loop {
|
||||
if interrupted() {
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::Interrupted,
|
||||
"Interrupted by Ctrl-C",
|
||||
));
|
||||
}
|
||||
let n = reader.read(&mut buf)?;
|
||||
if n == 0 {
|
||||
return Ok(copied);
|
||||
}
|
||||
writer.write_all(&buf[..n])?;
|
||||
copied += n as u64;
|
||||
}
|
||||
}
|
||||
|
||||
fn unpack_tar_archive_with<R: Read>(
|
||||
archive: &mut tar::Archive<R>,
|
||||
dest: &Path,
|
||||
interrupted: impl Fn() -> bool,
|
||||
) -> Result<()> {
|
||||
fs::create_dir_all(dest)
|
||||
.with_context(|| format!("Failed to create extraction dir {}", dest.display()))?;
|
||||
for entry in archive
|
||||
.entries()
|
||||
.context("Failed to iterate tar archive entries")?
|
||||
{
|
||||
check_with(&interrupted)?;
|
||||
let mut entry = entry.context("Failed to read tar archive entry")?;
|
||||
let path = entry
|
||||
.path()
|
||||
.context("Failed to inspect tar archive entry path")?
|
||||
.into_owned();
|
||||
if !entry
|
||||
.unpack_in(dest)
|
||||
.with_context(|| format!("Failed to unpack tar archive entry {}", path.display()))?
|
||||
{
|
||||
bail!(
|
||||
"Refusing to extract unsafe tar archive entry outside destination: {}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn configure_child_process_group(cmd: &mut Command) {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
// Put the child in its own process group so Ctrl-C can be forwarded to the
|
||||
// whole build/extract pipeline without killing the depot parent process.
|
||||
unsafe {
|
||||
cmd.pre_exec(|| {
|
||||
if nix::libc::setpgid(0, 0) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn configure_child_process_group(_cmd: &mut Command) {}
|
||||
|
||||
fn wait_for_child_with(
|
||||
child: &mut Child,
|
||||
interrupted: impl Fn() -> bool,
|
||||
) -> io::Result<ExitStatus> {
|
||||
let mut interrupted_at: Option<Instant> = None;
|
||||
let mut sent_term = false;
|
||||
let mut sent_kill = false;
|
||||
|
||||
loop {
|
||||
if let Some(status) = child.try_wait()? {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
if interrupted() {
|
||||
if interrupted_at.is_none() {
|
||||
interrupted_at = Some(Instant::now());
|
||||
signal_child_process_group(child, nix::libc::SIGINT);
|
||||
} else if interrupted_at
|
||||
.is_some_and(|started| started.elapsed() >= Duration::from_secs(2) && !sent_term)
|
||||
{
|
||||
sent_term = true;
|
||||
signal_child_process_group(child, nix::libc::SIGTERM);
|
||||
} else if interrupted_at
|
||||
.is_some_and(|started| started.elapsed() >= Duration::from_secs(4) && !sent_kill)
|
||||
{
|
||||
sent_kill = true;
|
||||
signal_child_process_group(child, nix::libc::SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn signal_child_process_group(child: &Child, signal: i32) {
|
||||
let pgid = -(child.id() as i32);
|
||||
let rc = unsafe { nix::libc::kill(pgid, signal) };
|
||||
if rc == 0 {
|
||||
return;
|
||||
}
|
||||
let err = io::Error::last_os_error();
|
||||
if err.raw_os_error() != Some(nix::libc::ESRCH) {
|
||||
crate::log_warn!(
|
||||
"Failed to forward signal {} to child process group {}: {}",
|
||||
signal,
|
||||
child.id(),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn signal_child_process_group(_child: &Child, _signal: i32) {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn copy_interruptibly_returns_interrupted_error() {
|
||||
let mut reader = Cursor::new(b"hello".to_vec());
|
||||
let mut writer = Vec::new();
|
||||
let interrupted = AtomicBool::new(true);
|
||||
let err = copy_interruptibly_with(&mut reader, &mut writer, || {
|
||||
interrupted.load(Ordering::Relaxed)
|
||||
})
|
||||
.expect_err("copy should stop once interrupted");
|
||||
assert_eq!(err.kind(), ErrorKind::Interrupted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpack_tar_archive_stops_when_interrupted() {
|
||||
let mut tar_data = Vec::new();
|
||||
{
|
||||
let mut builder = tar::Builder::new(&mut tar_data);
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_path("hello.txt").unwrap();
|
||||
header.set_size(5);
|
||||
header.set_mode(0o644);
|
||||
header.set_cksum();
|
||||
builder
|
||||
.append(&header, Cursor::new(b"hello".to_vec()))
|
||||
.unwrap();
|
||||
builder.finish().unwrap();
|
||||
}
|
||||
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let mut archive = tar::Archive::new(Cursor::new(tar_data));
|
||||
let interrupted = AtomicBool::new(true);
|
||||
let err = unpack_tar_archive_with(&mut archive, temp.path(), || {
|
||||
interrupted.load(Ordering::Relaxed)
|
||||
})
|
||||
.expect_err("tar unpack should stop once interrupted");
|
||||
assert!(err.to_string().contains("Interrupted by Ctrl-C"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn command_status_interrupts_child_process() {
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
|
||||
let interrupted = Arc::new(AtomicBool::new(false));
|
||||
let trigger_flag = interrupted.clone();
|
||||
let trigger = std::thread::spawn(move || {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
trigger_flag.store(true, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c").arg("sleep 10");
|
||||
configure_child_process_group(&mut cmd);
|
||||
let mut child = cmd.spawn().expect("sleep command should spawn");
|
||||
let status = wait_for_child_with(&mut child, || interrupted.load(Ordering::Relaxed))
|
||||
.expect("sleep command should be interruptible");
|
||||
trigger.join().unwrap();
|
||||
|
||||
assert!(start.elapsed() < Duration::from_secs(3));
|
||||
assert_eq!(status.signal(), Some(SIGINT));
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ mod deps;
|
||||
mod fakeroot;
|
||||
mod index;
|
||||
mod install;
|
||||
mod interrupts;
|
||||
mod locking;
|
||||
mod metadata_time;
|
||||
mod package;
|
||||
|
||||
@@ -942,6 +942,19 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
||||
),
|
||||
);
|
||||
}
|
||||
if !spec.build.flags.rustltoflags.is_empty() {
|
||||
flags_tbl.insert(
|
||||
"rustltoflags".into(),
|
||||
Value::Array(
|
||||
spec.build
|
||||
.flags
|
||||
.rustltoflags
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !spec.build.flags.keep.is_empty() {
|
||||
flags_tbl.insert(
|
||||
"keep".into(),
|
||||
|
||||
@@ -472,6 +472,17 @@ impl PackageSpec {
|
||||
self.build.flags.ltoflags = vec![s.to_string()];
|
||||
}
|
||||
}
|
||||
"rustltoflags" | "rust_ltoflags" | "rust-ltoflags" => {
|
||||
if let Some(arr) = v.as_array() {
|
||||
self.build.flags.rustltoflags = arr
|
||||
.iter()
|
||||
.filter_map(|x| x.as_str())
|
||||
.map(String::from)
|
||||
.collect();
|
||||
} else if let Some(s) = v.as_str() {
|
||||
self.build.flags.rustltoflags = vec![s.to_string()];
|
||||
}
|
||||
}
|
||||
"replace_ltoflags" | "replace_lto-flags" | "replace_lto_flags" => {
|
||||
if let Some(arr) = v.as_array() {
|
||||
self.build.flags.replace_ltoflags = arr
|
||||
@@ -1067,6 +1078,18 @@ impl PackageSpec {
|
||||
}
|
||||
}
|
||||
}
|
||||
"rustltoflags" | "rust_ltoflags" | "rust-ltoflags" => {
|
||||
for v in values {
|
||||
if let Some(arr) = v.as_array() {
|
||||
self.build
|
||||
.flags
|
||||
.rustltoflags
|
||||
.extend(arr.iter().filter_map(|x| x.as_str()).map(String::from));
|
||||
} else if let Some(s) = v.as_str() {
|
||||
self.build.flags.rustltoflags.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
"replace_ltoflags" | "replace_lto-flags" | "replace_lto_flags" => {
|
||||
for v in values {
|
||||
if let Some(arr) = v.as_array() {
|
||||
@@ -2316,6 +2339,7 @@ make_dirs = ["lib"]
|
||||
make_test_dirs = ["tests"]
|
||||
make_install_dirs = ["lib"]
|
||||
ltoflags = ["-flto=auto"]
|
||||
RUSTLTOFLAGS = ["-Clinker-plugin-lto"]
|
||||
replace_ltoflags = ["auto=>thin"]
|
||||
rustflags = ["-C", "debuginfo=2"]
|
||||
replace_rustflags = ["debuginfo=2=>opt-level=2"]
|
||||
@@ -2518,6 +2542,12 @@ post_configure = ["echo configured"]
|
||||
.ltoflags
|
||||
.contains(&"-flto=auto".to_string())
|
||||
);
|
||||
assert!(
|
||||
spec.build
|
||||
.flags
|
||||
.rustltoflags
|
||||
.contains(&"-Clinker-plugin-lto".to_string())
|
||||
);
|
||||
assert!(
|
||||
spec.build
|
||||
.flags
|
||||
@@ -3868,6 +3898,18 @@ pub struct BuildFlags {
|
||||
deserialize_with = "deserialize_string_or_array"
|
||||
)]
|
||||
pub ltoflags: Vec<String>,
|
||||
/// Rust LTO flags exported to `RUSTLTOFLAGS`.
|
||||
///
|
||||
/// When `use_lto` is true (default), these flags are also appended to
|
||||
/// `RUSTFLAGS`.
|
||||
#[serde(
|
||||
default,
|
||||
alias = "rust-ltoflags",
|
||||
alias = "rust_ltoflags",
|
||||
alias = "RUSTLTOFLAGS",
|
||||
deserialize_with = "deserialize_string_or_array"
|
||||
)]
|
||||
pub rustltoflags: Vec<String>,
|
||||
/// Ordered replacement rules applied to `ltoflags` before export/injection.
|
||||
#[serde(
|
||||
default,
|
||||
@@ -4236,6 +4278,7 @@ impl Default for BuildFlags {
|
||||
ldflags: Vec::new(),
|
||||
replace_ldflags: Vec::new(),
|
||||
ltoflags: Vec::new(),
|
||||
rustltoflags: Vec::new(),
|
||||
replace_ltoflags: Vec::new(),
|
||||
keep: Vec::new(),
|
||||
split_docs: false,
|
||||
|
||||
+123
-11
@@ -9,6 +9,7 @@ use lzma_rust2::{LzipReader, LzmaReader};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::os::unix::fs as unix_fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use tempfile::{NamedTempFile, tempdir};
|
||||
@@ -42,6 +43,7 @@ pub fn extract_archive(
|
||||
source: &Source,
|
||||
build_dir: &Path,
|
||||
) -> Result<PathBuf> {
|
||||
crate::interrupts::install()?;
|
||||
let extract_dir_name = spec.expand_vars(&source.extract_dir);
|
||||
let extract_path = build_dir.join(&extract_dir_name);
|
||||
|
||||
@@ -157,7 +159,7 @@ fn extract_zip(path: &Path, dest: &Path) -> Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let file = File::open(path)?;
|
||||
let mut archive = zip::ZipArchive::new(file)?;
|
||||
archive.extract(tmp.path())?;
|
||||
extract_zip_archive(&mut archive, tmp.path())?;
|
||||
finalize_extracted_tree(tmp.path(), dest)
|
||||
}
|
||||
|
||||
@@ -190,6 +192,20 @@ fn extract_tar_compress(path: &Path, dest: &Path) -> Result<()> {
|
||||
let mut child = Command::new("gzip");
|
||||
child.arg("-cd").arg(path);
|
||||
child.stdout(Stdio::piped());
|
||||
crate::interrupts::check()?;
|
||||
// Run gzip in its own process group so Ctrl-C can interrupt decompression too.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::CommandExt;
|
||||
unsafe {
|
||||
child.pre_exec(|| {
|
||||
if nix::libc::setpgid(0, 0) != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut child = child.spawn().with_context(|| {
|
||||
format!(
|
||||
"Failed to spawn gzip for .Z decompression: {}",
|
||||
@@ -201,13 +217,12 @@ fn extract_tar_compress(path: &Path, dest: &Path) -> Result<()> {
|
||||
.take()
|
||||
.context("Failed to capture gzip stdout")?;
|
||||
let mut archive = tar::Archive::new(stdout);
|
||||
archive
|
||||
.unpack(tmp.path())
|
||||
.with_context(|| format!("Failed to unpack .tar.Z archive {}", path.display()))?;
|
||||
let unpack_result = crate::interrupts::unpack_tar_archive(&mut archive, tmp.path())
|
||||
.with_context(|| format!("Failed to unpack .tar.Z archive {}", path.display()));
|
||||
drop(archive);
|
||||
let status = child
|
||||
.wait()
|
||||
let status = wait_for_child_interruptibly(&mut child)
|
||||
.with_context(|| format!("Failed waiting for gzip on {}", path.display()))?;
|
||||
unpack_result?;
|
||||
if !status.success() {
|
||||
bail!("gzip failed while decompressing {}", path.display());
|
||||
}
|
||||
@@ -224,7 +239,7 @@ fn extract_cpio(path: &Path, dest: &Path) -> Result<()> {
|
||||
fn extract_tar_reader<R: Read>(reader: R, dest: &Path) -> Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let mut archive = tar::Archive::new(reader);
|
||||
archive.unpack(tmp.path())?;
|
||||
crate::interrupts::unpack_tar_archive(&mut archive, tmp.path())?;
|
||||
finalize_extracted_tree(tmp.path(), dest)
|
||||
}
|
||||
|
||||
@@ -281,7 +296,7 @@ fn extract_gz_file(path: &Path, dest: &Path) -> Result<()> {
|
||||
|
||||
fs::create_dir_all(dest)?;
|
||||
let mut out = File::create(dest.join(out_name))?;
|
||||
std::io::copy(&mut decoder, &mut out)?;
|
||||
crate::interrupts::copy_interruptibly(&mut decoder, &mut out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -302,7 +317,7 @@ fn extract_xz_file(path: &Path, dest: &Path) -> Result<()> {
|
||||
|
||||
fs::create_dir_all(dest)?;
|
||||
let mut out = File::create(dest.join(out_name))?;
|
||||
std::io::copy(&mut decoder, &mut out)?;
|
||||
crate::interrupts::copy_interruptibly(&mut decoder, &mut out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -323,7 +338,7 @@ fn extract_zst_file(path: &Path, dest: &Path) -> Result<()> {
|
||||
|
||||
fs::create_dir_all(dest)?;
|
||||
let mut out = File::create(dest.join(out_name))?;
|
||||
std::io::copy(&mut decoder, &mut out)?;
|
||||
crate::interrupts::copy_interruptibly(&mut decoder, &mut out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -333,13 +348,14 @@ fn extract_deb(path: &Path, dest: &Path) -> Result<()> {
|
||||
let mut ar = ar::Archive::new(file);
|
||||
|
||||
while let Some(entry_result) = ar.next_entry() {
|
||||
crate::interrupts::check()?;
|
||||
let mut entry = entry_result?;
|
||||
let id = String::from_utf8_lossy(entry.header().identifier()).to_string();
|
||||
let lower = id.to_ascii_lowercase();
|
||||
if lower.starts_with("data.tar") {
|
||||
// write the inner member to a temporary file and reuse tar extraction logic
|
||||
let mut tmpf = NamedTempFile::new()?;
|
||||
std::io::copy(&mut entry, &mut tmpf)?;
|
||||
crate::interrupts::copy_interruptibly(&mut entry, &mut tmpf)?;
|
||||
let tmp_path = tmpf.path().to_path_buf();
|
||||
|
||||
if lower.ends_with(".gz") {
|
||||
@@ -364,6 +380,7 @@ fn extract_deb(path: &Path, dest: &Path) -> Result<()> {
|
||||
fn extract_cpio_newc_from_reader<R: Read>(mut r: R, dest: &Path) -> Result<()> {
|
||||
use std::str;
|
||||
loop {
|
||||
crate::interrupts::check()?;
|
||||
// read 6-byte magic
|
||||
let mut magic = [0u8; 6];
|
||||
if let Err(e) = r.read_exact(&mut magic) {
|
||||
@@ -437,6 +454,7 @@ fn extract_cpio_newc_from_reader<R: Read>(mut r: R, dest: &Path) -> Result<()> {
|
||||
let mut remaining = filesize;
|
||||
let mut buf = [0u8; 8192];
|
||||
while remaining > 0 {
|
||||
crate::interrupts::check()?;
|
||||
let to_read = std::cmp::min(remaining, buf.len());
|
||||
let n = r.read(&mut buf[..to_read])?;
|
||||
if n == 0 {
|
||||
@@ -466,6 +484,7 @@ fn extract_cpio_newc_from_reader<R: Read>(mut r: R, dest: &Path) -> Result<()> {
|
||||
|
||||
fn extract_rpm(path: &Path, dest: &Path) -> Result<()> {
|
||||
// Read entire file and search for compression/cpio magic
|
||||
crate::interrupts::check()?;
|
||||
let data = std::fs::read(path)?;
|
||||
|
||||
// search for known signatures
|
||||
@@ -520,6 +539,7 @@ fn find_subslice(hay: &[u8], needle: &[u8]) -> Option<usize> {
|
||||
fn move_dir_contents(src: &Path, dest: &Path) -> Result<()> {
|
||||
fs::create_dir_all(dest)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
crate::interrupts::check()?;
|
||||
let entry = entry?;
|
||||
let src_path = entry.path();
|
||||
let dest_path = dest.join(entry.file_name());
|
||||
@@ -580,6 +600,7 @@ fn copy_file_preserve_metadata(src: &Path, dst: &Path) -> Result<()> {
|
||||
|
||||
fn copy_dir_recursive_local(src: &Path, dst: &Path) -> Result<()> {
|
||||
for entry in WalkDir::new(src) {
|
||||
crate::interrupts::check()?;
|
||||
let entry = entry?;
|
||||
let rel = entry.path().strip_prefix(src).unwrap();
|
||||
let target = dst.join(rel);
|
||||
@@ -598,6 +619,97 @@ fn copy_dir_recursive_local(src: &Path, dst: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_zip_archive<R: Read + std::io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
dest: &Path,
|
||||
) -> Result<()> {
|
||||
for index in 0..archive.len() {
|
||||
crate::interrupts::check()?;
|
||||
let mut entry = archive.by_index(index)?;
|
||||
let enclosed = entry
|
||||
.enclosed_name()
|
||||
.with_context(|| format!("Zip archive entry has unsafe path: {}", entry.name()))?;
|
||||
let out_path = dest.join(enclosed);
|
||||
|
||||
if entry.is_dir() {
|
||||
fs::create_dir_all(&out_path)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mode = entry.unix_mode().unwrap_or(0);
|
||||
if (mode & 0o170000) == 0o120000 {
|
||||
if let Some(parent) = out_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut target = Vec::new();
|
||||
crate::interrupts::copy_interruptibly(&mut entry, &mut target)?;
|
||||
let target = String::from_utf8(target).context("Zip symlink target was not UTF-8")?;
|
||||
let _ = fs::remove_file(&out_path);
|
||||
unix_fs::symlink(target, &out_path)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(parent) = out_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut out = File::create(&out_path)?;
|
||||
crate::interrupts::copy_interruptibly(&mut entry, &mut out)?;
|
||||
if mode != 0 {
|
||||
fs::set_permissions(&out_path, fs::Permissions::from_mode(mode & 0o7777))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_for_child_interruptibly(
|
||||
child: &mut std::process::Child,
|
||||
) -> std::io::Result<std::process::ExitStatus> {
|
||||
let mut interrupted_at = None;
|
||||
let mut sent_term = false;
|
||||
let mut sent_kill = false;
|
||||
|
||||
loop {
|
||||
if let Some(status) = child.try_wait()? {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
if crate::interrupts::was_interrupted() {
|
||||
if interrupted_at.is_none() {
|
||||
interrupted_at = Some(std::time::Instant::now());
|
||||
signal_child_group(child, nix::libc::SIGINT);
|
||||
} else if interrupted_at.is_some_and(|started| {
|
||||
started.elapsed() >= std::time::Duration::from_secs(2) && !sent_term
|
||||
}) {
|
||||
sent_term = true;
|
||||
signal_child_group(child, nix::libc::SIGTERM);
|
||||
} else if interrupted_at.is_some_and(|started| {
|
||||
started.elapsed() >= std::time::Duration::from_secs(4) && !sent_kill
|
||||
}) {
|
||||
sent_kill = true;
|
||||
signal_child_group(child, nix::libc::SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
fn signal_child_group(child: &std::process::Child, signal: i32) {
|
||||
let rc = unsafe { nix::libc::kill(-(child.id() as i32), signal) };
|
||||
if rc != 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() != Some(nix::libc::ESRCH) {
|
||||
crate::log_warn!(
|
||||
"Failed to forward signal {} to extractor child process group {}: {}",
|
||||
signal,
|
||||
child.id(),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+15
-23
@@ -68,13 +68,13 @@ fn apply_patches(
|
||||
crate::log_info!(" patch: {}", patch_path.display());
|
||||
|
||||
// Apply with patch(1). Keep it simple: -p1 is the common case.
|
||||
let status = Command::new("patch")
|
||||
.current_dir(src_dir)
|
||||
.env("PATH", crate::runtime_env::safe_script_path())
|
||||
.arg("-p1")
|
||||
.arg("-i")
|
||||
.arg(&patch_path)
|
||||
.status()
|
||||
let mut patch_cmd = Command::new("patch");
|
||||
patch_cmd.current_dir(src_dir);
|
||||
patch_cmd.env("PATH", crate::runtime_env::safe_script_path());
|
||||
patch_cmd.arg("-p1");
|
||||
patch_cmd.arg("-i");
|
||||
patch_cmd.arg(&patch_path);
|
||||
let status = crate::interrupts::command_status(&mut patch_cmd)
|
||||
.with_context(|| format!("Failed to execute patch for {}", patch_path.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
@@ -107,10 +107,8 @@ fn run_post_extract_commands(spec: &PackageSpec, source: &Source, src_dir: &Path
|
||||
let mut shell_cmd = Command::new("sh");
|
||||
shell_cmd.current_dir(src_dir);
|
||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||
let status = shell_cmd
|
||||
.arg("-c")
|
||||
.arg(&wrapped_cmd)
|
||||
.status()
|
||||
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||
.with_context(|| format!("Failed to run post_extract command: {}", cmd_str))?;
|
||||
|
||||
if !status.success() {
|
||||
@@ -149,10 +147,8 @@ pub fn run_post_configure_commands(
|
||||
let mut shell_cmd = Command::new("sh");
|
||||
shell_cmd.current_dir(src_dir);
|
||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||
let status = shell_cmd
|
||||
.arg("-c")
|
||||
.arg(&wrapped_cmd)
|
||||
.status()
|
||||
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||
.with_context(|| format!("Failed to run post_configure command: {}", cmd_str))?;
|
||||
|
||||
if !status.success() {
|
||||
@@ -187,10 +183,8 @@ pub fn run_post_compile_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P
|
||||
let mut shell_cmd = Command::new("sh");
|
||||
shell_cmd.current_dir(src_dir);
|
||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||
let status = shell_cmd
|
||||
.arg("-c")
|
||||
.arg(&wrapped_cmd)
|
||||
.status()
|
||||
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||
.with_context(|| format!("Failed to run post_compile command: {}", cmd_str))?;
|
||||
|
||||
if !status.success() {
|
||||
@@ -229,10 +223,8 @@ pub fn run_post_install_commands_in_dir(
|
||||
let mut shell_cmd = Command::new("sh");
|
||||
shell_cmd.current_dir(work_dir);
|
||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||
let status = shell_cmd
|
||||
.arg("-c")
|
||||
.arg(&wrapped_cmd)
|
||||
.status()
|
||||
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||
.with_context(|| format!("Failed to run post_install command: {}", cmd_str))?;
|
||||
|
||||
if !status.success() {
|
||||
|
||||
@@ -296,6 +296,7 @@ pub fn prepare(spec: &PackageSpec, cache_dir: &Path, build_dir: &Path) -> Result
|
||||
let mut primary: Option<PathBuf> = None;
|
||||
|
||||
for (idx, src) in spec.sources().iter().enumerate() {
|
||||
crate::interrupts::check()?;
|
||||
let src_dir = prepare_one(spec, src, cache_dir, build_dir)
|
||||
.with_context(|| format!("Failed to prepare source #{}", idx + 1))?;
|
||||
if idx == 0 {
|
||||
@@ -409,6 +410,7 @@ fn prepare_one(
|
||||
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
|
||||
fs::create_dir_all(dst)?;
|
||||
for entry in WalkDir::new(src) {
|
||||
crate::interrupts::check()?;
|
||||
let entry = entry?;
|
||||
let rel = entry.path().strip_prefix(src).unwrap();
|
||||
let target = dst.join(rel);
|
||||
|
||||
Reference in New Issue
Block a user