Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented build.build-dir config option #15104

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

ranger-ross
Copy link
Contributor

@ranger-ross ranger-ross commented Jan 26, 2025

What does this PR try to resolve?

This PR adds a new build.build-dir configuration option that was proposed in #14125 (comment)

This new config option allows the user to specify a directory where intermediate build artifacts should be stored.
I have shortened it to just build-dir from target-build-dir, although naming is still subject to change.

What is a final artifact vs an intermediate build artifact

Final artifacts

These are the files that end users will typically want to access directly or indirectly via a third party tool.

Intermediate build artifact

These are files that are used internally by Cargo/Rustc during the build process

Feature Gating Strategy

We are following the "Ignore the feature that is used without a gate" approach as described here.

The rational for this is:
The build.build-dir is likely going to be set by by users "globally" (ie. $CARGO_HOME/config.toml) to set a shared build directory to reduce rebuilding dependencies. For users that multiple cargo versions having having an error would be disrupted.
The fallback behavior is to revert to the behavior of the current stable release (building in $CARGO_TARGET_DIR)

Testing Strategy

  • We have the existing Cargo testsuite to be sure we do not introduce regressions.
    • I have also run the testsuite locally with the cli flag remove to verify all tests pass with the default build dir (which falls back to the target dir)
  • For testing thus far, I have been using small hello world project with a few dependencies like rand to verify files are being output to the correct directory.
  • When this PR is closer to merging, I plan to test with some larger projects with more dependencies, build scripts, ect.
  • Other testing recommendations are welcome 🙇

How should we test and review this PR?

This is probably best reviewed commit by commit. I documented each commit.
I tied to follow the atomic commits recommendation in the Cargo contributors guide, but I split out some commits for ease of review. (Otherwise I think this would have ended up being 1 or 2 large commits 😅)

Questions

  • What is the expected behavior of cargo clean?
  • When using cargo package are was expecting just the .crate file to be in target while all other output be stored in build.build-dir? Not sure if we consider things like Cargo.toml, Cargo.toml.orig, .cargo_vcs_info.json part of the user facing interface.
    • Current consensus is that only .crate is considered a final artifact
  • Where should cargo doc output go? HTML/JS for many crates can be pretty large. Moving to the build-dir would help reduce duplication if we find the that acceptable. For cargo doc --open this is not a problem but may be problematic for other use cases?
  • Are bins generated from benches considered final artifacts?
    • Since bins from examples are considered final artifacts, it seems natural that benches should also be considered final artifacts. However, unlike examples the benches bins are stored in target/{profile}/deps instead of a dedicated directory (like target/{profile}/examples). We could move them into a dedicated directory (target/{profile}/benches) but that mean would also be changing the structure of the target directory which feels out of scope for this change. If we decide that benches are final artifacts, it would probably be better to consider that changes as part of --artifact-dir (nee --out-dir) Tracking Issue #6790
    • Answer: Implemented build.build-dir config option #15104 (comment)
  • Do we want to include a CARGO_BUILD_DIR shortcut env var?
    • The current commit (2af0c91) has included the CARGO_BUILD_DIR shortcut. This can be removed before merging if there a good reason to.

TODO

  • Implementation
    • Add support in cargo clean
    • Implement templating for build.build-dir
    • Fix issue with target/examples still containing "pre-uplifted" binaries
    • Verify build-dir with non-bin crate types
  • Prepare for review
    • Clean up/improve docs
    • Review tests and add more as needed
    • Fix tests in CI (Windows is currently failing)
    • Clean up commits
    • Resolve remaining questions
  • Request review

@rustbot
Copy link
Collaborator

rustbot commented Jan 26, 2025

r? @ehuss

rustbot has assigned @ehuss.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added A-build-execution Area: anything dealing with executing the compiler A-build-scripts Area: build.rs scripts A-configuration Area: cargo config files and env vars A-documenting-cargo-itself Area: Cargo's documentation A-filesystem Area: issues with filesystems A-future-incompat Area: future incompatible reporting A-layout Area: target output directory layout, naming, and organization A-rebuild-detection Area: rebuild detection and fingerprinting A-unstable Area: nightly unstable support A-workspaces Area: workspaces Command-package S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 26, 2025
@ranger-ross ranger-ross changed the title Added build-directory unstable feature flag Implemented build.build-dir config option Jan 26, 2025
Copy link
Member

@weihanglo weihanglo left a comment

