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]]
|
[[package]]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.22.1"
|
version = "0.23.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ar",
|
"ar",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.22.1"
|
version = "0.23.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ ldflags = ["-Wl,-O1"]
|
|||||||
|
|
||||||
# Default LTOFLAGS and automatic injection into CFLAGS/CXXFLAGS/LDFLAGS.
|
# Default LTOFLAGS and automatic injection into CFLAGS/CXXFLAGS/LDFLAGS.
|
||||||
ltoflags = ["-flto=auto"]
|
ltoflags = ["-flto=auto"]
|
||||||
|
# Rust-specific LTO flags appended only to RUSTFLAGS when use_lto is enabled.
|
||||||
|
#RUSTLTOFLAGS = ["-Clinker-plugin-lto"]
|
||||||
use_lto = true
|
use_lto = true
|
||||||
|
|
||||||
# CARCH short name (can be overridden per-package)
|
# CARCH short name (can be overridden per-package)
|
||||||
|
|||||||
+20
-1
@@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'depot',
|
'depot',
|
||||||
version: '0.22.1',
|
version: '0.23.0',
|
||||||
meson_version: '>=0.60.0',
|
meson_version: '>=0.60.0',
|
||||||
default_options: ['buildtype=release'],
|
default_options: ['buildtype=release'],
|
||||||
)
|
)
|
||||||
@@ -10,6 +10,8 @@ fs = import('fs')
|
|||||||
cargo = find_program('cargo', required: true)
|
cargo = find_program('cargo', required: true)
|
||||||
find_prog = find_program('find', required: true)
|
find_prog = find_program('find', required: true)
|
||||||
sh = find_program('sh', 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()
|
src_root = meson.project_source_root()
|
||||||
build_root = meson.project_build_root()
|
build_root = meson.project_build_root()
|
||||||
@@ -72,6 +74,23 @@ depot_bin = custom_target(
|
|||||||
install_dir: get_option('bindir'),
|
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(
|
test(
|
||||||
'cargo-test',
|
'cargo-test',
|
||||||
sh,
|
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);
|
configure_cmd.arg(expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = configure_cmd
|
let status = crate::interrupts::command_status(&mut configure_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run configure in {}", build_dir.display()))?;
|
.with_context(|| format!("Failed to run configure in {}", build_dir.display()))?;
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
@@ -164,7 +163,7 @@ pub fn build(
|
|||||||
|
|
||||||
crate::builder::prepare_tool_command(&mut make_cmd, &env_vars);
|
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())
|
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);
|
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||||
|
|
||||||
let test_targets_display = test_targets.join(" ");
|
let test_targets_display = test_targets.join(" ");
|
||||||
let status = test_cmd.status().with_context(|| {
|
let status =
|
||||||
format!(
|
crate::interrupts::command_status(&mut test_cmd).with_context(|| {
|
||||||
"Failed to run {} {} in {}",
|
format!(
|
||||||
make_exec,
|
"Failed to run {} {} in {}",
|
||||||
test_targets_display,
|
make_exec,
|
||||||
test_dir.display()
|
test_targets_display,
|
||||||
)
|
test_dir.display()
|
||||||
})?;
|
)
|
||||||
|
})?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"{} {} failed with status: {} (dir: {})",
|
"{} {} failed with status: {} (dir: {})",
|
||||||
@@ -344,15 +344,16 @@ pub fn build(
|
|||||||
));
|
));
|
||||||
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
||||||
|
|
||||||
let status = install_cmd.status().with_context(|| {
|
let status =
|
||||||
format!(
|
crate::interrupts::command_status(&mut install_cmd).with_context(|| {
|
||||||
"Failed to run {} {} for {} in {}",
|
format!(
|
||||||
make_exec,
|
"Failed to run {} {} for {} in {}",
|
||||||
install_targets.join(" "),
|
make_exec,
|
||||||
spec.package.name,
|
install_targets.join(" "),
|
||||||
install_dir.display()
|
spec.package.name,
|
||||||
)
|
install_dir.display()
|
||||||
})?;
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
|
|||||||
+117
-13
@@ -63,6 +63,9 @@ pub fn build(
|
|||||||
for arg in cmake_install_dir_args(flags) {
|
for arg in cmake_install_dir_args(flags) {
|
||||||
cmake_cmd.arg(arg);
|
cmake_cmd.arg(arg);
|
||||||
}
|
}
|
||||||
|
for arg in cmake_lib32_target_args(flags, cross) {
|
||||||
|
cmake_cmd.arg(arg);
|
||||||
|
}
|
||||||
|
|
||||||
// Add toolchain file for cross-compilation
|
// Add toolchain file for cross-compilation
|
||||||
if let Some(ref tf) = toolchain_file {
|
if let Some(ref tf) = toolchain_file {
|
||||||
@@ -90,7 +93,8 @@ pub fn build(
|
|||||||
|
|
||||||
crate::builder::prepare_tool_command(&mut cmake_cmd, &env_vars);
|
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() {
|
if !status.success() {
|
||||||
anyhow::bail!("cmake configure failed");
|
anyhow::bail!("cmake configure failed");
|
||||||
}
|
}
|
||||||
@@ -117,8 +121,7 @@ pub fn build(
|
|||||||
|
|
||||||
crate::builder::prepare_tool_command(&mut build_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut build_cmd, &env_vars);
|
||||||
|
|
||||||
let status = build_cmd
|
let status = crate::interrupts::command_status(&mut build_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run cmake build for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run cmake build for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("cmake build failed");
|
anyhow::bail!("cmake build failed");
|
||||||
@@ -139,12 +142,13 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||||
|
|
||||||
let status = test_cmd.status().with_context(|| {
|
let status =
|
||||||
format!(
|
crate::interrupts::command_status(&mut test_cmd).with_context(|| {
|
||||||
"Failed to run cmake build target(s) '{}' for {}",
|
format!(
|
||||||
joined, spec.package.name
|
"Failed to run cmake build target(s) '{}' for {}",
|
||||||
)
|
joined, spec.package.name
|
||||||
})?;
|
)
|
||||||
|
})?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("cmake test target(s) '{}' failed", joined);
|
anyhow::bail!("cmake test target(s) '{}' failed", joined);
|
||||||
}
|
}
|
||||||
@@ -157,8 +161,7 @@ pub fn build(
|
|||||||
test_cmd.arg("--output-on-failure");
|
test_cmd.arg("--output-on-failure");
|
||||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||||
|
|
||||||
let status = test_cmd
|
let status = crate::interrupts::command_status(&mut test_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run ctest for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run ctest for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("ctest failed");
|
anyhow::bail!("ctest failed");
|
||||||
@@ -203,8 +206,7 @@ pub fn build(
|
|||||||
));
|
));
|
||||||
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
||||||
|
|
||||||
let status = install_cmd
|
let status = crate::interrupts::command_status(&mut install_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run cmake install for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run cmake install for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
if !install_targets.is_empty() {
|
if !install_targets.is_empty() {
|
||||||
@@ -378,6 +380,55 @@ fn cmake_install_dir_args(flags: &crate::package::BuildFlags) -> Vec<String> {
|
|||||||
.collect()
|
.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 {
|
fn cmake_install_dir_value(prefix: &str, value: &str) -> String {
|
||||||
let trimmed_prefix = prefix.trim();
|
let trimmed_prefix = prefix.trim();
|
||||||
let trimmed_value = value.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]
|
#[test]
|
||||||
fn test_cmake_install_dir_args_respect_explicit_user_overrides() {
|
fn test_cmake_install_dir_args_respect_explicit_user_overrides() {
|
||||||
let flags = BuildFlags {
|
let flags = BuildFlags {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ pub fn build(
|
|||||||
crate::builder::prepare_tool_command(&mut cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut cmd, &env_vars);
|
||||||
|
|
||||||
// Run the command and include the OS error on spawn failures for clearer diagnostics
|
// 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!(
|
anyhow::anyhow!(
|
||||||
"Failed to run build script {}: {}",
|
"Failed to run build script {}: {}",
|
||||||
build_script.display(),
|
build_script.display(),
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ pub fn build(
|
|||||||
cmd.current_dir(src_dir);
|
cmd.current_dir(src_dir);
|
||||||
crate::builder::prepare_tool_command(&mut cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut cmd, &env_vars);
|
||||||
|
|
||||||
let status = cmd
|
let status = crate::interrupts::command_status(&mut cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run build command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run build command: {}", cmd_str))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("Build command failed: {}", cmd_str);
|
anyhow::bail!("Build command failed: {}", cmd_str);
|
||||||
@@ -89,8 +88,7 @@ pub fn build(
|
|||||||
));
|
));
|
||||||
crate::builder::prepare_tool_command(&mut cmd, &install_env);
|
crate::builder::prepare_tool_command(&mut cmd, &install_env);
|
||||||
|
|
||||||
let status = cmd
|
let status = crate::interrupts::command_status(&mut cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run install command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run install command: {}", cmd_str))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("Install command failed: {}", cmd_str);
|
anyhow::bail!("Install command failed: {}", cmd_str);
|
||||||
|
|||||||
+154
-8
@@ -29,9 +29,12 @@ pub fn build(
|
|||||||
// Environment variables
|
// Environment variables
|
||||||
let env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
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 {
|
let cross_file = if let Some(cc_cfg) = cross {
|
||||||
Some(cc_cfg.generate_meson_cross_file(&build_dir)?)
|
Some(cc_cfg.generate_meson_cross_file(&build_dir)?)
|
||||||
|
} else if flags.lib32_variant {
|
||||||
|
Some(generate_lib32_meson_cross_file(flags, &build_dir)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -56,7 +59,8 @@ pub fn build(
|
|||||||
|
|
||||||
crate::builder::prepare_tool_command(&mut meson_cmd, &env_vars);
|
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() {
|
if !status.success() {
|
||||||
anyhow::bail!("meson setup failed");
|
anyhow::bail!("meson setup failed");
|
||||||
}
|
}
|
||||||
@@ -76,8 +80,7 @@ pub fn build(
|
|||||||
|
|
||||||
crate::builder::prepare_tool_command(&mut ninja_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut ninja_cmd, &env_vars);
|
||||||
|
|
||||||
let status = ninja_cmd
|
let status = crate::interrupts::command_status(&mut ninja_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run ninja for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run ninja for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("ninja build failed");
|
anyhow::bail!("ninja build failed");
|
||||||
@@ -105,8 +108,7 @@ pub fn build(
|
|||||||
|
|
||||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||||
|
|
||||||
let status = test_cmd
|
let status = crate::interrupts::command_status(&mut test_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run meson test for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run meson test for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("meson test failed");
|
anyhow::bail!("meson test failed");
|
||||||
@@ -141,8 +143,7 @@ pub fn build(
|
|||||||
));
|
));
|
||||||
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
||||||
|
|
||||||
let status = install_cmd
|
let status = crate::interrupts::command_status(&mut install_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run meson install for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run meson install for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("meson install failed");
|
anyhow::bail!("meson install failed");
|
||||||
@@ -258,6 +259,109 @@ fn meson_setup_args(
|
|||||||
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)
|
/// Expand environment variables in a string (e.g., $DEPOT_SYSROOT)
|
||||||
fn expand_env_vars(input: &str) -> String {
|
fn expand_env_vars(input: &str) -> String {
|
||||||
let mut result = input.to_string();
|
let mut result = input.to_string();
|
||||||
@@ -439,6 +543,48 @@ mod tests {
|
|||||||
assert!(!args.iter().any(|a| a == "-Dcpp_ld=ld.lld"));
|
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]
|
#[test]
|
||||||
fn test_resolve_build_dir_uses_flag() {
|
fn test_resolve_build_dir_uses_flag() {
|
||||||
let flags = BuildFlags {
|
let flags = BuildFlags {
|
||||||
|
|||||||
+40
-2
@@ -194,12 +194,21 @@ fn compiler_flag_sets(
|
|||||||
(cflags, cxxflags, ldflags, ltoflags)
|
(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> {
|
pub(crate) fn effective_rustflags(flags: &crate::package::BuildFlags) -> Vec<String> {
|
||||||
replaced_flags(
|
let mut rustflags = replaced_flags(
|
||||||
&flags.rustflags,
|
&flags.rustflags,
|
||||||
&flags.replace_rustflags,
|
&flags.replace_rustflags,
|
||||||
"build.flags.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(
|
pub fn standard_build_env(
|
||||||
@@ -224,6 +233,10 @@ pub fn standard_build_env(
|
|||||||
if !ltoflags.is_empty() {
|
if !ltoflags.is_empty() {
|
||||||
set_env_var(&mut env_vars, "LTOFLAGS", ltoflags.join(" "));
|
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() {
|
let ldflags = if !ldflags.is_empty() || !flags.libc.is_empty() {
|
||||||
if 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"]);
|
let mut spec = mk_spec(vec!["-O2"], vec!["-Wl,--as-needed"]);
|
||||||
spec.build.flags.cxxflags = vec!["-O2".into()];
|
spec.build.flags.cxxflags = vec!["-O2".into()];
|
||||||
spec.build.flags.ltoflags = vec!["-flto=auto".into()];
|
spec.build.flags.ltoflags = vec!["-flto=auto".into()];
|
||||||
|
spec.build.flags.rustltoflags = vec!["-Clinker-plugin-lto".into()];
|
||||||
spec.build.flags.use_lto = false;
|
spec.build.flags.use_lto = false;
|
||||||
|
|
||||||
let env = standard_build_env(&spec, None, true, true);
|
let env = standard_build_env(&spec, None, true, true);
|
||||||
@@ -738,6 +752,12 @@ mod tests {
|
|||||||
.any(|(k, v)| k == "LTOFLAGS" && v == "-flto=auto"),
|
.any(|(k, v)| k == "LTOFLAGS" && v == "-flto=auto"),
|
||||||
"expected LTOFLAGS variable to be exported even when use_lto is false"
|
"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]
|
#[test]
|
||||||
@@ -778,6 +798,24 @@ mod tests {
|
|||||||
assert_eq!(effective_rustflags(&flags), vec!["-C", "opt-level=2"]);
|
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]
|
#[test]
|
||||||
fn test_standard_build_env_passthrough_does_not_override_default_vars() {
|
fn test_standard_build_env_passthrough_does_not_override_default_vars() {
|
||||||
let mut spec = mk_spec(Vec::new(), Vec::new());
|
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> {
|
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),
|
Ok(status) => Ok(status),
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
|
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||||
let Some(script) = resolved_script_path(cmd) else {
|
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),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,8 +273,7 @@ fn build_wheel_setup_py(
|
|||||||
.arg(dist_dir);
|
.arg(dist_dir);
|
||||||
crate::builder::prepare_tool_command(&mut cmd, &env_vars.to_vec());
|
crate::builder::prepare_tool_command(&mut cmd, &env_vars.to_vec());
|
||||||
|
|
||||||
let status = cmd
|
let status = crate::interrupts::command_status(&mut cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run setup.py in {}", src_dir.display()))?;
|
.with_context(|| format!("Failed to run setup.py in {}", src_dir.display()))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
bail!("setup.py bdist_wheel failed with status {}", status);
|
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);
|
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!(
|
format!(
|
||||||
"Failed to run python3 for PEP 517 build in {}",
|
"Failed to run python3 for PEP 517 build in {}",
|
||||||
src_dir.display()
|
src_dir.display()
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if !output.status.success() {
|
if !status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
let requires = if cfg.requires.is_empty() {
|
let requires = if cfg.requires.is_empty() {
|
||||||
"(none declared)".to_string()
|
"(none declared)".to_string()
|
||||||
} else {
|
} else {
|
||||||
cfg.requires.join(", ")
|
cfg.requires.join(", ")
|
||||||
};
|
};
|
||||||
bail!(
|
bail!(
|
||||||
"PEP 517 wheel build failed with status {}. Backend: {}. Declared build requirements: {}. stderr: {}",
|
"PEP 517 wheel build failed with status {}. Backend: {}. Declared build requirements: {}",
|
||||||
output.status,
|
status,
|
||||||
cfg.backend,
|
cfg.backend,
|
||||||
requires,
|
requires
|
||||||
stderr.trim()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -102,8 +102,7 @@ pub fn build(
|
|||||||
// Set environment
|
// Set environment
|
||||||
crate::builder::prepare_tool_command(&mut cargo_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut cargo_cmd, &env_vars);
|
||||||
|
|
||||||
let status = cargo_cmd
|
let status = crate::interrupts::command_status(&mut cargo_cmd)
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run cargo build for {}", spec.package.name))?;
|
.with_context(|| format!("Failed to run cargo build for {}", spec.package.name))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("cargo build failed");
|
anyhow::bail!("cargo build failed");
|
||||||
|
|||||||
+214
-25
@@ -9,14 +9,11 @@ use crate::{
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use git2::Direction;
|
use git2::Direction;
|
||||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||||
use signal_hook::consts::SIGINT;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use walkdir::WalkDir;
|
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 DEPOT_INSTALL_CONTEXT_ENV: &str = "DEPOT_INSTALL_CONTEXT";
|
||||||
const INSTALL_CONTEXT_UPDATE: &str = "update";
|
const INSTALL_CONTEXT_UPDATE: &str = "update";
|
||||||
const INSTALL_CONTEXT_PLANNED: &str = "planned";
|
const INSTALL_CONTEXT_PLANNED: &str = "planned";
|
||||||
|
const DEPOT_PACKAGE_NAME: &str = "depot";
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
enum InstallInvocationContext {
|
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
|
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> {
|
fn maybe_reexec_with_sudo(cli: &Cli) -> Result<bool> {
|
||||||
if !should_reexec_with_sudo(cli) {
|
if !should_reexec_with_sudo(cli) {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@@ -235,7 +282,7 @@ fn run_install_command_with_program(
|
|||||||
fn command_status_with_sh_fallback(
|
fn command_status_with_sh_fallback(
|
||||||
cmd: &mut std::process::Command,
|
cmd: &mut std::process::Command,
|
||||||
) -> std::io::Result<std::process::ExitStatus> {
|
) -> std::io::Result<std::process::ExitStatus> {
|
||||||
match cmd.status() {
|
match crate::interrupts::command_status(cmd) {
|
||||||
Ok(status) => Ok(status),
|
Ok(status) => Ok(status),
|
||||||
Err(err)
|
Err(err)
|
||||||
if err.kind() == std::io::ErrorKind::PermissionDenied
|
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),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
@@ -295,27 +342,21 @@ fn run_child_install_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct InterruptWatcher {
|
struct InterruptWatcher;
|
||||||
interrupted: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptWatcher {
|
impl InterruptWatcher {
|
||||||
fn install() -> Result<Self> {
|
fn install() -> Result<Self> {
|
||||||
let interrupted = Arc::new(AtomicBool::new(false));
|
crate::interrupts::install()?;
|
||||||
signal_hook::flag::register(SIGINT, interrupted.clone())
|
crate::interrupts::reset();
|
||||||
.context("Failed to register Ctrl-C handler")?;
|
Ok(Self)
|
||||||
Ok(Self { interrupted })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn was_interrupted(&self) -> bool {
|
fn was_interrupted(&self) -> bool {
|
||||||
self.interrupted.load(AtomicOrdering::Relaxed)
|
crate::interrupts::was_interrupted()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&self) -> Result<()> {
|
fn check(&self) -> Result<()> {
|
||||||
if self.was_interrupted() {
|
crate::interrupts::check()
|
||||||
anyhow::bail!("Interrupted by Ctrl-C");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +574,7 @@ fn current_process_env_vars() -> Vec<(String, String)> {
|
|||||||
"PYTHONNOUSERSITE",
|
"PYTHONNOUSERSITE",
|
||||||
"RANLIB",
|
"RANLIB",
|
||||||
"RUSTFLAGS",
|
"RUSTFLAGS",
|
||||||
|
"RUSTLTOFLAGS",
|
||||||
"SETUPTOOLS_USE_DISTUTILS",
|
"SETUPTOOLS_USE_DISTUTILS",
|
||||||
"STRIP",
|
"STRIP",
|
||||||
];
|
];
|
||||||
@@ -767,6 +809,7 @@ fn load_package_archive_into_staging(
|
|||||||
archive_path.display()
|
archive_path.display()
|
||||||
)
|
)
|
||||||
})? {
|
})? {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let mut entry = entry.with_context(|| {
|
let mut entry = entry.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to read archive entry from {}",
|
"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()))?;
|
.with_context(|| format!("Failed to read zstd stream {}", archive_path.display()))?;
|
||||||
let mut archive = tar::Archive::new(zstd_decoder);
|
let mut archive = tar::Archive::new(zstd_decoder);
|
||||||
archive.set_preserve_permissions(true);
|
archive.set_preserve_permissions(true);
|
||||||
archive.unpack(&extract_dir).with_context(|| {
|
crate::interrupts::unpack_tar_archive(&mut archive, &extract_dir).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to extract package archive {} into {}",
|
"Failed to extract package archive {} into {}",
|
||||||
archive_path.display(),
|
archive_path.display(),
|
||||||
@@ -836,7 +879,7 @@ fn extract_package_archive_to_staging(
|
|||||||
.with_context(|| format!("Failed to read zstd stream {}", archive_path.display()))?;
|
.with_context(|| format!("Failed to read zstd stream {}", archive_path.display()))?;
|
||||||
let mut archive = tar::Archive::new(zstd_decoder);
|
let mut archive = tar::Archive::new(zstd_decoder);
|
||||||
archive.set_preserve_permissions(true);
|
archive.set_preserve_permissions(true);
|
||||||
archive.unpack(&extract_dir).with_context(|| {
|
crate::interrupts::unpack_tar_archive(&mut archive, &extract_dir).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to extract package archive {} into {}",
|
"Failed to extract package archive {} into {}",
|
||||||
archive_path.display(),
|
archive_path.display(),
|
||||||
@@ -2514,8 +2557,6 @@ fn run_update_command(
|
|||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
options: UpdateCommandOptions<'_>,
|
options: UpdateCommandOptions<'_>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
sync_source_repositories_for_update(config)?;
|
|
||||||
|
|
||||||
let updates = collect_update_candidates(config, options.rootfs, packages)?;
|
let updates = collect_update_candidates(config, options.rootfs, packages)?;
|
||||||
if updates.is_empty() {
|
if updates.is_empty() {
|
||||||
ui::info("All installed packages are up to date.");
|
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);
|
cmd.arg("install").arg(path);
|
||||||
|
|
||||||
let status = cmd
|
let status = crate::interrupts::command_status(&mut cmd)
|
||||||
.status()
|
|
||||||
.context("Failed to spawn planned install step")?;
|
.context("Failed to spawn planned install step")?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
anyhow::bail!("Planned install step for '{}' failed", step.package);
|
anyhow::bail!("Planned install step for '{}' failed", step.package);
|
||||||
@@ -4080,6 +4120,8 @@ fn run_direct_install_request(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(cli: Cli) -> Result<()> {
|
pub fn run(cli: Cli) -> Result<()> {
|
||||||
|
crate::interrupts::install()?;
|
||||||
|
crate::interrupts::reset();
|
||||||
ui::set_assume_yes(command_assume_yes(&cli.command));
|
ui::set_assume_yes(command_assume_yes(&cli.command));
|
||||||
if maybe_reexec_with_sudo(&cli)? {
|
if maybe_reexec_with_sudo(&cli)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -4126,6 +4168,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
|
|
||||||
// Load configuration early so we can use configured repos/paths.
|
// Load configuration early so we can use configured repos/paths.
|
||||||
let config = config::Config::for_rootfs(&rootfs);
|
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 install_test_deps = install_test_deps_enabled(cli_test_deps, &config);
|
||||||
let mut planned_targets = Vec::new();
|
let mut planned_targets = Vec::new();
|
||||||
let mut planned_spec_paths = 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 dry_run = build_exec_args.dry_run;
|
||||||
let cli_lib32_only = lib32_args.lib32_only;
|
let cli_lib32_only = lib32_args.lib32_only;
|
||||||
warn_if_running_as_root_for_build("build", &rootfs);
|
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")?;
|
let spec_path = spec.or(spec_pos).context("No spec file provided")?;
|
||||||
ui::info(format!("Building package from: {}", spec_path.display()));
|
ui::info(format!("Building package from: {}", spec_path.display()));
|
||||||
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
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 install_test_deps = install_test_deps_enabled(cli_test_deps, &config);
|
||||||
let interrupt_watcher = if cleanup_deps {
|
let interrupt_watcher = if cleanup_deps {
|
||||||
Some(InterruptWatcher::install()?)
|
Some(InterruptWatcher::install()?)
|
||||||
@@ -4783,6 +4826,10 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
let clean = build_exec_args.clean;
|
let clean = build_exec_args.clean;
|
||||||
let dry_run = build_exec_args.dry_run;
|
let dry_run = build_exec_args.dry_run;
|
||||||
let config = config::Config::for_rootfs(&rootfs);
|
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(
|
run_update_command(
|
||||||
&packages,
|
&packages,
|
||||||
&config,
|
&config,
|
||||||
@@ -5422,6 +5469,57 @@ mod tests {
|
|||||||
spec_dir: PathBuf::from("."),
|
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 anyhow::Context;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
@@ -6154,6 +6252,97 @@ optional = []
|
|||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn collect_missing_update_dependencies_skips_planned_provides_and_installed_deps() -> Result<()>
|
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)
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
"#,
|
"#,
|
||||||
self.prefix,
|
self.prefix,
|
||||||
self.target_arch(),
|
target_arch_from_triple(&self.prefix),
|
||||||
self.cc,
|
self.cc,
|
||||||
self.cxx,
|
self.cxx,
|
||||||
self.ar,
|
self.ar,
|
||||||
@@ -169,8 +169,8 @@ endian = 'little'
|
|||||||
self.objcopy,
|
self.objcopy,
|
||||||
self.objdump,
|
self.objdump,
|
||||||
self.readelf,
|
self.readelf,
|
||||||
self.cpu_family(),
|
cpu_family_for_arch(target_arch_from_triple(&self.prefix)),
|
||||||
self.target_arch(),
|
target_arch_from_triple(&self.prefix),
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::create_dir_all(build_dir)?;
|
fs::create_dir_all(build_dir)?;
|
||||||
@@ -179,28 +179,65 @@ endian = 'little'
|
|||||||
|
|
||||||
Ok(cross_path)
|
Ok(cross_path)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract target architecture from prefix
|
/// Extract the leading architecture component from a target triple.
|
||||||
fn target_arch(&self) -> &str {
|
pub fn target_arch_from_triple(triple: &str) -> &str {
|
||||||
self.prefix.split('-').next().unwrap_or("unknown")
|
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
|
/// Derive the lib32 target triple while preserving the explicit x86 variant.
|
||||||
fn cpu_family(&self) -> &str {
|
pub fn lib32_target_triple(host: &str) -> String {
|
||||||
let arch = self.target_arch();
|
let mut parts = host.splitn(2, '-');
|
||||||
match arch {
|
let arch = parts.next().unwrap_or(host);
|
||||||
"x86_64" | "amd64" => "x86_64",
|
let rest = parts.next();
|
||||||
"i686" | "i586" | "i486" | "i386" => "x86",
|
let lib32_arch = match arch {
|
||||||
"aarch64" | "arm64" => "aarch64",
|
"x86_64" | "amd64" => "i686",
|
||||||
"arm" | "armv7" | "armv7l" => "arm",
|
"i686" | "i586" | "i486" | "i386" => arch,
|
||||||
"riscv64" => "riscv64",
|
_ => arch,
|
||||||
"riscv32" => "riscv32",
|
};
|
||||||
"powerpc64" | "ppc64" => "ppc64",
|
|
||||||
"powerpc" | "ppc" => "ppc",
|
match rest {
|
||||||
"mips64" => "mips64",
|
Some(rest) if !rest.is_empty() => format!("{lib32_arch}-{rest}"),
|
||||||
"mips" => "mips",
|
_ => lib32_arch.to_string(),
|
||||||
_ => arch,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 fakeroot;
|
||||||
mod index;
|
mod index;
|
||||||
mod install;
|
mod install;
|
||||||
|
mod interrupts;
|
||||||
mod locking;
|
mod locking;
|
||||||
mod metadata_time;
|
mod metadata_time;
|
||||||
mod package;
|
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() {
|
if !spec.build.flags.keep.is_empty() {
|
||||||
flags_tbl.insert(
|
flags_tbl.insert(
|
||||||
"keep".into(),
|
"keep".into(),
|
||||||
|
|||||||
@@ -472,6 +472,17 @@ impl PackageSpec {
|
|||||||
self.build.flags.ltoflags = vec![s.to_string()];
|
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" => {
|
"replace_ltoflags" | "replace_lto-flags" | "replace_lto_flags" => {
|
||||||
if let Some(arr) = v.as_array() {
|
if let Some(arr) = v.as_array() {
|
||||||
self.build.flags.replace_ltoflags = arr
|
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" => {
|
"replace_ltoflags" | "replace_lto-flags" | "replace_lto_flags" => {
|
||||||
for v in values {
|
for v in values {
|
||||||
if let Some(arr) = v.as_array() {
|
if let Some(arr) = v.as_array() {
|
||||||
@@ -2316,6 +2339,7 @@ make_dirs = ["lib"]
|
|||||||
make_test_dirs = ["tests"]
|
make_test_dirs = ["tests"]
|
||||||
make_install_dirs = ["lib"]
|
make_install_dirs = ["lib"]
|
||||||
ltoflags = ["-flto=auto"]
|
ltoflags = ["-flto=auto"]
|
||||||
|
RUSTLTOFLAGS = ["-Clinker-plugin-lto"]
|
||||||
replace_ltoflags = ["auto=>thin"]
|
replace_ltoflags = ["auto=>thin"]
|
||||||
rustflags = ["-C", "debuginfo=2"]
|
rustflags = ["-C", "debuginfo=2"]
|
||||||
replace_rustflags = ["debuginfo=2=>opt-level=2"]
|
replace_rustflags = ["debuginfo=2=>opt-level=2"]
|
||||||
@@ -2518,6 +2542,12 @@ post_configure = ["echo configured"]
|
|||||||
.ltoflags
|
.ltoflags
|
||||||
.contains(&"-flto=auto".to_string())
|
.contains(&"-flto=auto".to_string())
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
spec.build
|
||||||
|
.flags
|
||||||
|
.rustltoflags
|
||||||
|
.contains(&"-Clinker-plugin-lto".to_string())
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
spec.build
|
spec.build
|
||||||
.flags
|
.flags
|
||||||
@@ -3868,6 +3898,18 @@ pub struct BuildFlags {
|
|||||||
deserialize_with = "deserialize_string_or_array"
|
deserialize_with = "deserialize_string_or_array"
|
||||||
)]
|
)]
|
||||||
pub ltoflags: Vec<String>,
|
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.
|
/// Ordered replacement rules applied to `ltoflags` before export/injection.
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
@@ -4236,6 +4278,7 @@ impl Default for BuildFlags {
|
|||||||
ldflags: Vec::new(),
|
ldflags: Vec::new(),
|
||||||
replace_ldflags: Vec::new(),
|
replace_ldflags: Vec::new(),
|
||||||
ltoflags: Vec::new(),
|
ltoflags: Vec::new(),
|
||||||
|
rustltoflags: Vec::new(),
|
||||||
replace_ltoflags: Vec::new(),
|
replace_ltoflags: Vec::new(),
|
||||||
keep: Vec::new(),
|
keep: Vec::new(),
|
||||||
split_docs: false,
|
split_docs: false,
|
||||||
|
|||||||
+123
-11
@@ -9,6 +9,7 @@ use lzma_rust2::{LzipReader, LzmaReader};
|
|||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Write};
|
||||||
use std::os::unix::fs as unix_fs;
|
use std::os::unix::fs as unix_fs;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use tempfile::{NamedTempFile, tempdir};
|
use tempfile::{NamedTempFile, tempdir};
|
||||||
@@ -42,6 +43,7 @@ pub fn extract_archive(
|
|||||||
source: &Source,
|
source: &Source,
|
||||||
build_dir: &Path,
|
build_dir: &Path,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<PathBuf> {
|
||||||
|
crate::interrupts::install()?;
|
||||||
let extract_dir_name = spec.expand_vars(&source.extract_dir);
|
let extract_dir_name = spec.expand_vars(&source.extract_dir);
|
||||||
let extract_path = build_dir.join(&extract_dir_name);
|
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 tmp = tempdir()?;
|
||||||
let file = File::open(path)?;
|
let file = File::open(path)?;
|
||||||
let mut archive = zip::ZipArchive::new(file)?;
|
let mut archive = zip::ZipArchive::new(file)?;
|
||||||
archive.extract(tmp.path())?;
|
extract_zip_archive(&mut archive, tmp.path())?;
|
||||||
finalize_extracted_tree(tmp.path(), dest)
|
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");
|
let mut child = Command::new("gzip");
|
||||||
child.arg("-cd").arg(path);
|
child.arg("-cd").arg(path);
|
||||||
child.stdout(Stdio::piped());
|
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(|| {
|
let mut child = child.spawn().with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to spawn gzip for .Z decompression: {}",
|
"Failed to spawn gzip for .Z decompression: {}",
|
||||||
@@ -201,13 +217,12 @@ fn extract_tar_compress(path: &Path, dest: &Path) -> Result<()> {
|
|||||||
.take()
|
.take()
|
||||||
.context("Failed to capture gzip stdout")?;
|
.context("Failed to capture gzip stdout")?;
|
||||||
let mut archive = tar::Archive::new(stdout);
|
let mut archive = tar::Archive::new(stdout);
|
||||||
archive
|
let unpack_result = crate::interrupts::unpack_tar_archive(&mut archive, tmp.path())
|
||||||
.unpack(tmp.path())
|
.with_context(|| format!("Failed to unpack .tar.Z archive {}", path.display()));
|
||||||
.with_context(|| format!("Failed to unpack .tar.Z archive {}", path.display()))?;
|
|
||||||
drop(archive);
|
drop(archive);
|
||||||
let status = child
|
let status = wait_for_child_interruptibly(&mut child)
|
||||||
.wait()
|
|
||||||
.with_context(|| format!("Failed waiting for gzip on {}", path.display()))?;
|
.with_context(|| format!("Failed waiting for gzip on {}", path.display()))?;
|
||||||
|
unpack_result?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
bail!("gzip failed while decompressing {}", path.display());
|
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<()> {
|
fn extract_tar_reader<R: Read>(reader: R, dest: &Path) -> Result<()> {
|
||||||
let tmp = tempdir()?;
|
let tmp = tempdir()?;
|
||||||
let mut archive = tar::Archive::new(reader);
|
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)
|
finalize_extracted_tree(tmp.path(), dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +296,7 @@ fn extract_gz_file(path: &Path, dest: &Path) -> Result<()> {
|
|||||||
|
|
||||||
fs::create_dir_all(dest)?;
|
fs::create_dir_all(dest)?;
|
||||||
let mut out = File::create(dest.join(out_name))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +317,7 @@ fn extract_xz_file(path: &Path, dest: &Path) -> Result<()> {
|
|||||||
|
|
||||||
fs::create_dir_all(dest)?;
|
fs::create_dir_all(dest)?;
|
||||||
let mut out = File::create(dest.join(out_name))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +338,7 @@ fn extract_zst_file(path: &Path, dest: &Path) -> Result<()> {
|
|||||||
|
|
||||||
fs::create_dir_all(dest)?;
|
fs::create_dir_all(dest)?;
|
||||||
let mut out = File::create(dest.join(out_name))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,13 +348,14 @@ fn extract_deb(path: &Path, dest: &Path) -> Result<()> {
|
|||||||
let mut ar = ar::Archive::new(file);
|
let mut ar = ar::Archive::new(file);
|
||||||
|
|
||||||
while let Some(entry_result) = ar.next_entry() {
|
while let Some(entry_result) = ar.next_entry() {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let mut entry = entry_result?;
|
let mut entry = entry_result?;
|
||||||
let id = String::from_utf8_lossy(entry.header().identifier()).to_string();
|
let id = String::from_utf8_lossy(entry.header().identifier()).to_string();
|
||||||
let lower = id.to_ascii_lowercase();
|
let lower = id.to_ascii_lowercase();
|
||||||
if lower.starts_with("data.tar") {
|
if lower.starts_with("data.tar") {
|
||||||
// write the inner member to a temporary file and reuse tar extraction logic
|
// write the inner member to a temporary file and reuse tar extraction logic
|
||||||
let mut tmpf = NamedTempFile::new()?;
|
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();
|
let tmp_path = tmpf.path().to_path_buf();
|
||||||
|
|
||||||
if lower.ends_with(".gz") {
|
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<()> {
|
fn extract_cpio_newc_from_reader<R: Read>(mut r: R, dest: &Path) -> Result<()> {
|
||||||
use std::str;
|
use std::str;
|
||||||
loop {
|
loop {
|
||||||
|
crate::interrupts::check()?;
|
||||||
// read 6-byte magic
|
// read 6-byte magic
|
||||||
let mut magic = [0u8; 6];
|
let mut magic = [0u8; 6];
|
||||||
if let Err(e) = r.read_exact(&mut magic) {
|
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 remaining = filesize;
|
||||||
let mut buf = [0u8; 8192];
|
let mut buf = [0u8; 8192];
|
||||||
while remaining > 0 {
|
while remaining > 0 {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let to_read = std::cmp::min(remaining, buf.len());
|
let to_read = std::cmp::min(remaining, buf.len());
|
||||||
let n = r.read(&mut buf[..to_read])?;
|
let n = r.read(&mut buf[..to_read])?;
|
||||||
if n == 0 {
|
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<()> {
|
fn extract_rpm(path: &Path, dest: &Path) -> Result<()> {
|
||||||
// Read entire file and search for compression/cpio magic
|
// Read entire file and search for compression/cpio magic
|
||||||
|
crate::interrupts::check()?;
|
||||||
let data = std::fs::read(path)?;
|
let data = std::fs::read(path)?;
|
||||||
|
|
||||||
// search for known signatures
|
// 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<()> {
|
fn move_dir_contents(src: &Path, dest: &Path) -> Result<()> {
|
||||||
fs::create_dir_all(dest)?;
|
fs::create_dir_all(dest)?;
|
||||||
for entry in fs::read_dir(src)? {
|
for entry in fs::read_dir(src)? {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let src_path = entry.path();
|
let src_path = entry.path();
|
||||||
let dest_path = dest.join(entry.file_name());
|
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<()> {
|
fn copy_dir_recursive_local(src: &Path, dst: &Path) -> Result<()> {
|
||||||
for entry in WalkDir::new(src) {
|
for entry in WalkDir::new(src) {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let rel = entry.path().strip_prefix(src).unwrap();
|
let rel = entry.path().strip_prefix(src).unwrap();
|
||||||
let target = dst.join(rel);
|
let target = dst.join(rel);
|
||||||
@@ -598,6 +619,97 @@ fn copy_dir_recursive_local(src: &Path, dst: &Path) -> Result<()> {
|
|||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+15
-23
@@ -68,13 +68,13 @@ fn apply_patches(
|
|||||||
crate::log_info!(" patch: {}", patch_path.display());
|
crate::log_info!(" patch: {}", patch_path.display());
|
||||||
|
|
||||||
// Apply with patch(1). Keep it simple: -p1 is the common case.
|
// Apply with patch(1). Keep it simple: -p1 is the common case.
|
||||||
let status = Command::new("patch")
|
let mut patch_cmd = Command::new("patch");
|
||||||
.current_dir(src_dir)
|
patch_cmd.current_dir(src_dir);
|
||||||
.env("PATH", crate::runtime_env::safe_script_path())
|
patch_cmd.env("PATH", crate::runtime_env::safe_script_path());
|
||||||
.arg("-p1")
|
patch_cmd.arg("-p1");
|
||||||
.arg("-i")
|
patch_cmd.arg("-i");
|
||||||
.arg(&patch_path)
|
patch_cmd.arg(&patch_path);
|
||||||
.status()
|
let status = crate::interrupts::command_status(&mut patch_cmd)
|
||||||
.with_context(|| format!("Failed to execute patch for {}", patch_path.display()))?;
|
.with_context(|| format!("Failed to execute patch for {}", patch_path.display()))?;
|
||||||
|
|
||||||
if !status.success() {
|
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");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(src_dir);
|
shell_cmd.current_dir(src_dir);
|
||||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||||
let status = shell_cmd
|
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||||
.arg("-c")
|
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||||
.arg(&wrapped_cmd)
|
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run post_extract command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_extract command: {}", cmd_str))?;
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
@@ -149,10 +147,8 @@ pub fn run_post_configure_commands(
|
|||||||
let mut shell_cmd = Command::new("sh");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(src_dir);
|
shell_cmd.current_dir(src_dir);
|
||||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||||
let status = shell_cmd
|
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||||
.arg("-c")
|
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||||
.arg(&wrapped_cmd)
|
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run post_configure command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_configure command: {}", cmd_str))?;
|
||||||
|
|
||||||
if !status.success() {
|
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");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(src_dir);
|
shell_cmd.current_dir(src_dir);
|
||||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||||
let status = shell_cmd
|
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||||
.arg("-c")
|
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||||
.arg(&wrapped_cmd)
|
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run post_compile command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_compile command: {}", cmd_str))?;
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
@@ -229,10 +223,8 @@ pub fn run_post_install_commands_in_dir(
|
|||||||
let mut shell_cmd = Command::new("sh");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(work_dir);
|
shell_cmd.current_dir(work_dir);
|
||||||
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
crate::builder::prepare_command(&mut shell_cmd, &env_vars);
|
||||||
let status = shell_cmd
|
shell_cmd.arg("-c").arg(&wrapped_cmd);
|
||||||
.arg("-c")
|
let status = crate::interrupts::command_status(&mut shell_cmd)
|
||||||
.arg(&wrapped_cmd)
|
|
||||||
.status()
|
|
||||||
.with_context(|| format!("Failed to run post_install command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_install command: {}", cmd_str))?;
|
||||||
|
|
||||||
if !status.success() {
|
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;
|
let mut primary: Option<PathBuf> = None;
|
||||||
|
|
||||||
for (idx, src) in spec.sources().iter().enumerate() {
|
for (idx, src) in spec.sources().iter().enumerate() {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let src_dir = prepare_one(spec, src, cache_dir, build_dir)
|
let src_dir = prepare_one(spec, src, cache_dir, build_dir)
|
||||||
.with_context(|| format!("Failed to prepare source #{}", idx + 1))?;
|
.with_context(|| format!("Failed to prepare source #{}", idx + 1))?;
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
@@ -409,6 +410,7 @@ fn prepare_one(
|
|||||||
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
|
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
|
||||||
fs::create_dir_all(dst)?;
|
fs::create_dir_all(dst)?;
|
||||||
for entry in WalkDir::new(src) {
|
for entry in WalkDir::new(src) {
|
||||||
|
crate::interrupts::check()?;
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let rel = entry.path().strip_prefix(src).unwrap();
|
let rel = entry.path().strip_prefix(src).unwrap();
|
||||||
let target = dst.join(rel);
|
let target = dst.join(rel);
|
||||||
|
|||||||
Reference in New Issue
Block a user