Skip to content

feat(build-dir): Added the Cargo version to the inputs of workspace-path-hash #15340

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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
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
11 changes: 10 additions & 1 deletion src/cargo/util/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,16 @@ impl GlobalContext {
.to_string(),
),
("{workspace-path-hash}", {
let hash = crate::util::hex::short_hash(&workspace_manifest_path);
// We include the version in the hash to prevent external tools from relying on
// our hash implmentation. Note that we explictly do not include the prerelease
// and build parts of the semver to avoid a new hash for every nightly version.
let version = crate::version::version().semver();
let hash = crate::util::hex::short_hash(&(
version.major,
version.minor,
version.patch,
workspace_manifest_path,
));
format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
}),
];
Expand Down
61 changes: 39 additions & 22 deletions src/cargo/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ pub struct VersionInfo {
pub description: Option<String>,
}

impl VersionInfo {
/// The Cargo version as a [`semver::Version`].
pub fn semver(&self) -> semver::Version {
semver::Version::parse(&self.version).expect("Cargo version was not a valid semver version")
}
}

impl fmt::Display for VersionInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.version)?;
Expand All @@ -58,28 +65,30 @@ pub fn version() -> VersionInfo {
};
}

// This is the version set in bootstrap, which we use to match rustc.
let version = option_env_str!("CFG_RELEASE").unwrap_or_else(|| {
// If cargo is not being built by bootstrap, then we just use the
// version from cargo's own `Cargo.toml`.
//
// There are two versions at play here:
// - version of cargo-the-binary, which you see when you type `cargo --version`
// - version of cargo-the-library, which you download from crates.io for use
// in your packages.
//
// The library is permanently unstable, so it always has a 0 major
// version. However, the CLI now reports a stable 1.x version
// (starting in 1.26) which stays in sync with rustc's version.
//
// Coincidentally, the minor version for cargo-the-library is always
// +1 of rustc's minor version (that is, `rustc 1.11.0` corresponds to
// `cargo `0.12.0`). The versions always get bumped in lockstep, so
// this should continue to hold.
let minor = env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap() - 1;
let patch = env!("CARGO_PKG_VERSION_PATCH").parse::<u8>().unwrap();
format!("1.{}.{}", minor, patch)
});
let version = version_for_testing()
// This is the version set in bootstrap, which we use to match rustc.
.or_else(|| option_env_str!("CFG_RELEASE"))
.unwrap_or_else(|| {
// If cargo is not being built by bootstrap, then we just use the
// version from cargo's own `Cargo.toml`.
//
// There are two versions at play here:
// - version of cargo-the-binary, which you see when you type `cargo --version`
// - version of cargo-the-library, which you download from crates.io for use
// in your packages.
//
// The library is permanently unstable, so it always has a 0 major
// version. However, the CLI now reports a stable 1.x version
// (starting in 1.26) which stays in sync with rustc's version.
//
// Coincidentally, the minor version for cargo-the-library is always
// +1 of rustc's minor version (that is, `rustc 1.11.0` corresponds to
// `cargo `0.12.0`). The versions always get bumped in lockstep, so
// this should continue to hold.
let minor = env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap() - 1;
let patch = env!("CARGO_PKG_VERSION_PATCH").parse::<u8>().unwrap();
format!("1.{}.{}", minor, patch)
});

let release_channel = option_env_str!("CFG_RELEASE_CHANNEL");
let commit_info = option_env_str!("CARGO_COMMIT_HASH").map(|commit_hash| CommitInfo {
Expand All @@ -96,3 +105,11 @@ pub fn version() -> VersionInfo {
description,
}
}

/// Provides a way for tests to inject an abitrary version for testing purposes.
///
// __CARGO_TEST_CARGO_VERSION should not be relied on outside of tests.
#[allow(clippy::disallowed_methods)]
fn version_for_testing() -> Option<String> {
std::env::var("__CARGO_TEST_CARGO_VERSION").ok()
}
61 changes: 60 additions & 1 deletion tests/testsuite/build_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ fn template_cargo_cache_home() {
}

#[cargo_test]
fn template_workspace_manfiest_path_hash() {
fn template_workspace_path_hash() {
let p = project()
.file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
.file(
Expand Down Expand Up @@ -609,6 +609,65 @@ fn template_workspace_manfiest_path_hash() {
assert_exists(&p.root().join(&format!("target-dir/debug/foo{EXE_SUFFIX}")));
}

#[cargo_test]
fn template_workspace_path_hash_should_change_between_cargo_versions() {
let p = project()
.file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "1.0.0"
authors = []
edition = "2015"
"#,
)
.file(
".cargo/config.toml",
r#"
[build]
build-dir = "foo/{workspace-path-hash}/build-dir"
target-dir = "target-dir"
"#,
)
.build();

p.cargo("build -Z build-dir")
.env("__CARGO_TEST_CARGO_VERSION", "1.0.0")
.masquerade_as_nightly_cargo(&["build-dir"])
.enable_mac_dsym()
.run();

let foo_dir = p.root().join("foo");
assert_exists(&foo_dir);
let hash_dir = parse_workspace_manifest_path_hash(&foo_dir);

let first_run_build_dir = hash_dir.as_path().join("build-dir");
assert_build_dir_layout(first_run_build_dir.clone(), "debug");

// Remove the build-dir since we already know the hash and it makes
// finding the directory in the next run simpler.
foo_dir.as_path().rm_rf();

// Run Cargo build again with a different Cargo version
p.cargo("build -Z build-dir")
.env("__CARGO_TEST_CARGO_VERSION", "1.1.0")
.masquerade_as_nightly_cargo(&["build-dir"])
.enable_mac_dsym()
.run();

let hash_dir = parse_workspace_manifest_path_hash(&foo_dir);
let second_run_build_dir = hash_dir.as_path().join("build-dir");
assert_build_dir_layout(second_run_build_dir.clone(), "debug");

// Finally check that the build-dir is in different location between both Cargo versions
assert_ne!(
first_run_build_dir, second_run_build_dir,
"The workspace path hash generated between 2 Cargo versions matched when it should not"
);
}

fn parse_workspace_manifest_path_hash(hash_dir: &PathBuf) -> PathBuf {
// Since the hash will change between test runs simply find the first directories and assume
// that is the hash dir. The format is a 2 char directory followed by the remaining hash in the
Expand Down