Choose a reason for hiding this comment

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

Just want to make sure I didn't miss something. From what I can tell these directories/files have been removed right?

  • target/<profile>/.metabuild
  • target/<profile>/.fingerprint
  • target/<profile>/deps
  • target/<profile>/incremental
  • target/<profile>/build
  • target/.cargo-lock
  • target/tmp
  • target/.rustc_info.json

Copy link
Member

Choose a reason for hiding this comment

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

We usually add some tests verifying a nightly flag is really gated.

@@ -290,7 +290,9 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
});
}

super::output_depinfo(&mut self, unit)?;
if !self.bcx.gctx.cli_unstable().build_dir {
super::output_depinfo(&mut self, unit)?;
Copy link
Member

Choose a reason for hiding this comment

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

Why do we stop generating this when build-dir is enabled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, actually I believe this is a mistake on my part 😅

My original assumption was that the /target/debug/{project-name}.d file was the same file as /target/debug/deps/{project-name}-{hash}.d, so I was trying to avoid copying it into target.
But now that I compared the files they appear to be different so it looks like that was a bad assumption.

I believe this should probably be considered an intermediate build artifact so it should probably be moved to the build-dir location.

Modifying this file in particular was a bit tricky as it was not just replacing .target_dir() with .build_dir().
I think I will probably need to go to where the OutputFile is created and modify the hardlink to be the new build-dir path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given this comment I am going to revert this change

@ranger-ross
Copy link
Contributor Author

Just want to make sure I didn't miss something. From what I can tell these directories/files have been removed right?

  • target/<profile>/.metabuild
  • target/<profile>/.fingerprint
  • target/<profile>/deps
  • target/<profile>/incremental
  • target/<profile>/build
  • target/.cargo-lock
  • target/tmp
  • target/.rustc_info.json

Yes, that with the exception of target/.cargo-lock.
I think we will still want this cargo lock for backwards compatibility with previous versions of cargo.

Ideally in the longer term it can be removed in favor of fine grain locking like #4282

So a typical target directory will be something like

target
├── CACHEDIR.TAG
└── debug
    ├── .cargo-lock
    ├── examples
    └── hello_world // (the binary)

Comment on lines 241 to 260
## build-dir
* Original Issue: [#14125](https://github.com/rust-lang/cargo/issues/14125)
* Tracking Issue: [#14125](https://github.com/rust-lang/cargo/issues/14125)

This feature allows you to specify the directory where intermediate build artifacts will be stored.
The final artifacts will also be contained in this directory but will be hardlinked into the artifact directory (usually `target`)

This can be specified in `.cargo/config.toml` files using the `build.build-dir`.

Example:

```toml
[build]
build-dir = "out"
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you include documentation in this that mirrors what would be in https://doc.rust-lang.org/cargo/reference/config.html

Comment on lines 138 to 143
/// Same as `_lock` but for the build directory.
///
/// NOTE: `_lock` and `_build_lock` can eventually be merged once #14125 is stablized.
/// Will be `None` when the build-directory and target-directory are the same path as we cannot
/// lock the same path twice.
Copy link
Contributor

Choose a reason for hiding this comment

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

How can we merge these locks?

In the short term, we'll have

  • target-dir
  • build-dir

which both need locking

In the long term, we'll have

  • artifact-dir
  • build-dir

which will both need locking

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, you are right. Not quiet sure what I was thinking when I wrote this comment.

tests/testsuite/build_dir.rs Outdated Show resolved Hide resolved
@epage
Copy link
Contributor

epage commented Jan 27, 2025

What is the expected behavior of cargo clean?

It should clean the build dir

@epage
Copy link
Contributor

epage commented Jan 27, 2025

When using cargo package are was expecting just the .crate file to be in target while all other output be stored in build.build-dir? Not sure if we consider things like Cargo.toml, Cargo.toml.orig, .cargo_vcs_info.json part of the user facing interface.

imo The artifact for cargo package is the .crate. Everything else is part of the "build" process.

@epage
Copy link
Contributor

epage commented Jan 27, 2025

Can we call out explicitly what our testing strategy is?

We likely should also explicitly document in the PR what is considered an artifact and what is a build output and make sure we have tests for these.

@ranger-ross
Copy link
Contributor Author

One other question that came to my mind was the output of cargo doc. HTML/JS for many crates can be pretty large. Moving to the build-dir would help reduce duplication if we find the that acceptable. For cargo doc --open this is not a problem but may be problematic for other use cases?

Perhaps symlinking the index.html from the build dir into target could be an option if we care about keeping an entry point.

@ranger-ross
Copy link
Contributor Author

Can we call out explicitly what our testing strategy is?

We likely should also explicitly document in the PR what is considered an artifact and what is a build output and make sure we have tests for these.

@epage sure, I updated the PR description but let me know if I missed anything.

@epage
Copy link
Contributor

epage commented Jan 28, 2025

One other question that came to my mind was the output of cargo doc. HTML/JS for many crates can be pretty large. Moving to the build-dir would help reduce duplication if we find the that acceptable. For cargo doc --open this is not a problem but may be problematic for other use cases?

imo cargo docs output is an artifact that people will want access to. I suspect it'd be a breaking change to move it out of target-dir.

@epage
Copy link
Contributor

epage commented Jan 28, 2025

depinfo files (.d files)

There are multiple types of depinfo files. I suspect the ones next to final artifacts are also considered final artifacts, see https://doc.rust-lang.org/cargo/reference/build-cache.html#dep-info-files

@epage
Copy link
Contributor

epage commented Jan 28, 2025

FYI I added to the PR description a couple more intermediate artifacts

  • rlibs and debug info from dependencies
  • build script OUT_DIR

When are workspace member rlibs considered final artifacts? We're putting them in target/<profile> at times, so I take it that has already been answered.

@ranger-ross
Copy link
Contributor Author

I spent some more time this weekend making some progress on this PR.

Here are my updates

  • Fixed some issues to make cargo clean & cargo package work with build.build-dir
  • Added templating support for build.build-dir
  • Cleanup the commits a bit and tried to follow the request in this comment regarding tests

I updated the PR description with more details as well. notably:

  • Added another question I would like input form the Cargo team on.
    • Are bins generated from benches considered final artifacts? (see PR desc for more details)
  • Updated the TODO list with a more detailed list of tasks before marking this PR as ready for review.
  • Cleaned up the "What is a final artifact vs an intermediate build artifact" section
    • Also added the following file types to the list of intermediate build artifacts
      • "pre-uplifted" binary executables. (ie. bins for examples that contain the hash in the name)
      • CARGO_TARGET_TMPDIR files (see rational for this here)

@epage
Copy link
Contributor

epage commented Feb 4, 2025

Added templating support for build.build-dir

I haven't looked but would it work to split that out into a follow up PR? Keeping PRs focused can help with ensuring a smooth review and that we are less likely to overlook something.

Are bins generated from benches considered final artifacts? (see PR desc for more details)

My take is that if it isn't staged/uplifted, then its not a final artifact.

I assume this also applies to test binaries, build scripts, and proc-macros.

@ranger-ross
Copy link
Contributor Author

I haven't looked but would it work to split that out into a follow up PR? Keeping PRs focused can help with ensuring a smooth review and that we are less likely to overlook something.

Sure. Its not a ton of code, but I have no problems breaking that out into anther PR to reduce the scope of this one 👍

@ranger-ross
Copy link
Contributor Author

Are bins generated from benches considered final artifacts? (see PR desc for more details)

My take is that if it isn't staged/uplifted, then its not a final artifact.

I assume this also applies to test binaries, build scripts, and proc-macros.

Thanks, I think that sounds reasonable.
I will add that to the classification list in PR description with this as well.

@ranger-ross ranger-ross force-pushed the target-build-dir branch 3 times, most recently from a68a5eb to e49bf62 Compare February 5, 2025 14:52
tests/testsuite/build_dir.rs Outdated Show resolved Hide resolved
name.to_string()
};

assert_eq!(path.join(executable).exists(), exists);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you emit the path in the assert message?

}

#[cargo_test]
fn verify_build_dir_is_disabled_by_feature_flag() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add this test to the first commit?

Comment on lines 160 to 289
#[cargo_test]
fn verify_build_dir_is_disabled_by_feature_flag() {
let p = project()
.file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
.file(
".cargo/config.toml",
r#"
[build]
build-dir = "build"
"#,
)
.build();

p.cargo("build")
.masquerade_as_nightly_cargo(&[])
.enable_mac_dsym()
.run();

assert_build_dir(p.root().join("target"), "debug", true);
assert_executeable_exists(p.root().join("target/debug"), "foo", true);
assert!(!p.root().join("build").exists());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like you are taking the approach "Ignore the feature that is used without a gate " (see https://doc.rust-lang.org/nightly/nightly-rustc/cargo/core/features/index.html)

Could you speak to that (and update the PR description with that)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I updated the PR description with a feature gating section

Comment on lines 647 to 654
if let Some(val) = &self.build_config()?.build_dir {
let path = val.resolve_path(self);
Copy link
Contributor

Choose a reason for hiding this comment

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

Compared to pub fn target_dir(),

  • We don't have self.build_dir which is fine as we don't have a CLI flag
  • We don't check CARGO_BUILD_DIR env variable as a shortcut for CARGO_BUIILD_BUILD_DIR. Should we have this shortcut?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't check CARGO_BUILD_DIR env variable as a shortcut for CARGO_BUILD_BUILD_DIR. Should we have this shortcut?

I am in favor of adding this as CARGO_BUILD_BUILD_DIR is a bit rough on the eyes.
I can see this being useful when modifying the .cargo/config.toml is not possible or inconvenient.
ie. Its much easier to set an env var than update a toml file in CI bash script.

Comment on lines +659 to +669
} else {
// For now, fallback to the previous implementation.
// This will change in the future.
return self.target_dir();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we handle this fallback here or in Workspace::build_dir?

(I can see both ways)

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like it is in the follow up commit, duplicating the fallback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I added the fallback in both places "just in case"

Comment on lines 1859 to 1860
fn target_root(build_runner: &BuildRunner<'_, '_>) -> PathBuf {
build_runner.bcx.ws.target_dir().into_path_unlocked()
build_runner.bcx.ws.build_dir().into_path_unlocked()
Copy link
Contributor

Choose a reason for hiding this comment

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

Should target_root be renamed?

Comment on lines 169 to 171
.build_dir()
.open_rw_exclusive_create(
FUTURE_INCOMPAT_FILE,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing this discussed in the PR as intermediate vs final.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I reverted the change and added the following to the "final artifacts" category

@@ -297,7 +297,7 @@ fn rustc(
let exec = exec.clone();

let root_output = build_runner.files().host_dest().to_path_buf();
let target_dir = build_runner.bcx.ws.target_dir().into_path_unlocked();
let target_dir = build_runner.bcx.ws.build_dir().into_path_unlocked();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we rename this variable?

Comment on lines 26 to 27
assert_build_dir(p.root().join("build"), "debug", true);
assert_executeable_exists(p.root().join("build/debug"), "foo", true);
assert!(!p.root().join("target").exists());
assert_build_dir(p.root().join("target"), "debug", false);
Copy link
Contributor

Choose a reason for hiding this comment

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

In reading these tests, the intent of the assertion isn't too clear (asserting that the target dir is a build dir? what does false mean?). I think it'd be better to have two separate functions. If they truly must share an implementation, then they can just be wrappers on top of that.

@ranger-ross ranger-ross force-pushed the target-build-dir branch 2 times, most recently from ec7d7ba to 2af0c91 Compare February 9, 2025 07:27
These ares are in preparation to split target-dir into artifact-dir and build-dir
This is in preparation for splitting the intermediate build artifacts
from the `target` directory.
This commit adds a `build_dir` option to the `build` table in
`config.toml` and adds the equivalent field to `Workspace` and `GlobalContext`.
This commits implements the seperation of the intermidate artifact
directory (called "build directory") from the target directory. (see rust-lang#14125)
@ranger-ross
Copy link
Contributor Author

ranger-ross commented Feb 9, 2025

I think this PR is now complete to the point that I will mark this PR as ready to review.

Since the last review I have:

  • Added a shortcut for CARGO_BUILD_DIR
  • Updated the docs in unstable.md
  • Cleaned up/fixed the tests and added a few more different crate types
  • Updated the PR description with a feature gating strategy

r? @epage

@rustbot rustbot assigned epage and unassigned ehuss Feb 9, 2025
@ranger-ross ranger-ross marked this pull request as ready for review February 9, 2025 08:35
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-configuration Area: cargo config files and env vars A-documenting-cargo-itself Area: Cargo's documentation A-filesystem Area: issues with filesystems A-future-incompat Area: future incompatible reporting A-layout Area: target output directory layout, naming, and organization A-rebuild-detection Area: rebuild detection and fingerprinting A-unstable Area: nightly unstable support A-workspaces Area: workspaces Command-clean Command-package 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.

5 participants