Skip to content

Commit f082805

Browse files
committed
Auto merge of #5203 - matklad:out-dir, r=alexcrichton
cargo build --out-dir This is intended to fix #4875. Very much work in progress, just to figure out how exactly `--out-dir` should work :) The path of least resistance for implementing `out-dir` would be to just link everything from `target/debug` to the `out-dir`. However, the problem with this approach is that we link *too much* to the `target/debug`. For example, if you run `cargo build --bin foo`, you'll find not only the `foo` itself there, but also rlibs from the same workspace. I think it's rather important not to copy extra stuff to out-dir, so it is implemented as a completely new parameter, which is threaded through various config structs and applies *only* to the top-level units (i.e, to the stuff user explicitly requested to compile on the command line). I also plan to add platform specific tests here, to check that we get .pdb on windows and .dSYM on macs for various crate-types. Because, in general, a single target may produce multiple files, `out-dir` has to be a directory, you can't directly specify the output *file*. Note that artifacts are still generated to the `target` dir as well. Also cc @nirbheek, I hope that this might be useful for Meson integration, because `--out-dir` should be more straightforward to use than `--message-format=json`. The end result, for `cargo build --bin rustraytracer --out-dir out` on windows looks like this: ![image](https://user-images.githubusercontent.com/1711539/37605115-941c0b22-2ba3-11e8-9685-9756a10fdfac.png) Note how we have fewer files in the `out` :) r? @alexcrichton
2 parents 9bd7134 + c919118 commit f082805

File tree

11 files changed

+385
-38
lines changed

11 files changed

+385
-38
lines changed

src/bin/command_prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ pub trait ArgMatchesExt {
288288
message_format,
289289
target_rustdoc_args: None,
290290
target_rustc_args: None,
291+
export_dir: None,
291292
};
292293
Ok(opts)
293294
}

src/bin/commands/build.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub fn cli() -> App {
2727
.arg_release("Build artifacts in release mode, with optimizations")
2828
.arg_features()
2929
.arg_target_triple("Build for the target triple")
30+
.arg(opt("out-dir", "Copy final artifacts to this directory").value_name("PATH"))
3031
.arg_manifest_path()
3132
.arg_message_format()
3233
.after_help(
@@ -49,7 +50,13 @@ the --release flag will use the `release` profile instead.
4950

5051
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
5152
let ws = args.workspace(config)?;
52-
let compile_opts = args.compile_options(config, CompileMode::Build)?;
53+
let mut compile_opts = args.compile_options(config, CompileMode::Build)?;
54+
compile_opts.export_dir = args.value_of_path("out-dir", config);
55+
if compile_opts.export_dir.is_some() && !config.cli_unstable().out_dir {
56+
Err(format_err!(
57+
"`--out-dir` flag is unstable, pass `-Z out-dir` to enable it"
58+
))?;
59+
};
5360
ops::compile(&ws, &compile_opts)?;
5461
Ok(())
5562
}

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ pub struct CliUnstable {
286286
pub no_index_update: bool,
287287
pub avoid_dev_deps: bool,
288288
pub minimal_versions: bool,
289+
pub out_dir: bool,
289290
}
290291

291292
impl CliUnstable {
@@ -319,6 +320,7 @@ impl CliUnstable {
319320
"no-index-update" => self.no_index_update = true,
320321
"avoid-dev-deps" => self.avoid_dev_deps = true,
321322
"minimal-versions" => self.minimal_versions = true,
323+
"out-dir" => self.out_dir = true,
322324
_ => bail!("unknown `-Z` flag specified: {}", k),
323325
}
324326

src/cargo/ops/cargo_clean.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> {
9898
..BuildConfig::default()
9999
},
100100
profiles,
101+
None,
101102
&units,
102103
)?;
103104

src/cargo/ops/cargo_compile.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ pub struct CompileOptions<'a> {
6464
/// The specified target will be compiled with all the available arguments,
6565
/// note that this only accounts for the *final* invocation of rustc
6666
pub target_rustc_args: Option<Vec<String>>,
67+
/// The directory to copy final artifacts to. Note that even if `out_dir` is
68+
/// set, a copy of artifacts still could be found a `target/(debug\release)`
69+
/// as usual.
70+
// Note that, although the cmd-line flag name is `out-dir`, in code we use
71+
// `export_dir`, to avoid confusion with out dir at `target/debug/deps`.
72+
pub export_dir: Option<PathBuf>,
6773
}
6874

