]> git.proxmox.com Git - cargo.git/commitdiff
Implement `--out-dir` option
authorAleksey Kladov <aleksey.kladov@gmail.com>
Tue, 3 Apr 2018 06:51:52 +0000 (09:51 +0300)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Tue, 3 Apr 2018 12:52:21 +0000 (15:52 +0300)
src/bin/command_prelude.rs
src/bin/commands/build.rs
src/cargo/ops/cargo_clean.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_package.rs
src/cargo/ops/cargo_rustc/context/compilation_files.rs
src/cargo/ops/cargo_rustc/context/mod.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/util/toml/mod.rs
tests/testsuite/main.rs
tests/testsuite/out_dir.rs [new file with mode: 0644]

index bc738a052b808df783520a2f320a5bae880525d5..a057a1fc59bd2e97f80cd60fa254541ac79ac6f5 100644 (file)
@@ -288,6 +288,7 @@ pub trait ArgMatchesExt {
             message_format,
             target_rustdoc_args: None,
             target_rustc_args: None,
+            export_dir: None,
         };
         Ok(opts)
     }
index 2a772e03da23ced9731d0293847ee13b57775840..21d4e860d0eecf0b84b5ec2532807db771944920 100644 (file)
@@ -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(())
 }
index 30009c7a63bf6390723130e3d206dbb25ecc645c..2319470b22c264d5951a71ebc2bc0f1bd3b5b40c 100644 (file)
@@ -98,6 +98,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> {
             ..BuildConfig::default()
         },
         profiles,
+        None,
         &units,
     )?;
 
index 74cc8949aae520d2b140a4a9dd7ce939d54c0a4c..27ba181aef127638f13079a0eb31147077a815f7 100644 (file)
@@ -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<Vec<String>>,
+    /// 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<PathBuf>,
 }
 
 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,
         )?
     };
index 1ae724fe77f965958a189e7a8417be4d4ecae81b..8bf04979c1e3161743d3bb60fe0b462cadeb3434 100644 (file)
@@ -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),
     )?;
index d78364a3a8acd0e58a6cd6b18fdf61571d101e58..1f1d0cd3bc395d7b08d76910dcdd6506420f4155 100644 (file)
@@ -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<Layout>,
+    export_dir: Option<(PathBuf, Vec<Unit<'a>>)>,
     ws: &'a Workspace<'cfg>,
     metas: HashMap<Unit<'a>, Option<Metadata>>,
     /// 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<Layout>,
+        export_dir: Option<PathBuf>,
         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<PathBuf> {
+        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] {
index f7456fa31613b92eab33c27aa4f3daa62e7a2b53..958ccc21a303ff7f6180dc3c74441248b110d90b 100644 (file)
@@ -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<PathBuf>,
         units: &[Unit<'a>],
     ) -> CargoResult<Context<'a, 'cfg>> {
         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)
     }
index e6da685635714a7382d4ef3fdcd47db5c806c22d..fe6a5b5f991971ad749e0f6c1f9795528ce82156 100644 (file)
@@ -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<PathBuf>,
     exec: &Arc<Executor>,
 ) -> CargoResult<Compilation<'cfg>> {
     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<Work> {
     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<Arc<BuildScripts>> {
     cx.build_scripts.get(unit).cloned()
 }
index 3aa4f58755086f34a578c0350e6b65b7e035c91e..651928efdb430ce9463cd10d7bbf168e07684245 100644 (file)
@@ -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;
index 9f07fa873dc31409c67bc2f1efdd344b6edf295c..da6ef5ea04a5ea4493a803c365fc143d2534fb93 100644 (file)
@@ -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 (file)
index 0000000..775122c
--- /dev/null
@@ -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::<Vec<_>>();
+    expected.sort();
+    assert_eq!(actual, expected);
+}
+
+fn list_dir(dir: &Path) -> Vec<String> {
+    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
+}