Skip to content

Commit 1890d16

Browse files
authored
refactor: Define cargo script's target-dir using build-dir templating (#16073)
### What does this PR try to resolve? `build-dir`s templating was modeled off of the `target-dir` for cargo scripts but the root dir and hashing are currently duplicated. This consolidates the logic by defining the cargo script `target-dir` as `{cargo-cache-home}/target/{workspace-path-hash}`. This also serves as preparation for splitting cargo scripts `build-dir` and `target-dir`. ### How to test and review this PR?
2 parents c169b66 + 5b5455c commit 1890d16

File tree

6 files changed

+106
-69
lines changed

6 files changed

+106
-69
lines changed

src/cargo/core/workspace.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ impl<'gctx> Workspace<'gctx> {
215215
/// before returning it, so `Ok` is only returned for valid workspaces.
216216
pub fn new(manifest_path: &Path, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
217217
let mut ws = Workspace::new_default(manifest_path.to_path_buf(), gctx);
218-
ws.target_dir = gctx.target_dir()?;
219218

220219
if manifest_path.is_relative() {
221220
bail!(
@@ -226,11 +225,8 @@ impl<'gctx> Workspace<'gctx> {
226225
ws.root_manifest = ws.find_root(manifest_path)?;
227226
}
228227

229-
ws.build_dir = gctx.build_dir(
230-
ws.root_manifest
231-
.as_ref()
232-
.unwrap_or(&manifest_path.to_path_buf()),
233-
)?;
228+
ws.target_dir = gctx.target_dir()?;
229+
ws.build_dir = gctx.build_dir(ws.root_manifest())?;
234230

235231
ws.custom_metadata = ws
236232
.load_workspace_config()?
@@ -448,13 +444,14 @@ impl<'gctx> Workspace<'gctx> {
448444

449445
fn default_target_dir(&self) -> Filesystem {
450446
if self.root_maybe().is_embedded() {
451-
let hash = crate::util::hex::short_hash(&self.root_manifest().to_string_lossy());
452-
let mut rel_path = PathBuf::new();
453-
rel_path.push("target");
454-
rel_path.push(&hash[0..2]);
455-
rel_path.push(&hash[2..]);
456-
457-
self.gctx().home().join(rel_path)
447+
let default = ConfigRelativePath::new(
448+
"{cargo-cache-home}/target/{workspace-path-hash}"
449+
.to_owned()
450+
.into(),
451+
);
452+
self.gctx()
453+
.custom_build_dir(&default, self.root_manifest())
454+
.expect("template is correct")
458455
} else {
459456
Filesystem::new(self.root().join("target"))
460457
}

src/cargo/util/context/de.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,18 +564,19 @@ impl<'de, 'gctx> de::MapAccess<'de> for ValueDeserializer<'gctx> {
564564
// ... otherwise we're deserializing the `definition` field, so we need
565565
// to figure out where the field we just deserialized was defined at.
566566
match self.definition() {
567+
Definition::BuiltIn => seed.deserialize(0.into_deserializer()),
567568
Definition::Path(path) => {
568-
seed.deserialize(Tuple2Deserializer(0i32, path.to_string_lossy()))
569+
seed.deserialize(Tuple2Deserializer(1i32, path.to_string_lossy()))
569570
}
570571
Definition::Environment(env) => {
571-
seed.deserialize(Tuple2Deserializer(1i32, env.as_str()))
572+
seed.deserialize(Tuple2Deserializer(2i32, env.as_str()))
572573
}
573574
Definition::Cli(path) => {
574575
let s = path
575576
.as_ref()
576577
.map(|p| p.to_string_lossy())
577578
.unwrap_or_default();
578-
seed.deserialize(Tuple2Deserializer(2i32, s))
579+
seed.deserialize(Tuple2Deserializer(3i32, s))
579580
}
580581
}
581582
}

src/cargo/util/context/mod.rs

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -653,42 +653,54 @@ impl GlobalContext {
653653

654654
/// The directory to use for intermediate build artifacts.
655655
///
656-
/// Falls back to the target directory if not specified.
656+
/// Callers should prefer [`Workspace::build_dir`] instead.
657+
pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
658+
let Some(val) = &self.build_config()?.build_dir else {
659+
return Ok(None);
660+
};
661+
self.custom_build_dir(val, workspace_manifest_path)
662+
.map(Some)
663+
}
664+
665+
/// The directory to use for intermediate build artifacts.
657666
///
658667
/// Callers should prefer [`Workspace::build_dir`] instead.
659-
pub fn build_dir(&self, workspace_manifest_path: &PathBuf) -> CargoResult<Option<Filesystem>> {
660-
if let Some(val) = &self.build_config()?.build_dir {
661-
let replacements = vec![
662-
(
663-
"{workspace-root}",
664-
workspace_manifest_path
665-
.parent()
666-
.unwrap()
667-
.to_str()
668-
.context("workspace root was not valid utf-8")?
669-
.to_string(),
670-
),
671-
(
672-
"{cargo-cache-home}",
673-
self.home()
674-
.as_path_unlocked()
675-
.to_str()
676-
.context("cargo home was not valid utf-8")?
677-
.to_string(),
678-
),
679-
("{workspace-path-hash}", {
680-
let real_path = std::fs::canonicalize(workspace_manifest_path)?;
681-
let hash = crate::util::hex::short_hash(&real_path);
682-
format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
683-
}),
684-
];
685-
686-
let template_variables = replacements
687-
.iter()
688-
.map(|(key, _)| key[1..key.len() - 1].to_string())
689-
.collect_vec();
668+
pub fn custom_build_dir(
669+
&self,
670+
val: &ConfigRelativePath,
671+
workspace_manifest_path: &Path,
672+
) -> CargoResult<Filesystem> {
673+
let replacements = [
674+
(
675+
"{workspace-root}",
676+
workspace_manifest_path
677+
.parent()
678+
.unwrap()
679+
.to_str()
680+
.context("workspace root was not valid utf-8")?
681+
.to_string(),
682+
),
683+
(
684+
"{cargo-cache-home}",
685+
self.home()
686+
.as_path_unlocked()
687+
.to_str()
688+
.context("cargo home was not valid utf-8")?
689+
.to_string(),
690+
),
691+
("{workspace-path-hash}", {
692+
let real_path = std::fs::canonicalize(workspace_manifest_path)?;
693+
let hash = crate::util::hex::short_hash(&real_path);
694+
format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
695+
}),
696+
];
697+
698+
let template_variables = replacements
699+
.iter()
700+
.map(|(key, _)| key[1..key.len() - 1].to_string())
701+
.collect_vec();
690702

691-
let path = val
703+
let path = val
692704
.resolve_templated_path(self, replacements)
693705
.map_err(|e| match e {
694706
path::ResolveTemplateError::UnexpectedVariable {
@@ -716,20 +728,15 @@ impl GlobalContext {
716728
}
717729
})?;
718730

719-
// Check if the target directory is set to an empty string in the config.toml file.
720-
if val.raw_value().is_empty() {
721-
bail!(
722-
"the build directory is set to an empty string in {}",
723-
val.value().definition
724-
)
725-
}
726-
727-
Ok(Some(Filesystem::new(path)))
728-
} else {
729-
// For now, fallback to the previous implementation.
730-
// This will change in the future.
731-
return self.target_dir();
731+
// Check if the target directory is set to an empty string in the config.toml file.
732+
if val.raw_value().is_empty() {
733+
bail!(
734+
"the build directory is set to an empty string in {}",
735+
val.value().definition
736+
)
732737
}
738+
739+
Ok(Filesystem::new(path))
733740
}
734741

735742
/// Get a configuration value by key.
@@ -1364,7 +1371,9 @@ impl GlobalContext {
13641371
let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
13651372
let abs_path = match def {
13661373
Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1367-
Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1374+
Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => {
1375+
self.cwd().join(&path)
1376+
}
13681377
};
13691378
(path.to_string(), abs_path, def.clone())
13701379
};

src/cargo/util/context/value.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD];
5555
/// Location where a config value is defined.
5656
#[derive(Clone, Debug, Eq)]
5757
pub enum Definition {
58+
BuiltIn,
5859
/// Defined in a `.cargo/config`, includes the path to the file.
5960
Path(PathBuf),
6061
/// Defined in an environment variable, includes the environment key.
@@ -90,7 +91,7 @@ impl Definition {
9091
pub fn root<'a>(&'a self, gctx: &'a GlobalContext) -> &'a Path {
9192
match self {
9293
Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().parent().unwrap(),
93-
Definition::Environment(_) | Definition::Cli(None) => gctx.cwd(),
94+
Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
9495
}
9596
}
9697

@@ -102,7 +103,10 @@ impl Definition {
102103
(self, other),
103104
(Definition::Cli(_), Definition::Environment(_))
104105
| (Definition::Cli(_), Definition::Path(_))
106+
| (Definition::Cli(_), Definition::BuiltIn)
105107
| (Definition::Environment(_), Definition::Path(_))
108+
| (Definition::Environment(_), Definition::BuiltIn)
109+
| (Definition::Path(_), Definition::BuiltIn)
106110
)
107111
}
108112
}
@@ -123,6 +127,16 @@ impl fmt::Display for Definition {
123127
Definition::Path(p) | Definition::Cli(Some(p)) => p.display().fmt(f),
124128
Definition::Environment(key) => write!(f, "environment variable `{}`", key),
125129
Definition::Cli(None) => write!(f, "--config cli option"),
130+
Definition::BuiltIn => write!(f, "default"),
131+
}
132+
}
133+
}
134+
135+
impl<T> From<T> for Value<T> {
136+
fn from(val: T) -> Self {
137+
Self {
138+
val,
139+
definition: Definition::BuiltIn,
126140
}
127141
}
128142
}
@@ -236,9 +250,10 @@ impl<'de> de::Deserialize<'de> for Definition {
236250
{
237251
let (discr, value) = <(u32, String)>::deserialize(deserializer)?;
238252
match discr {
239-
0 => Ok(Definition::Path(value.into())),
240-
1 => Ok(Definition::Environment(value)),
241-
2 => {
253+
0 => Ok(Definition::BuiltIn),
254+
1 => Ok(Definition::Path(value.into())),
255+
2 => Ok(Definition::Environment(value)),
256+
3 => {
242257
let path = (value.len() > 0).then_some(value.into());
243258
Ok(Definition::Cli(path))
244259
}

tests/testsuite/bad_config.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ fn invalid_global_config() {
159159
p.cargo("check -v")
160160
.with_status(101)
161161
.with_stderr_data(str![[r#"
162-
[ERROR] could not load Cargo configuration
162+
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
163+
164+
Caused by:
165+
could not load Cargo configuration
163166
164167
Caused by:
165168
could not parse TOML configuration in `[ROOT]/foo/.cargo/config.toml`

tests/testsuite/config.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1649,7 +1649,19 @@ target-dir = ''
16491649

16501650
#[cargo_test]
16511651
fn cargo_target_empty_env() {
1652-
let project = project().build();
1652+
let project = project()
1653+
.file(
1654+
"Cargo.toml",
1655+
r#"
1656+
[package]
1657+
name = "foo"
1658+
authors = []
1659+
version = "0.0.0"
1660+
build = "build.rs"
1661+
"#,
1662+
)
1663+
.file("src/lib.rs", "")
1664+
.build();
16531665

16541666
project.cargo("check")
16551667
.env("CARGO_TARGET_DIR", "")

0 commit comments

Comments
 (0)