Skip to content

Conversation

@ranger-ross
Copy link
Member

What does this PR try to resolve?

This PR modifies the locking logic to avoid locking artifact-dir for check builds.
Part of #4282

Note: This change is not behind -Zbuild-dir-new-layout or -Zfine-grain-locking unstable flags.

How to test and review this PR?

See the tests included in the PR.
You can run cargo check to verify.

r? @epage

@rustbot rustbot added A-layout Area: target output directory layout, naming, and organization Command-clean S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 8, 2025
Copy link
Member

Choose a reason for hiding this comment

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

This should be in check command test file maybe?
https://github.com/rust-lang/cargo/blob/master/tests/testsuite/check.rs

Copy link
Member Author

Choose a reason for hiding this comment

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

ah good idea. I was thinking about putting it in https://github.com/rust-lang/cargo/blob/master/tests/testsuite/artifact_dep.rs but noticed that file only has test for the unstable --artifact-dir flag.

master/tests/testsuite/check.rs seems like a good place for this

Copy link
Member

Choose a reason for hiding this comment

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

Should we also add one test asserting that no files are generated to the artifact directory from cargo check?

(Maybe we already have one though)

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a test in da0dfd3

// directory, so just lock the entire thing for the duration of this
// compile.
let artifact_dir_lock = if is_on_nfs_mount(root.as_path_unlocked()) {
let artifact_dir_lock = if !must_take_artifact_dir_lock
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of conditionally grabbing the lock, can we put the entire artifact layout in an Option? That has the potential to help us find bugs but unsure how messy that would be.

Copy link
Member Author

Choose a reason for hiding this comment

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

I looked into this and it got pretty hairy. Switching from T to Option<T> is not too bad but there quiet a few places during compilation where the paths are used but not written to.

For example, in build_runner::prepare we build up the root_output

self.compilation
.root_output
.insert(kind, layout.artifact_dir().dest().to_path_buf());

and we use this in fill_env()

search_path.extend(super::filter_dynamic_search_path(
self.native_dirs.iter(),
&self.root_output[&kind],
));

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't that make the case even stronger? We are trying to read from a location we do not write to and do not have locked.

Copy link
Member Author

Choose a reason for hiding this comment

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

I looked into this and I think this is a good call. I ended up missing that we still need to lock the artifact-dir for cargo check --timings. Making this Option caused a test to fail instead of silently write files without locking.

I split out this change into a refactor commit before changing the behavior.

// We try to only lock the artifact-dir if we need to.
// For example, `cargo check` does not write any files to the artifact-dir so we don't need
// to lock it.
let must_take_artifact_dir_lock = match self.bcx.build_config.intent {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of intent, can we check if artifacts are produced? Do we know that?

Copy link
Member Author

Choose a reason for hiding this comment

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

I checked and I think it may be possible to use some of the logic from CompilationFiles output but we would likely need to duplicate it as we need to construct Layout before CompilationFiles.

See https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/build_runner/compilation_files.rs#L405

Let me know if there is a better way to get this data.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, that can get messy. We could create an enum for the base path and calculate the full path later but unsure how well that will work out.

However, this does highlight a need for either this or conditionally grabbing the lock (#16230 (comment))

if bcx.build_config.sbom && bcx.gctx.cli_unstable().sbom {
let sbom_files: Vec<_> = outputs
.iter()
.filter(|o| matches!(o.flavor, FileFlavor::Normal | FileFlavor::Linkable))
.map(|output| OutputFile {
path: Self::append_sbom_suffix(&output.path),
hardlink: output.hardlink.as_ref().map(Self::append_sbom_suffix),
export_path: output.export_path.as_ref().map(Self::append_sbom_suffix),
flavor: FileFlavor::Sbom,
})
.collect();
outputs.extend(sbom_files.into_iter());

We would have been writing out SBOMs without holding the lock. If we don't feel SBOMs are relevant for cargo check then making the layout optional (to catch problems like this) is sufficient. If not, then we need to somehow be aware of this for locking and then that starts to look like needing to resolve this item.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: if we change SBOMs, lets do that in its own PR and make a note against rust-lang/rfcs#3553

CC @arlosi

@rustbot rustbot added A-build-execution Area: anything dealing with executing the compiler A-build-scripts Area: build.rs scripts A-cfg-expr Area: Platform cfg expressions A-timings Area: timings labels Nov 11, 2025
@ranger-ross ranger-ross force-pushed the artifact-dir-locking branch 2 times, most recently from 298152d to 9be3ef9 Compare November 11, 2025 14:01
Comment on lines 362 to 364
// 1. If check has `--test` or `--tests` we need to pass `CARGO_BIN_EXE_` which
// points into the artifact-dir. In theory we don't need to take the lock here
// but we do to satify the Layout struct.
Copy link
Contributor

Choose a reason for hiding this comment

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

Yikes, that is a pretty big exception for us to make that can negate this.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, its unfortunate.

I think we still need to lock the artifact-dir since it could be read during compile time like

let tool_bytes = include_bytes!(env!("CARGO_BIN_EXE_foo"));

Though, I'd imagine this would be a pretty rare usecase?

Copy link
Contributor

Choose a reason for hiding this comment

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

The case where this could come up is with artifact deps though I don't know if those are based on uplifted binaries or not

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I've wondered about moving CARGO_BIN_EXE_* to also work at runtime for tests. Encouraging a migration to that would make it clearer what the intent is with these.

#t-cargo > cargo_bin_exe and tests @ 💬

Copy link
Member Author

Choose a reason for hiding this comment

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

okay I was finally able to check on this.

It does point to the uplifted bin location in artifact-dir.
However for cargo check --tests that path points to a nonexisting location in artifact-dir.

Interestingly, I tried doing include_bytes!(env!("CARGO_BIN_EXE_bar")) and I get a compiler error:

error: couldn't read `/home/ross/test-artifact-deps-uplift/target/debug/bar`: No such file or directory (os error 2)
 --> tests/foo.rs:4:13
  |
4 |     let a = include_bytes!(env!("CARGO_BIN_EXE_bar"));
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Even though I see the bar bin in that location after the build. (pipelined build race condition perhaps?)

I am on the fence about the best thing to do here. The options I could think of are:

  1. We could add an escape hatch to get the artifact_dir() location from Layout even if we don't lock it.
  2. We could point to the pre-uplifted bin for check builds (though it might not exist, same as current behavior)
  3. We could have a dummy value for check builds to satisfy compilation.

I think if I had to pick, I'd go with option 2 but input on this is welcome. :D

Copy link
Contributor

Choose a reason for hiding this comment

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

Considering the value is unusable for compilation, I would lean towards (3).

  • This protects against race conditions where you see an old version of things
  • Pre-uplifted is never a valid answer because it will have the -Cextra-filename

rustc reports CARGO_BIN_EXE_bar in its dep-info file. Does cargo preserve that in its own or filter it out? If it preserves it then that means we are ruining people's caching. We can at least make things more cache friendly with a dummy value. Moving people to reading at runtime would help even more.

Copy link
Member Author

Choose a reason for hiding this comment

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

Does cargo preserve that in its own or filter it out?

I checked this and it appears that cargo is filtering it out in translate_dep_info

on_disk_info.env.retain(|(key, _)| {
ManifestMetadata::should_track(key)
|| env_config.contains_key(key)
|| !rustc_cmd.get_envs().contains_key(key)
|| key == CARGO_ENV
});

So I suppose a dummy value would be fine since we wouldn't be invalidating fingerprints. 👍

Copy link
Member Author

@ranger-ross ranger-ross Nov 15, 2025

Choose a reason for hiding this comment

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

I went ahead and updated this PR to add a placeholder CARGO_BIN_EXE_<name>=placeholder:<name> for check builds.
Also removing the need to lock the artifact-dir for check --test builds

Copy link
Member

Choose a reason for hiding this comment

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

Probably not really on topic, but I recently felt it inconvenient that CARGO_TARGET_TMPDIR is with build-dir. I usually cd target/tmp to access test files but now I need to navigate to a shared build-dir directory.

I wonder if we should have a project private directory under artifact directory, so that we only lock that for bins

Copy link
Member

Choose a reason for hiding this comment

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

Thought came from me reading this thread #16230 (comment), but placeholder method looks nice also

Copy link
Member

Choose a reason for hiding this comment

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

bin checks are never usable, forget about my comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-build-execution Area: anything dealing with executing the compiler A-build-scripts Area: build.rs scripts A-cfg-expr Area: Platform cfg expressions A-layout Area: target output directory layout, naming, and organization A-timings Area: timings Command-clean S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants