Refactor build flags handling and add tool alias management

- Introduced a new method `apply_default_string` to streamline the application of default values for string fields in build flags.
- Enhanced `apply_flags_table` to utilize the new method for setting various build flags, improving code readability and maintainability.
- Added support for additional build flags: `fuse_ld`, `ranlib`, `strip`, `nm`, `objcopy`, `objdump`, and `readelf`.
- Implemented a new command `set` to manage tool aliases for compilers, linkers, and shells, allowing users to set preferred tools in the build environment.
- Improved error handling and validation for tool alias configuration, ensuring that existing non-symlink files are not replaced.
- Added tests to verify the correct behavior of tool alias management and build flag application.
This commit is contained in:
2026-05-27 14:39:01 -05:00
parent 1833a2c42d
commit 5c33a36100
23 changed files with 1895 additions and 170 deletions
Generated
+41 -14
View File
@@ -363,6 +363,15 @@ dependencies = [
"roff", "roff",
] ]
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "cmov" name = "cmov"
version = "0.5.3" version = "0.5.3"
@@ -417,9 +426,9 @@ dependencies = [
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -934,7 +943,7 @@ dependencies = [
"libc", "libc",
"libgit2-sys", "libgit2-sys",
"log", "log",
"openssl-probe", "openssl-probe 0.1.6",
"openssl-sys", "openssl-sys",
"url", "url",
] ]
@@ -1385,9 +1394,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libgit2-sys" name = "libgit2-sys"
version = "0.18.3+1.9.2" version = "0.18.4+1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -1403,6 +1412,7 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
dependencies = [ dependencies = [
"cc",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
] ]
@@ -1428,6 +1438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839"
dependencies = [ dependencies = [
"cc", "cc",
"cmake",
"libc", "libc",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
@@ -1581,14 +1592,14 @@ dependencies = [
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.14" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"openssl", "openssl",
"openssl-probe", "openssl-probe 0.2.1",
"openssl-sys", "openssl-sys",
"schannel", "schannel",
"security-framework", "security-framework",
@@ -1693,6 +1704,21 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-src"
version = "300.6.0+3.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.111" version = "0.9.111"
@@ -1701,6 +1727,7 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
"openssl-src",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
] ]
@@ -1964,9 +1991,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.3" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -2132,9 +2159,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.11.1" version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
@@ -2440,9 +2467,9 @@ dependencies = [
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.45" version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [ dependencies = [
"filetime", "filetime",
"libc", "libc",
+14 -2
View File
@@ -6,6 +6,18 @@ edition = "2024"
[lints.rust] [lints.rust]
warnings = "deny" warnings = "deny"
[features]
default = []
# Prefer vendored/static native libraries while keeping libc dynamically linked.
static-except-libc = [
"git2/vendored-libgit2",
"git2/vendored-openssl",
"git2/zlib-ng-compat",
"reqwest/native-tls-vendored",
"rusqlite/bundled",
"xz2/static",
]
[dependencies] [dependencies]
anyhow = "1.0.102" anyhow = "1.0.102"
ar = "0.9.0" ar = "0.9.0"
@@ -20,7 +32,7 @@ semver = "1.0.28"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
yaml-rust2 = "0.11.0" yaml-rust2 = "0.11.0"
sha2 = "0.11.0" sha2 = "0.11.0"
tar = "0.4.45" tar = "0.4.46"
tempfile = "=3.27.0" tempfile = "=3.27.0"
thiserror = "2.0.18" thiserror = "2.0.18"
toml = "1.1.2" toml = "1.1.2"
@@ -35,7 +47,7 @@ suppaftp = "8.0.2"
minisign = "0.9.1" minisign = "0.9.1"
petgraph = "0.8.3" petgraph = "0.8.3"
fd-lock = "4.0.4" fd-lock = "4.0.4"
reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "native-tls"] } reqwest = { version = "0.13.4", default-features = false, features = ["blocking", "native-tls"] }
filetime = "0.2.29" filetime = "0.2.29"
clap_complete = "4.6.5" clap_complete = "4.6.5"
clap_mangen = "0.3.0" clap_mangen = "0.3.0"
+31
View File
@@ -11,10 +11,41 @@ const BUILD_OPTION_KEYS: &[&str] = &[
]; ];
fn main() { fn main() {
println!("cargo:rerun-if-env-changed=CC");
for key in BUILD_OPTION_KEYS { for key in BUILD_OPTION_KEYS {
println!("cargo:rerun-if-env-changed={key}"); println!("cargo:rerun-if-env-changed={key}");
if let Ok(value) = std::env::var(key) { if let Ok(value) = std::env::var(key) {
println!("cargo:rustc-env={key}={value}"); println!("cargo:rustc-env={key}={value}");
} }
} }
if std::env::var_os("CARGO_FEATURE_STATIC_EXCEPT_LIBC").is_some()
&& std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux")
{
let libgcc_eh = gcc_runtime_archive("libgcc_eh.a").unwrap_or_else(|| {
panic!("static-except-libc requires libgcc_eh.a for static libgcc linkage")
});
println!("cargo:rustc-link-arg-bin=depot=-Wl,--whole-archive");
println!("cargo:rustc-link-arg-bin=depot={libgcc_eh}");
println!("cargo:rustc-link-arg-bin=depot=-Wl,--no-whole-archive");
println!("cargo:rustc-link-arg-bin=depot=-static-libgcc");
}
}
fn gcc_runtime_archive(name: &str) -> Option<String> {
let compiler = std::env::var_os("CC").unwrap_or_else(|| "cc".into());
let output = std::process::Command::new(compiler)
.arg(format!("-print-file-name={name}"))
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = String::from_utf8(output.stdout).ok()?;
let path = path.trim();
if path.is_empty() || path == name || !std::path::Path::new(path).exists() {
None
} else {
Some(path.to_string())
}
} }
+12 -3
View File
@@ -5,10 +5,17 @@
# apply everywhere without crowding the main /etc/depot.toml. # apply everywhere without crowding the main /etc/depot.toml.
# Toolchain selection. # Toolchain selection.
# tool_dir = "/usr/bin"
cc = "clang" cc = "clang"
# cxx = "clang++" # cxx = "$TOOL_DIR/clang++"
# ar = "ar" # ar = "$TOOL_DIR/ar"
# ld = "ld.lld" # ranlib = "$TOOL_DIR/ranlib"
# strip = "$TOOL_DIR/strip"
# ld = "$TOOL_DIR/ld.lld"
# nm = "$TOOL_DIR/nm"
# objcopy = "$TOOL_DIR/objcopy"
# objdump = "$TOOL_DIR/objdump"
# readelf = "$TOOL_DIR/readelf"
# Compiler defaults. These are merged with package-level flags unless a package # Compiler defaults. These are merged with package-level flags unless a package
# explicitly replaces them. # explicitly replaces them.
@@ -18,6 +25,8 @@ cflags += ["-pipe"]
# Linker defaults. # Linker defaults.
ldflags = ["-Wl,-O1"] ldflags = ["-Wl,-O1"]
# Select a linker through compiler drivers such as clang/gcc.
# fuse_ld = "lld"
# Link-time optimization policy. # Link-time optimization policy.
ltoflags = ["-flto=auto"] ltoflags = ["-flto=auto"]
+12 -5
View File
@@ -16,15 +16,20 @@ prefix = "/usr"
# Default toolchain commands. These are passed through to builders that honor # Default toolchain commands. These are passed through to builders that honor
# CC/CXX/AR and related variables. # CC/CXX/AR and related variables.
# tool_dir = "/usr/bin"
cc = "clang" cc = "clang"
ar = "ar" ar = "ar"
# Optional examples: # Optional examples:
# cxx = "clang++" # cxx = "$TOOL_DIR/clang++"
# ld = "ld.lld" # ld = "$TOOL_DIR/ld.lld"
# cpp = "cpp" # cpp = "$TOOL_DIR/cpp"
# strip = "strip" # strip = "$TOOL_DIR/strip"
# ranlib = "ranlib" # ranlib = "$TOOL_DIR/ranlib"
# nm = "$TOOL_DIR/nm"
# objcopy = "$TOOL_DIR/objcopy"
# objdump = "$TOOL_DIR/objdump"
# readelf = "$TOOL_DIR/readelf"
[build.flags] [build.flags]
# Baseline C compiler flags for packages that do not supply their own. # Baseline C compiler flags for packages that do not supply their own.
@@ -39,6 +44,8 @@ cflags += ["-g"]
# Optional C++ and linker defaults: # Optional C++ and linker defaults:
# cxxflags = ["-O2"] # cxxflags = ["-O2"]
ldflags = ["-Wl,-O1"] ldflags = ["-Wl,-O1"]
# Select a linker through compiler drivers such as clang/gcc.
# fuse_ld = "lld"
# Link-time optimization defaults. When use_lto is true, Depot will inject # Link-time optimization defaults. When use_lto is true, Depot will inject
# ltoflags into the appropriate compiler and linker flag sets automatically. # ltoflags into the appropriate compiler and linker flag sets automatically.
+5 -1
View File
@@ -10,8 +10,11 @@
prefix = "/usr/local" prefix = "/usr/local"
# Personal toolchain preference. # Personal toolchain preference.
# tool_dir = "/usr/bin"
cc = "clang" cc = "clang"
# cxx = "clang++" # cxx = "$TOOL_DIR/clang++"
# ranlib = "$TOOL_DIR/ranlib"
# strip = "$TOOL_DIR/strip"
[build.flags] [build.flags]
# User-local compiler preferences. # User-local compiler preferences.
@@ -22,6 +25,7 @@ carch = "x86_64"
# Examples: # Examples:
# cxxflags = ["-O2", "-g"] # cxxflags = ["-O2", "-g"]
# ldflags = ["-Wl,-O1"] # ldflags = ["-Wl,-O1"]
# fuse_ld = "lld"
# makeflags = "-j4" # makeflags = "-j4"
# Optional install directory overrides for supported builders. # Optional install directory overrides for supported builders.
+125 -14
View File
@@ -335,6 +335,7 @@ fn bootstrap_package_is_complete(
fn bootstrap_required_payload_paths(package: &str) -> &'static [&'static str] { fn bootstrap_required_payload_paths(package: &str) -> &'static [&'static str] {
match package { match package {
"bmake" => &["system/binaries/bmake", "system/share/mk/sys.mk"], "bmake" => &["system/binaries/bmake", "system/share/mk/sys.mk"],
"byacc" => &["system/binaries/yacc"],
"llvm-clang-pass1" => &[ "llvm-clang-pass1" => &[
"system/tools/bin/llvm-config", "system/tools/bin/llvm-config",
"system/tools/bin/llvm-tblgen", "system/tools/bin/llvm-tblgen",
@@ -348,6 +349,7 @@ fn bootstrap_required_payload_paths(package: &str) -> &'static [&'static str] {
fn bootstrap_recipe_revision(package: &str) -> Option<u32> { fn bootstrap_recipe_revision(package: &str) -> Option<u32> {
match package { match package {
"bmake" => Some(2), "bmake" => Some(2),
"byacc" => Some(1),
"llvm" => Some(3), "llvm" => Some(3),
"ubase" => Some(1), "ubase" => Some(1),
_ => None, _ => None,
@@ -1584,7 +1586,7 @@ fn write_generated_recipe(
} }
spec.push_str("no_strip = true\n"); spec.push_str("no_strip = true\n");
spec.push_str( spec.push_str(
"passthrough_env = [\"DEPOT_LBI_CHROOT\", \"DEPOT_LBI_CHROOT_ROOT\", \"DEPOT_LBI_DEPOT_EXE\", \"DEPOT_LBI_SYSROOT\", \"LWI_MAKE_FLAGS\", \"LWI_MAKE_JOBS\"]\n\n", "passthrough_env = [\"DEPOT_LBI_CHROOT\", \"DEPOT_LBI_CHROOT_ROOT\", \"DEPOT_LBI_DEPOT_EXE\", \"DEPOT_LBI_SYSROOT\", \"LBI_CCACHE\", \"LWI_MAKE_FLAGS\", \"LWI_MAKE_JOBS\"]\n\n",
); );
if let Some(primary) = primary_source { if let Some(primary) = primary_source {
@@ -1753,6 +1755,19 @@ fi
script.push_str("export LWI_CFLAGS=\"${LWI_CFLAGS:-${CFLAGS:-}}\"\n"); script.push_str("export LWI_CFLAGS=\"${LWI_CFLAGS:-${CFLAGS:-}}\"\n");
script.push_str("export LWI_CXXFLAGS=\"${LWI_CXXFLAGS:-$LWI_CFLAGS}\"\n"); script.push_str("export LWI_CXXFLAGS=\"${LWI_CXXFLAGS:-$LWI_CFLAGS}\"\n");
script.push_str("export LBI_CUSTOM_LDFLAGS=\"${LBI_CUSTOM_LDFLAGS:-${LDFLAGS:-}}\"\n\n"); script.push_str("export LBI_CUSTOM_LDFLAGS=\"${LBI_CUSTOM_LDFLAGS:-${LDFLAGS:-}}\"\n\n");
if uses_llvm_cmake_ccache(package) {
script.push_str(
r#"if [ -z "${LBI_CCACHE:-}" ]; then
LBI_CCACHE="$(command -v ccache)" || {
echo "depot: LLVM bootstrap requires ccache in PATH for the CMake compiler launcher" >&2
exit 1
}
fi
export LBI_CCACHE
"#,
);
}
let uses_cross_toolchain = use_cross_toolchain_by_default(package); let uses_cross_toolchain = use_cross_toolchain_by_default(package);
if uses_cross_toolchain { if uses_cross_toolchain {
script.push_str( script.push_str(
@@ -2112,6 +2127,8 @@ fn apply_bootstrap_package_overrides(package: &BookPackage, command: &str) -> St
ensure_mandoc_postinstall_uses_staged_man(command) ensure_mandoc_postinstall_uses_staged_man(command)
} else if package.name == "bmake" { } else if package.name == "bmake" {
ensure_bmake_install_avoids_double_destdir(command) ensure_bmake_install_avoids_double_destdir(command)
} else if package.name == "byacc" {
ensure_byacc_exposes_unprefixed_yacc(command)
} else if package.name == "ubase" { } else if package.name == "ubase" {
ensure_ubase_moves_bin_payload(command) ensure_ubase_moves_bin_payload(command)
} else if package.name == "sqlite" { } else if package.name == "sqlite" {
@@ -2141,6 +2158,23 @@ fi"#
) )
} }
fn ensure_byacc_exposes_unprefixed_yacc(command: &str) -> String {
if !command_runs_install_step(command) {
return command.to_string();
}
format!(
"{command}\n{}",
r#"if [ ! -e "$DESTDIR/system/binaries/yacc" ] && [ -e "$DESTDIR/system/binaries/$LBI_TARGET-yacc" ]; then
ln -sf "$LBI_TARGET-yacc" "$DESTDIR/system/binaries/yacc"
fi
if [ ! -e "$DESTDIR/system/documentation/man-pages/man1/yacc.1" ] \
&& [ -e "$DESTDIR/system/documentation/man-pages/man1/$LBI_TARGET-yacc.1" ]; then
ln -sf "$LBI_TARGET-yacc.1" "$DESTDIR/system/documentation/man-pages/man1/yacc.1"
fi"#
)
}
fn command_runs_install_step(command: &str) -> bool { fn command_runs_install_step(command: &str) -> bool {
command.lines().any(|line| { command.lines().any(|line| {
let trimmed = line.trim_start(); let trimmed = line.trim_start();
@@ -2479,25 +2513,46 @@ fn ensure_llvm_runtime_links_skip_missing_crt(command: &str) -> String {
} }
fn ensure_llvm_cmake_uses_ccache(command: &str) -> String { fn ensure_llvm_cmake_uses_ccache(command: &str) -> String {
if command.contains("-DLLVM_CCACHE_BUILD=ON") { let command = command
return command.to_string(); .replace(" -DLLVM_CCACHE_BUILD=ON \\\n", "")
.replace(" -DLLVM_CCACHE_BUILD=ON \\", "")
.replace("-DLLVM_CCACHE_BUILD=ON", "");
if command.contains("-DCMAKE_C_COMPILER_LAUNCHER=")
&& command.contains("-DCMAKE_CXX_COMPILER_LAUNCHER=")
&& command.contains("-DCMAKE_ASM_COMPILER_LAUNCHER=")
{
return command;
} }
let launcher_flags = concat!(
" -DCMAKE_C_COMPILER_LAUNCHER=\"$LBI_CCACHE\" \\\n",
" -DCMAKE_CXX_COMPILER_LAUNCHER=\"$LBI_CCACHE\" \\\n",
" -DCMAKE_ASM_COMPILER_LAUNCHER=\"$LBI_CCACHE\" \\"
);
if command.contains("lbi_cmake build-llvm") { if command.contains("lbi_cmake build-llvm") {
return command.replace( return command.replace(
"lbi_cmake build-llvm \\", "lbi_cmake build-llvm \\",
"lbi_cmake build-llvm \\\n -DLLVM_CCACHE_BUILD=ON \\", &format!("lbi_cmake build-llvm \\\n{launcher_flags}"),
); );
} }
if command.contains("cmake -G Ninja \"../llvm\"") { if command.contains("cmake -G Ninja \"../llvm\"") {
return command.replace( return command.replace(
"cmake -G Ninja \"../llvm\" \\", "cmake -G Ninja \"../llvm\" \\",
"cmake -G Ninja \"../llvm\" \\\n -DLLVM_CCACHE_BUILD=ON \\", &format!("cmake -G Ninja \"../llvm\" \\\n{launcher_flags}"),
); );
} }
command.to_string() command
}
fn uses_llvm_cmake_ccache(package: &BookPackage) -> bool {
matches!(
package.name.as_str(),
"llvm-clang-pass1" | "llvm-clang-pass2"
)
} }
fn ensure_llvm_pass2_uses_pass1_native_tools(command: &str) -> String { fn ensure_llvm_pass2_uses_pass1_native_tools(command: &str) -> String {
@@ -4688,10 +4743,13 @@ EOF"#
let build_script = let build_script =
generated_build_script(&package, &recipe, "x86_64-unknown-linux-musl", "x86_64"); generated_build_script(&package, &recipe, "x86_64-unknown-linux-musl", "x86_64");
assert!(build_script.contains("-DLLVM_CCACHE_BUILD=ON")); assert!(build_script.contains("export LBI_CCACHE"));
assert!( assert!(build_script.contains(
build_script.contains("cmake -G Ninja \"../llvm\" \\\n -DLLVM_CCACHE_BUILD=ON \\") "cmake -G Ninja \"../llvm\" \\\n -DCMAKE_C_COMPILER_LAUNCHER=\"$LBI_CCACHE\" \\"
); ));
assert!(build_script.contains("-DCMAKE_CXX_COMPILER_LAUNCHER=\"$LBI_CCACHE\""));
assert!(build_script.contains("-DCMAKE_ASM_COMPILER_LAUNCHER=\"$LBI_CCACHE\""));
assert!(!build_script.contains("-DLLVM_CCACHE_BUILD=ON"));
assert!(build_script.contains("-DLLVM_NATIVE_TOOL_DIR=\"$LBI_SYSROOT/system/tools/bin\"")); assert!(build_script.contains("-DLLVM_NATIVE_TOOL_DIR=\"$LBI_SYSROOT/system/tools/bin\""));
assert!( assert!(
build_script build_script
@@ -6231,6 +6289,43 @@ sed -i \
)); ));
} }
#[test]
fn byacc_generated_script_exposes_unprefixed_yacc() {
let mut package = test_recipe(8).package;
package.name = "byacc".to_string();
package.title = "byacc stage 2 20260126".to_string();
package.version = "20260126".to_string();
package.recipe_id = "8-12-byacc".to_string();
let recipe = PageRecipe {
input_files: vec!["byacc-20260126.tgz".to_string()],
source_urls: vec!["https://example.invalid/byacc-20260126.tgz".to_string()],
extract_dir: Some("byacc-20260126".to_string()),
commands: vec![
"lbi_configure --with-manpage-format=normal".to_string(),
"make $LWI_MAKE_FLAGS".to_string(),
"make ${LWI_MAKE_FLAGS:-} install".to_string(),
],
dependencies: Vec::new(),
license: "BSD-3-Clause".to_string(),
description: "byacc".to_string(),
};
let build_script =
generated_build_script(&package, &recipe, "x86_64-lbi-linux-musl", "x86_64");
assert!(
build_script.contains(r#"ln -sf "$LBI_TARGET-yacc" "$DESTDIR/system/binaries/yacc""#)
);
assert!(build_script.contains(
r#"ln -sf "$LBI_TARGET-yacc.1" "$DESTDIR/system/documentation/man-pages/man1/yacc.1""#
));
assert_eq!(
bootstrap_required_payload_paths("byacc"),
&["system/binaries/yacc"]
);
assert_eq!(bootstrap_recipe_revision("byacc"), Some(1));
}
#[test] #[test]
fn generated_python_bootstrap_specs_use_custom_chroot_script() { fn generated_python_bootstrap_specs_use_custom_chroot_script() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@@ -6329,10 +6424,13 @@ sed -i \
assert!(build_script.contains("export LBI_SYSROOT=\"${DEPOT_LBI_SYSROOT:-$LBI_ROOT}\"")); assert!(build_script.contains("export LBI_SYSROOT=\"${DEPOT_LBI_SYSROOT:-$LBI_ROOT}\""));
assert!(build_script.contains("export LWI_CXXFLAGS=\"${LWI_CXXFLAGS:-$LWI_CFLAGS}\"")); assert!(build_script.contains("export LWI_CXXFLAGS=\"${LWI_CXXFLAGS:-$LWI_CFLAGS}\""));
assert!(build_script.contains("rm -rf build-llvm")); assert!(build_script.contains("rm -rf build-llvm"));
assert!(build_script.contains("-DLLVM_CCACHE_BUILD=ON")); assert!(build_script.contains("LBI_CCACHE=\"$(command -v ccache)\""));
assert!( assert!(build_script.contains(
build_script.contains("cmake -G Ninja \"../llvm\" \\\n -DLLVM_CCACHE_BUILD=ON \\") "cmake -G Ninja \"../llvm\" \\\n -DCMAKE_C_COMPILER_LAUNCHER=\"$LBI_CCACHE\" \\"
); ));
assert!(build_script.contains("-DCMAKE_CXX_COMPILER_LAUNCHER=\"$LBI_CCACHE\""));
assert!(build_script.contains("-DCMAKE_ASM_COMPILER_LAUNCHER=\"$LBI_CCACHE\""));
assert!(!build_script.contains("-DLLVM_CCACHE_BUILD=ON"));
assert!(build_script.contains("LLVM_ENABLE_RUNTIMES=\"compiler-rt\"")); assert!(build_script.contains("LLVM_ENABLE_RUNTIMES=\"compiler-rt\""));
assert!(build_script.contains("COMPILER_RT_BUILD_BUILTINS=ON")); assert!(build_script.contains("COMPILER_RT_BUILD_BUILTINS=ON"));
assert!(build_script.contains("COMPILER_RT_DEFAULT_TARGET_ONLY=ON")); assert!(build_script.contains("COMPILER_RT_DEFAULT_TARGET_ONLY=ON"));
@@ -6456,6 +6554,7 @@ sed -i \
assert!(build_script.contains(" --target=\"$LBI_TARGET\" \\\n")); assert!(build_script.contains(" --target=\"$LBI_TARGET\" \\\n"));
assert!(build_script.contains(" --host=\"$LBI_TARGET\" \\\n")); assert!(build_script.contains(" --host=\"$LBI_TARGET\" \\\n"));
assert!(!build_script.contains(" --build=\"$LBI_TARGET\" \\\n"));
assert!(build_script.contains("-DCMAKE_C_COMPILER_TARGET=\"$LBI_TARGET\"")); assert!(build_script.contains("-DCMAKE_C_COMPILER_TARGET=\"$LBI_TARGET\""));
assert!(build_script.contains("-DCMAKE_CXX_COMPILER_TARGET=\"$LBI_TARGET\"")); assert!(build_script.contains("-DCMAKE_CXX_COMPILER_TARGET=\"$LBI_TARGET\""));
assert!( assert!(
@@ -6470,6 +6569,18 @@ sed -i \
); );
} }
#[test]
fn copied_build_profile_does_not_default_build_tuple() {
let tmp = tempfile::tempdir().unwrap();
copy_build_profile(tmp.path(), "x86_64-unknown-linux-musl", "x86_64").unwrap();
let profile = fs::read_to_string(tmp.path().join("etc/profile.d/lbi-build.sh")).unwrap();
assert!(profile.contains(" --target=\"$LBI_TARGET\" \\\n"));
assert!(profile.contains(" --host=\"$LBI_TARGET\" \\\n"));
assert!(!profile.contains(" --build=\"$LBI_TARGET\" \\\n"));
}
#[test] #[test]
fn rewrite_make_flags_does_not_duplicate_existing_parallel_flags() { fn rewrite_make_flags_does_not_duplicate_existing_parallel_flags() {
assert_eq!( assert_eq!(
+23
View File
@@ -491,6 +491,18 @@ fn generate_lib32_meson_cross_file(
let ld = meson_binary_value(&command_words(&flags.ld), "linker")?; let ld = meson_binary_value(&command_words(&flags.ld), "linker")?;
content.push_str(&format!("ld = {ld}\n")); content.push_str(&format!("ld = {ld}\n"));
} }
for (name, command, label) in [
("strip", flags.strip.as_str(), "strip"),
("nm", flags.nm.as_str(), "nm"),
("objcopy", flags.objcopy.as_str(), "objcopy"),
("objdump", flags.objdump.as_str(), "objdump"),
("readelf", flags.readelf.as_str(), "readelf"),
] {
if !command.trim().is_empty() {
let value = meson_binary_value(&command_words(command), label)?;
content.push_str(&format!("{name} = {value}\n"));
}
}
content.push_str(&format!( content.push_str(&format!(
"\n[host_machine]\nsystem = 'linux'\ncpu_family = '{cpu_family}'\ncpu = '{arch}'\nendian = 'little'\n" "\n[host_machine]\nsystem = 'linux'\ncpu_family = '{cpu_family}'\ncpu = '{arch}'\nendian = 'little'\n"
)); ));
@@ -961,7 +973,12 @@ mod tests {
cc: "clang -m32".to_string(), cc: "clang -m32".to_string(),
cxx: "clang++ -m32".to_string(), cxx: "clang++ -m32".to_string(),
ar: "llvm-ar".to_string(), ar: "llvm-ar".to_string(),
strip: "llvm-strip".to_string(),
ld: "ld.lld".to_string(), ld: "ld.lld".to_string(),
nm: "llvm-nm".to_string(),
objcopy: "llvm-objcopy".to_string(),
objdump: "llvm-objdump".to_string(),
readelf: "llvm-readelf".to_string(),
..BuildFlags::default() ..BuildFlags::default()
}; };
@@ -970,6 +987,12 @@ mod tests {
assert!(content.contains("Generated by depot for target: i686-sfg-linux-gnu")); 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("c = ['clang', '-m32', '--target=i686-sfg-linux-gnu']"));
assert!(content.contains("cpp = ['clang++', '-m32', '--target=i686-sfg-linux-gnu']")); assert!(content.contains("cpp = ['clang++', '-m32', '--target=i686-sfg-linux-gnu']"));
assert!(content.contains("strip = 'llvm-strip'"));
assert!(content.contains("ld = 'ld.lld'"));
assert!(content.contains("nm = 'llvm-nm'"));
assert!(content.contains("objcopy = 'llvm-objcopy'"));
assert!(content.contains("objdump = 'llvm-objdump'"));
assert!(content.contains("readelf = 'llvm-readelf'"));
assert!(content.contains("cpu_family = 'x86'")); assert!(content.contains("cpu_family = 'x86'"));
assert!(content.contains("cpu = 'i686'")); assert!(content.contains("cpu = 'i686'"));
Ok(()) Ok(())
+388 -26
View File
@@ -205,9 +205,36 @@ pub fn set_env_var(env_vars: &mut EnvVars, key: &str, value: impl Into<String>)
} }
} }
fn set_expanded_env_var(env_vars: &mut EnvVars, key: &str, value: impl AsRef<str>) {
let expanded = expand_with_envs(value.as_ref(), env_vars);
set_env_var(env_vars, key, expanded);
}
fn configured_tool_or_default(configured: &str, default: &str) -> String {
let configured = configured.trim();
if configured.is_empty() {
default.to_string()
} else {
configured.to_string()
}
}
fn configured_defaulted_tool_or_default(
configured: &str,
implicit_default: &str,
default: &str,
) -> String {
let configured = configured.trim();
if configured.is_empty() || configured == implicit_default {
default.to_string()
} else {
configured.to_string()
}
}
fn apply_declared_env_vars(spec: &PackageSpec, env_vars: &mut EnvVars) { fn apply_declared_env_vars(spec: &PackageSpec, env_vars: &mut EnvVars) {
for raw in &spec.build.flags.env_vars { for raw in &spec.build.flags.env_vars {
let expanded = spec.expand_vars(raw); let expanded = expand_with_envs(&spec.expand_vars(raw), env_vars);
let entry = expanded.trim(); let entry = expanded.trim();
if entry.is_empty() { if entry.is_empty() {
continue; continue;
@@ -234,6 +261,21 @@ fn apply_declared_env_vars(spec: &PackageSpec, env_vars: &mut EnvVars) {
} }
} }
/// Expand environment variables in a string using Depot's build env first,
/// falling back to the parent process environment.
pub(crate) fn expand_with_envs(input: &str, envs: &[(String, String)]) -> String {
let mut result = input.to_string();
for (key, value) in envs {
result = result.replace(&format!("${key}"), value);
result = result.replace(&format!("${{{key}}}"), value);
}
for (key, value) in std::env::vars() {
result = result.replace(&format!("${key}"), &value);
result = result.replace(&format!("${{{key}}}"), &value);
}
result
}
fn default_libdir_for_variant(lib32_variant: bool) -> &'static str { fn default_libdir_for_variant(lib32_variant: bool) -> &'static str {
if lib32_variant { if lib32_variant {
"/usr/lib32" "/usr/lib32"
@@ -513,6 +555,23 @@ pub(crate) fn install_dirs(flags: &crate::package::BuildFlags) -> InstallDirs {
} }
} }
fn apply_install_dir_env_vars(env_vars: &mut EnvVars, flags: &crate::package::BuildFlags) {
let dirs = install_dirs(flags);
set_env_var(env_vars, "PREFIX", flags.prefix.clone());
set_env_var(env_vars, "BINDIR", dirs.bindir);
set_env_var(env_vars, "SBINDIR", dirs.sbindir);
set_env_var(env_vars, "LIBDIR", dirs.libdir);
set_env_var(env_vars, "LIBEXECDIR", dirs.libexecdir);
set_env_var(env_vars, "SYSCONFDIR", dirs.sysconfdir);
set_env_var(env_vars, "LOCALSTATEDIR", dirs.localstatedir);
set_env_var(env_vars, "SHAREDSTATEDIR", dirs.sharedstatedir);
set_env_var(env_vars, "INCLUDEDIR", dirs.includedir);
set_env_var(env_vars, "DATAROOTDIR", dirs.datarootdir);
set_env_var(env_vars, "DATADIR", dirs.datadir);
set_env_var(env_vars, "MANDIR", dirs.mandir);
set_env_var(env_vars, "INFODIR", dirs.infodir);
}
pub(crate) fn install_destdir_path( pub(crate) fn install_destdir_path(
build_dir: &Path, build_dir: &Path,
destdir: &Path, destdir: &Path,
@@ -628,6 +687,10 @@ fn compiler_flag_sets(
"build.flags.ltoflags", "build.flags.ltoflags",
); );
if let Some(fuse_ld) = fuse_ld_flag(&flags.fuse_ld) {
ldflags.insert(0, fuse_ld);
}
if flags.use_lto && !ltoflags.is_empty() { if flags.use_lto && !ltoflags.is_empty() {
cflags.extend(ltoflags.iter().cloned()); cflags.extend(ltoflags.iter().cloned());
cxxflags.extend(ltoflags.iter().cloned()); cxxflags.extend(ltoflags.iter().cloned());
@@ -637,6 +700,26 @@ fn compiler_flag_sets(
(cflags, cxxflags, ldflags, ltoflags) (cflags, cxxflags, ldflags, ltoflags)
} }
fn fuse_ld_flag(fuse_ld: &str) -> Option<String> {
let fuse_ld = fuse_ld.trim();
if fuse_ld.is_empty() {
None
} else if fuse_ld.starts_with("-fuse-ld=") || fuse_ld.starts_with("--ld-path=") {
Some(fuse_ld.to_string())
} else if fuse_ld.contains('/') {
Some(format!("-fuse-ld={fuse_ld}"))
} else {
let driver_name = match fuse_ld {
"ld.lld" => "lld",
"ld.mold" => "mold",
"ld.gold" => "gold",
"ld.bfd" => "bfd",
_ => fuse_ld,
};
Some(format!("-fuse-ld={driver_name}"))
}
}
fn rust_ltoflags(flags: &crate::package::BuildFlags) -> Vec<String> { fn rust_ltoflags(flags: &crate::package::BuildFlags) -> Vec<String> {
sanitize_flag_list(flags.rustltoflags.clone(), "build.flags.rustltoflags") sanitize_flag_list(flags.rustltoflags.clone(), "build.flags.rustltoflags")
} }
@@ -664,21 +747,25 @@ pub fn standard_build_env(
let mut env_vars: EnvVars = Vec::new(); let mut env_vars: EnvVars = Vec::new();
let export_compiler_flags = export_compiler_flags && !flags.no_flags; let export_compiler_flags = export_compiler_flags && !flags.no_flags;
if !flags.tool_dir.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "TOOL_DIR", flags.tool_dir.trim());
}
if include_compiler_env && export_compiler_flags { if include_compiler_env && export_compiler_flags {
let (cflags, cxxflags, ldflags, ltoflags) = compiler_flag_sets(flags); let (cflags, cxxflags, ldflags, ltoflags) = compiler_flag_sets(flags);
if !cflags.is_empty() { if !cflags.is_empty() {
set_env_var(&mut env_vars, "CFLAGS", cflags.join(" ")); set_expanded_env_var(&mut env_vars, "CFLAGS", cflags.join(" "));
} }
if !cxxflags.is_empty() { if !cxxflags.is_empty() {
set_env_var(&mut env_vars, "CXXFLAGS", cxxflags.join(" ")); set_expanded_env_var(&mut env_vars, "CXXFLAGS", cxxflags.join(" "));
} }
if !ltoflags.is_empty() { if !ltoflags.is_empty() {
set_env_var(&mut env_vars, "LTOFLAGS", ltoflags.join(" ")); set_expanded_env_var(&mut env_vars, "LTOFLAGS", ltoflags.join(" "));
} }
let rust_ltoflags = rust_ltoflags(flags); let rust_ltoflags = rust_ltoflags(flags);
if !rust_ltoflags.is_empty() { if !rust_ltoflags.is_empty() {
set_env_var(&mut env_vars, "RUSTLTOFLAGS", rust_ltoflags.join(" ")); set_expanded_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() {
@@ -693,7 +780,7 @@ pub fn standard_build_env(
String::new() String::new()
}; };
if !ldflags.is_empty() { if !ldflags.is_empty() {
set_env_var(&mut env_vars, "LDFLAGS", ldflags); set_expanded_env_var(&mut env_vars, "LDFLAGS", ldflags);
} }
} }
@@ -712,15 +799,9 @@ pub fn standard_build_env(
if !effective_carch.is_empty() { if !effective_carch.is_empty() {
set_env_var(&mut env_vars, "CARCH", effective_carch); set_env_var(&mut env_vars, "CARCH", effective_carch);
} }
if !flags.prefix.is_empty() { apply_install_dir_env_vars(&mut env_vars, flags);
set_env_var(&mut env_vars, "PREFIX", flags.prefix.clone());
}
if !flags.makeflags.trim().is_empty() { if !flags.makeflags.trim().is_empty() {
set_env_var( set_expanded_env_var(&mut env_vars, "MAKEFLAGS", flags.makeflags.trim());
&mut env_vars,
"MAKEFLAGS",
flags.makeflags.trim().to_string(),
);
} }
set_env_var(&mut env_vars, "DEPOT_ROOTFS", flags.rootfs.clone()); set_env_var(&mut env_vars, "DEPOT_ROOTFS", flags.rootfs.clone());
@@ -732,13 +813,60 @@ pub fn standard_build_env(
if include_compiler_env { if include_compiler_env {
if let Some(cc_cfg) = cross { if let Some(cc_cfg) = cross {
set_env_var(&mut env_vars, "CC", cc_cfg.cc.clone()); let default_flags = BuildFlags::default();
set_env_var(&mut env_vars, "CXX", cc_cfg.cxx.clone()); set_expanded_env_var(
set_env_var(&mut env_vars, "AR", cc_cfg.ar.clone()); &mut env_vars,
set_env_var(&mut env_vars, "RANLIB", cc_cfg.ranlib.clone()); "CC",
set_env_var(&mut env_vars, "STRIP", cc_cfg.strip.clone()); configured_defaulted_tool_or_default(&flags.cc, &default_flags.cc, &cc_cfg.cc),
set_env_var(&mut env_vars, "LD", cc_cfg.ld.clone()); );
set_env_var(&mut env_vars, "NM", cc_cfg.nm.clone()); set_expanded_env_var(
&mut env_vars,
"CXX",
configured_defaulted_tool_or_default(&flags.cxx, &default_flags.cxx, &cc_cfg.cxx),
);
set_expanded_env_var(
&mut env_vars,
"AR",
configured_defaulted_tool_or_default(&flags.ar, &default_flags.ar, &cc_cfg.ar),
);
set_expanded_env_var(
&mut env_vars,
"RANLIB",
configured_tool_or_default(&flags.ranlib, &cc_cfg.ranlib),
);
set_expanded_env_var(
&mut env_vars,
"STRIP",
configured_tool_or_default(&flags.strip, &cc_cfg.strip),
);
set_expanded_env_var(
&mut env_vars,
"LD",
configured_tool_or_default(&flags.ld, &cc_cfg.ld),
);
set_expanded_env_var(
&mut env_vars,
"NM",
configured_tool_or_default(&flags.nm, &cc_cfg.nm),
);
set_expanded_env_var(
&mut env_vars,
"OBJCOPY",
configured_tool_or_default(&flags.objcopy, &cc_cfg.objcopy),
);
set_expanded_env_var(
&mut env_vars,
"OBJDUMP",
configured_tool_or_default(&flags.objdump, &cc_cfg.objdump),
);
set_expanded_env_var(
&mut env_vars,
"READELF",
configured_tool_or_default(&flags.readelf, &cc_cfg.readelf),
);
if !flags.cpp.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "CPP", flags.cpp.trim());
}
set_env_var(&mut env_vars, "CROSS_PREFIX", cc_cfg.prefix.clone()); set_env_var(&mut env_vars, "CROSS_PREFIX", cc_cfg.prefix.clone());
set_env_var( set_env_var(
&mut env_vars, &mut env_vars,
@@ -746,14 +874,32 @@ pub fn standard_build_env(
format!("{}-", cc_cfg.prefix), format!("{}-", cc_cfg.prefix),
); );
} else { } else {
set_env_var(&mut env_vars, "CC", flags.cc.clone()); set_expanded_env_var(&mut env_vars, "CC", flags.cc.trim());
set_env_var(&mut env_vars, "CXX", flags.cxx.clone()); set_expanded_env_var(&mut env_vars, "CXX", flags.cxx.trim());
set_env_var(&mut env_vars, "AR", flags.ar.clone()); set_expanded_env_var(&mut env_vars, "AR", flags.ar.trim());
if !flags.ranlib.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "RANLIB", flags.ranlib.trim());
}
if !flags.strip.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "STRIP", flags.strip.trim());
}
if !flags.ld.trim().is_empty() { if !flags.ld.trim().is_empty() {
set_env_var(&mut env_vars, "LD", flags.ld.clone()); set_expanded_env_var(&mut env_vars, "LD", flags.ld.trim());
}
if !flags.nm.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "NM", flags.nm.trim());
}
if !flags.objcopy.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "OBJCOPY", flags.objcopy.trim());
}
if !flags.objdump.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "OBJDUMP", flags.objdump.trim());
}
if !flags.readelf.trim().is_empty() {
set_expanded_env_var(&mut env_vars, "READELF", flags.readelf.trim());
} }
if !flags.cpp.trim().is_empty() { if !flags.cpp.trim().is_empty() {
set_env_var(&mut env_vars, "CPP", flags.cpp.clone()); set_expanded_env_var(&mut env_vars, "CPP", flags.cpp.trim());
} }
} }
} }
@@ -1026,6 +1172,12 @@ mod tests {
} }
} }
fn env_value<'a>(env: &'a EnvVars, key: &str) -> Option<&'a str> {
env.iter()
.find(|(candidate, _)| candidate == key)
.map(|(_, value)| value.as_str())
}
#[test] #[test]
fn test_prepare_command() { fn test_prepare_command() {
let mut cmd = Command::new("ls"); let mut cmd = Command::new("ls");
@@ -1143,14 +1295,121 @@ mod tests {
#[test] #[test]
fn test_standard_build_env_exports_native_linker_and_cpp() { fn test_standard_build_env_exports_native_linker_and_cpp() {
let mut spec = mk_spec(Vec::new(), Vec::new()); let mut spec = mk_spec(Vec::new(), Vec::new());
spec.build.flags.ranlib = "llvm-ranlib".to_string();
spec.build.flags.strip = "llvm-strip".to_string();
spec.build.flags.ld = "ld.lld".to_string(); spec.build.flags.ld = "ld.lld".to_string();
spec.build.flags.nm = "llvm-nm".to_string();
spec.build.flags.objcopy = "llvm-objcopy".to_string();
spec.build.flags.objdump = "llvm-objdump".to_string();
spec.build.flags.readelf = "llvm-readelf".to_string();
spec.build.flags.cpp = "clang-cpp".to_string(); spec.build.flags.cpp = "clang-cpp".to_string();
let env = standard_build_env(&spec, None, true, true); let env = standard_build_env(&spec, None, true, true);
assert!(env.iter().any(|(k, v)| k == "RANLIB" && v == "llvm-ranlib"));
assert!(env.iter().any(|(k, v)| k == "STRIP" && v == "llvm-strip"));
assert!(env.iter().any(|(k, v)| k == "LD" && v == "ld.lld")); assert!(env.iter().any(|(k, v)| k == "LD" && v == "ld.lld"));
assert!(env.iter().any(|(k, v)| k == "NM" && v == "llvm-nm"));
assert!(
env.iter()
.any(|(k, v)| k == "OBJCOPY" && v == "llvm-objcopy")
);
assert!(
env.iter()
.any(|(k, v)| k == "OBJDUMP" && v == "llvm-objdump")
);
assert!(
env.iter()
.any(|(k, v)| k == "READELF" && v == "llvm-readelf")
);
assert!(env.iter().any(|(k, v)| k == "CPP" && v == "clang-cpp")); assert!(env.iter().any(|(k, v)| k == "CPP" && v == "clang-cpp"));
} }
#[test]
fn test_standard_build_env_exports_tool_dir_and_expands_tool_commands() {
let mut spec = mk_spec(
vec!["--gcc-toolchain=$TOOL_DIR"],
vec!["-B$TOOL_DIR", "-Wl,--as-needed"],
);
spec.build.flags.tool_dir = "/opt/depot-tools/bin".to_string();
spec.build.flags.cc = "$TOOL_DIR/clang".to_string();
spec.build.flags.cxx = "$TOOL_DIR/clang++".to_string();
spec.build.flags.ar = "$TOOL_DIR/llvm-ar".to_string();
spec.build.flags.ranlib = "$TOOL_DIR/llvm-ranlib".to_string();
spec.build.flags.ld = "$TOOL_DIR/ld.lld".to_string();
spec.build.flags.env_vars = vec!["LLVM_CONFIG=$TOOL_DIR/llvm-config".to_string()];
let env = standard_build_env(&spec, None, true, true);
assert_eq!(env_value(&env, "TOOL_DIR"), Some("/opt/depot-tools/bin"));
assert_eq!(env_value(&env, "CC"), Some("/opt/depot-tools/bin/clang"));
assert_eq!(env_value(&env, "CXX"), Some("/opt/depot-tools/bin/clang++"));
assert_eq!(env_value(&env, "AR"), Some("/opt/depot-tools/bin/llvm-ar"));
assert_eq!(
env_value(&env, "RANLIB"),
Some("/opt/depot-tools/bin/llvm-ranlib")
);
assert_eq!(env_value(&env, "LD"), Some("/opt/depot-tools/bin/ld.lld"));
assert_eq!(
env_value(&env, "CFLAGS"),
Some("--gcc-toolchain=/opt/depot-tools/bin")
);
assert_eq!(
env_value(&env, "LDFLAGS"),
Some("-B/opt/depot-tools/bin -Wl,--as-needed")
);
assert_eq!(
env_value(&env, "LLVM_CONFIG"),
Some("/opt/depot-tools/bin/llvm-config")
);
}
#[test]
fn test_standard_build_env_cross_uses_package_tool_overrides() {
let mut spec = mk_spec(Vec::new(), Vec::new());
spec.build.flags.cc = "/tools/bin/clang".to_string();
spec.build.flags.cxx = "/tools/bin/clang++".to_string();
spec.build.flags.ar = "/tools/bin/llvm-ar".to_string();
spec.build.flags.ranlib = "/tools/bin/llvm-ranlib".to_string();
spec.build.flags.strip = "/tools/bin/llvm-strip".to_string();
spec.build.flags.ld = "/tools/bin/ld.lld".to_string();
spec.build.flags.nm = "/tools/bin/llvm-nm".to_string();
spec.build.flags.objcopy = "/tools/bin/llvm-objcopy".to_string();
spec.build.flags.objdump = "/tools/bin/llvm-objdump".to_string();
spec.build.flags.readelf = "/tools/bin/llvm-readelf".to_string();
spec.build.flags.cpp = "/tools/bin/clang-cpp".to_string();
let cross = CrossConfig {
prefix: "x86_64-test-linux-gnu".into(),
cc: "x86_64-test-linux-gnu-gcc".into(),
cxx: "x86_64-test-linux-gnu-g++".into(),
ar: "x86_64-test-linux-gnu-ar".into(),
ranlib: "x86_64-test-linux-gnu-ranlib".into(),
strip: "x86_64-test-linux-gnu-strip".into(),
ld: "x86_64-test-linux-gnu-ld".into(),
nm: "x86_64-test-linux-gnu-nm".into(),
objcopy: "x86_64-test-linux-gnu-objcopy".into(),
objdump: "x86_64-test-linux-gnu-objdump".into(),
readelf: "x86_64-test-linux-gnu-readelf".into(),
};
let env = standard_build_env(&spec, Some(&cross), true, true);
assert_eq!(env_value(&env, "CC"), Some("/tools/bin/clang"));
assert_eq!(env_value(&env, "CXX"), Some("/tools/bin/clang++"));
assert_eq!(env_value(&env, "AR"), Some("/tools/bin/llvm-ar"));
assert_eq!(env_value(&env, "RANLIB"), Some("/tools/bin/llvm-ranlib"));
assert_eq!(env_value(&env, "STRIP"), Some("/tools/bin/llvm-strip"));
assert_eq!(env_value(&env, "LD"), Some("/tools/bin/ld.lld"));
assert_eq!(env_value(&env, "NM"), Some("/tools/bin/llvm-nm"));
assert_eq!(env_value(&env, "OBJCOPY"), Some("/tools/bin/llvm-objcopy"));
assert_eq!(env_value(&env, "OBJDUMP"), Some("/tools/bin/llvm-objdump"));
assert_eq!(env_value(&env, "READELF"), Some("/tools/bin/llvm-readelf"));
assert_eq!(env_value(&env, "CPP"), Some("/tools/bin/clang-cpp"));
assert_eq!(
env_value(&env, "CROSS_PREFIX"),
Some("x86_64-test-linux-gnu")
);
}
#[test] #[test]
fn test_standard_build_env_exports_effective_carch_for_cross_and_lib32() { fn test_standard_build_env_exports_effective_carch_for_cross_and_lib32() {
let spec = mk_spec(Vec::new(), Vec::new()); let spec = mk_spec(Vec::new(), Vec::new());
@@ -1175,6 +1434,24 @@ mod tests {
.any(|(k, v)| k == "CARCH" && v == "aarch64"), .any(|(k, v)| k == "CARCH" && v == "aarch64"),
"expected cross builds to export target CARCH" "expected cross builds to export target CARCH"
); );
assert!(
cross_env
.iter()
.any(|(k, v)| k == "OBJCOPY" && v == "aarch64-linux-gnu-objcopy"),
"expected cross builds to export OBJCOPY"
);
assert!(
cross_env
.iter()
.any(|(k, v)| k == "OBJDUMP" && v == "aarch64-linux-gnu-objdump"),
"expected cross builds to export OBJDUMP"
);
assert!(
cross_env
.iter()
.any(|(k, v)| k == "READELF" && v == "aarch64-linux-gnu-readelf"),
"expected cross builds to export READELF"
);
let mut lib32_spec = spec.clone(); let mut lib32_spec = spec.clone();
lib32_spec.build.flags.lib32_variant = true; lib32_spec.build.flags.lib32_variant = true;
@@ -1271,6 +1548,29 @@ mod tests {
); );
} }
#[test]
fn test_standard_build_env_injects_fuse_ld_into_ldflags() {
let mut spec = mk_spec(Vec::new(), vec!["-Wl,--as-needed"]);
spec.build.flags.fuse_ld = "/usr/bin/ld.lld".into();
let env = standard_build_env(&spec, None, true, true);
assert_eq!(
env_value(&env, "LDFLAGS"),
Some("-fuse-ld=/usr/bin/ld.lld -Wl,--as-needed")
);
}
#[test]
fn test_standard_build_env_normalizes_fuse_ld_tool_names() {
let mut spec = mk_spec(Vec::new(), Vec::new());
spec.build.flags.fuse_ld = "ld.lld".into();
let env = standard_build_env(&spec, None, true, true);
assert_eq!(env_value(&env, "LDFLAGS"), Some("-fuse-ld=lld"));
}
#[test] #[test]
fn test_standard_build_env_applies_replace_flag_rules() { fn test_standard_build_env_applies_replace_flag_rules() {
let mut spec = mk_spec(vec!["-D_FORTIFY_SOURCE=3", "-O2"], vec!["-Wl,-O3"]); let mut spec = mk_spec(vec!["-D_FORTIFY_SOURCE=3", "-O2"], vec!["-Wl,-O3"]);
@@ -1383,6 +1683,62 @@ mod tests {
); );
} }
#[test]
fn test_standard_build_env_exports_install_dir_vars() {
let mut spec = mk_spec(Vec::new(), Vec::new());
spec.build.flags.prefix = "/system".into();
spec.build.flags.bindir = "/system/binaries".into();
spec.build.flags.sbindir = "/system/systembinaries".into();
spec.build.flags.libdir = "/system/libraries".into();
spec.build.flags.libexecdir = "/system/libexec".into();
spec.build.flags.sysconfdir = "/system/configuration".into();
spec.build.flags.localstatedir = "/system/variable".into();
spec.build.flags.sharedstatedir = "/system/variable/lib".into();
spec.build.flags.includedir = "/system/headers".into();
spec.build.flags.datarootdir = "/system/share".into();
spec.build.flags.datadir = "/system/data".into();
spec.build.flags.mandir = "/system/documentation/man-pages".into();
spec.build.flags.infodir = "/system/documentation/info".into();
let env = standard_build_env(&spec, None, false, true);
assert_eq!(env_value(&env, "PREFIX"), Some("/system"));
assert_eq!(env_value(&env, "BINDIR"), Some("/system/binaries"));
assert_eq!(env_value(&env, "SBINDIR"), Some("/system/systembinaries"));
assert_eq!(env_value(&env, "LIBDIR"), Some("/system/libraries"));
assert_eq!(env_value(&env, "LIBEXECDIR"), Some("/system/libexec"));
assert_eq!(env_value(&env, "SYSCONFDIR"), Some("/system/configuration"));
assert_eq!(env_value(&env, "LOCALSTATEDIR"), Some("/system/variable"));
assert_eq!(
env_value(&env, "SHAREDSTATEDIR"),
Some("/system/variable/lib")
);
assert_eq!(env_value(&env, "INCLUDEDIR"), Some("/system/headers"));
assert_eq!(env_value(&env, "DATAROOTDIR"), Some("/system/share"));
assert_eq!(env_value(&env, "DATADIR"), Some("/system/data"));
assert_eq!(
env_value(&env, "MANDIR"),
Some("/system/documentation/man-pages")
);
assert_eq!(
env_value(&env, "INFODIR"),
Some("/system/documentation/info")
);
}
#[test]
fn test_standard_build_env_install_dir_vars_use_effective_defaults() {
let mut spec = mk_spec(Vec::new(), Vec::new());
spec.build.flags.lib32_variant = true;
let env = standard_build_env(&spec, None, false, true);
assert_eq!(env_value(&env, "LIBDIR"), Some("/usr/lib32"));
assert_eq!(env_value(&env, "LIBEXECDIR"), Some("/usr/lib32"));
assert_eq!(env_value(&env, "DATAROOTDIR"), Some("/usr/share"));
assert_eq!(env_value(&env, "DATADIR"), Some("/usr/share"));
}
#[test] #[test]
fn test_standard_build_env_exports_passthrough_env() { fn test_standard_build_env_exports_passthrough_env() {
let mut spec = mk_spec(Vec::new(), Vec::new()); let mut spec = mk_spec(Vec::new(), Vec::new());
@@ -1407,6 +1763,7 @@ mod tests {
spec.build.flags.env_vars = vec![ spec.build.flags.env_vars = vec![
"SETUPTOOLS_SCM_PRETEND_VERSION=$version".into(), "SETUPTOOLS_SCM_PRETEND_VERSION=$version".into(),
"PYO3_CONFIG_FILE=$specdir/pyo3.toml".into(), "PYO3_CONFIG_FILE=$specdir/pyo3.toml".into(),
"PKG_CONFIG_PATH=$LIBDIR/pkgconfig".into(),
]; ];
let env = standard_build_env(&spec, None, false, true); let env = standard_build_env(&spec, None, false, true);
@@ -1420,6 +1777,11 @@ mod tests {
.any(|(k, v)| k == "PYO3_CONFIG_FILE" && v == "/tmp/specs/demo/pyo3.toml"), .any(|(k, v)| k == "PYO3_CONFIG_FILE" && v == "/tmp/specs/demo/pyo3.toml"),
"expected env_vars values to expand specdir variables" "expected env_vars values to expand specdir variables"
); );
assert!(
env.iter()
.any(|(k, v)| k == "PKG_CONFIG_PATH" && v == "/usr/lib/pkgconfig"),
"expected env_vars values to expand install directory variables"
);
} }
#[test] #[test]
+19 -7
View File
@@ -58,7 +58,10 @@ pub fn build(
configure_cmd.arg("INSTALLDIRS=vendor"); configure_cmd.arg("INSTALLDIRS=vendor");
} }
for arg in &flags.configure { for arg in &flags.configure {
configure_cmd.arg(spec.expand_vars(arg)); configure_cmd.arg(crate::builder::expand_with_envs(
&spec.expand_vars(arg),
&env_vars,
));
} }
for arg in crate::builder::static_build_args_for(crate::package::BuildType::Perl, flags)? { for arg in crate::builder::static_build_args_for(crate::package::BuildType::Perl, flags)? {
configure_cmd.arg(arg); configure_cmd.arg(arg);
@@ -291,13 +294,19 @@ pub(crate) fn run_helper_configure(
configure_cmd.arg("INSTALLDIRS=vendor"); configure_cmd.arg("INSTALLDIRS=vendor");
} }
for arg in &flags.configure { for arg in &flags.configure {
configure_cmd.arg(context.expand_vars(arg)); configure_cmd.arg(crate::builder::expand_with_envs(
&context.expand_vars(arg),
&helper_env,
));
} }
for arg in crate::builder::static_build_args_for(crate::package::BuildType::Perl, &flags)? { for arg in crate::builder::static_build_args_for(crate::package::BuildType::Perl, &flags)? {
configure_cmd.arg(arg); configure_cmd.arg(arg);
} }
for arg in extra_args { for arg in extra_args {
configure_cmd.arg(context.expand_vars(arg)); configure_cmd.arg(crate::builder::expand_with_envs(
&context.expand_vars(arg),
&helper_env,
));
} }
crate::builder::prepare_tool_command(&mut configure_cmd, &helper_env); crate::builder::prepare_tool_command(&mut configure_cmd, &helper_env);
@@ -350,12 +359,15 @@ pub(crate) fn run_helper_install(
for target in &install_targets { for target in &install_targets {
install_cmd.arg(target); install_cmd.arg(target);
} }
for arg in extra_args {
install_cmd.arg(context.expand_vars(arg));
}
let mut install_env = env_vars.to_vec(); let mut install_env = env_vars.to_vec();
crate::builder::set_env_var(&mut install_env, "DESTDIR", destdir.clone()); crate::builder::set_env_var(&mut install_env, "DESTDIR", destdir.clone());
for arg in extra_args {
install_cmd.arg(crate::builder::expand_with_envs(
&context.expand_vars(arg),
&install_env,
));
}
crate::builder::prepare_tool_command(&mut install_cmd, &install_env); crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
let status = command_status_with_sh_fallback(&mut install_cmd).with_context(|| { let status = command_status_with_sh_fallback(&mut install_cmd).with_context(|| {
+12 -5
View File
@@ -36,7 +36,6 @@ pub fn build(
) -> Result<()> { ) -> Result<()> {
let flags = &spec.build.flags; let flags = &spec.build.flags;
let actual_src = resolve_actual_src(spec, src_dir)?; let actual_src = resolve_actual_src(spec, src_dir)?;
let config_settings = normalize_pep517_config_settings(spec)?;
fs::create_dir_all(destdir) fs::create_dir_all(destdir)
.with_context(|| format!("Failed to create DESTDIR: {}", destdir.display()))?; .with_context(|| format!("Failed to create DESTDIR: {}", destdir.display()))?;
@@ -44,6 +43,7 @@ pub fn build(
crate::builder::set_env_var(&mut env_vars, "PYTHONNOUSERSITE", "1"); crate::builder::set_env_var(&mut env_vars, "PYTHONNOUSERSITE", "1");
crate::builder::set_env_var(&mut env_vars, "PYTHONDONTWRITEBYTECODE", "1"); crate::builder::set_env_var(&mut env_vars, "PYTHONDONTWRITEBYTECODE", "1");
crate::builder::set_env_var(&mut env_vars, "SETUPTOOLS_USE_DISTUTILS", "local"); crate::builder::set_env_var(&mut env_vars, "SETUPTOOLS_USE_DISTUTILS", "local");
let config_settings = normalize_pep517_config_settings(spec, &env_vars)?;
let mut state = StateTracker::new_with_namespace( let mut state = StateTracker::new_with_namespace(
&actual_src, &actual_src,
@@ -84,10 +84,13 @@ pub fn build(
Ok(()) Ok(())
} }
fn normalize_pep517_config_settings(spec: &PackageSpec) -> Result<Vec<String>> { fn normalize_pep517_config_settings(
spec: &PackageSpec,
env_vars: &[(String, String)],
) -> Result<Vec<String>> {
let mut out = Vec::new(); let mut out = Vec::new();
for raw in &spec.build.flags.config_settings { for raw in &spec.build.flags.config_settings {
let expanded = spec.expand_vars(raw); let expanded = crate::builder::expand_with_envs(&spec.expand_vars(raw), env_vars);
let trimmed = expanded.trim(); let trimmed = expanded.trim();
if trimmed.is_empty() { if trimmed.is_empty() {
continue; continue;
@@ -821,9 +824,11 @@ mod tests {
"setup-args=--plat-name=x86_64".into(), "setup-args=--plat-name=x86_64".into(),
"builddir".into(), "builddir".into(),
"version=$version".into(), "version=$version".into(),
"libdir=$LIBDIR".into(),
]; ];
let normalized = normalize_pep517_config_settings(&spec)?; let env_vars = crate::builder::standard_build_env(&spec, None, false, true);
let normalized = normalize_pep517_config_settings(&spec, &env_vars)?;
assert_eq!( assert_eq!(
normalized, normalized,
vec![ vec![
@@ -831,6 +836,7 @@ mod tests {
"setup-args=--plat-name=x86_64".to_string(), "setup-args=--plat-name=x86_64".to_string(),
"builddir=".to_string(), "builddir=".to_string(),
"version=1.2.3".to_string(), "version=1.2.3".to_string(),
"libdir=/usr/lib".to_string(),
] ]
); );
Ok(()) Ok(())
@@ -840,7 +846,8 @@ mod tests {
fn normalize_pep517_config_settings_rejects_invalid_key() { fn normalize_pep517_config_settings_rejects_invalid_key() {
let mut spec = mk_spec(); let mut spec = mk_spec();
spec.build.flags.config_settings = vec!["bad key=value".into()]; spec.build.flags.config_settings = vec!["bad key=value".into()];
let err = normalize_pep517_config_settings(&spec) let env_vars = crate::builder::standard_build_env(&spec, None, false, true);
let err = normalize_pep517_config_settings(&spec, &env_vars)
.expect_err("invalid config setting key should fail"); .expect_err("invalid config setting key should fail");
assert!(err.to_string().contains("expected KEY=VALUE or KEY")); assert!(err.to_string().contains("expected KEY=VALUE or KEY"));
} }
+75 -1
View File
@@ -250,6 +250,32 @@ pub struct ConfigArgs {
pub rootfs_args: RootfsArgs, pub rootfs_args: RootfsArgs,
} }
/// Arguments for selecting canonical tool aliases in the configured tool directory.
#[derive(Debug, Clone, Args)]
pub struct SetArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
/// Tool role to configure, such as compiler, linker, or shell.
#[arg(value_enum)]
pub role: ToolRoleArg,
/// Literal separator used by commands like `depot set compiler to clang`.
#[arg(value_name = "to")]
pub connector: String,
/// Tool implementation to select for the requested role.
pub implementation: String,
}
/// Tool alias role configurable through `depot set`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum ToolRoleArg {
Compiler,
Linker,
Shell,
}
/// Arguments for Depot system-build state management commands. /// Arguments for Depot system-build state management commands.
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct SystemArgs { pub struct SystemArgs {
@@ -373,6 +399,8 @@ pub enum Commands {
Repo(RepoArgs), Repo(RepoArgs),
/// Show current configuration /// Show current configuration
Config(ConfigArgs), Config(ConfigArgs),
/// Select canonical tool aliases, e.g. `depot set compiler to clang`
Set(SetArgs),
/// Manage system build stage, package layers, and book-style layout state /// Manage system build stage, package layers, and book-style layout state
System(SystemArgs), System(SystemArgs),
/// Generate shell completion scripts and a man page into an output directory. /// Generate shell completion scripts and a man page into an output directory.
@@ -591,7 +619,7 @@ pub enum RepoKindArg {
mod tests { mod tests {
use super::{ use super::{
BuildArgs, Cli, Commands, ConvertArgs, InstallArgs, RepoArgs, RepoCommands, SearchArgs, BuildArgs, Cli, Commands, ConvertArgs, InstallArgs, RepoArgs, RepoCommands, SearchArgs,
UpdateArgs, SetArgs, ToolRoleArg, UpdateArgs,
}; };
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use std::path::PathBuf; use std::path::PathBuf;
@@ -631,6 +659,52 @@ mod tests {
} }
} }
#[test]
fn set_accepts_role_to_implementation_form() {
let cli = Cli::try_parse_from([
"depot",
"set",
"--rootfs",
"/tmp/rootfs",
"compiler",
"to",
"clang",
])
.unwrap();
match cli.command {
Commands::Set(SetArgs {
rootfs_args,
role,
connector,
implementation,
}) => {
assert_eq!(rootfs_args.rootfs, PathBuf::from("/tmp/rootfs"));
assert_eq!(role, ToolRoleArg::Compiler);
assert_eq!(connector, "to");
assert_eq!(implementation, "clang");
}
_ => panic!("expected set command"),
}
}
#[test]
fn set_accepts_shell_role() {
let cli = Cli::try_parse_from(["depot", "set", "shell", "to", "dash"]).unwrap();
match cli.command {
Commands::Set(SetArgs {
role,
connector,
implementation,
..
}) => {
assert_eq!(role, ToolRoleArg::Shell);
assert_eq!(connector, "to");
assert_eq!(implementation, "dash");
}
_ => panic!("expected set command"),
}
}
#[test] #[test]
fn update_accepts_no_package_names() { fn update_accepts_no_package_names() {
let cli = Cli::try_parse_from(["depot", "update"]).unwrap(); let cli = Cli::try_parse_from(["depot", "update"]).unwrap();
+9 -3
View File
@@ -1,7 +1,7 @@
use crate::cli::{ use crate::cli::{
BuildArgs, Cli, Commands, ConfigArgs, ConvertArgs, InfoArgs, InstallArgs, InternalCommands, BuildArgs, Cli, Commands, ConfigArgs, ConvertArgs, InfoArgs, InstallArgs, InternalCommands,
ListArgs, OwnsArgs, RemoveArgs, RepoCommands, RepoKindArg, SearchArgs, SignArgs, SystemArgs, ListArgs, OwnsArgs, RemoveArgs, RepoCommands, RepoKindArg, SearchArgs, SetArgs, SignArgs,
UpdateArgs, SystemArgs, UpdateArgs,
}; };
use crate::{ use crate::{
bootstrap, builder, cli_assets, config, cross, db, deps, index, install, locking, package, bootstrap, builder, cli_assets, config, cross, db, deps, index, install, locking, package,
@@ -66,7 +66,7 @@ fn rootfs_is_system_root(rootfs: &Path) -> bool {
fn command_requires_live_root(command: &Commands) -> bool { fn command_requires_live_root(command: &Commands) -> bool {
matches!( matches!(
command, command,
Commands::Install(_) | Commands::Remove(_) | Commands::Update(_) Commands::Install(_) | Commands::Remove(_) | Commands::Update(_) | Commands::Set(_)
) )
} }
@@ -100,6 +100,7 @@ fn command_rootfs(command: &Commands) -> Option<&Path> {
Commands::Sign(args) => Some(&args.rootfs_args.rootfs), Commands::Sign(args) => Some(&args.rootfs_args.rootfs),
Commands::Repo(args) => Some(repo_command_rootfs(&args.command)), Commands::Repo(args) => Some(repo_command_rootfs(&args.command)),
Commands::Config(args) => Some(&args.rootfs_args.rootfs), Commands::Config(args) => Some(&args.rootfs_args.rootfs),
Commands::Set(args) => Some(&args.rootfs_args.rootfs),
Commands::System(args) => Some(&args.rootfs_args.rootfs), Commands::System(args) => Some(&args.rootfs_args.rootfs),
Commands::Check(_) Commands::Check(_)
| Commands::Convert(_) | Commands::Convert(_)
@@ -125,6 +126,7 @@ fn command_assume_yes(command: &Commands) -> bool {
| Commands::Sign(_) | Commands::Sign(_)
| Commands::Repo(_) | Commands::Repo(_)
| Commands::Config(_) | Commands::Config(_)
| Commands::Set(_)
| Commands::System(_) | Commands::System(_)
| Commands::GenerateArtifacts(_) | Commands::GenerateArtifacts(_)
| Commands::MakeSpec(_) | Commands::MakeSpec(_)
@@ -2167,6 +2169,7 @@ fn run_direct_install_request(
// Install from spec (normal build) // Install from spec (normal build)
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?; let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
pkg_spec.apply_config(config); pkg_spec.apply_config(config);
pkg_spec.build.flags.rootfs = build_cmd::build_env_rootfs(options.rootfs);
(pkg_spec, None) (pkg_spec, None)
}; };
let built_from_source = staging_dir.is_none(); let built_from_source = staging_dir.is_none();
@@ -2567,6 +2570,7 @@ mod check;
mod install_cmd; mod install_cmd;
mod misc; mod misc;
mod repo; mod repo;
mod set;
mod update; mod update;
pub fn run(cli: Cli) -> Result<()> { pub fn run(cli: Cli) -> Result<()> {
@@ -2591,6 +2595,7 @@ pub fn run(cli: Cli) -> Result<()> {
| Commands::Sign(_) | Commands::Sign(_)
| Commands::Repo(_) | Commands::Repo(_)
| Commands::Config(_) | Commands::Config(_)
| Commands::Set(_)
| Commands::System(_) | Commands::System(_)
| Commands::GenerateArtifacts(_) | Commands::GenerateArtifacts(_)
| Commands::Convert(_) | Commands::Convert(_)
@@ -2613,6 +2618,7 @@ pub fn run(cli: Cli) -> Result<()> {
Commands::Repo(args) => repo::run_repo(args.command)?, Commands::Repo(args) => repo::run_repo(args.command)?,
Commands::GenerateArtifacts(args) => misc::run_generate_artifacts(args)?, Commands::GenerateArtifacts(args) => misc::run_generate_artifacts(args)?,
Commands::Config(args) => misc::run_config(args)?, Commands::Config(args) => misc::run_config(args)?,
Commands::Set(args) => set::run_set(args)?,
Commands::System(args) => misc::run_system(args)?, Commands::System(args) => misc::run_system(args)?,
Commands::MakeSpec(args) => misc::run_make_spec(args)?, Commands::MakeSpec(args) => misc::run_make_spec(args)?,
Commands::Convert(args) => misc::run_convert(args)?, Commands::Convert(args) => misc::run_convert(args)?,
+12
View File
@@ -11,6 +11,17 @@ use self::support::{
requested_outputs, should_install_test_deps, warn_if_running_as_root_for_build, requested_outputs, should_install_test_deps, warn_if_running_as_root_for_build,
}; };
pub(crate) fn build_env_rootfs(rootfs: &Path) -> String {
if rootfs == Path::new("/") {
return "/".to_string();
}
rootfs
.canonicalize()
.unwrap_or_else(|_| rootfs.to_path_buf())
.to_string_lossy()
.into_owned()
}
pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> { pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> {
let BuildArgs { let BuildArgs {
rootfs_args, rootfs_args,
@@ -46,6 +57,7 @@ pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> {
let mut auto_installed_deps = AutoInstalledDependencyTracker::default(); let mut auto_installed_deps = AutoInstalledDependencyTracker::default();
let build_result: Result<()> = (|| { let build_result: Result<()> = (|| {
pkg_spec.apply_config(&config); pkg_spec.apply_config(&config);
pkg_spec.build.flags.rootfs = build_env_rootfs(&rootfs);
let lib32_only = effective_lib32_only(&pkg_spec, cli_lib32_only); let lib32_only = effective_lib32_only(&pkg_spec, cli_lib32_only);
let requested_outputs = requested_outputs(&pkg_spec, lib32_only); let requested_outputs = requested_outputs(&pkg_spec, lib32_only);
let db_path = config.installed_db_path(&rootfs); let db_path = config.installed_db_path(&rootfs);
+1 -1
View File
@@ -138,7 +138,7 @@ pub(crate) fn package_spec_from_repo_record(
runtime: record.runtime_dependencies.clone(), runtime: record.runtime_dependencies.clone(),
test: Vec::new(), test: Vec::new(),
optional: record.optional_dependencies.clone(), optional: record.optional_dependencies.clone(),
groups: Vec::new(), groups: record.groups.clone(),
lib32: None, lib32: None,
}, },
package_alternatives: Default::default(), package_alternatives: Default::default(),
+1
View File
@@ -35,6 +35,7 @@ fn current_process_env_vars() -> Vec<(String, String)> {
"RUSTLTOFLAGS", "RUSTLTOFLAGS",
"SETUPTOOLS_USE_DISTUTILS", "SETUPTOOLS_USE_DISTUTILS",
"STRIP", "STRIP",
"TOOL_DIR",
]; ];
ALLOWED_ENV_VARS ALLOWED_ENV_VARS
+434
View File
@@ -0,0 +1,434 @@
use super::*;
use crate::cli::ToolRoleArg;
use std::io::ErrorKind;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ToolAlias {
alias: &'static str,
target: &'static str,
}
pub(super) fn run_set(args: SetArgs) -> Result<()> {
let SetArgs {
rootfs_args,
role,
connector,
implementation,
} = args;
if connector != "to" {
anyhow::bail!("Expected `to` in set command, for example: depot set compiler to clang");
}
let implementation = implementation.trim().to_ascii_lowercase();
let aliases = aliases_for_selection(role, &implementation)?;
let rootfs = rootfs_args.rootfs;
let config = config::Config::for_rootfs(&rootfs);
let mut set_lock = locking::open_lock(&config)?;
let set_lock_path = locking::lock_path(&config);
let _set_lock_guard = locking::try_write(&mut set_lock, &set_lock_path, "set")?;
let alias_dir = configured_alias_dir(&config);
let host_alias_dir = dir_in_rootfs(&rootfs, &alias_dir);
configure_tool_aliases(&host_alias_dir, &aliases).with_context(|| {
format!(
"Failed to set {} to {} in {}",
role_name(role),
implementation,
host_alias_dir.display()
)
})?;
ui::success(format!(
"Set {} to {} in {}",
role_name(role),
implementation,
host_alias_dir.display()
));
Ok(())
}
fn role_name(role: ToolRoleArg) -> &'static str {
match role {
ToolRoleArg::Compiler => "compiler",
ToolRoleArg::Linker => "linker",
ToolRoleArg::Shell => "shell",
}
}
fn configured_alias_dir(config: &config::Config) -> PathBuf {
let configured = config
.build_overrides
.get("flags")
.and_then(|flags| flags.get("bindir"))
.or_else(|| config.build_overrides.get("bindir"))
.and_then(|value| value.as_str())
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("/system/binaries");
PathBuf::from(configured)
}
fn dir_in_rootfs(rootfs: &Path, dir: &Path) -> PathBuf {
let rootfs = resolve_rootfs_base(rootfs);
if dir.is_absolute() && dir.starts_with(&rootfs) {
dir.to_path_buf()
} else if dir.is_absolute() {
rootfs.join(dir.strip_prefix("/").unwrap_or(dir))
} else {
rootfs.join(dir)
}
}
fn resolve_rootfs_base(rootfs: &Path) -> PathBuf {
if rootfs.exists() {
rootfs.canonicalize().unwrap_or_else(|_| {
std::env::current_dir()
.map(|cwd| cwd.join(rootfs))
.unwrap_or_else(|_| rootfs.to_path_buf())
})
} else {
std::env::current_dir()
.map(|cwd| cwd.join(rootfs))
.unwrap_or_else(|_| rootfs.to_path_buf())
}
}
fn aliases_for_selection(role: ToolRoleArg, implementation: &str) -> Result<Vec<ToolAlias>> {
let aliases = match (role, implementation) {
(ToolRoleArg::Compiler, "clang") => vec![
ToolAlias {
alias: "cc",
target: "clang",
},
ToolAlias {
alias: "c++",
target: "clang++",
},
],
(ToolRoleArg::Compiler, "gcc") => vec![
ToolAlias {
alias: "cc",
target: "gcc",
},
ToolAlias {
alias: "c++",
target: "g++",
},
],
(ToolRoleArg::Linker, "lld" | "ld.lld") => vec![ToolAlias {
alias: "ld",
target: "ld.lld",
}],
(ToolRoleArg::Linker, "mold") => vec![ToolAlias {
alias: "ld",
target: "mold",
}],
(ToolRoleArg::Shell, "bash") => vec![ToolAlias {
alias: "sh",
target: "bash",
}],
(ToolRoleArg::Shell, "dash") => vec![ToolAlias {
alias: "sh",
target: "dash",
}],
(ToolRoleArg::Shell, "zsh") => vec![ToolAlias {
alias: "sh",
target: "zsh",
}],
(ToolRoleArg::Compiler, _) => {
anyhow::bail!(
"Unsupported compiler selection `{implementation}`; supported: clang, gcc"
)
}
(ToolRoleArg::Linker, _) => {
anyhow::bail!("Unsupported linker selection `{implementation}`; supported: lld, mold")
}
(ToolRoleArg::Shell, _) => {
anyhow::bail!(
"Unsupported shell selection `{implementation}`; supported: bash, dash, zsh"
)
}
};
Ok(aliases)
}
#[cfg(unix)]
fn configure_tool_aliases(tool_dir: &Path, aliases: &[ToolAlias]) -> Result<()> {
use std::os::unix::fs as unix_fs;
for alias in aliases {
validate_tool_name("alias", alias.alias)?;
validate_tool_name("target", alias.target)?;
let target_path = tool_dir.join(alias.target);
match fs::metadata(&target_path) {
Ok(_) => {}
Err(err) if err.kind() == ErrorKind::NotFound => {
anyhow::bail!(
"Cannot set {} to {}; target tool is missing: {}",
alias.alias,
alias.target,
target_path.display()
);
}
Err(err) => {
return Err(err)
.with_context(|| format!("Failed to inspect {}", target_path.display()));
}
}
let alias_path = tool_dir.join(alias.alias);
match fs::symlink_metadata(&alias_path) {
Ok(metadata) if metadata.file_type().is_symlink() => {
let current = fs::read_link(&alias_path)
.with_context(|| format!("Failed to read symlink {}", alias_path.display()))?;
if current == Path::new(alias.target) {
continue;
}
fs::remove_file(&alias_path)
.with_context(|| format!("Failed to replace {}", alias_path.display()))?;
}
Ok(_) => {
anyhow::bail!(
"Refusing to replace non-symlink tool alias: {}",
alias_path.display()
);
}
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => {
return Err(err)
.with_context(|| format!("Failed to inspect {}", alias_path.display()));
}
}
unix_fs::symlink(alias.target, &alias_path)
.with_context(|| format!("Failed to create symlink {}", alias_path.display()))?;
}
Ok(())
}
#[cfg(not(unix))]
fn configure_tool_aliases(_tool_dir: &Path, _aliases: &[ToolAlias]) -> Result<()> {
anyhow::bail!("depot set requires Unix symlink support")
}
fn validate_tool_name(kind: &str, name: &str) -> Result<()> {
if name.is_empty() || name.contains('/') || name.contains('\0') {
anyhow::bail!("Invalid tool {kind}: {name:?}");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::{RootfsArgs, ToolRoleArg};
fn set_args(rootfs: &Path, role: ToolRoleArg, implementation: &str) -> SetArgs {
SetArgs {
rootfs_args: RootfsArgs {
rootfs: rootfs.to_path_buf(),
},
role,
connector: "to".into(),
implementation: implementation.into(),
}
}
fn make_tool_dir(rootfs: &Path) -> PathBuf {
let etc = rootfs.join("etc/depot.d");
fs::create_dir_all(&etc).unwrap();
fs::write(
etc.join("build.toml"),
r#"
[flags]
tool_dir = "/system/tools/bin"
bindir = "/system/binaries"
"#,
)
.unwrap();
let tool_dir = rootfs.join("system/binaries");
fs::create_dir_all(&tool_dir).unwrap();
tool_dir
}
#[test]
fn configured_alias_dir_prefers_bindir_over_tool_dir() {
let tmp = tempfile::tempdir().unwrap();
make_tool_dir(tmp.path());
let config = config::Config::for_rootfs(tmp.path());
assert_eq!(
configured_alias_dir(&config),
PathBuf::from("/system/binaries")
);
}
#[test]
fn dir_in_rootfs_does_not_duplicate_host_absolute_rootfs_paths() {
let tmp = tempfile::tempdir().unwrap();
let rootfs = tmp.path().canonicalize().unwrap();
let host_dir = rootfs.join("system/binaries");
assert_eq!(dir_in_rootfs(&rootfs, &host_dir), host_dir);
}
#[test]
fn compiler_selection_maps_aliases() {
assert_eq!(
aliases_for_selection(ToolRoleArg::Compiler, "clang").unwrap(),
vec![
ToolAlias {
alias: "cc",
target: "clang"
},
ToolAlias {
alias: "c++",
target: "clang++"
}
]
);
assert_eq!(
aliases_for_selection(ToolRoleArg::Compiler, "gcc").unwrap(),
vec![
ToolAlias {
alias: "cc",
target: "gcc"
},
ToolAlias {
alias: "c++",
target: "g++"
}
]
);
}
#[test]
fn linker_selection_maps_aliases() {
assert_eq!(
aliases_for_selection(ToolRoleArg::Linker, "lld").unwrap(),
vec![ToolAlias {
alias: "ld",
target: "ld.lld"
}]
);
assert_eq!(
aliases_for_selection(ToolRoleArg::Linker, "mold").unwrap(),
vec![ToolAlias {
alias: "ld",
target: "mold"
}]
);
}
#[test]
fn shell_selection_maps_aliases() {
for shell in ["bash", "dash", "zsh"] {
assert_eq!(
aliases_for_selection(ToolRoleArg::Shell, shell).unwrap(),
vec![ToolAlias {
alias: "sh",
target: shell
}]
);
}
}
#[cfg(unix)]
#[test]
fn run_set_switches_compiler_aliases() {
let tmp = tempfile::tempdir().unwrap();
let tool_dir = make_tool_dir(tmp.path());
fs::write(tool_dir.join("clang"), "").unwrap();
fs::write(tool_dir.join("clang++"), "").unwrap();
fs::write(tool_dir.join("gcc"), "").unwrap();
fs::write(tool_dir.join("g++"), "").unwrap();
run_set(set_args(tmp.path(), ToolRoleArg::Compiler, "clang")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("cc")).unwrap(),
PathBuf::from("clang")
);
assert_eq!(
fs::read_link(tool_dir.join("c++")).unwrap(),
PathBuf::from("clang++")
);
run_set(set_args(tmp.path(), ToolRoleArg::Compiler, "gcc")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("cc")).unwrap(),
PathBuf::from("gcc")
);
assert_eq!(
fs::read_link(tool_dir.join("c++")).unwrap(),
PathBuf::from("g++")
);
}
#[cfg(unix)]
#[test]
fn run_set_switches_linker_alias() {
let tmp = tempfile::tempdir().unwrap();
let tool_dir = make_tool_dir(tmp.path());
fs::write(tool_dir.join("ld.lld"), "").unwrap();
fs::write(tool_dir.join("mold"), "").unwrap();
run_set(set_args(tmp.path(), ToolRoleArg::Linker, "lld")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("ld")).unwrap(),
PathBuf::from("ld.lld")
);
run_set(set_args(tmp.path(), ToolRoleArg::Linker, "mold")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("ld")).unwrap(),
PathBuf::from("mold")
);
}
#[cfg(unix)]
#[test]
fn run_set_switches_shell_alias() {
let tmp = tempfile::tempdir().unwrap();
let tool_dir = make_tool_dir(tmp.path());
fs::write(tool_dir.join("dash"), "").unwrap();
fs::write(tool_dir.join("zsh"), "").unwrap();
fs::write(tool_dir.join("bash"), "").unwrap();
run_set(set_args(tmp.path(), ToolRoleArg::Shell, "dash")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("sh")).unwrap(),
PathBuf::from("dash")
);
run_set(set_args(tmp.path(), ToolRoleArg::Shell, "zsh")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("sh")).unwrap(),
PathBuf::from("zsh")
);
run_set(set_args(tmp.path(), ToolRoleArg::Shell, "bash")).unwrap();
assert_eq!(
fs::read_link(tool_dir.join("sh")).unwrap(),
PathBuf::from("bash")
);
}
#[cfg(unix)]
#[test]
fn run_set_refuses_to_replace_real_tool_alias() {
let tmp = tempfile::tempdir().unwrap();
let tool_dir = make_tool_dir(tmp.path());
fs::write(tool_dir.join("clang"), "").unwrap();
fs::write(tool_dir.join("clang++"), "").unwrap();
fs::write(tool_dir.join("cc"), "real binary").unwrap();
let err = run_set(set_args(tmp.path(), ToolRoleArg::Compiler, "clang")).unwrap_err();
assert!(err.to_string().contains("Failed to set compiler to clang"));
assert!(err.chain().any(|cause| {
cause
.to_string()
.contains("Refusing to replace non-symlink")
}));
}
}
+85 -1
View File
@@ -41,6 +41,90 @@ fn lib32_args() -> Lib32Args {
Lib32Args { lib32_only: false } Lib32Args { lib32_only: false }
} }
#[test]
fn build_env_rootfs_uses_selected_non_live_rootfs() {
let tmp = tempfile::tempdir().unwrap();
let expected = tmp.path().canonicalize().unwrap();
assert_eq!(
build_cmd::build_env_rootfs(tmp.path()),
expected.to_string_lossy()
);
assert_eq!(build_cmd::build_env_rootfs(Path::new("/")), "/");
}
#[test]
fn install_post_extract_env_uses_selected_non_live_rootfs() -> Result<()> {
let _guard = assume_yes_test_lock();
let temp = tempfile::tempdir().context("Failed to create temp dir")?;
let rootfs = temp.path().join("rootfs");
let spec_dir = temp.path().join("packages").join("demo");
let source_dir = temp.path().join("source").join("demo-1.0.0");
let observed_env = temp.path().join("post-extract-rootfs.txt");
fs::create_dir_all(&rootfs)?;
fs::create_dir_all(&spec_dir)?;
fs::create_dir_all(&source_dir)?;
fs::write(source_dir.join("README"), "demo source")?;
fs::write(
spec_dir.join("build.sh"),
"mkdir -p \"$DESTDIR/usr/bin\"\nprintf demo > \"$DESTDIR/usr/bin/demo\"\n",
)?;
let spec_path = spec_dir.join("demo.toml");
fs::write(
&spec_path,
format!(
r#"[package]
name = "demo"
version = "1.0.0"
revision = 1
description = "demo"
homepage = "https://example.test/demo"
license = "MIT"
[[source]]
url = "file://{}"
sha256 = "skip"
extract_dir = "demo-1.0.0"
post_extract = ["printf '%s' \"$DEPOT_ROOTFS\" > '{}'"]
[build]
type = "custom"
[dependencies]
build = []
runtime = []
optional = []
"#,
source_dir.display(),
observed_env.display()
),
)?;
let config = config::Config::for_rootfs(&rootfs);
register_required_development_package_if_configured(&config, &rootfs)?;
run(Cli {
command: Commands::Install(InstallArgs {
rootfs_args: rootfs_args(rootfs.clone()),
prompt_args: prompt_args(true),
build_exec_args: BuildExecArgs {
no_deps: true,
..build_exec_args()
},
lib32_args: lib32_args(),
spec_or_archive: vec![spec_path],
spec: None,
}),
})?;
assert_eq!(
fs::read_to_string(&observed_env)?,
build_cmd::build_env_rootfs(&rootfs)
);
Ok(())
}
fn test_binary_repo_record(name: &str, filename: &str) -> db::repo::BinaryRepoPackageRecord { fn test_binary_repo_record(name: &str, filename: &str) -> db::repo::BinaryRepoPackageRecord {
db::repo::BinaryRepoPackageRecord { db::repo::BinaryRepoPackageRecord {
repo_name: "core".into(), repo_name: "core".into(),
@@ -378,7 +462,7 @@ fn binary_install_path_uses_repo_record_metadata_without_archive_metadata() -> R
assert_eq!(installed[0].package.name, "pkg"); assert_eq!(installed[0].package.name, "pkg");
assert!(rootfs.path().join("usr/bin/hello").exists()); assert!(rootfs.path().join("usr/bin/hello").exists());
let db_path = cfg.db_dir.join("packages.db"); let db_path = cfg.installed_db_path(rootfs.path());
assert_eq!( assert_eq!(
db::get_package_version(&db_path, "pkg")?, db::get_package_version(&db_path, "pkg")?,
Some("1.0".into()) Some("1.0".into())
+31 -9
View File
@@ -269,11 +269,16 @@ impl Config {
home.join(".local/share/depot"), home.join(".local/share/depot"),
) )
} else { } else {
let variable_root = if is_system_root {
abs_rootfs.join("var")
} else {
abs_rootfs.join("system/variable")
};
( (
abs_rootfs.join("var/cache/depot/sources"), variable_root.join("cache/depot/sources"),
abs_rootfs.join("var/cache/depot/packages"), variable_root.join("cache/depot/packages"),
abs_rootfs.join("var/cache/depot/build"), variable_root.join("cache/depot/build"),
abs_rootfs.join("var/lib/depot"), variable_root.join("lib/depot"),
) )
}; };
@@ -307,7 +312,11 @@ impl Config {
/// to per-user directories under `$HOME`. /// to per-user directories under `$HOME`.
pub fn installed_db_path(&self, rootfs: &Path) -> PathBuf { pub fn installed_db_path(&self, rootfs: &Path) -> PathBuf {
let abs_rootfs = resolve_rootfs_base(rootfs); let abs_rootfs = resolve_rootfs_base(rootfs);
abs_rootfs.join("var/lib/depot/packages.db") if abs_rootfs == Path::new("/") || abs_rootfs.as_os_str() == "/" {
abs_rootfs.join("var/lib/depot/packages.db")
} else {
abs_rootfs.join("system/variable/lib/depot/packages.db")
}
} }
/// Load system-level and user-level overrides /// Load system-level and user-level overrides
@@ -592,15 +601,20 @@ mod tests {
config config
.cache_dir .cache_dir
.to_string_lossy() .to_string_lossy()
.contains("var/cache/depot/sources") .contains("system/variable/cache/depot/sources")
); );
assert!( assert!(
config config
.build_dir .build_dir
.to_string_lossy() .to_string_lossy()
.contains("var/cache/depot/build") .contains("system/variable/cache/depot/build")
);
assert!(
config
.db_dir
.to_string_lossy()
.contains("system/variable/lib/depot")
); );
assert!(config.db_dir.to_string_lossy().contains("var/lib/depot"));
} }
#[test] #[test]
@@ -713,7 +727,7 @@ cflags += ["-g"]
let config = Config::for_rootfs(&root); let config = Config::for_rootfs(&root);
assert_eq!( assert_eq!(
config.installed_db_path(&root), config.installed_db_path(&root),
PathBuf::from("/tmp/test_root/var/lib/depot/packages.db") PathBuf::from("/tmp/test_root/system/variable/lib/depot/packages.db")
); );
} }
@@ -738,6 +752,7 @@ cflags += ["-g"]
r#" r#"
[build] [build]
prefix = "/opt/depot" prefix = "/opt/depot"
tool_dir = "/opt/toolchain/bin"
cc = "clang" cc = "clang"
[build.flags] [build.flags]
@@ -763,6 +778,13 @@ test_deps = true
config.build_overrides.get("cc").and_then(|v| v.as_str()), config.build_overrides.get("cc").and_then(|v| v.as_str()),
Some("clang") Some("clang")
); );
assert_eq!(
config
.build_overrides
.get("tool_dir")
.and_then(|v| v.as_str()),
Some("/opt/toolchain/bin")
);
assert_eq!( assert_eq!(
config config
.build_overrides .build_overrides
+47
View File
@@ -1014,6 +1014,12 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
), ),
); );
} }
if !spec.build.flags.fuse_ld.is_empty() {
flags_tbl.insert(
"fuse_ld".into(),
Value::String(spec.build.flags.fuse_ld.clone()),
);
}
if !spec.build.flags.ltoflags.is_empty() { if !spec.build.flags.ltoflags.is_empty() {
flags_tbl.insert( flags_tbl.insert(
"ltoflags".into(), "ltoflags".into(),
@@ -1114,6 +1120,45 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
if spec.build.flags.ar != defaults.ar { if spec.build.flags.ar != defaults.ar {
flags_tbl.insert("ar".into(), Value::String(spec.build.flags.ar.clone())); flags_tbl.insert("ar".into(), Value::String(spec.build.flags.ar.clone()));
} }
if !spec.build.flags.ranlib.is_empty() {
flags_tbl.insert(
"ranlib".into(),
Value::String(spec.build.flags.ranlib.clone()),
);
}
if !spec.build.flags.strip.is_empty() {
flags_tbl.insert(
"strip".into(),
Value::String(spec.build.flags.strip.clone()),
);
}
if !spec.build.flags.ld.is_empty() {
flags_tbl.insert("ld".into(), Value::String(spec.build.flags.ld.clone()));
}
if !spec.build.flags.nm.is_empty() {
flags_tbl.insert("nm".into(), Value::String(spec.build.flags.nm.clone()));
}
if !spec.build.flags.objcopy.is_empty() {
flags_tbl.insert(
"objcopy".into(),
Value::String(spec.build.flags.objcopy.clone()),
);
}
if !spec.build.flags.objdump.is_empty() {
flags_tbl.insert(
"objdump".into(),
Value::String(spec.build.flags.objdump.clone()),
);
}
if !spec.build.flags.readelf.is_empty() {
flags_tbl.insert(
"readelf".into(),
Value::String(spec.build.flags.readelf.clone()),
);
}
if !spec.build.flags.cpp.is_empty() {
flags_tbl.insert("cpp".into(), Value::String(spec.build.flags.cpp.clone()));
}
if !spec.build.flags.libc.is_empty() { if !spec.build.flags.libc.is_empty() {
flags_tbl.insert("libc".into(), Value::String(spec.build.flags.libc.clone())); flags_tbl.insert("libc".into(), Value::String(spec.build.flags.libc.clone()));
} }
@@ -1849,6 +1894,7 @@ mod tests {
config_settings: vec!["editable_mode=compat".into()], config_settings: vec!["editable_mode=compat".into()],
rustflags: vec!["-Ctarget-cpu=native".into()], rustflags: vec!["-Ctarget-cpu=native".into()],
cxxflags: vec!["-O2".into(), "-fno-rtti".into()], cxxflags: vec!["-O2".into(), "-fno-rtti".into()],
fuse_ld: "lld".into(),
ltoflags: vec!["-flto=auto".into()], ltoflags: vec!["-flto=auto".into()],
target: "x86_64-unknown-linux-gnu".into(), target: "x86_64-unknown-linux-gnu".into(),
keep: vec!["etc/locale.gen".into()], keep: vec!["etc/locale.gen".into()],
@@ -1928,6 +1974,7 @@ mod tests {
assert!(toml.contains("config_setting = [")); assert!(toml.contains("config_setting = ["));
assert!(toml.contains("rustflags = [")); assert!(toml.contains("rustflags = ["));
assert!(toml.contains("cxxflags = [")); assert!(toml.contains("cxxflags = ["));
assert!(toml.contains("fuse_ld = \"lld\""));
assert!(toml.contains("ltoflags = [")); assert!(toml.contains("ltoflags = ["));
assert!(toml.contains("target = \"x86_64-unknown-linux-gnu\"")); assert!(toml.contains("target = \"x86_64-unknown-linux-gnu\""));
assert!(toml.contains("keep = [")); assert!(toml.contains("keep = ["));
+313 -63
View File
@@ -368,7 +368,16 @@ impl PackageSpec {
} }
} }
fn apply_default_string(target: &mut String, default: &str, value: &toml::Value) {
if let Some(s) = value.as_str()
&& (target.trim().is_empty() || target == default)
{
*target = s.to_string();
}
}
fn apply_flags_table(&mut self, table: &toml::map::Map<String, toml::Value>) { fn apply_flags_table(&mut self, table: &toml::map::Map<String, toml::Value>) {
let default_flags = BuildFlags::default();
for (k, v) in table { for (k, v) in table {
// match case-insensitively for common keys (allow CXX/Cc etc.) // match case-insensitively for common keys (allow CXX/Cc etc.)
match k.to_lowercase().as_str() { match k.to_lowercase().as_str() {
@@ -471,6 +480,20 @@ impl PackageSpec {
self.build.flags.ldflags = vec![s.to_string()]; self.build.flags.ldflags = vec![s.to_string()];
} }
} }
"fuse_ld" | "fuse-ld" => {
Self::apply_default_string(
&mut self.build.flags.fuse_ld,
&default_flags.fuse_ld,
v,
);
}
"tool_dir" | "tool-dir" | "tools_dir" | "tools-dir" => {
Self::apply_default_string(
&mut self.build.flags.tool_dir,
&default_flags.tool_dir,
v,
);
}
"replace_ldflags" | "replace-ldflags" => { "replace_ldflags" | "replace-ldflags" => {
if let Some(arr) = v.as_array() { if let Some(arr) = v.as_array() {
self.build.flags.replace_ldflags = arr self.build.flags.replace_ldflags = arr
@@ -565,109 +588,169 @@ impl PackageSpec {
} }
} }
"cc" => { "cc" => {
if let Some(s) = v.as_str() { Self::apply_default_string(&mut self.build.flags.cc, &default_flags.cc, v);
self.build.flags.cc = s.to_string();
}
} }
"cxx" => { "cxx" => {
if let Some(s) = v.as_str() { Self::apply_default_string(&mut self.build.flags.cxx, &default_flags.cxx, v);
self.build.flags.cxx = s.to_string();
}
} }
"ar" => { "ar" => {
if let Some(s) = v.as_str() { Self::apply_default_string(&mut self.build.flags.ar, &default_flags.ar, v);
self.build.flags.ar = s.to_string(); }
} "ranlib" => {
Self::apply_default_string(
&mut self.build.flags.ranlib,
&default_flags.ranlib,
v,
);
}
"strip" => {
Self::apply_default_string(
&mut self.build.flags.strip,
&default_flags.strip,
v,
);
} }
"ld" => { "ld" => {
if let Some(s) = v.as_str() { Self::apply_default_string(&mut self.build.flags.ld, &default_flags.ld, v);
self.build.flags.ld = s.to_string(); }
} "nm" => {
Self::apply_default_string(&mut self.build.flags.nm, &default_flags.nm, v);
}
"objcopy" => {
Self::apply_default_string(
&mut self.build.flags.objcopy,
&default_flags.objcopy,
v,
);
}
"objdump" => {
Self::apply_default_string(
&mut self.build.flags.objdump,
&default_flags.objdump,
v,
);
}
"readelf" => {
Self::apply_default_string(
&mut self.build.flags.readelf,
&default_flags.readelf,
v,
);
} }
"cpp" => { "cpp" => {
if let Some(s) = v.as_str() { Self::apply_default_string(&mut self.build.flags.cpp, &default_flags.cpp, v);
self.build.flags.cpp = s.to_string();
}
} }
"prefix" => { "prefix" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.prefix = s.to_string(); &mut self.build.flags.prefix,
} &default_flags.prefix,
v,
);
} }
"bindir" => { "bindir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.bindir = s.to_string(); &mut self.build.flags.bindir,
} &default_flags.bindir,
v,
);
} }
"sbindir" => { "sbindir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.sbindir = s.to_string(); &mut self.build.flags.sbindir,
} &default_flags.sbindir,
v,
);
} }
"libdir" => { "libdir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.libdir = s.to_string(); &mut self.build.flags.libdir,
} &default_flags.libdir,
v,
);
} }
"libexecdir" => { "libexecdir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.libexecdir = s.to_string(); &mut self.build.flags.libexecdir,
} &default_flags.libexecdir,
v,
);
} }
"sysconfdir" => { "sysconfdir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.sysconfdir = s.to_string(); &mut self.build.flags.sysconfdir,
} &default_flags.sysconfdir,
v,
);
} }
"localstatedir" => { "localstatedir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.localstatedir = s.to_string(); &mut self.build.flags.localstatedir,
} &default_flags.localstatedir,
v,
);
} }
"sharedstatedir" => { "sharedstatedir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.sharedstatedir = s.to_string(); &mut self.build.flags.sharedstatedir,
} &default_flags.sharedstatedir,
v,
);
} }
"includedir" => { "includedir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.includedir = s.to_string(); &mut self.build.flags.includedir,
} &default_flags.includedir,
v,
);
} }
"datarootdir" => { "datarootdir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.datarootdir = s.to_string(); &mut self.build.flags.datarootdir,
} &default_flags.datarootdir,
v,
);
} }
"datadir" => { "datadir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.datadir = s.to_string(); &mut self.build.flags.datadir,
} &default_flags.datadir,
v,
);
} }
"mandir" => { "mandir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.mandir = s.to_string(); &mut self.build.flags.mandir,
} &default_flags.mandir,
v,
);
} }
"infodir" => { "infodir" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.infodir = s.to_string(); &mut self.build.flags.infodir,
} &default_flags.infodir,
v,
);
} }
"chost" => { "chost" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.chost = s.to_string(); &mut self.build.flags.chost,
} &default_flags.chost,
v,
);
} }
"cbuild" => { "cbuild" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.cbuild = s.to_string(); &mut self.build.flags.cbuild,
} &default_flags.cbuild,
v,
);
} }
"carch" => { "carch" => {
if let Some(s) = v.as_str() { Self::apply_default_string(
self.build.flags.carch = s.to_string(); &mut self.build.flags.carch,
} &default_flags.carch,
v,
);
} }
"makeflags" | "make_flags" | "make-flags" => { "makeflags" | "make_flags" | "make-flags" => {
if let Some(s) = v.as_str() { if let Some(s) = v.as_str() {
@@ -1318,16 +1401,51 @@ impl PackageSpec {
self.build.flags.cc = s.to_string(); self.build.flags.cc = s.to_string();
} }
} }
"cxx" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.cxx = s.to_string();
}
}
"ar" => { "ar" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) { if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.ar = s.to_string(); self.build.flags.ar = s.to_string();
} }
} }
"ranlib" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.ranlib = s.to_string();
}
}
"strip" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.strip = s.to_string();
}
}
"ld" => { "ld" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) { if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.ld = s.to_string(); self.build.flags.ld = s.to_string();
} }
} }
"nm" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.nm = s.to_string();
}
}
"objcopy" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.objcopy = s.to_string();
}
}
"objdump" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.objdump = s.to_string();
}
}
"readelf" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.readelf = s.to_string();
}
}
"cpp" => { "cpp" => {
if let Some(s) = values.last().and_then(|v| v.as_str()) { if let Some(s) = values.last().and_then(|v| v.as_str()) {
self.build.flags.cpp = s.to_string(); self.build.flags.cpp = s.to_string();
@@ -2511,8 +2629,17 @@ type = "custom"
r#" r#"
[flags] [flags]
cc = "my-cc" cc = "my-cc"
cxx = "my-cxx"
ar = "my-ar"
ranlib = "my-ranlib"
strip = "my-strip"
ld = "ld.lld" ld = "ld.lld"
nm = "my-nm"
objcopy = "my-objcopy"
objdump = "my-objdump"
readelf = "my-readelf"
CPP = "clang-cpp" CPP = "clang-cpp"
tool_dir = "/opt/toolchain/bin"
cflags = ["-O2"] cflags = ["-O2"]
replace_cflags = ["-O2=>-O3"] replace_cflags = ["-O2=>-O3"]
cxxflags = ["-O2", "-pipe"] cxxflags = ["-O2", "-pipe"]
@@ -2672,8 +2799,17 @@ post_configure = ["echo configured"]
spec.apply_config(&config); spec.apply_config(&config);
assert_eq!(spec.build.flags.cc, "my-cc"); assert_eq!(spec.build.flags.cc, "my-cc");
assert_eq!(spec.build.flags.cxx, "my-cxx");
assert_eq!(spec.build.flags.ar, "my-ar");
assert_eq!(spec.build.flags.ranlib, "my-ranlib");
assert_eq!(spec.build.flags.strip, "my-strip");
assert_eq!(spec.build.flags.ld, "ld.lld"); assert_eq!(spec.build.flags.ld, "ld.lld");
assert_eq!(spec.build.flags.nm, "my-nm");
assert_eq!(spec.build.flags.objcopy, "my-objcopy");
assert_eq!(spec.build.flags.objdump, "my-objdump");
assert_eq!(spec.build.flags.readelf, "my-readelf");
assert_eq!(spec.build.flags.cpp, "clang-cpp"); assert_eq!(spec.build.flags.cpp, "clang-cpp");
assert_eq!(spec.build.flags.tool_dir, "/opt/toolchain/bin");
assert!(spec.build.flags.cflags.contains(&"-O2".to_string())); assert!(spec.build.flags.cflags.contains(&"-O2".to_string()));
assert!(spec.build.flags.cflags.contains(&"-g".to_string())); assert!(spec.build.flags.cflags.contains(&"-g".to_string()));
assert!( assert!(
@@ -2873,6 +3009,33 @@ post_configure = ["echo configured"]
); );
} }
#[test]
fn test_apply_config_preserves_package_scalar_tool_and_layout_overrides() {
let mut spec = mk_spec("foo", "1.0");
spec.build.flags.ld = "ld.lld".to_string();
spec.build.flags.libdir = "/package/lib".to_string();
spec.build.flags.sysconfdir = "/package/etc".to_string();
let mut config = crate::config::Config::for_rootfs(Path::new("/tmp/nonexistent"));
config.build_overrides = toml::from_str(
r#"
ld = "/config/bin/ld"
fuse_ld = "/config/bin/ld.lld"
ranlib = "/config/bin/ranlib"
libdir = "/config/lib"
sysconfdir = "/config/etc"
"#,
)
.unwrap();
spec.apply_config(&config);
assert_eq!(spec.build.flags.ld, "ld.lld");
assert_eq!(spec.build.flags.libdir, "/package/lib");
assert_eq!(spec.build.flags.sysconfdir, "/package/etc");
assert_eq!(spec.build.flags.ranlib, "/config/bin/ranlib");
assert_eq!(spec.build.flags.fuse_ld, "/config/bin/ld.lld");
}
#[test] #[test]
fn parse_no_flags_from_spec() { fn parse_no_flags_from_spec() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@@ -2906,6 +3069,61 @@ no_flags = true
assert!(spec.build.flags.no_flags); assert!(spec.build.flags.no_flags);
} }
#[test]
fn parse_tool_commands_from_spec() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("pkg.toml");
std::fs::write(
&path,
r#"
[package]
name = "foo"
version = "1.0"
description = "d"
homepage = "h"
license = "MIT"
[source]
url = "https://example.com/foo.tar.gz"
sha256 = "skip"
extract_dir = "foo"
[build]
type = "custom"
[build.flags]
cc = "/tools/bin/cc"
cxx = "/tools/bin/c++"
ar = "/tools/bin/ar"
ranlib = "/tools/bin/ranlib"
strip = "/tools/bin/strip"
ld = "/tools/bin/ld"
fuse_ld = "/usr/bin/ld.lld"
nm = "/tools/bin/nm"
objcopy = "/tools/bin/objcopy"
objdump = "/tools/bin/objdump"
readelf = "/tools/bin/readelf"
cpp = "/tools/bin/cpp"
"#,
)
.unwrap();
let spec = PackageSpec::from_file(&path).unwrap();
assert_eq!(spec.build.flags.cc, "/tools/bin/cc");
assert_eq!(spec.build.flags.cxx, "/tools/bin/c++");
assert_eq!(spec.build.flags.ar, "/tools/bin/ar");
assert_eq!(spec.build.flags.ranlib, "/tools/bin/ranlib");
assert_eq!(spec.build.flags.strip, "/tools/bin/strip");
assert_eq!(spec.build.flags.ld, "/tools/bin/ld");
assert_eq!(spec.build.flags.fuse_ld, "/usr/bin/ld.lld");
assert_eq!(spec.build.flags.nm, "/tools/bin/nm");
assert_eq!(spec.build.flags.objcopy, "/tools/bin/objcopy");
assert_eq!(spec.build.flags.objdump, "/tools/bin/objdump");
assert_eq!(spec.build.flags.readelf, "/tools/bin/readelf");
assert_eq!(spec.build.flags.cpp, "/tools/bin/cpp");
}
#[test] #[test]
fn parse_ltoflags_and_use_lto_from_spec() { fn parse_ltoflags_and_use_lto_from_spec() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@@ -4282,6 +4500,9 @@ pub struct BuildFlags {
/// Extra flags exported to `LDFLAGS`. /// Extra flags exported to `LDFLAGS`.
#[serde(default, deserialize_with = "deserialize_string_or_array")] #[serde(default, deserialize_with = "deserialize_string_or_array")]
pub ldflags: Vec<String>, pub ldflags: Vec<String>,
/// Linker selected through compiler drivers with `-fuse-ld=<value>`.
#[serde(default, alias = "fuse-ld")]
pub fuse_ld: String,
/// Ordered replacement rules applied to `ldflags` before export. /// Ordered replacement rules applied to `ldflags` before export.
#[serde( #[serde(
default, default,
@@ -4416,6 +4637,9 @@ pub struct BuildFlags {
/// Autotools configure script path, relative to source root or absolute. /// Autotools configure script path, relative to source root or absolute.
#[serde(default, alias = "configure-file")] #[serde(default, alias = "configure-file")]
pub configure_file: String, pub configure_file: String,
/// Directory containing the configured compiler, linker, and binutils tools.
#[serde(default, alias = "tool-dir", alias = "tools_dir", alias = "tools-dir")]
pub tool_dir: String,
/// C compiler /// C compiler
#[serde(default = "default_cc")] #[serde(default = "default_cc")]
pub cc: String, pub cc: String,
@@ -4425,9 +4649,27 @@ pub struct BuildFlags {
/// Archiver /// Archiver
#[serde(default = "default_ar")] #[serde(default = "default_ar")]
pub ar: String, pub ar: String,
/// Archive indexer exported as `RANLIB` when configured.
#[serde(default)]
pub ranlib: String,
/// Strip executable exported as `STRIP` when configured.
#[serde(default)]
pub strip: String,
/// Linker executable or linker flavor override for supported builders. /// Linker executable or linker flavor override for supported builders.
#[serde(default)] #[serde(default)]
pub ld: String, pub ld: String,
/// Symbol table dumper exported as `NM` when configured.
#[serde(default)]
pub nm: String,
/// Object copy tool exported as `OBJCOPY` when configured.
#[serde(default)]
pub objcopy: String,
/// Object dump tool exported as `OBJDUMP` when configured.
#[serde(default)]
pub objdump: String,
/// ELF reader exported as `READELF` when configured.
#[serde(default)]
pub readelf: String,
/// C preprocessor executable exported as `CPP` when configured. /// C preprocessor executable exported as `CPP` when configured.
#[serde(default, alias = "CPP")] #[serde(default, alias = "CPP")]
pub cpp: String, pub cpp: String,
@@ -4700,6 +4942,7 @@ impl Default for BuildFlags {
cxxflags_lib32: Vec::new(), cxxflags_lib32: Vec::new(),
replace_cxxflags_lib32: Vec::new(), replace_cxxflags_lib32: Vec::new(),
ldflags: Vec::new(), ldflags: Vec::new(),
fuse_ld: String::new(),
replace_ldflags: Vec::new(), replace_ldflags: Vec::new(),
ltoflags: Vec::new(), ltoflags: Vec::new(),
rustltoflags: Vec::new(), rustltoflags: Vec::new(),
@@ -4720,10 +4963,17 @@ impl Default for BuildFlags {
config_settings: Vec::new(), config_settings: Vec::new(),
configure_lib32: Vec::new(), configure_lib32: Vec::new(),
configure_file: String::new(), configure_file: String::new(),
tool_dir: String::new(),
cc: default_cc(), cc: default_cc(),
cxx: default_cxx(), cxx: default_cxx(),
ar: default_ar(), ar: default_ar(),
ranlib: String::new(),
strip: String::new(),
ld: String::new(), ld: String::new(),
nm: String::new(),
objcopy: String::new(),
objdump: String::new(),
readelf: String::new(),
cpp: String::new(), cpp: String::new(),
libc: String::new(), libc: String::new(),
rootfs: default_rootfs(), rootfs: default_rootfs(),
+200 -11
View File
@@ -531,8 +531,14 @@ fn is_elf_file(path: &Path) -> Result<bool> {
Ok(n == 4 && magic == [0x7F, b'E', b'L', b'F']) Ok(n == 4 && magic == [0x7F, b'E', b'L', b'F'])
} }
fn auto_strip_elf_files(destdir: &Path) -> Result<usize> { fn auto_strip_elf_files(destdir: &Path, strip_command: &str) -> Result<usize> {
let mut stripped = 0usize; let mut stripped = 0usize;
let strip_command = strip_command.trim();
let strip_command = if strip_command.is_empty() {
"strip"
} else {
strip_command
};
for entry in WalkDir::new(destdir).into_iter().filter_map(|e| e.ok()) { for entry in WalkDir::new(destdir).into_iter().filter_map(|e| e.ok()) {
if !entry.file_type().is_file() { if !entry.file_type().is_file() {
continue; continue;
@@ -542,19 +548,21 @@ fn auto_strip_elf_files(destdir: &Path) -> Result<usize> {
continue; continue;
} }
let status = Command::new("strip") let status = Command::new(strip_command)
.arg("--strip-debug") .arg("--strip-debug")
.arg(path) .arg(path)
.status() .status()
.with_context(|| { .with_context(|| {
format!( format!(
"Failed to execute strip for {} (disable with build.flags.no_strip = true)", "Failed to execute {} for {} (disable with build.flags.no_strip = true)",
strip_command,
path.display() path.display()
) )
})?; })?;
if !status.success() { if !status.success() {
anyhow::bail!( anyhow::bail!(
"strip failed for {} with status {} (disable with build.flags.no_strip = true)", "{} failed for {} with status {} (disable with build.flags.no_strip = true)",
strip_command,
path.display(), path.display(),
status status
); );
@@ -878,7 +886,7 @@ pub fn process(destdir: &Path, spec: &PackageSpec) -> Result<()> {
if spec.build.flags.no_strip { if spec.build.flags.no_strip {
crate::log_info!("Skipping auto-strip: disabled by build.flags.no_strip"); crate::log_info!("Skipping auto-strip: disabled by build.flags.no_strip");
} else { } else {
let stripped = auto_strip_elf_files(destdir)?; let stripped = auto_strip_elf_files(destdir, &spec.build.flags.strip)?;
if stripped > 0 { if stripped > 0 {
crate::log_info!("Stripped {} ELF file(s)", stripped); crate::log_info!("Stripped {} ELF file(s)", stripped);
} }
@@ -968,7 +976,14 @@ fn normalize_lbi_tree_paths(root: &Path) -> Result<usize> {
let mut changed = 0usize; let mut changed = 0usize;
for (from, to) in mappings { for (from, to) in mappings {
let src = root.join(from); let src = root.join(from);
if !src.exists() { let src_meta = match fs::symlink_metadata(&src) {
Ok(metadata) => metadata,
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
Err(err) => {
return Err(err).with_context(|| format!("Failed to inspect {}", src.display()));
}
};
if src_meta.file_type().is_symlink() {
continue; continue;
} }
let dst = root.join(to); let dst = root.join(to);
@@ -1242,7 +1257,10 @@ fn copy_tree_preserving_layout_no_overwrite(
.with_context(|| format!("Failed to create directory {}", parent.display()))?; .with_context(|| format!("Failed to create directory {}", parent.display()))?;
} }
if dst_path.symlink_metadata().is_ok() { if let Ok(dst_metadata) = dst_path.symlink_metadata() {
if duplicate_staged_path_is_equivalent(src_path, &metadata, &dst_path, &dst_metadata)? {
continue;
}
anyhow::bail!( anyhow::bail!(
"Failed to replay relocated path into {}: destination already exists", "Failed to replay relocated path into {}: destination already exists",
dst_path.display() dst_path.display()
@@ -1281,6 +1299,65 @@ fn copy_tree_preserving_layout_no_overwrite(
Ok(()) Ok(())
} }
fn symlink_target_path_inside_rootfs(
rootfs: &Path,
link_rel: &str,
target: &Path,
) -> Result<Option<PathBuf>> {
let mut normalized = PathBuf::new();
if target.is_absolute() {
for component in target.components() {
match component {
Component::RootDir => {}
Component::CurDir => {}
Component::Normal(segment) => normalized.push(segment),
Component::ParentDir | Component::Prefix(_) => return Ok(None),
}
}
} else {
if let Some(parent) = Path::new(link_rel).parent() {
normalized.push(parent);
}
for component in target.components() {
match component {
Component::CurDir => {}
Component::Normal(segment) => normalized.push(segment),
Component::ParentDir => {
if !normalized.pop() {
return Ok(None);
}
}
Component::RootDir | Component::Prefix(_) => return Ok(None),
}
}
}
if normalized.as_os_str().is_empty() {
return Ok(None);
}
Ok(Some(rootfs.join(normalized)))
}
fn can_relocate_directory_for_symlink_swap(
rootfs: &Path,
link_rel: &str,
symlink_path: &Path,
) -> Result<bool> {
let target = fs::read_link(symlink_path)
.with_context(|| format!("Failed to read symlink {}", symlink_path.display()))?;
let Some(target_path) = symlink_target_path_inside_rootfs(rootfs, link_rel, &target)? else {
return Ok(false);
};
match target_path.symlink_metadata() {
Ok(metadata) => Ok(metadata.file_type().is_dir()),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(err)
.with_context(|| format!("Failed to inspect symlink target {}", target_path.display())),
}
}
fn remove_path_in_place(path: &Path, rel: &str) -> Result<()> { fn remove_path_in_place(path: &Path, rel: &str) -> Result<()> {
let metadata = path let metadata = path
.symlink_metadata() .symlink_metadata()
@@ -1530,9 +1607,25 @@ impl FsTransaction {
/// Commit the transaction (delete backup directory). /// Commit the transaction (delete backup directory).
pub fn commit(self) -> Result<()> { pub fn commit(self) -> Result<()> {
let tx_base_dir = self.tx_dir.parent().map(Path::to_path_buf);
if self.tx_dir.exists() { if self.tx_dir.exists() {
fs::remove_dir_all(&self.tx_dir)?; fs::remove_dir_all(&self.tx_dir)?;
} }
if let Some(tx_base_dir) = tx_base_dir {
match fs::remove_dir(&tx_base_dir) {
Ok(()) => {}
Err(err)
if matches!(
err.kind(),
io::ErrorKind::DirectoryNotEmpty | io::ErrorKind::NotFound
) => {}
Err(err) => {
return Err(err).with_context(|| {
format!("Failed to remove tx dir {}", tx_base_dir.display())
});
}
}
}
Ok(()) Ok(())
} }
} }
@@ -1548,6 +1641,11 @@ pub fn install_atomic(
remove_paths: &[String], remove_paths: &[String],
keep_paths: &[String], keep_paths: &[String],
) -> Result<FsTransaction> { ) -> Result<FsTransaction> {
let tx_base_dir = if rootfs != Path::new("/") && tx_base_dir.starts_with(rootfs) {
rootfs.join(".depot-tx")
} else {
tx_base_dir.to_path_buf()
};
let keep_rules: Vec<KeepMatcher> = keep_paths let keep_rules: Vec<KeepMatcher> = keep_paths
.iter() .iter()
.map(|p| KeepMatcher::from_spec(p)) .map(|p| KeepMatcher::from_spec(p))
@@ -1561,7 +1659,7 @@ pub fn install_atomic(
.collect(); .collect();
let remove_set: HashSet<&str> = remove_paths.iter().map(String::as_str).collect(); let remove_set: HashSet<&str> = remove_paths.iter().map(String::as_str).collect();
fs::create_dir_all(tx_base_dir) fs::create_dir_all(&tx_base_dir)
.with_context(|| format!("Failed to create tx dir: {}", tx_base_dir.display()))?; .with_context(|| format!("Failed to create tx dir: {}", tx_base_dir.display()))?;
let ts = SystemTime::now() let ts = SystemTime::now()
@@ -1655,7 +1753,13 @@ pub fn install_atomic(
if let Ok(dest_meta) = dest_path.symlink_metadata() { if let Ok(dest_meta) = dest_path.symlink_metadata() {
let backup_path = tx.backup_path(&install_rel_path); let backup_path = tx.backup_path(&install_rel_path);
if dest_meta.file_type().is_dir() { if dest_meta.file_type().is_dir() {
if !remove_set.contains(install_rel_path.as_str()) { let can_relocate = file_type.is_symlink()
&& can_relocate_directory_for_symlink_swap(
rootfs,
&install_rel_path,
src_path,
)?;
if !remove_set.contains(install_rel_path.as_str()) && !can_relocate {
anyhow::bail!( anyhow::bail!(
"Refusing to replace existing directory with packaged file/symlink: {}", "Refusing to replace existing directory with packaged file/symlink: {}",
install_rel_path install_rel_path
@@ -1684,7 +1788,11 @@ pub fn install_atomic(
// Remove destination if it exists (we backed it up) so we can overwrite // Remove destination if it exists (we backed it up) so we can overwrite
if let Ok(dest_meta) = dest_path.symlink_metadata() { if let Ok(dest_meta) = dest_path.symlink_metadata() {
if dest_meta.file_type().is_dir() { if dest_meta.file_type().is_dir() {
if !remove_set.contains(install_rel_path.as_str()) { let relocated_for_symlink_swap =
tx.relocated.iter().any(|rel| rel == &install_rel_path);
if !remove_set.contains(install_rel_path.as_str())
&& !relocated_for_symlink_swap
{
anyhow::bail!( anyhow::bail!(
"Refusing to replace existing directory with packaged file/symlink: {}", "Refusing to replace existing directory with packaged file/symlink: {}",
install_rel_path install_rel_path
@@ -1703,7 +1811,8 @@ pub fn install_atomic(
} }
if file_type.is_symlink() { if file_type.is_symlink() {
let target = fs::read_link(src_path)?; let target = fs::read_link(src_path)
.with_context(|| format!("Failed to read staged symlink {}", rel_path))?;
std::os::unix::fs::symlink(target, &dest_path) std::os::unix::fs::symlink(target, &dest_path)
.with_context(|| format!("Failed to create symlink: {}", install_rel_path))?; .with_context(|| format!("Failed to create symlink: {}", install_rel_path))?;
tx.replay_relocated_dir_if_present(&install_rel_path)?; tx.replay_relocated_dir_if_present(&install_rel_path)?;
@@ -1949,6 +2058,49 @@ mod tests {
); );
} }
#[cfg(unix)]
#[test]
fn process_preserves_lbi_compatibility_symlinks() {
use std::os::unix::fs as unix_fs;
let tmp = tempfile::tempdir().unwrap();
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(destdir.join("system/binaries")).unwrap();
std::fs::create_dir_all(destdir.join("system/documentation/man-pages")).unwrap();
std::fs::create_dir_all(destdir.join("system/share")).unwrap();
std::fs::create_dir_all(destdir.join("usr")).unwrap();
unix_fs::symlink("system/binaries", destdir.join("bin")).unwrap();
unix_fs::symlink("../system/binaries", destdir.join("usr/bin")).unwrap();
unix_fs::symlink("../system/share", destdir.join("usr/share")).unwrap();
unix_fs::symlink(
"../documentation/man-pages",
destdir.join("system/share/man"),
)
.unwrap();
let spec = mk_spec_for_stage_processing();
process(&destdir, &spec).unwrap();
assert_eq!(
std::fs::read_link(destdir.join("bin")).unwrap(),
PathBuf::from("system/binaries")
);
assert_eq!(
std::fs::read_link(destdir.join("usr/bin")).unwrap(),
PathBuf::from("../system/binaries")
);
assert_eq!(
std::fs::read_link(destdir.join("usr/share")).unwrap(),
PathBuf::from("../system/share")
);
assert_eq!(
std::fs::read_link(destdir.join("system/share/man")).unwrap(),
PathBuf::from("../documentation/man-pages")
);
assert!(destdir.join("system/documentation/man-pages").is_dir());
}
#[test] #[test]
fn process_rejects_conflicting_lbi_normalized_paths() { fn process_rejects_conflicting_lbi_normalized_paths() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@@ -2256,6 +2408,43 @@ mod tests {
); );
} }
#[test]
#[cfg(unix)]
fn install_atomic_relocates_existing_directory_into_packaged_symlink_target() {
let tmp = tempfile::tempdir().unwrap();
let rootfs = tmp.path().join("root");
let destdir = tmp.path().join("dest");
let tx_base = rootfs.join("var/cache/depot/build/tx");
std::fs::create_dir_all(rootfs.join("var/lib/depot")).unwrap();
std::fs::write(rootfs.join("var/lib/depot/lock"), "state").unwrap();
std::fs::create_dir_all(rootfs.join("system/variable/lib/depot")).unwrap();
std::fs::write(rootfs.join("system/variable/lib/depot/lock"), "state").unwrap();
std::fs::create_dir_all(destdir.join("system/variable/lib/misc")).unwrap();
std::os::unix::fs::symlink("system/variable", destdir.join("var")).unwrap();
let tx = install_atomic(&destdir, &rootfs, &tx_base, &[], &[]).unwrap();
let var_meta = rootfs.join("var").symlink_metadata().unwrap();
assert!(var_meta.file_type().is_symlink());
assert_eq!(
std::fs::read_link(rootfs.join("var")).unwrap(),
PathBuf::from("system/variable")
);
assert_eq!(
std::fs::read_to_string(rootfs.join("system/variable/lib/depot/lock")).unwrap(),
"state"
);
assert!(rootfs.join("system/variable/lib/misc").is_dir());
tx.rollback().unwrap();
let restored = rootfs.join("var").symlink_metadata().unwrap();
assert!(restored.file_type().is_dir());
assert_eq!(
std::fs::read_to_string(rootfs.join("var/lib/depot/lock")).unwrap(),
"state"
);
}
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn install_atomic_replaces_obsolete_directory_with_symlink() { fn install_atomic_replaces_obsolete_directory_with_symlink() {
+5 -4
View File
@@ -348,10 +348,11 @@ datarootdir = "/system/share"
datadir = "/system/share" datadir = "/system/share"
mandir = "/system/documentation/man-pages" mandir = "/system/documentation/man-pages"
infodir = "/system/documentation/info" infodir = "/system/documentation/info"
cc = "clang" tool_dir = "/system/tools/bin"
cxx = "clang++" cc = "$TOOL_DIR/clang"
ar = "llvm-ar" cxx = "$TOOL_DIR/clang++"
ld = "ld.lld" ar = "$TOOL_DIR/llvm-ar"
ld = "$TOOL_DIR/ld.lld"
carch = "{arch}" carch = "{arch}"
chost = "{target}" chost = "{target}"
target = "{target}" target = "{target}"