From: Aleksey Kladov Date: Tue, 3 Apr 2018 06:51:52 +0000 (+0300) Subject: Implement `--out-dir` option X-Git-Url: https://git.proxmox.com/?a=commitdiff_plain;h=5baac6b3b88f7001bd4c9f4294ed59760f551469;p=cargo.git Implement `--out-dir` option --- diff --git a/src/bin/command_prelude.rs b/src/bin/command_prelude.rs index bc738a052..a057a1fc5 100644 --- a/src/bin/command_prelude.rs +++ b/src/bin/command_prelude.rs @@ -288,6 +288,7 @@ pub trait ArgMatchesExt { message_format, target_rustdoc_args: None, target_rustc_args: None, + export_dir: None, }; Ok(opts) } diff --git a/src/bin/commands/build.rs b/src/bin/commands/build.rs index 2a772e03d..21d4e860d 100644 --- a/src/bin/commands/build.rs +++ b/src/bin/commands/build.rs @@ -27,6 +27,7 @@ pub fn cli() -> App { .arg_release("Build artifacts in release mode, with optimizations") .arg_features() .arg_target_triple("Build for the target triple") + .arg(opt("out-dir", "Copy final artifacts to this directory").value_name("PATH")) .arg_manifest_path() .arg_message_format() .after_help( @@ -49,7 +50,8 @@ the --release flag will use the `release` profile instead. pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { let ws = args.workspace(config)?; - let compile_opts = args.compile_options(config, CompileMode::Build)?; + let mut compile_opts = args.compile_options(config, CompileMode::Build)?; + compile_opts.export_dir = args.value_of_path("out-dir", config); ops::compile(&ws, &compile_opts)?; Ok(()) } diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 30009c7a6..2319470b2 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -98,6 +98,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { ..BuildConfig::default() }, profiles, + None, &units, )?; diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 74cc8949a..27ba181ae 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -64,6 +64,12 @@ pub struct CompileOptions<'a> { /// The specified target will be compiled with all the available arguments, /// note that this only accounts for the *final* invocation of rustc pub target_rustc_args: Option>, + /// The directory to copy final artifacts to. Note that even if `out_dir` is + /// set, a copy of artifacts still could be found a `target/(debug\release)` + /// as usual. + // Note that, although the cmd-line flag name is `out-dir`, in code we use + // `export_dir`, to avoid confusion with out dir at `target/debug/deps`. + pub export_dir: Option, } impl<'a> CompileOptions<'a> { @@ -84,6 +90,7 @@ impl<'a> CompileOptions<'a> { message_format: MessageFormat::Human, target_rustdoc_args: None, target_rustc_args: None, + export_dir: None, } } } @@ -233,6 +240,7 @@ pub fn compile_ws<'a>( ref filter, ref target_rustdoc_args, ref target_rustc_args, + ref export_dir, } = *options; let target = match target { @@ -330,7 +338,6 @@ pub fn compile_ws<'a>( package_targets.push((to_build, vec![(target, profile)])); } } - let mut ret = { let _p = profile::start("compiling"); let mut build_config = scrape_build_config(config, jobs, target)?; @@ -349,6 +356,7 @@ pub fn compile_ws<'a>( config, build_config, profiles, + export_dir.clone(), &exec, )? }; diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 1ae724fe7..8bf04979c 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -350,6 +350,7 @@ fn run_verify(ws: &Workspace, tar: &FileLock, opts: &PackageOpts) -> CargoResult mode: ops::CompileMode::Build, target_rustdoc_args: None, target_rustc_args: None, + export_dir: None, }, Arc::new(DefaultExecutor), )?; diff --git a/src/cargo/ops/cargo_rustc/context/compilation_files.rs b/src/cargo/ops/cargo_rustc/context/compilation_files.rs index d78364a3a..1f1d0cd3b 100644 --- a/src/cargo/ops/cargo_rustc/context/compilation_files.rs +++ b/src/cargo/ops/cargo_rustc/context/compilation_files.rs @@ -27,6 +27,7 @@ pub struct CompilationFiles<'a, 'cfg: 'a> { pub(super) host: Layout, /// The target directory layout for the target (if different from then host) pub(super) target: Option, + export_dir: Option<(PathBuf, Vec>)>, ws: &'a Workspace<'cfg>, metas: HashMap, Option>, /// For each Unit, a list all files produced. @@ -49,6 +50,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { roots: &[Unit<'a>], host: Layout, target: Option, + export_dir: Option, ws: &'a Workspace<'cfg>, cx: &Context<'a, 'cfg>, ) -> CompilationFiles<'a, 'cfg> { @@ -65,6 +67,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { ws, host, target, + export_dir: export_dir.map(|dir| (dir, roots.to_vec())), metas, outputs, } @@ -107,6 +110,15 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { } } + pub fn export_dir(&self, unit: &Unit<'a>) -> Option { + let &(ref dir, ref roots) = self.export_dir.as_ref()?; + if roots.contains(unit) { + Some(dir.clone()) + } else { + None + } + } + pub fn pkg_dir(&self, unit: &Unit<'a>) -> String { let name = unit.pkg.package_id().name(); match self.metas[unit] { diff --git a/src/cargo/ops/cargo_rustc/context/mod.rs b/src/cargo/ops/cargo_rustc/context/mod.rs index f7456fa31..958ccc21a 100644 --- a/src/cargo/ops/cargo_rustc/context/mod.rs +++ b/src/cargo/ops/cargo_rustc/context/mod.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::env; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str::{self, FromStr}; use std::sync::Arc; @@ -105,6 +105,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { config: &'cfg Config, build_config: BuildConfig, profiles: &'a Profiles, + export_dir: Option, units: &[Unit<'a>], ) -> CargoResult> { let dest = if build_config.release { @@ -164,7 +165,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { cx.probe_target_info()?; let deps = build_unit_dependencies(units, &cx)?; cx.unit_dependencies = deps; - let files = CompilationFiles::new(units, host_layout, target_layout, ws, &cx); + let files = CompilationFiles::new(units, host_layout, target_layout, export_dir, ws, &cx); cx.files = Some(files); Ok(cx) } diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index e6da68563..fe6a5b5f9 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -3,7 +3,7 @@ use std::env; use std::ffi::{OsStr, OsString}; use std::fs; use std::io::{self, Write}; -use std::path::{self, PathBuf}; +use std::path::{self, Path, PathBuf}; use std::sync::Arc; use same_file::is_same_file; @@ -141,6 +141,7 @@ pub fn compile_targets<'a, 'cfg: 'a>( config: &'cfg Config, build_config: BuildConfig, profiles: &'a Profiles, + export_dir: Option, exec: &Arc, ) -> CargoResult> { let units = pkg_targets @@ -171,6 +172,7 @@ pub fn compile_targets<'a, 'cfg: 'a>( config, build_config, profiles, + export_dir, &units, )?; @@ -569,6 +571,7 @@ fn link_targets<'a, 'cfg>( fresh: bool, ) -> CargoResult { let outputs = cx.outputs(unit)?; + let export_dir = cx.files().export_dir(unit); let package_id = unit.pkg.package_id().clone(); let target = unit.target.clone(); let profile = unit.profile.clone(); @@ -600,41 +603,15 @@ fn link_targets<'a, 'cfg>( } }; destinations.push(dst.display().to_string()); + hardlink_or_copy(&src, &dst)?; + if let Some(ref path) = export_dir { + //TODO: check if dir + if !path.exists() { + fs::create_dir(path)?; + } - debug!("linking {} to {}", src.display(), dst.display()); - if is_same_file(src, dst).unwrap_or(false) { - continue; - } - if dst.exists() { - paths::remove_file(&dst)?; + hardlink_or_copy(&src, &path.join(dst.file_name().unwrap()))?; } - - let link_result = if src.is_dir() { - #[cfg(unix)] - use std::os::unix::fs::symlink; - #[cfg(target_os = "redox")] - use std::os::redox::fs::symlink; - #[cfg(windows)] - use std::os::windows::fs::symlink_dir as symlink; - - let dst_dir = dst.parent().unwrap(); - assert!(src.starts_with(dst_dir)); - symlink(src.strip_prefix(dst_dir).unwrap(), dst) - } else { - fs::hard_link(src, dst) - }; - link_result - .or_else(|err| { - debug!("link failed {}. falling back to fs::copy", err); - fs::copy(src, dst).map(|_| ()) - }) - .chain_err(|| { - format!( - "failed to link or copy `{}` to `{}`", - src.display(), - dst.display() - ) - })?; } if json_messages { @@ -651,6 +628,44 @@ fn link_targets<'a, 'cfg>( })) } +fn hardlink_or_copy(src: &Path, dst: &Path) -> CargoResult<()> { + debug!("linking {} to {}", src.display(), dst.display()); + if is_same_file(src, dst).unwrap_or(false) { + return Ok(()); + } + if dst.exists() { + paths::remove_file(&dst)?; + } + + let link_result = if src.is_dir() { + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(target_os = "redox")] + use std::os::redox::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + let dst_dir = dst.parent().unwrap(); + assert!(src.starts_with(dst_dir)); + symlink(src.strip_prefix(dst_dir).unwrap(), dst) + } else { + fs::hard_link(src, dst) + }; + link_result + .or_else(|err| { + debug!("link failed {}. falling back to fs::copy", err); + fs::copy(src, dst).map(|_| ()) + }) + .chain_err(|| { + format!( + "failed to link or copy `{}` to `{}`", + src.display(), + dst.display() + ) + })?; + Ok(()) +} + fn load_build_deps(cx: &Context, unit: &Unit) -> Option> { cx.build_scripts.get(unit).cloned() } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 3aa4f5875..651928efd 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -14,7 +14,7 @@ use url::Url; use core::{GitReference, PackageIdSpec, Profiles, SourceId, WorkspaceConfig, WorkspaceRootConfig}; use core::{Dependency, Manifest, PackageId, Summary, Target}; -use core::{EitherManifest, Edition, Feature, Features, VirtualManifest}; +use core::{Edition, EitherManifest, Feature, Features, VirtualManifest}; use core::dependency::{Kind, Platform}; use core::manifest::{LibKind, Lto, ManifestMetadata, Profile}; use sources::CRATES_IO; diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 9f07fa873..da6ef5ea0 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -62,6 +62,7 @@ mod login; mod metadata; mod net_config; mod new; +mod out_dir; mod overrides; mod package; mod patch; diff --git a/tests/testsuite/out_dir.rs b/tests/testsuite/out_dir.rs new file mode 100644 index 000000000..775122c5f --- /dev/null +++ b/tests/testsuite/out_dir.rs @@ -0,0 +1,208 @@ +use cargotest::support::{execs, project}; +use hamcrest::assert_that; +use std::path::Path; +use std::fs; + +#[test] +fn binary_with_debug() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + "#, + ) + .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) + .build(); + + assert_that(p.cargo("build --out-dir out"), execs().with_status(0)); + check_dir_contents( + &p.root().join("out"), + &["foo"], + &["foo"], + &["foo.exe", "foo.pdb"], + ); +} + +#[test] +fn static_library_with_debug() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [lib] + crate-type = ["staticlib"] + "#, + ) + .file( + "src/lib.rs", + r#" + #[no_mangle] + pub extern "C" fn foo() { println!("Hello, World!") } + "#, + ) + .build(); + + assert_that(p.cargo("build --out-dir out"), execs().with_status(0)); + check_dir_contents( + &p.root().join("out"), + &["libfoo.a"], + &["libfoo.a"], + &["foo.lib"], + ); +} + +#[test] +fn dynamic_library_with_debug() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [lib] + crate-type = ["cdylib"] + "#, + ) + .file( + "src/lib.rs", + r#" + #[no_mangle] + pub extern "C" fn foo() { println!("Hello, World!") } + "#, + ) + .build(); + + assert_that(p.cargo("build --out-dir out"), execs().with_status(0)); + check_dir_contents( + &p.root().join("out"), + &["libfoo.so"], + &["libfoo.so"], + &["foo.dll", "foo.dll.lib"], + ); +} + +#[test] +fn rlib_with_debug() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [lib] + crate-type = ["rlib"] + "#, + ) + .file( + "src/lib.rs", + r#" + pub fn foo() { println!("Hello, World!") } + "#, + ) + .build(); + + assert_that(p.cargo("build --out-dir out"), execs().with_status(0)); + check_dir_contents( + &p.root().join("out"), + &["libfoo.rlib"], + &["libfoo.rlib"], + &["libfoo.rlib"], + ); +} + +#[test] +fn include_only_the_binary_from_the_current_package() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [workspace] + + [dependencies] + utils = { path = "./utils" } + "#, + ) + .file("src/lib.rs", "extern crate utils;") + .file( + "src/main.rs", + r#" + extern crate foo; + extern crate utils; + fn main() { + println!("Hello, World!") + } + "#, + ) + .file( + "utils/Cargo.toml", + r#" + [project] + name = "utils" + version = "0.0.1" + authors = [] + "#, + ) + .file("utils/src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build --bin foo --out-dir out"), + execs().with_status(0), + ); + check_dir_contents( + &p.root().join("out"), + &["foo"], + &["foo"], + &["foo.exe", "foo.pdb"], + ); +} + +fn check_dir_contents( + out_dir: &Path, + expected_linux: &[&str], + expected_mac: &[&str], + expected_win: &[&str], +) { + let expected = if cfg!(target_os = "windows") { + expected_win + } else if cfg!(target_os = "macos") { + expected_mac + } else { + expected_linux + }; + + let actual = list_dir(out_dir); + let mut expected = expected.iter().map(|s| s.to_string()).collect::>(); + expected.sort(); + assert_eq!(actual, expected); +} + +fn list_dir(dir: &Path) -> Vec { + let mut res = Vec::new(); + for entry in fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + res.push(entry.file_name().into_string().unwrap()); + } + res.sort(); + res +}