Skip to content

Commit 468240a

Browse files
committed
Add support for CARGO_TARGET_DIR_PREFIX
This change adds support for a new environment variable, CARGO_TARGET_DIR_PREFIX, to cargo. This variable, when set, is treated as a prefix to the target directory. Note that support for the functionality behind this variable is not trivial to implement with the current design. In particular, we wanted to stick as close to the existing CARGO_TARGET_DIR logic. However, the Config in which it is implemented really does not know anything about the directory of the particular crate we concerned with. As a quick work around to this problem, we just pass in the path to the Cargo.toml from the "upper layer". That works, but ultimately it would be better to make the other layer handle the CARGO_TARGET_DIR_PREFIX logic. This change addresses rust-lang#5544. TODO: Definitely not finished. This patch needs more tests and may need additional config.toml support (?). TODO: There is also the potential for a permission related problems. E.g., when user root compiles something below /tmp/ and then user nobody tries to do the same the resulting directory ${CARGO_TARGET_DIR_PREFIX}/tmp/ may be owned by root, causing the build for nobody to fail with a permission denied error.
1 parent 4ed5d13 commit 468240a

File tree

6 files changed

+94
-8
lines changed

6 files changed

+94
-8
lines changed

src/cargo/core/workspace.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ impl<'cfg> Workspace<'cfg> {
144144
/// before returning it, so `Ok` is only returned for valid workspaces.
145145
pub fn new(manifest_path: &Path, config: &'cfg Config) -> CargoResult<Workspace<'cfg>> {
146146
let mut ws = Workspace::new_default(manifest_path.to_path_buf(), config);
147-
ws.target_dir = config.target_dir()?;
148147

149148
if manifest_path.is_relative() {
150149
bail!(
@@ -155,6 +154,12 @@ impl<'cfg> Workspace<'cfg> {
155154
ws.root_manifest = ws.find_root(manifest_path)?;
156155
}
157156

157+
if let Some(ref root_manifest) = ws.root_manifest {
158+
ws.target_dir = config.target_dir(root_manifest)?;
159+
} else {
160+
ws.target_dir = config.target_dir(manifest_path)?;
161+
}
162+
158163
ws.custom_metadata = ws
159164
.load_workspace_config()?
160165
.and_then(|cfg| cfg.custom_metadata);
@@ -194,7 +199,11 @@ impl<'cfg> Workspace<'cfg> {
194199
) -> CargoResult<Workspace<'cfg>> {
195200
let mut ws = Workspace::new_default(current_manifest, config);
196201
ws.root_manifest = Some(root_path.join("Cargo.toml"));
197-
ws.target_dir = config.target_dir()?;
202+
if let Some(ref root_manifest) = ws.root_manifest {
203+
ws.target_dir = config.target_dir(root_manifest)?;
204+
} else {
205+
ws.target_dir = config.target_dir(&ws.current_manifest)?;
206+
}
198207
ws.packages
199208
.packages
200209
.insert(root_path, MaybePackage::Virtual(manifest));
@@ -225,13 +234,13 @@ impl<'cfg> Workspace<'cfg> {
225234
ws.require_optional_deps = require_optional_deps;
226235
let key = ws.current_manifest.parent().unwrap();
227236
let id = package.package_id();
228-
let package = MaybePackage::Package(package);
229-
ws.packages.packages.insert(key.to_path_buf(), package);
230237
ws.target_dir = if let Some(dir) = target_dir {
231238
Some(dir)
232239
} else {
233-
ws.config.target_dir()?
240+
ws.config.target_dir(package.manifest_path())?
234241
};
242+
let package = MaybePackage::Package(package);
243+
ws.packages.packages.insert(key.to_path_buf(), package);
235244
ws.members.push(ws.current_manifest.clone());
236245
ws.member_ids.insert(id);
237246
ws.default_members.push(ws.current_manifest.clone());

src/cargo/ops/cargo_install.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,13 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
275275
fn install_one(mut self) -> CargoResult<()> {
276276
self.config.shell().status("Installing", &self.pkg)?;
277277

278+
let manifest_path = self.pkg.manifest_path().to_path_buf();
278279
let dst = self.root.join("bin").into_path_unlocked();
279280

280281
let mut td_opt = None;
281282
let mut needs_cleanup = false;
282283
if !self.source_id.is_path() {
283-
let target_dir = if let Some(dir) = self.config.target_dir()? {
284+
let target_dir = if let Some(dir) = self.config.target_dir(manifest_path)? {
284285
dir
285286
} else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() {
286287
let p = td.path().to_owned();

src/cargo/util/config/mod.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ impl Config {
484484
/// Returns `None` if the user has not chosen an explicit directory.
485485
///
486486
/// Callers should prefer `Workspace::target_dir` instead.
487-
pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
487+
pub fn target_dir(&self, manifest: impl Into<PathBuf>) -> CargoResult<Option<Filesystem>> {
488488
if let Some(dir) = &self.target_dir {
489489
Ok(Some(dir.clone()))
490490
} else if let Some(dir) = self.env.get("CARGO_TARGET_DIR") {
@@ -497,6 +497,21 @@ impl Config {
497497
}
498498

499499
Ok(Some(Filesystem::new(self.cwd.join(dir))))
500+
} else if let Some(dir) = env::var_os("CARGO_TARGET_DIR_PREFIX") {
501+
let prefix = Path::new(&dir);
502+
if !prefix.is_absolute() {
503+
bail!("CARGO_TARGET_DIR_PREFIX must describe an absolute path");
504+
}
505+
let mut manifest = manifest.into();
506+
let result = manifest.pop();
507+
assert!(result);
508+
509+
match manifest.strip_prefix("/") {
510+
Ok(dir) => Ok(Some(Filesystem::new(prefix.join(&dir).join("target")))),
511+
// FIXME: This logic is probably not safe on Windows. Not sure how
512+
// to make a path relative there.
513+
Err(_) => bail!("Current directory must be an absolute path"),
514+
}
500515
} else if let Some(val) = &self.build_config()?.target_dir {
501516
let path = val.resolve_path(self);
502517

src/doc/src/reference/environment-variables.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ system:
1515
location of this directory. Once a crate is cached it is not removed by the
1616
clean command.
1717
For more details refer to the [guide](../guide/cargo-home.md).
18+
* `CARGO_TARGET_DIR_PREFIX` — Prefix to the location where to place all
19+
generated artifacts. The current working directory will be appended to this
20+
prefix to form the final path for generated artifacts. Note that
21+
`CARGO_TARGET_DIR`, if set, takes precedence over this variable.
1822
* `CARGO_TARGET_DIR` — Location of where to place all generated artifacts,
1923
relative to the current working directory. See [`build.target-dir`] to set
2024
via config.

tests/testsuite/build.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3190,6 +3190,63 @@ fn panic_abort_compiles_with_panic_abort() {
31903190
.run();
31913191
}
31923192

3193+
#[cargo_test]
3194+
fn custom_target_dir_prefix() {
3195+
fn test(cwd: &str) {
3196+
let tmpdir = tempfile::Builder::new()
3197+
.tempdir()
3198+
.unwrap()
3199+
.path()
3200+
.to_path_buf();
3201+
3202+
let p = project()
3203+
.file(
3204+
"Cargo.toml",
3205+
r#"
3206+
[package]
3207+
name = "foo"
3208+
version = "0.0.1"
3209+
authors = []
3210+
"#,
3211+
)
3212+
.file("src/main.rs", "fn main() {}")
3213+
.build();
3214+
3215+
let root = p.root();
3216+
let root_suffix = root.strip_prefix("/").unwrap();
3217+
let exe_name = format!("foo{}", env::consts::EXE_SUFFIX);
3218+
3219+
p.cargo("build")
3220+
.env("CARGO_TARGET_DIR_PREFIX", tmpdir.clone())
3221+
.cwd(p.root().join(cwd))
3222+
.run();
3223+
3224+
assert!(
3225+
tmpdir
3226+
.clone()
3227+
.join(root_suffix)
3228+
.join("target/debug")
3229+
.join(&exe_name)
3230+
.is_file()
3231+
);
3232+
assert!(!&p.root().join("target/debug").join(&exe_name).is_file());
3233+
3234+
p.cargo("build").run();
3235+
assert!(
3236+
tmpdir
3237+
.clone()
3238+
.join(root_suffix)
3239+
.join("target/debug")
3240+
.join(&exe_name)
3241+
.is_file()
3242+
);
3243+
assert!(&p.root().join("target/debug").join(&exe_name).is_file())
3244+
}
3245+
3246+
test(".");
3247+
test("src");
3248+
}
3249+
31933250
#[cargo_test]
31943251
fn compiler_json_error_format() {
31953252
let p = project()

tests/testsuite/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,7 @@ target-dir = ''
14481448
let config = new_config();
14491449

14501450
assert_error(
1451-
config.target_dir().unwrap_err(),
1451+
config.target_dir("").unwrap_err(),
14521452
"the target directory is set to an empty string in [..]/.cargo/config",
14531453
);
14541454
}

0 commit comments

Comments
 (0)