Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions crates/cargo-util/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> Result<OsString> {
})
}

/// The environment variables that control the temporary directory returned by
/// `std::env::temp_dir()`.
///
/// Cargo overrides this directory when invoking the compiler or when running
/// build scripts. This is useful when debugging, as it allows more easily
/// inspecting the state the compiler was working with when something went
/// wrong (especially if using `-Csave-temps=yes`).
///
/// Additionally, this is useful for preventing information leakage and
/// Man-in-the-middle attacks: `/tmp` is world readable and semi-writable by
/// design. This is even an issue on macOS, where the temporary directory is
/// scoped to the current user with `getconf DARWIN_USER_TEMP_DIR`, since you
/// can still have information leakage / MITM to less privileged processes by
/// the same user.
pub fn tmpdir_envvars() -> &'static [&'static str] {
// NOTE: On Windows, there's two environment variables that control the
// temporary directory, `TMP` and `TEMP`, see `GetTempPath2`:
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppath2a
// We set both of them for consistency with programs that only read one.
if cfg!(windows) {
&["TMP", "TEMP"]
} else {
&["TMPDIR"]
}
}

/// Returns the name of the environment variable used for searching for
/// dynamic libraries.
pub fn dylib_path_envvar() -> &'static str {
Expand Down
14 changes: 14 additions & 0 deletions src/cargo/core/compiler/build_runner/compilation_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,20 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> {
self.build_script_run_dir(unit).join("out")
}

/// Returns the directory which build scripts should use for temporary
/// files.
/// `/path/to/target/{debug,release}/build/PKG-HASH/tmp`
pub fn build_script_tmp_dir(&self, unit: &Unit) -> PathBuf {
self.build_script_run_dir(unit).join("tmp")
}
Comment on lines +392 to +397
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option here would be to simply use self.build_script_run_dir(unit) given that people using std::env::temp_dir() already know that they have to generate a unique name for anything they want to write there.


/// Returns the directory which `rustc` invocations should use for
/// temporary files, and which `CARGO_CFG_TMPDIR` should be set to.
/// `/path/to/target/tmp`
pub fn rustc_tmp_dir(&self, unit: &Unit) -> &Path {
self.layout(unit.kind).build_dir().tmp()
}

/// Returns the path to the executable binary for the given bin target.
///
/// This should only to be used when a `Unit` is not available.
Expand Down
9 changes: 9 additions & 0 deletions src/cargo/core/compiler/custom_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ fn emit_build_output(
///
/// * Set environment variables for the build script run.
/// * Create the output dir (`OUT_DIR`) for the build script output.
/// * Create the temporary dir (`TMPDIR`/`TMP`/`TEMP`) that will be set.
/// * Determine if the build script needs a re-run.
/// * Run the build script and store its output.
fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Job> {
Expand All @@ -339,6 +340,7 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
let script_dir = build_runner.files().build_script_dir(build_script_unit);
let script_out_dir = build_runner.files().build_script_out_dir(unit);
let script_run_dir = build_runner.files().build_script_run_dir(unit);
let script_tmp_dir = build_runner.files().build_script_tmp_dir(unit);

if let Some(deps) = unit.pkg.manifest().metabuild() {
prepare_metabuild(build_runner, build_script_unit, deps)?;
Expand Down Expand Up @@ -376,6 +378,12 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
.env("RUSTDOC", &*bcx.gctx.rustdoc()?)
.inherit_jobserver(&build_runner.jobserver);

// Make build scripts output temporary files to `script_tmp_dir` instead
// of the path returned by `std::env::temp_dir()`.
for key in paths::tmpdir_envvars() {
cmd.env(key, &script_tmp_dir);
}

// Find all artifact dependencies and make their file and containing directory discoverable using environment variables.
for (var, value) in artifact::get_env(build_runner, dependencies)? {
cmd.env(&var, value);
Expand Down Expand Up @@ -495,6 +503,7 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul

paths::create_dir_all(&script_dir)?;
paths::create_dir_all(&script_out_dir)?;
paths::create_dir_all(&script_tmp_dir)?;

let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed;
let targets: Vec<Target> = unit.pkg.targets().to_vec();
Expand Down
7 changes: 3 additions & 4 deletions src/cargo/core/compiler/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,8 @@ impl BuildDirLayout {
pub fn build_unit(&self, pkg_dir: &str) -> PathBuf {
self.build().join(pkg_dir)
}
/// Create and return the tmp path.
pub fn prepare_tmp(&self) -> CargoResult<&Path> {
paths::create_dir_all(&self.tmp)?;
Ok(&self.tmp)
/// Return the tmp path.
pub fn tmp(&self) -> &Path {
&self.tmp
}
}
25 changes: 19 additions & 6 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,13 +787,17 @@ fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult
}
}

let tmpdir = build_runner.files().rustc_tmp_dir(unit);
paths::create_dir_all(tmpdir)?;

// Make `rustc`, proc-macros and the linker output temporary files to
// `tmpdir` instead of the path returned by `std::env::temp_dir()`.
for key in paths::tmpdir_envvars() {
base.env(key, tmpdir);
}

if unit.target.is_test() || unit.target.is_bench() {
let tmp = build_runner
.files()
.layout(unit.kind)
.build_dir()
.prepare_tmp()?;
base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string());
base.env("CARGO_TARGET_TMPDIR", tmpdir.display().to_string());
}

Ok(base)
Expand Down Expand Up @@ -923,6 +927,15 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu
append_crate_version_flag(unit, &mut rustdoc);
}

let tmpdir = build_runner.files().rustc_tmp_dir(unit);
paths::create_dir_all(tmpdir)?;

// Make `rustdoc` and proc-macros output temporary files to `tmpdir`
// instead of the path returned by `std::env::temp_dir()`.
for key in paths::tmpdir_envvars() {
rustdoc.env(key, tmpdir);
}

Ok(rustdoc)
}

Expand Down
66 changes: 66 additions & 0 deletions tests/testsuite/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6425,3 +6425,69 @@ fn embed_metadata_dylib_dep() {
)
.run();
}

#[cargo_test]
fn temp_dir_is_inside_build_dir() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
pm = { path = "pm" }
"#,
)
.file(
"src/lib.rs",
r#"
pm::foo!();
"#,
)
.file(
"build.rs",
r#"
fn main() {
let temp_dir = std::env::temp_dir();
assert!(temp_dir.components().any(|c| c.as_os_str() == "custom-build-dir"), "{temp_dir:?}");
}
"#,
)
.file(
".cargo/config.toml",
r#"
[build]
target-dir = "custom-target-dir"
build-dir = "custom-build-dir"
"#,
)
.file(
"pm/Cargo.toml",
r#"
[package]
name = "pm"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
"#,
)
.file(
"pm/src/lib.rs",
r#"
use proc_macro::TokenStream;

#[proc_macro]
pub fn foo(input: TokenStream) -> TokenStream {
let temp_dir = std::env::temp_dir();
assert!(temp_dir.components().any(|c| c.as_os_str() == "custom-build-dir"), "{temp_dir:?}");
input
}
"#,
)
.build();

p.cargo("build").run();
}