Skip to content

Commit

Permalink
Allow specifying a custom output directory
Browse files Browse the repository at this point in the history
This commit adds support to allow specifying a custom output directory to Cargo.
First, the `build.target-dir` configuration key is checked, and failing that the
`CARGO_TARGET_DIR` environment variable is checked, and failing that the root
package's directory joined with the directory name "target" is used.

There are a few caveats to switching target directories, however:

* If the target directory is in the current source tree, and the folder name is
  not called "target", then Cargo may walk the output directory when determining
  whether a tree is fresh.
* If the target directory is not called "target", then Cargo may look inside it
  currently for `Cargo.toml` files to learn about local packages.
* Concurrent usage of Cargo will still result in badness (rust-lang#354), and this is now
  exascerbated because many Cargo projects can share the same output directory.
* The top-level crate is not cached for future compilations, so if a crate is
  built into directory `foo` and then that crate is later used as a dependency,
  it will be recompiled.

The naming limitations can be overcome in time, but for now it greatly
simplifies the crawling routines and shouldn't have much of a negative impact
other than some Cargo runtimes (which can in turn be negated by following the
"target" name convention).

Closes rust-lang#482
  • Loading branch information
alexcrichton committed May 28, 2015
1 parent 655c40b commit 80a198f
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 43 deletions.
15 changes: 0 additions & 15 deletions src/cargo/core/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ use util::{CargoResult, human};
pub struct Manifest {
summary: Summary,
targets: Vec<Target>,
target_dir: PathBuf,
doc_dir: PathBuf,
links: Option<String>,
warnings: Vec<String>,
exclude: Vec<String>,
Expand Down Expand Up @@ -51,8 +49,6 @@ pub struct SerializedManifest {
version: String,
dependencies: Vec<SerializedDependency>,
targets: Vec<Target>,
target_dir: String,
doc_dir: String,
}

impl Encodable for Manifest {
Expand All @@ -64,8 +60,6 @@ impl Encodable for Manifest {
SerializedDependency::from_dependency(d)
}).collect(),
targets: self.targets.clone(),
target_dir: self.target_dir.display().to_string(),
doc_dir: self.doc_dir.display().to_string(),
}.encode(s)
}
}
Expand Down Expand Up @@ -181,7 +175,6 @@ impl Encodable for Target {

impl Manifest {
pub fn new(summary: Summary, targets: Vec<Target>,
target_dir: PathBuf, doc_dir: PathBuf,
exclude: Vec<String>,
include: Vec<String>,
links: Option<String>,
Expand All @@ -190,8 +183,6 @@ impl Manifest {
Manifest {
summary: summary,
targets: targets,
target_dir: target_dir,
doc_dir: doc_dir,
warnings: Vec::new(),
exclude: exclude,
include: include,
Expand All @@ -202,14 +193,12 @@ impl Manifest {
}

pub fn dependencies(&self) -> &[Dependency] { self.summary.dependencies() }
pub fn doc_dir(&self) -> &Path { &self.doc_dir }
pub fn exclude(&self) -> &[String] { &self.exclude }
pub fn include(&self) -> &[String] { &self.include }
pub fn metadata(&self) -> &ManifestMetadata { &self.metadata }
pub fn name(&self) -> &str { self.package_id().name() }
pub fn package_id(&self) -> &PackageId { self.summary.package_id() }
pub fn summary(&self) -> &Summary { &self.summary }
pub fn target_dir(&self) -> &Path { &self.target_dir }
pub fn targets(&self) -> &[Target] { &self.targets }
pub fn version(&self) -> &Version { self.package_id().version() }
pub fn warnings(&self) -> &[String] { &self.warnings }
Expand All @@ -225,10 +214,6 @@ impl Manifest {
pub fn set_summary(&mut self, summary: Summary) {
self.summary = summary;
}

pub fn set_target_dir(&mut self, target_dir: PathBuf) {
self.target_dir = target_dir;
}
}

impl Target {
Expand Down
5 changes: 0 additions & 5 deletions src/cargo/core/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,9 @@ impl Package {
pub fn package_id(&self) -> &PackageId { self.manifest.package_id() }
pub fn root(&self) -> &Path { self.manifest_path.parent().unwrap() }
pub fn summary(&self) -> &Summary { self.manifest.summary() }
pub fn target_dir(&self) -> &Path { self.manifest.target_dir() }
pub fn targets(&self) -> &[Target] { self.manifest().targets() }
pub fn version(&self) -> &Version { self.package_id().version() }

pub fn absolute_target_dir(&self) -> PathBuf {
self.root().join(self.target_dir())
}

pub fn has_custom_build(&self) -> bool {
self.targets().iter().any(|t| t.is_custom_build())
}
Expand Down
8 changes: 4 additions & 4 deletions src/cargo/ops/cargo_clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ pub fn clean(manifest_path: &Path, opts: &CleanOptions) -> CargoResult<()> {
opts.config));
try!(src.update());
let root = try!(src.root_package());
let manifest = root.manifest();
let target_dir = opts.config.target_dir(&root);

// If we have a spec, then we need to delete some package,s otherwise, just
// remove the whole target directory and be done with it!
let spec = match opts.spec {
Some(spec) => spec,
None => return rm_rf(manifest.target_dir()),
None => return rm_rf(&target_dir),
};

// Load the lockfile (if one's available), and resolve spec to a pkgid
Expand All @@ -52,14 +52,14 @@ pub fn clean(manifest_path: &Path, opts: &CleanOptions) -> CargoResult<()> {
let pkgs = PackageSet::new(&[]);
let profiles = Profiles::default();
let cx = try!(Context::new(&resolve, &srcs, &pkgs, opts.config,
Layout::at(root.absolute_target_dir()),
Layout::at(target_dir),
None, &pkg, BuildConfig::default(),
&profiles));

// And finally, clean everything out!
for target in pkg.targets().iter() {
// TODO: `cargo clean --release`
let layout = Layout::new(&root, opts.target, "debug");
let layout = Layout::new(opts.config, &root, opts.target, "debug");
try!(rm_rf(&layout.fingerprint(&pkg)));
let profiles = [Profile::default_dev(), Profile::default_test()];
for profile in profiles.iter() {
Expand Down
4 changes: 2 additions & 2 deletions src/cargo/ops/cargo_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ pub fn doc(manifest_path: &Path,
}
};

let path = package.absolute_target_dir().join("doc").join(&name)
.join("index.html");
let target_dir = options.compile_opts.config.target_dir(&package);
let path = target_dir.join("doc").join(&name).join("index.html");
if fs::metadata(&path).is_ok() {
open_docs(&path);
}
Expand Down
4 changes: 2 additions & 2 deletions src/cargo/ops/cargo_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ pub fn package(manifest_path: &Path,
}

let filename = format!("package/{}-{}.crate", pkg.name(), pkg.version());
let dst = pkg.absolute_target_dir().join(&filename);
let target_dir = config.target_dir(&pkg);
let dst = target_dir.join(&filename);
if fs::metadata(&dst).is_ok() { return Ok(Some(dst)) }

let mut bomb = Bomb { path: Some(dst.clone()) };
Expand Down Expand Up @@ -174,7 +175,6 @@ fn run_verify(config: &Config, pkg: &Package, tar: &Path)
});
let mut new_manifest = pkg.manifest().clone();
new_manifest.set_summary(new_summary.override_id(new_pkgid));
new_manifest.set_target_dir(dst.join("target"));
let new_pkg = Package::new(new_manifest, &manifest_path, &new_src);

// Now that we've rewritten all our path dependencies, compile it!
Expand Down
6 changes: 4 additions & 2 deletions src/cargo/ops/cargo_rustc/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use std::io;
use std::path::{PathBuf, Path};

use core::Package;
use util::Config;
use util::hex::short_hash;

pub struct Layout {
Expand All @@ -67,8 +68,9 @@ pub struct LayoutProxy<'a> {
}

impl Layout {
pub fn new(pkg: &Package, triple: Option<&str>, dest: &str) -> Layout {
let mut path = pkg.absolute_target_dir();
pub fn new(config: &Config, pkg: &Package, triple: Option<&str>,
dest: &str) -> Layout {
let mut path = config.target_dir(pkg);
// Flexible target specifications often point at filenames, so interpret
// the target triple as a Path and then just use the file stem as the
// component for the directory name.
Expand Down
9 changes: 4 additions & 5 deletions src/cargo/ops/cargo_rustc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ pub fn compile_targets<'a, 'cfg: 'a>(targets: &[(&'a Target, &'a Profile)],
} else {
deps.iter().find(|p| p.package_id() == resolve.root()).unwrap()
};
let host_layout = Layout::new(root, None, &dest);
let host_layout = Layout::new(config, root, None, &dest);
let target_layout = build_config.requested_target.as_ref().map(|target| {
layout::Layout::new(root, Some(&target), &dest)
layout::Layout::new(config, root, Some(&target), &dest)
});

let mut cx = try!(Context::new(resolve, sources, deps, config,
Expand Down Expand Up @@ -535,20 +535,19 @@ fn prepare_rustc(package: &Package, target: &Target, profile: &Profile,
fn rustdoc(package: &Package, target: &Target, profile: &Profile,
cx: &mut Context) -> CargoResult<Work> {
let kind = Kind::Target;
let mut doc_dir = cx.get_package(cx.resolve.root()).absolute_target_dir();
let mut rustdoc = try!(process(CommandType::Rustdoc, package, target, cx));
rustdoc.arg(&root_path(cx, package, target))
.cwd(cx.config.cwd())
.arg("--crate-name").arg(&target.crate_name());

let mut doc_dir = cx.config.target_dir(cx.get_package(cx.resolve.root()));
if let Some(target) = cx.requested_target() {
rustdoc.arg("--target").arg(target);
doc_dir.push(target);
}

doc_dir.push("doc");

rustdoc.arg("-o").arg(&doc_dir);
rustdoc.arg("-o").arg(doc_dir);

match cx.resolve.features(package.package_id()) {
Some(features) => {
Expand Down
44 changes: 38 additions & 6 deletions src/cargo/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};

use rustc_serialize::{Encodable,Encoder};
use toml;
use core::MultiShell;
use core::{MultiShell, Package};
use ops;
use util::{CargoResult, ChainError, internal, human};

Expand All @@ -30,6 +30,7 @@ pub struct Config {
cwd: PathBuf,
rustc: PathBuf,
rustdoc: PathBuf,
target_dir: Option<PathBuf>,
}

impl Config {
Expand All @@ -51,13 +52,12 @@ impl Config {
values_loaded: Cell::new(false),
rustc: PathBuf::from("rustc"),
rustdoc: PathBuf::from("rustdoc"),
target_dir: None,
};

cfg.rustc = try!(cfg.get_tool("rustc"));
cfg.rustdoc = try!(cfg.get_tool("rustdoc"));
let (rustc_version, rustc_host) = try!(ops::rustc_version(cfg.rustc()));
cfg.rustc_version = rustc_version;
cfg.rustc_host = rustc_host;
try!(cfg.scrape_tool_config());
try!(cfg.scrape_rustc_version());
try!(cfg.scrape_target_dir_config());

Ok(cfg)
}
Expand Down Expand Up @@ -108,6 +108,12 @@ impl Config {

pub fn cwd(&self) -> &Path { &self.cwd }

pub fn target_dir(&self, pkg: &Package) -> PathBuf {
self.target_dir.clone().unwrap_or_else(|| {
pkg.root().join("target")
})
}

pub fn get(&self, key: &str) -> CargoResult<Option<ConfigValue>> {
let vals = try!(self.values());
let mut parts = key.split('.').enumerate();
Expand Down Expand Up @@ -206,6 +212,32 @@ impl Config {
Ok(())
}

fn scrape_tool_config(&mut self) -> CargoResult<()> {
self.rustc = try!(self.get_tool("rustc"));
self.rustdoc = try!(self.get_tool("rustdoc"));
Ok(())
}

fn scrape_rustc_version(&mut self) -> CargoResult<()> {
let (rustc_version, rustc_host) = try!(ops::rustc_version(&self.rustc));
self.rustc_version = rustc_version;
self.rustc_host = rustc_host;
Ok(())
}

fn scrape_target_dir_config(&mut self) -> CargoResult<()> {
if let Some((dir, dir2)) = try!(self.get_string("build.target-dir")) {
let mut path = PathBuf::from(dir2);
path.pop();
path.pop();
path.push(dir);
self.target_dir = Some(path);
} else if let Some(dir) = env::var_os("CARGO_TARGET_DIR") {
self.target_dir = Some(self.cwd.join(dir));
}
Ok(())
}

fn get_tool(&self, tool: &str) -> CargoResult<PathBuf> {
let var = format!("build.{}", tool);
if let Some((tool, path)) = try!(self.get_string(&var)) {
Expand Down
2 changes: 0 additions & 2 deletions src/cargo/util/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,6 @@ impl TomlManifest {
let profiles = build_profiles(&self.profile);
let mut manifest = Manifest::new(summary,
targets,
layout.root.join("target"),
layout.root.join("doc"),
exclude,
include,
project.links.clone(),
Expand Down
42 changes: 42 additions & 0 deletions tests/test_cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1821,3 +1821,45 @@ test!(ignore_dotfile {
assert_that(p.cargo("build"),
execs().with_status(0));
});

test!(custom_target_dir {
let p = project("foo")
.file("Cargo.toml", r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/main.rs", "fn main() {}");
p.build();

let exe_name = format!("foo{}", env::consts::EXE_SUFFIX);

assert_that(p.cargo("build").env("CARGO_TARGET_DIR", "foo/target"),
execs().with_status(0));
assert_that(&p.root().join("foo/target/debug").join(&exe_name),
existing_file());
assert_that(&p.root().join("target/debug").join(&exe_name),
is_not(existing_file()));

assert_that(p.cargo("build"),
execs().with_status(0));
assert_that(&p.root().join("foo/target/debug").join(&exe_name),
existing_file());
assert_that(&p.root().join("target/debug").join(&exe_name),
existing_file());

fs::create_dir(p.root().join(".cargo")).unwrap();
File::create(p.root().join(".cargo/config")).unwrap().write_all(br#"
[build]
target-dir = "bar/target"
"#).unwrap();
assert_that(p.cargo("build").env("CARGO_TARGET_DIR", "foo/target"),
execs().with_status(0));
assert_that(&p.root().join("bar/target/debug").join(&exe_name),
existing_file());
assert_that(&p.root().join("foo/target/debug").join(&exe_name),
existing_file());
assert_that(&p.root().join("target/debug").join(&exe_name),
existing_file());
});

0 comments on commit 80a198f

Please sign in to comment.