build(xbuild): introduce cargo_profile_dir()
What changed, and why it matters
This is a build-system maintenance change. It replaces hard-coded guesses about where Cargo places compiled files with a single helper function that walks the directory tree more reliably. There is no user-facing feature change and no security fix or vulnerability.
No security action needed. Treat as normal build-system refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces cargo_profile_dir() in the xbuild helper crate and replaces several OUT_DIR/../../../ style path traversals with calls to this helper. It also adds unit tests covering both legacy and Cargo ‘layout v2’ build directory structures. The change is purely about build robustness across Cargo versions.
Changed components
core/embed/xbuild build helpersfirmware build.rskernel build.rsupymod build.rsInspect captured patch +107 / −23
### core/embed/projects/firmware/build.rs
@@ -1,6 +1,3 @@
-use std::env;
-use std::path::PathBuf;
-
use xbuild::{CLibrary, Result, bail_unsupported};
fn main() -> Result<()> {
@@ -45,8 +42,7 @@ fn main() -> Result<()> {
}
fn embed_kernel_binary(lib: &mut CLibrary) -> Result<()> {
- let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
- let kernel = out_dir.join("../../../kernel.bin");
+ let kernel = xbuild::cargo_profile_dir()?.join("kernel.bin");
lib.embed_binary(&kernel, "kernel")
}
### core/embed/projects/kernel/build.rs
@@ -1,4 +1,3 @@
-use std::env;
use std::path::PathBuf;
use xbuild::{CLibrary, Result};
@@ -42,8 +41,8 @@ fn embed_secmon_binary(lib: &mut CLibrary) -> Result<()> {
lib.add_object(dir.join("secmon_api_DEV.o"));
lib.embed_binary(dir.join("secmon_DEV.bin"), "secmon")?;
} else {
- // Take recently built secmon from the output directory
- let dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../..");
+ // Take recently built secmon from Cargo's profile directory
+ let dir = xbuild::cargo_profile_dir()?;
lib.add_object(dir.join("secmon_api.o"));
lib.embed_binary(dir.join("secmon.bin"), "secmon")?;
}
### core/embed/upymod/build.rs
@@ -355,7 +355,7 @@ fn define_scm_revision(lib: &mut CLibrary) -> Result<u8> {
/// when generating test coverage reports that need to process *.i files
fn create_mpy_files_symlink() -> Result<()> {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
- let symlink_path = out_dir.join("../../../mpy-files");
+ let symlink_path = xbuild::cargo_profile_dir()?.join("mpy-files");
let target_path = out_dir.join("__oot/src");
if symlink_path.exists() {
### core/embed/xbuild/src/clibrary/cc_generator.rs
@@ -3,7 +3,9 @@ use color_eyre::eyre::WrapErr;
use super::CLibrary;
use crate::cargo_out;
-use crate::helpers::{derive_output_path, join_paths_lexically, links_name, path_from_env};
+use crate::helpers::{
+ cargo_profile_dir, derive_output_path, join_paths_lexically, links_name, path_from_env,
+};
/// Path to the partial compile_commands.json fragment within OUT_DIR.
const COMPILE_COMMANDS_FILE: &str = "compile_commands.json";
@@ -96,7 +98,7 @@ impl CLibrary {
// Write merged compile_commands.json next to the final binary
let name = links_name()?;
- let target_path = out_dir.join(format!("../../../{name}.cc.json"));
+ let target_path = cargo_profile_dir()?.join(format!("{name}.cc.json"));
std::fs::write(&target_path, serde_json::to_string_pretty(&all_entries)?)?;
Ok(())
### core/embed/xbuild/src/helpers.rs
@@ -209,25 +209,49 @@ pub fn path_from_env(name: &str) -> Result<PathBuf> {
.with_context(|| format!("Environment variable `{name}` is required but not set"))
}
+/// Returns the Cargo profile directory that holds final artifacts
+/// (`secmon.bin`, map files, …) for the current build.
+///
+/// `OUT_DIR` is always nested under this directory, but the nesting depth
+/// depends on Cargo's build-dir layout:
+///
+/// * legacy: `{profile}/build/<pkg>-<hash>/out`
+/// * layout v2: `{profile}/build/<pkg>/<hash>/out`
+///
+/// Walking to the nearest ancestor named `build` and taking its parent works
+/// for both. Hard-coding `OUT_DIR/../../..` does not.
+pub fn cargo_profile_dir() -> Result<PathBuf> {
+ cargo_profile_dir_from_out_dir(&path_from_env("OUT_DIR")?)
+}
+
/// Returns Cargo's configured target directory for the current build.
pub fn cargo_target_dir() -> Result<PathBuf> {
- let out_dir = path_from_env("OUT_DIR")?;
let target = env::var("TARGET").wrap_err("Failed to get TARGET")?;
+ cargo_target_dir_from_out_dir(&path_from_env("OUT_DIR")?, &target)
+}
- let build_dir = out_dir
+fn cargo_build_dir_from_out_dir(out_dir: &Path) -> Result<&Path> {
+ out_dir
.ancestors()
.find(|path| path.file_name() == Some(OsStr::new("build")))
- .ok_or_else(|| eyre!("Failed to locate Cargo build dir from OUT_DIR"))?;
+ .ok_or_else(|| eyre!("Failed to locate Cargo build dir from OUT_DIR"))
+}
- let profile_dir = build_dir
+fn cargo_profile_dir_from_out_dir(out_dir: &Path) -> Result<PathBuf> {
+ cargo_build_dir_from_out_dir(out_dir)?
.parent()
- .ok_or_else(|| eyre!("Failed to locate Cargo profile dir from OUT_DIR"))?;
+ .map(Path::to_path_buf)
+ .ok_or_else(|| eyre!("Failed to locate Cargo profile dir from OUT_DIR"))
+}
+
+fn cargo_target_dir_from_out_dir(out_dir: &Path, target: &str) -> Result<PathBuf> {
+ let profile_dir = cargo_profile_dir_from_out_dir(out_dir)?;
let parent = profile_dir
.parent()
.ok_or_else(|| eyre!("Failed to locate Cargo target dir from OUT_DIR"))?;
- let target_dir = if parent.file_name() == Some(OsStr::new(&target)) {
+ let target_dir = if parent.file_name() == Some(OsStr::new(target)) {
parent
.parent()
.ok_or_else(|| eyre!("Failed to locate Cargo target dir from target triple dir"))?
@@ -281,3 +305,66 @@ where
cargo_out::rerun_if_changed(file);
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const TARGET: &str = "thumbv8m.main-none-eabihf";
+
+ fn assert_dirs(out_dir: &str, profile: &str, target_dir: &str) {
+ let out_dir = Path::new(out_dir);
+ assert_eq!(
+ cargo_profile_dir_from_out_dir(out_dir).unwrap(),
+ PathBuf::from(profile)
+ );
+ assert_eq!(
+ cargo_target_dir_from_out_dir(out_dir, TARGET).unwrap(),
+ PathBuf::from(target_dir)
+ );
+ }
+
+ #[test]
+ fn locates_dirs_in_legacy_cross_layout() {
+ assert_dirs(
+ "/repo/core/build-xtask/thumbv8m.main-none-eabihf/release/build/kernel-e281cbafac5dc040/out",
+ "/repo/core/build-xtask/thumbv8m.main-none-eabihf/release",
+ "/repo/core/build-xtask",
+ );
+ }
+
+ #[test]
+ fn locates_dirs_in_layout_v2_cross() {
+ assert_dirs(
+ "/repo/core/build-xtask/thumbv8m.main-none-eabihf/release/build/kernel/e281cbafac5dc040/out",
+ "/repo/core/build-xtask/thumbv8m.main-none-eabihf/release",
+ "/repo/core/build-xtask",
+ );
+ }
+
+ #[test]
+ fn locates_dirs_in_legacy_host_layout() {
+ let out_dir = Path::new("/repo/core/build-xtask/debug/build/xbuild-abcdef0123456789/out");
+ assert_eq!(
+ cargo_profile_dir_from_out_dir(out_dir).unwrap(),
+ PathBuf::from("/repo/core/build-xtask/debug")
+ );
+ assert_eq!(
+ cargo_target_dir_from_out_dir(out_dir, "aarch64-apple-darwin").unwrap(),
+ PathBuf::from("/repo/core/build-xtask")
+ );
+ }
+
+ #[test]
+ fn locates_dirs_in_layout_v2_host() {
+ let out_dir = Path::new("/repo/core/build-xtask/debug/build/xbuild/abcdef0123456789/out");
+ assert_eq!(
+ cargo_profile_dir_from_out_dir(out_dir).unwrap(),
+ PathBuf::from("/repo/core/build-xtask/debug")
+ );
+ assert_eq!(
+ cargo_target_dir_from_out_dir(out_dir, "aarch64-apple-darwin").unwrap(),
+ PathBuf::from("/repo/core/build-xtask")
+ );
+ }
+}
### core/embed/xbuild/src/lib.rs
@@ -17,8 +17,8 @@ pub use dep_tracking::{
emit_command_output, needs_rebuild, run_command, run_command_to_file, run_if_changed,
};
pub use helpers::{
- cargo_target_dir, derive_output_path, diagnostics_color_flag, emit_rerun_if_changed,
- is_rust_analyzer, scm_revision, trace_enabled,
+ cargo_profile_dir, cargo_target_dir, derive_output_path, diagnostics_color_flag,
+ emit_rerun_if_changed, is_rust_analyzer, scm_revision, trace_enabled,
};
pub use input_files::InputFiles;
pub use parallel::{optimal_parallel_job_count, run_parallel};
### core/embed/xbuild/src/trezor.rs
@@ -11,7 +11,7 @@ use std::{env, fs};
use color_eyre::Result;
use color_eyre::eyre::{WrapErr, bail};
-use crate::helpers::{is_rust_analyzer, links_name};
+use crate::helpers::{cargo_profile_dir, is_rust_analyzer, links_name};
use crate::{CLibrary, cargo_out};
fn package_name() -> Result<String> {
@@ -247,8 +247,8 @@ impl CLibrary {
// Generate a map file for the final binary in the same directory
// as the final binary.
- let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
- let map_file = out_dir.join(format!("../../../{package_name}.map"));
+ let profile_dir = cargo_profile_dir()?;
+ let map_file = profile_dir.join(format!("{package_name}.map"));
cargo_out::rustc_link_arg(format!("-Wl,-Map={}", map_file.display()));
// Instruct the linker to perform garbage collection of
@@ -262,7 +262,7 @@ impl CLibrary {
// in the same directory as the final binary. The Kernel will link
// against this import library to call the secure monitor API.
- let implib_file = out_dir.join("../../../secmon_api.o");
+ let implib_file = profile_dir.join("secmon_api.o");
cargo_out::rustc_link_arg("-Wl,-cmse-implib");
cargo_out::rustc_link_arg(format!("-Wl,--out-implib={}", implib_file.display()));
}Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.