6975
impl<'a> CompileOptions<'a> {
@@ -84,6 +90,7 @@ impl<'a> CompileOptions<'a> {
8490
message_format: MessageFormat::Human,
8591
target_rustdoc_args: None,
8692
target_rustc_args: None,
93+
export_dir: None,
8794
}
8895
}
8996
}
@@ -233,6 +240,7 @@ pub fn compile_ws<'a>(
233240
ref filter,
234241
ref target_rustdoc_args,
235242
ref target_rustc_args,
243+
ref export_dir,
236244
} = *options;
237245

238246
let target = match target {
@@ -330,7 +338,6 @@ pub fn compile_ws<'a>(
330338
package_targets.push((to_build, vec![(target, profile)]));
331339
}
332340
}
333-
334341
let mut ret = {
335342
let _p = profile::start("compiling");
336343
let mut build_config = scrape_build_config(config, jobs, target)?;
@@ -349,6 +356,7 @@ pub fn compile_ws<'a>(
349356
config,
350357
build_config,
351358
profiles,
359+
export_dir.clone(),
352360
&exec,
353361
)?
354362
};

src/cargo/ops/cargo_package.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ fn run_verify(ws: &Workspace, tar: &FileLock, opts: &PackageOpts) -> CargoResult
350350
mode: ops::CompileMode::Build,
351351
target_rustdoc_args: None,
352352
target_rustc_args: None,
353+
export_dir: None,
353354
},
354355
Arc::new(DefaultExecutor),
355356
)?;

src/cargo/ops/cargo_rustc/context/compilation_files.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct CompilationFiles<'a, 'cfg: 'a> {
2727
pub(super) host: Layout,
2828
/// The target directory layout for the target (if different from then host)
2929
pub(super) target: Option<Layout>,
30+
export_dir: Option<(PathBuf, Vec<Unit<'a>>)>,
3031
ws: &'a Workspace<'cfg>,
3132
metas: HashMap<Unit<'a>, Option<Metadata>>,
3233
/// For each Unit, a list all files produced.
@@ -49,6 +50,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
4950
roots: &[Unit<'a>],
5051
host: Layout,
5152
target: Option<Layout>,
53+
export_dir: Option<PathBuf>,
5254
ws: &'a Workspace<'cfg>,
5355
cx: &Context<'a, 'cfg>,
5456
) -> CompilationFiles<'a, 'cfg> {
@@ -65,6 +67,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
6567
ws,
6668
host,
6769
target,
70+
export_dir: export_dir.map(|dir| (dir, roots.to_vec())),
6871
metas,
6972
outputs,
7073
}
@@ -107,6 +110,15 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
107110
}
108111
}
109112

113+
pub fn export_dir(&self, unit: &Unit<'a>) -> Option<PathBuf> {
114+
let &(ref dir, ref roots) = self.export_dir.as_ref()?;
115+
if roots.contains(unit) {
116+
Some(dir.clone())
117+
} else {
118+
None
119+
}
120+
}
121+
110122
pub fn pkg_dir(&self, unit: &Unit<'a>) -> String {
111123
let name = unit.pkg.package_id().name();
112124
match self.metas[unit] {

src/cargo/ops/cargo_rustc/context/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use std::collections::{HashMap, HashSet};
44
use std::env;
5-
use std::path::Path;
5+
use std::path::{Path, PathBuf};
66
use std::str::{self, FromStr};
77
use std::sync::Arc;
88

@@ -105,6 +105,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
105105
config: &'cfg Config,
106106
build_config: BuildConfig,
107107
profiles: &'a Profiles,
108+
export_dir: Option<PathBuf>,
108109
units: &[Unit<'a>],
109110
) -> CargoResult<Context<'a, 'cfg>> {
110111
let dest = if build_config.release {
@@ -164,7 +165,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
164165
cx.probe_target_info()?;
165166
let deps = build_unit_dependencies(units, &cx)?;
166167
cx.unit_dependencies = deps;
167-
let files = CompilationFiles::new(units, host_layout, target_layout, ws, &cx);
168+
let files = CompilationFiles::new(units, host_layout, target_layout, export_dir, ws, &cx);
168169
cx.files = Some(files);
169170
Ok(cx)
170171
}

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::env;
33
use std::ffi::{OsStr, OsString};
44
use std::fs;
55
use std::io::{self, Write};
6-
use std::path::{self, PathBuf};
6+
use std::path::{self, Path, PathBuf};
77
use std::sync::Arc;
88

99
use same_file::is_same_file;
@@ -141,6 +141,7 @@ pub fn compile_targets<'a, 'cfg: 'a>(
141141
config: &'cfg Config,
142142
build_config: BuildConfig,
143143
profiles: &'a Profiles,
144+
export_dir: Option<PathBuf>,
144145
exec: &Arc<Executor>,
145146
) -> CargoResult<Compilation<'cfg>> {
146147
let units = pkg_targets
@@ -171,6 +172,7 @@ pub fn compile_targets<'a, 'cfg: 'a>(
171172
config,
172173
build_config,
173174
profiles,
175+
export_dir,
174176
&units,
175177
)?;
176178

@@ -569,6 +571,7 @@ fn link_targets<'a, 'cfg>(
569571
fresh: bool,
570572
) -> CargoResult<Work> {
571573
let outputs = cx.outputs(unit)?;
574+
let export_dir = cx.files().export_dir(unit);
572575
let package_id = unit.pkg.package_id().clone();
573576
let target = unit.target.clone();
574577
let profile = unit.profile.clone();
@@ -600,41 +603,14 @@ fn link_targets<'a, 'cfg>(
600603
}
601604
};
602605
destinations.push(dst.display().to_string());
606+
hardlink_or_copy(&src, &dst)?;
607+
if let Some(ref path) = export_dir {
608+
if !path.exists() {
609+
fs::create_dir_all(path)?;
610+
}
603611

604-
debug!("linking {} to {}", src.display(), dst.display());
605-
if is_same_file(src, dst).unwrap_or(false) {
606-
continue;
607-
}
608-
if dst.exists() {
609-
paths::remove_file(&dst)?;
612+
hardlink_or_copy(&src, &path.join(dst.file_name().unwrap()))?;
610613
}
611-
612-
let link_result = if src.is_dir() {
613-
#[cfg(unix)]
614-
use std::os::unix::fs::symlink;
615-
#[cfg(target_os = "redox")]
616-
use std::os::redox::fs::symlink;
617-
#[cfg(windows)]
618-
use std::os::windows::fs::symlink_dir as symlink;
619-
620-
let dst_dir = dst.parent().unwrap();
621-
assert!(src.starts_with(dst_dir));
622-
symlink(src.strip_prefix(dst_dir).unwrap(), dst)
623-
} else {
624-
fs::hard_link(src, dst)
625-
};
626-
link_result
627-
.or_else(|err| {
628-
debug!("link failed {}. falling back to fs::copy", err);
629-
fs::copy(src, dst).map(|_| ())
630-
})
631-
.chain_err(|| {
632-
format!(
633-
"failed to link or copy `{}` to `{}`",
634-
src.display(),
635-
dst.display()
636-
)
637-
})?;
638614
}
639615

640616
if json_messages {
@@ -651,6 +627,48 @@ fn link_targets<'a, 'cfg>(
651627
}))
652628
}
653629

630+
fn hardlink_or_copy(src: &Path, dst: &Path) -> CargoResult<()> {
631+
debug!("linking {} to {}", src.display(), dst.display());
632+
if is_same_file(src, dst).unwrap_or(false) {
633+
return Ok(());
634+
}
635+
if dst.exists() {
636+
paths::remove_file(&dst)?;
637+
}
638+
639+
let link_result = if src.is_dir() {
640+
#[cfg(unix)]
641+
use std::os::unix::fs::symlink;
642+
#[cfg(target_os = "redox")]
643+
use std::os::redox::fs::symlink;
644+
#[cfg(windows)]
645+
use std::os::windows::fs::symlink_dir as symlink;
646+
647+
let dst_dir = dst.parent().unwrap();
648+
let src = if src.starts_with(dst_dir) {
649+
src.strip_prefix(dst_dir).unwrap()
650+
} else {
651+
src
652+
};
653+
symlink(src, dst)
654+
} else {
655+
fs::hard_link(src, dst)
656+
};
657+
link_result
658+
.or_else(|err| {
659+
debug!("link failed {}. falling back to fs::copy", err);
660+
fs::copy(src, dst).map(|_| ())
661+
})
662+
.chain_err(|| {
663+
format!(
664+
"failed to link or copy `{}` to `{}`",
665+
src.display(),
666+
dst.display()
667+
)
668+
})?;
669+
Ok(())
670+
}
671+
654672
fn load_build_deps(cx: &Context, unit: &Unit) -> Option<Arc<BuildScripts>> {
655673
cx.build_scripts.get(unit).cloned()
656674
}

tests/testsuite/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ mod login;
6262
mod metadata;
6363
mod net_config;
6464
mod new;
65+
mod out_dir;
6566
mod overrides;
6667
mod package;
6768
mod patch;

0 commit comments

Comments
 (0)