diff --git a/src/cargo/core/compiler/context/compilation_files.rs b/src/cargo/core/compiler/context/compilation_files.rs index 88aa1cfc761..9fcacb33614 100644 --- a/src/cargo/core/compiler/context/compilation_files.rs +++ b/src/cargo/core/compiler/context/compilation_files.rs @@ -7,8 +7,8 @@ use std::sync::Arc; use lazycell::LazyCell; -use core::{TargetKind, Workspace}; use super::{Context, FileFlavor, Kind, Layout, Unit}; +use core::{TargetKind, Workspace}; use util::{self, CargoResult}; #[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -97,7 +97,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the appropriate output directory for the specified package and /// target. pub fn out_dir(&self, unit: &Unit<'a>) -> PathBuf { - if unit.profile.doc { + if unit.mode.is_doc() { self.layout(unit.kind).root().parent().unwrap().join("doc") } else if unit.target.is_custom_build() { self.build_script_dir(unit) @@ -148,7 +148,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the appropriate directory layout for either a plugin or not. pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf { assert!(unit.target.is_custom_build()); - assert!(!unit.profile.run_custom_build); + assert!(!unit.mode.is_run_custom_build()); let dir = self.pkg_dir(unit); self.layout(Kind::Host).build().join(dir) } @@ -156,7 +156,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the appropriate directory layout for either a plugin or not. pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf { assert!(unit.target.is_custom_build()); - assert!(unit.profile.run_custom_build); + assert!(unit.mode.is_run_custom_build()); let dir = self.pkg_dir(unit); self.layout(unit.kind).build().join(dir).join("out") } @@ -211,7 +211,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { } else { Some(( out_dir.parent().unwrap().to_owned(), - if unit.profile.test { + if unit.mode.is_any_test() { file_stem } else { bin_stem @@ -244,7 +244,10 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { let mut ret = Vec::new(); let mut unsupported = Vec::new(); { - if unit.profile.check { + if unit.mode.is_check() { + // This is not quite correct for non-lib targets. rustc + // currently does not emit rmeta files, so there is nothing to + // check for! See #3624. let path = out_dir.join(format!("lib{}.rmeta", file_stem)); let hardlink = link_stem .clone() @@ -296,7 +299,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { | TargetKind::Test => { add("bin", FileFlavor::Normal)?; } - TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.profile.test => { + TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.mode.is_any_test() => { add("bin", FileFlavor::Normal)?; } TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => { @@ -378,7 +381,7 @@ fn compute_metadata<'a, 'cfg>( // just here for rustbuild. We need a more principled method // doing this eventually. let __cargo_default_lib_metadata = env::var("__CARGO_DEFAULT_LIB_METADATA"); - if !(unit.profile.test || unit.profile.check) + if !(unit.mode.is_any_test() || unit.mode.is_check()) && (unit.target.is_dylib() || unit.target.is_cdylib() || (unit.target.is_bin() && cx.build_config.target_triple().starts_with("wasm32-"))) && unit.pkg.package_id().source_id().is_path() @@ -423,6 +426,10 @@ fn compute_metadata<'a, 'cfg>( // panic=abort and panic=unwind artifacts, additionally with various // settings like debuginfo and whatnot. unit.profile.hash(&mut hasher); + unit.mode.hash(&mut hasher); + if let Some(ref args) = cx.extra_args_for(unit) { + args.hash(&mut hasher); + } // Artifacts compiled for the host should have a different metadata // piece than those compiled for the target, so make sure we throw in diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index fdf47780b76..8326e16d4a4 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -1,5 +1,4 @@ #![allow(deprecated)] - use std::collections::{HashMap, HashSet}; use std::env; use std::path::{Path, PathBuf}; @@ -8,25 +7,27 @@ use std::sync::Arc; use jobserver::Client; -use core::{Package, PackageId, PackageSet, Profile, Resolve, Target}; -use core::{Dependency, Profiles, Workspace}; -use util::{internal, profile, Cfg, CfgExpr, Config}; +use core::profiles::{Profile, Profiles}; +use core::{Dependency, Workspace}; +use core::{Package, PackageId, PackageSet, Resolve, Target}; +use ops::CompileMode; use util::errors::{CargoResult, CargoResultExt}; +use util::{internal, profile, Cfg, CfgExpr, Config}; -use super::TargetConfig; use super::custom_build::{self, BuildDeps, BuildScripts, BuildState}; use super::fingerprint::Fingerprint; use super::job_queue::JobQueue; use super::layout::Layout; use super::links::Links; +use super::TargetConfig; use super::{BuildConfig, Compilation, Executor, Kind}; mod unit_dependencies; use self::unit_dependencies::build_unit_dependencies; mod compilation_files; -use self::compilation_files::{CompilationFiles, OutputFile}; pub use self::compilation_files::Metadata; +use self::compilation_files::{CompilationFiles, OutputFile}; mod target_info; pub use self::target_info::{FileFlavor, TargetInfo}; @@ -45,7 +46,7 @@ pub use self::target_info::{FileFlavor, TargetInfo}; /// example, it needs to know the target architecture (OS, chip arch etc.) and it needs to know /// whether you want a debug or release build. There is enough information in this struct to figure /// all that out. -#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] pub struct Unit<'a> { /// Information about available targets, which files to include/exclude, etc. Basically stuff in /// `Cargo.toml`. @@ -55,8 +56,8 @@ pub struct Unit<'a> { /// build. pub target: &'a Target, /// The profile contains information about *how* the build should be run, including debug - /// level, extra args to pass to rustc, etc. - pub profile: &'a Profile, + /// level, etc. + pub profile: Profile, /// Whether this compilation unit is for the host or target architecture. /// /// For example, when @@ -64,6 +65,9 @@ pub struct Unit<'a> { /// the host architecture so the host rustc can use it (when compiling to the target /// architecture). pub kind: Kind, + /// The "mode" this unit is being compiled for. See `CompileMode` for + /// more details. + pub mode: CompileMode, } /// The build context, containing all information about a build task @@ -87,10 +91,16 @@ pub struct Context<'a, 'cfg: 'a> { pub links: Links<'a>, pub used_in_plugin: HashSet>, pub jobserver: Client, + pub profiles: &'a Profiles, + /// This is a workaround to carry the extra compiler args for either + /// `rustc` or `rustdoc` given on the command-line for the commands `cargo + /// rustc` and `cargo rustdoc`. These commands only support one target, + /// but we don't want the args passed to any dependencies, so we include + /// the `Unit` corresponding to the top-level target. + extra_compiler_args: Option<(Unit<'a>, Vec)>, target_info: TargetInfo, host_info: TargetInfo, - profiles: &'a Profiles, incremental_env: Option, unit_dependencies: HashMap, Vec>>, @@ -105,6 +115,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { config: &'cfg Config, build_config: BuildConfig, profiles: &'a Profiles, + extra_compiler_args: Option<(Unit<'a>, Vec)>, ) -> CargoResult> { let incremental_env = match env::var("CARGO_INCREMENTAL") { Ok(v) => Some(v == "1"), @@ -156,6 +167,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { unit_dependencies: HashMap::new(), files: None, + extra_compiler_args, }; cx.compilation.host_dylib_path = cx.host_info.sysroot_libdir.clone(); @@ -174,8 +186,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> { let mut queue = JobQueue::new(&self); self.prepare_units(export_dir, units)?; self.prepare()?; - self.build_used_in_plugin_map(&units)?; - custom_build::build_map(&mut self, &units)?; + self.build_used_in_plugin_map(units)?; + custom_build::build_map(&mut self, units)?; for unit in units.iter() { // Build up a list of pending jobs, each of which represent @@ -200,7 +212,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { None => &output.path, }; - if unit.profile.test { + if unit.mode.is_any_test() && !unit.mode.is_check() { self.compilation.tests.push(( unit.pkg.clone(), unit.target.kind().clone(), @@ -224,7 +236,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { continue; } - if dep.profile.run_custom_build { + if dep.mode.is_run_custom_build() { let out_dir = self.files().build_script_out_dir(dep).display().to_string(); self.compilation .extra_env @@ -236,7 +248,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { if !dep.target.is_lib() { continue; } - if dep.profile.doc { + if dep.mode.is_doc() { continue; } @@ -313,16 +325,10 @@ impl<'a, 'cfg> Context<'a, 'cfg> { None => None, }; - let deps = build_unit_dependencies(units, &self)?; + let deps = build_unit_dependencies(units, self)?; self.unit_dependencies = deps; - let files = CompilationFiles::new( - units, - host_layout, - target_layout, - export_dir, - self.ws, - &self, - ); + let files = + CompilationFiles::new(units, host_layout, target_layout, export_dir, self.ws, self); self.files = Some(files); Ok(()) } @@ -414,7 +420,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { // dependencies. However, that code itself calls this method and // gets a full pre-filtered set of dependencies. This is not super // obvious, and clear, but it does work at the moment. - if unit.profile.run_custom_build { + if unit.target.is_custom_build() { let key = (unit.pkg.package_id().clone(), unit.kind); if self.build_script_overridden.contains(&key) { return Vec::new(); @@ -476,25 +482,6 @@ impl<'a, 'cfg> Context<'a, 'cfg> { self.build_config.jobs } - pub fn lib_profile(&self) -> &'a Profile { - let (normal, test) = if self.build_config.release { - (&self.profiles.release, &self.profiles.bench_deps) - } else { - (&self.profiles.dev, &self.profiles.test_deps) - }; - if self.build_config.test { - test - } else { - normal - } - } - - pub fn build_script_profile(&self, _pkg: &PackageId) -> &'a Profile { - // TODO: should build scripts always be built with the same library - // profile? How is this controlled at the CLI layer? - self.lib_profile() - } - pub fn incremental_args(&self, unit: &Unit) -> CargoResult> { // There's a number of ways to configure incremental compilation right // now. In order of descending priority (first is highest priority) we @@ -572,6 +559,15 @@ impl<'a, 'cfg> Context<'a, 'cfg> { Kind::Target => &self.target_info, } } + + pub fn extra_args_for(&self, unit: &Unit<'a>) -> Option<&Vec> { + if let Some((ref args_unit, ref args)) = self.extra_compiler_args { + if args_unit == unit { + return Some(args); + } + } + None + } } /// Acquire extra flags to pass to the compiler from various locations. diff --git a/src/cargo/core/compiler/context/unit_dependencies.rs b/src/cargo/core/compiler/context/unit_dependencies.rs index b65a55f15da..84ee755b732 100644 --- a/src/cargo/core/compiler/context/unit_dependencies.rs +++ b/src/cargo/core/compiler/context/unit_dependencies.rs @@ -16,11 +16,12 @@ //! graph of `Unit`s, which capture these properties. use super::{Context, Kind, Unit}; +use core::dependency::Kind as DepKind; +use core::profiles::ProfileFor; +use core::{Package, Target}; +use ops::CompileMode; use std::collections::HashMap; use CargoResult; -use core::dependency::Kind as DepKind; -use core::Target; -use core::Profile; pub fn build_unit_dependencies<'a, 'cfg>( roots: &[Unit<'a>], @@ -28,7 +29,18 @@ pub fn build_unit_dependencies<'a, 'cfg>( ) -> CargoResult, Vec>>> { let mut deps = HashMap::new(); for unit in roots.iter() { - deps_of(unit, cx, &mut deps)?; + // Dependencies of tests/benches should not have `panic` set. + // We check the global test mode to see if we are running in `cargo + // test` in which case we ensure all dependencies have `panic` + // cleared, and avoid building the lib thrice (once with `panic`, once + // without, once for --test). In particular, the lib included for + // doctests and examples are `Build` mode here. + let profile_for = if unit.mode.is_any_test() || cx.build_config.test { + ProfileFor::TestDependency + } else { + ProfileFor::Any + }; + deps_of(unit, cx, &mut deps, profile_for)?; } Ok(deps) @@ -38,12 +50,20 @@ fn deps_of<'a, 'b, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, deps: &'b mut HashMap, Vec>>, + profile_for: ProfileFor, ) -> CargoResult<&'b [Unit<'a>]> { + // Currently the `deps` map does not include `profile_for`. This should + // be safe for now. `TestDependency` only exists to clear the `panic` + // flag, and you'll never ask for a `unit` with `panic` set as a + // `TestDependency`. `CustomBuild` should also be fine since if the + // requested unit's settings are the same as `Any`, `CustomBuild` can't + // affect anything else in the hierarchy. if !deps.contains_key(unit) { - let unit_deps = compute_deps(unit, cx, deps)?; - deps.insert(*unit, unit_deps.clone()); - for unit in unit_deps { - deps_of(&unit, cx, deps)?; + let unit_deps = compute_deps(unit, cx, deps, profile_for)?; + let to_insert: Vec<_> = unit_deps.iter().map(|&(unit, _)| unit).collect(); + deps.insert(*unit, to_insert); + for (unit, profile_for) in unit_deps { + deps_of(&unit, cx, deps, profile_for)?; } } Ok(deps[unit].as_ref()) @@ -51,68 +71,64 @@ fn deps_of<'a, 'b, 'cfg>( /// For a package, return all targets which are registered as dependencies /// for that package. +/// This returns a vec of `(Unit, ProfileFor)` pairs. The `ProfileFor` +/// is the profile type that should be used for dependencies of the unit. fn compute_deps<'a, 'b, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, deps: &'b mut HashMap, Vec>>, -) -> CargoResult>> { - if unit.profile.run_custom_build { + profile_for: ProfileFor, +) -> CargoResult, ProfileFor)>> { + if unit.mode.is_run_custom_build() { return compute_deps_custom_build(unit, cx, deps); - } else if unit.profile.doc && !unit.profile.test { + } else if unit.mode.is_doc() && !unit.mode.is_any_test() { + // Note: This does not include Doctest. return compute_deps_doc(unit, cx); } let id = unit.pkg.package_id(); let deps = cx.resolve.deps(id); - let mut ret = deps - .filter(|&(_id, deps)| { - assert!(deps.len() > 0); - deps.iter().any(|dep| { - // If this target is a build command, then we only want build - // dependencies, otherwise we want everything *other than* build - // dependencies. - if unit.target.is_custom_build() != dep.is_build() { - return false; - } + let mut ret = deps.filter(|&(_id, deps)| { + assert!(deps.len() > 0); + deps.iter().any(|dep| { + // If this target is a build command, then we only want build + // dependencies, otherwise we want everything *other than* build + // dependencies. + if unit.target.is_custom_build() != dep.is_build() { + return false; + } - // If this dependency is *not* a transitive dependency, then it - // only applies to test/example targets - if !dep.is_transitive() && !unit.target.is_test() && !unit.target.is_example() - && !unit.profile.test - { - return false; - } + // If this dependency is *not* a transitive dependency, then it + // only applies to test/example targets + if !dep.is_transitive() && !unit.target.is_test() && !unit.target.is_example() + && !unit.mode.is_any_test() + { + return false; + } - // If this dependency is only available for certain platforms, - // make sure we're only enabling it for that platform. - if !cx.dep_platform_activated(dep, unit.kind) { - return false; - } + // If this dependency is only available for certain platforms, + // make sure we're only enabling it for that platform. + if !cx.dep_platform_activated(dep, unit.kind) { + return false; + } - // If the dependency is optional, then we're only activating it - // if the corresponding feature was activated - if dep.is_optional() && !cx.resolve.features(id).contains(&*dep.name()) { - return false; - } + // If the dependency is optional, then we're only activating it + // if the corresponding feature was activated + if dep.is_optional() && !cx.resolve.features(id).contains(&*dep.name()) { + return false; + } - // If we've gotten past all that, then this dependency is - // actually used! - true - }) + // If we've gotten past all that, then this dependency is + // actually used! + true }) - .filter_map(|(id, _)| { - match cx.get_package(id) { - Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| { - let unit = Unit { - pkg, - target: t, - profile: lib_or_check_profile(unit, t, cx), - kind: unit.kind.for_target(t), - }; - Ok(unit) - }), - Err(e) => Some(Err(e)), - } + }).filter_map(|(id, _)| match cx.get_package(id) { + Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| { + let mode = check_or_build_mode(&unit.mode, t); + let unit = new_unit(cx, pkg, t, profile_for, unit.kind.for_target(t), mode); + Ok((unit, profile_for)) + }), + Err(e) => Some(Err(e)), }) .collect::>>()?; @@ -128,34 +144,11 @@ fn compute_deps<'a, 'b, 'cfg>( // the library of the same package. The call to `resolve.deps` above // didn't include `pkg` in the return values, so we need to special case // it here and see if we need to push `(pkg, pkg_lib_target)`. - if unit.target.is_lib() && !unit.profile.doc { + if unit.target.is_lib() && unit.mode != CompileMode::Doctest { return Ok(ret); } - ret.extend(maybe_lib(unit, cx)); + ret.extend(maybe_lib(unit, cx, profile_for)); - // Integration tests/benchmarks require binaries to be built - if unit.profile.test && (unit.target.is_test() || unit.target.is_bench()) { - ret.extend( - unit.pkg - .targets() - .iter() - .filter(|t| { - let no_required_features = Vec::new(); - - t.is_bin() && - // Skip binaries with required features that have not been selected. - t.required_features().unwrap_or(&no_required_features).iter().all(|f| { - cx.resolve.features(id).contains(f) - }) - }) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: lib_or_check_profile(unit, t, cx), - kind: unit.kind.for_target(t), - }), - ); - } Ok(ret) } @@ -167,10 +160,10 @@ fn compute_deps_custom_build<'a, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, deps: &mut HashMap, Vec>>, -) -> CargoResult>> { +) -> CargoResult, ProfileFor)>> { // When not overridden, then the dependencies to run a build script are: // - // 1. Compiling the build script itcx + // 1. Compiling the build script itself // 2. For each immediate dependency of our package which has a `links` // key, the execution of that build script. let not_custom_build = unit.pkg @@ -179,11 +172,13 @@ fn compute_deps_custom_build<'a, 'cfg>( .find(|t| !t.is_custom_build()) .unwrap(); let tmp = Unit { + pkg: unit.pkg, target: not_custom_build, - profile: &cx.profiles.dev, - ..*unit + profile: unit.profile, + kind: unit.kind, + mode: CompileMode::Build, }; - let deps = deps_of(&tmp, cx, deps)?; + let deps = deps_of(&tmp, cx, deps, ProfileFor::Any)?; Ok(deps.iter() .filter_map(|unit| { if !unit.target.linkable() || unit.pkg.manifest().links().is_none() { @@ -191,11 +186,19 @@ fn compute_deps_custom_build<'a, 'cfg>( } dep_build_script(unit, cx) }) - .chain(Some(Unit { - profile: cx.build_script_profile(unit.pkg.package_id()), - kind: Kind::Host, // build scripts always compiled for the host - ..*unit - })) + .chain(Some(( + new_unit( + cx, + unit.pkg, + unit.target, + ProfileFor::CustomBuild, + Kind::Host, // build scripts always compiled for the host + CompileMode::Build, + ), + // All dependencies of this unit should use profiles for custom + // builds. + ProfileFor::CustomBuild, + ))) .collect()) } @@ -203,15 +206,13 @@ fn compute_deps_custom_build<'a, 'cfg>( fn compute_deps_doc<'a, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, -) -> CargoResult>> { +) -> CargoResult, ProfileFor)>> { let deps = cx.resolve .deps(unit.pkg.package_id()) .filter(|&(_id, deps)| { - deps.iter().any(|dep| { - match dep.kind() { - DepKind::Normal => cx.dep_platform_activated(dep, unit.kind), - _ => false, - } + deps.iter().any(|dep| match dep.kind() { + DepKind::Normal => cx.dep_platform_activated(dep, unit.kind), + _ => false, }) }) .map(|(id, _deps)| cx.get_package(id)); @@ -226,19 +227,29 @@ fn compute_deps_doc<'a, 'cfg>( Some(lib) => lib, None => continue, }; - ret.push(Unit { - pkg: dep, - target: lib, - profile: lib_or_check_profile(unit, lib, cx), - kind: unit.kind.for_target(lib), - }); - if cx.build_config.doc_all { - ret.push(Unit { - pkg: dep, - target: lib, - profile: &cx.profiles.doc, - kind: unit.kind.for_target(lib), - }); + // rustdoc only needs rmeta files for regular dependencies. + // However, for plugins/proc-macros, deps should be built like normal. + let mode = check_or_build_mode(&unit.mode, lib); + let lib_unit = new_unit( + cx, + dep, + lib, + ProfileFor::Any, + unit.kind.for_target(lib), + mode, + ); + ret.push((lib_unit, ProfileFor::Any)); + if let CompileMode::Doc { deps: true } = unit.mode { + // Document this lib as well. + let doc_unit = new_unit( + cx, + dep, + lib, + ProfileFor::Any, + unit.kind.for_target(lib), + unit.mode, + ); + ret.push((doc_unit, ProfileFor::Any)); } } @@ -247,22 +258,21 @@ fn compute_deps_doc<'a, 'cfg>( // If we document a binary, we need the library available if unit.target.is_bin() { - ret.extend(maybe_lib(unit, cx)); + ret.extend(maybe_lib(unit, cx, ProfileFor::Any)); } Ok(ret) } -fn maybe_lib<'a, 'cfg>(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option> { - unit.pkg - .targets() - .iter() - .find(|t| t.linkable()) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: lib_or_check_profile(unit, t, cx), - kind: unit.kind.for_target(t), - }) +fn maybe_lib<'a>( + unit: &Unit<'a>, + cx: &Context, + profile_for: ProfileFor, +) -> Option<(Unit<'a>, ProfileFor)> { + let mode = check_or_build_mode(&unit.mode, unit.target); + unit.pkg.targets().iter().find(|t| t.linkable()).map(|t| { + let unit = new_unit(cx, unit.pkg, t, profile_for, unit.kind.for_target(t), mode); + (unit, profile_for) + }) } /// If a build script is scheduled to be run for the package specified by @@ -272,28 +282,65 @@ fn maybe_lib<'a, 'cfg>(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option> { +fn dep_build_script<'a>(unit: &Unit<'a>, cx: &Context) -> Option<(Unit<'a>, ProfileFor)> { unit.pkg .targets() .iter() .find(|t| t.is_custom_build()) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: &cx.profiles.custom_build, - kind: unit.kind, + .map(|t| { + // The profile stored in the Unit is the profile for the thing + // the custom build script is running for. + ( + Unit { + pkg: unit.pkg, + target: t, + profile: cx.profiles.get_profile_run_custom_build(&unit.profile), + kind: unit.kind, + mode: CompileMode::RunCustomBuild, + }, + ProfileFor::CustomBuild, + ) }) } -fn lib_or_check_profile<'a, 'cfg>( - unit: &Unit, - target: &Target, - cx: &Context<'a, 'cfg>, -) -> &'a Profile { - if !target.is_custom_build() && !target.for_host() - && (unit.profile.check || (unit.profile.doc && !unit.profile.test)) - { - return &cx.profiles.check; +/// Choose the correct mode for dependencies. +fn check_or_build_mode(mode: &CompileMode, target: &Target) -> CompileMode { + match *mode { + CompileMode::Check { .. } | CompileMode::Doc { .. } => { + if target.for_host() { + // Plugin and proc-macro targets should be compiled like + // normal. + CompileMode::Build + } else { + // Regular dependencies should not be checked with --test. + // Regular dependencies of doc targets should emit rmeta only. + CompileMode::Check { test: false } + } + } + _ => CompileMode::Build, + } +} + +fn new_unit<'a>( + cx: &Context, + pkg: &'a Package, + target: &'a Target, + profile_for: ProfileFor, + kind: Kind, + mode: CompileMode, +) -> Unit<'a> { + let profile = cx.profiles.get_profile( + &pkg.name(), + cx.ws.is_member(pkg), + profile_for, + mode, + cx.build_config.release, + ); + Unit { + pkg, + target, + profile, + kind, + mode, } - cx.lib_profile() } diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 320af640314..ff28c0e065a 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -1,15 +1,15 @@ -use std::collections::{BTreeSet, HashSet}; use std::collections::hash_map::{Entry, HashMap}; +use std::collections::{BTreeSet, HashSet}; use std::fs; use std::path::{Path, PathBuf}; use std::str; use std::sync::{Arc, Mutex}; use core::PackageId; -use util::{Cfg, Freshness}; use util::errors::{CargoResult, CargoResultExt}; -use util::{self, internal, paths, profile}; use util::machine_message; +use util::{self, internal, paths, profile}; +use util::{Cfg, Freshness}; use super::job::Work; use super::{fingerprint, Context, Kind, Unit}; @@ -102,11 +102,11 @@ pub fn prepare<'a, 'cfg>( } fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<(Work, Work)> { - assert!(unit.profile.run_custom_build); + assert!(unit.mode.is_run_custom_build()); let dependencies = cx.dep_targets(unit); let build_script_unit = dependencies .iter() - .find(|d| !d.profile.run_custom_build && d.target.is_custom_build()) + .find(|d| !d.mode.is_run_custom_build() && d.target.is_custom_build()) .expect("running a script not depending on an actual script"); let script_output = cx.files().build_script_dir(build_script_unit); let build_output = cx.files().build_script_out_dir(unit); @@ -118,7 +118,9 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes // environment variables. Note that the profile-related environment // variables are not set with this the build script's profile but rather the // package's library profile. - let profile = cx.lib_profile(); + // NOTE: If you add any profile flags, be sure to update + // `Profiles::get_profile_run_custom_build` so that those flags get + // carried over. let to_exec = to_exec.into_os_string(); let mut cmd = cx.compilation.host_process(to_exec, unit.pkg)?; cmd.env("OUT_DIR", &build_output) @@ -131,8 +133,8 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes Kind::Target => cx.build_config.target_triple(), }, ) - .env("DEBUG", &profile.debuginfo.is_some().to_string()) - .env("OPT_LEVEL", &profile.opt_level) + .env("DEBUG", &unit.profile.debuginfo.is_some().to_string()) + .env("OPT_LEVEL", &unit.profile.opt_level.to_string()) .env( "PROFILE", if cx.build_config.release { @@ -196,7 +198,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes dependencies .iter() .filter_map(|unit| { - if unit.profile.run_custom_build { + if unit.mode.is_run_custom_build() { Some(( unit.pkg.manifest().links().unwrap().to_string(), unit.pkg.package_id().clone(), diff --git a/src/cargo/core/compiler/fingerprint.rs b/src/cargo/core/compiler/fingerprint.rs index 9d3fc4d503f..9020c5cf2a8 100644 --- a/src/cargo/core/compiler/fingerprint.rs +++ b/src/cargo/core/compiler/fingerprint.rs @@ -5,19 +5,19 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use filetime::FileTime; -use serde::ser::{self, Serialize}; use serde::de::{self, Deserialize}; +use serde::ser::{self, Serialize}; use serde_json; use core::{Edition, Package, TargetKind}; use util; -use util::{internal, profile, Dirty, Fresh, Freshness}; use util::errors::{CargoResult, CargoResultExt}; use util::paths; +use util::{internal, profile, Dirty, Fresh, Freshness}; -use super::job::Work; use super::context::{Context, FileFlavor, Unit}; use super::custom_build::BuildDeps; +use super::job::Work; /// A tuple result of the `prepare_foo` functions in this module. /// @@ -86,7 +86,7 @@ pub fn prepare_target<'a, 'cfg>( let root = cx.files().out_dir(unit); let mut missing_outputs = false; - if unit.profile.doc { + if unit.mode.is_doc() { missing_outputs = !root.join(unit.target.crate_name()) .join("index.html") .exists(); @@ -102,7 +102,7 @@ pub fn prepare_target<'a, 'cfg>( } } - let allow_failure = unit.profile.rustc_args.is_some(); + let allow_failure = cx.extra_args_for(unit).is_some(); let target_root = cx.files().target_root().to_path_buf(); let write_fingerprint = Work::new(move |_| { match fingerprint.update_local(&target_root) { @@ -353,14 +353,7 @@ impl hash::Hash for Fingerprint { .. } = *self; ( - rustc, - features, - target, - path, - profile, - local, - edition, - rustflags, + rustc, features, target, path, profile, local, edition, rustflags, ).hash(h); h.write_usize(deps.len()); @@ -449,15 +442,21 @@ fn calculate<'a, 'cfg>( }; let mut deps = deps; deps.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); - let extra_flags = if unit.profile.doc { + let extra_flags = if unit.mode.is_doc() { cx.rustdocflags_args(unit)? } else { cx.rustflags_args(unit)? }; + let profile_hash = util::hash_u64(&( + &unit.profile, + unit.mode, + cx.extra_args_for(unit), + cx.incremental_args(unit)?, + )); let fingerprint = Arc::new(Fingerprint { rustc: util::hash_u64(&cx.build_config.rustc.verbose_version), target: util::hash_u64(&unit.target), - profile: util::hash_u64(&(&unit.profile, cx.incremental_args(unit)?)), + profile: profile_hash, // Note that .0 is hashed here, not .1 which is the cwd. That doesn't // actually affect the output artifact so there's no need to hash it. path: util::hash_u64(&super::path_args(cx, unit).0), @@ -478,7 +477,7 @@ fn calculate<'a, 'cfg>( // responsibility of the source) fn use_dep_info(unit: &Unit) -> bool { let path = unit.pkg.summary().source_id().is_path(); - !unit.profile.doc && path + !unit.mode.is_doc() && path } /// Prepare the necessary work for the fingerprint of a build command. @@ -758,9 +757,9 @@ fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String { TargetKind::Bench => "bench", TargetKind::CustomBuild => "build-script", }; - let flavor = if unit.profile.test { + let flavor = if unit.mode.is_any_test() { "test-" - } else if unit.profile.doc { + } else if unit.mode.is_doc() { "doc-" } else { "" diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 9f33e9ca4bf..026ac0962ba 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -1,5 +1,5 @@ -use std::collections::HashSet; use std::collections::hash_map::HashMap; +use std::collections::HashSet; use std::fmt; use std::io; use std::mem; @@ -8,13 +8,15 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use crossbeam::{self, Scope}; use jobserver::{Acquired, HelperThread}; -use core::{PackageId, Profile, Target}; -use util::{Config, DependencyQueue, Dirty, Fresh, Freshness}; -use util::{internal, profile, CargoResult, CargoResultExt, ProcessBuilder}; +use core::profiles::Profile; +use core::{PackageId, Target}; use handle_error; +use ops::CompileMode; +use util::{internal, profile, CargoResult, CargoResultExt, ProcessBuilder}; +use util::{Config, DependencyQueue, Dirty, Fresh, Freshness}; -use super::{Context, Kind, Unit}; use super::job::Job; +use super::{Context, Kind, Unit}; /// A management structure of the entire dependency graph to compile. /// @@ -46,8 +48,9 @@ struct PendingBuild { struct Key<'a> { pkg: &'a PackageId, target: &'a Target, - profile: &'a Profile, + profile: Profile, kind: Kind, + mode: CompileMode, } pub struct JobState<'a> { @@ -231,7 +234,7 @@ impl<'a> JobQueue<'a> { Ok(()) => self.finish(key, cx)?, Err(e) => { let msg = "The following warnings were emitted during compilation:"; - self.emit_warnings(Some(msg), key, cx)?; + self.emit_warnings(Some(msg), &key, cx)?; if self.active > 0 { error = Some(format_err!("build failed")); @@ -253,8 +256,15 @@ impl<'a> JobQueue<'a> { } let build_type = if self.is_release { "release" } else { "dev" }; - let profile = cx.lib_profile(); - let mut opt_type = String::from(if profile.opt_level == "0" { + // NOTE: This may be a bit inaccurate, since this may not display the + // profile for what was actually built. Profile overrides can change + // these settings, and in some cases different targets are built with + // different profiles. To be accurate, it would need to collect a + // list of Units built, and maybe display a list of the different + // profiles used. However, to keep it simple and compatible with old + // behavior, we just display what the base profile is. + let profile = cx.profiles.base_profile(self.is_release); + let mut opt_type = String::from(if profile.opt_level.as_str() == "0" { "unoptimized" } else { "optimized" @@ -316,7 +326,7 @@ impl<'a> JobQueue<'a> { Ok(()) } - fn emit_warnings(&self, msg: Option<&str>, key: Key<'a>, cx: &mut Context) -> CargoResult<()> { + fn emit_warnings(&self, msg: Option<&str>, key: &Key<'a>, cx: &mut Context) -> CargoResult<()> { let output = cx.build_state.outputs.lock().unwrap(); if let Some(output) = output.get(&(key.pkg.clone(), key.kind)) { if let Some(msg) = msg { @@ -339,8 +349,8 @@ impl<'a> JobQueue<'a> { } fn finish(&mut self, key: Key<'a>, cx: &mut Context) -> CargoResult<()> { - if key.profile.run_custom_build && cx.show_warnings(key.pkg) { - self.emit_warnings(None, key, cx)?; + if key.mode.is_run_custom_build() && cx.show_warnings(key.pkg) { + self.emit_warnings(None, &key, cx)?; } let state = self.pending.get_mut(&key).unwrap(); @@ -366,8 +376,8 @@ impl<'a> JobQueue<'a> { key: &Key<'a>, fresh: Freshness, ) -> CargoResult<()> { - if (self.compiled.contains(key.pkg) && !key.profile.doc) - || (self.documented.contains(key.pkg) && key.profile.doc) + if (self.compiled.contains(key.pkg) && !key.mode.is_doc()) + || (self.documented.contains(key.pkg) && key.mode.is_doc()) { return Ok(()); } @@ -376,14 +386,15 @@ impl<'a> JobQueue<'a> { // Any dirty stage which runs at least one command gets printed as // being a compiled package Dirty => { - if key.profile.doc { - if !key.profile.test { + if key.mode.is_doc() { + // Skip Doctest + if !key.mode.is_any_test() { self.documented.insert(key.pkg); config.shell().status("Documenting", key.pkg)?; } } else { self.compiled.insert(key.pkg); - if key.profile.check { + if key.mode.is_check() { config.shell().status("Checking", key.pkg)?; } else { config.shell().status("Compiling", key.pkg)?; @@ -407,6 +418,7 @@ impl<'a> Key<'a> { target: unit.target, profile: unit.profile, kind: unit.kind, + mode: unit.mode, } } @@ -416,6 +428,7 @@ impl<'a> Key<'a> { target: self.target, profile: self.profile, kind: self.kind, + mode: self.mode, }; let targets = cx.dep_targets(&unit); Ok(targets diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index fccdfb6c2c7..c0a948cc911 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -9,13 +9,14 @@ use std::sync::Arc; use same_file::is_same_file; use serde_json; -use core::{Feature, PackageId, Profile, Target}; -use core::manifest::Lto; +use core::profiles::{Lto, Profile}; use core::shell::ColorChoice; +use core::{Feature, PackageId, Target}; +use ops::CompileMode; +use util::errors::{CargoResult, CargoResultExt, Internal}; +use util::paths; use util::{self, machine_message, Config, Freshness, ProcessBuilder, Rustc}; use util::{internal, join_paths, profile}; -use util::paths; -use util::errors::{CargoResult, CargoResultExt, Internal}; use self::job::{Job, Work}; use self::job_queue::JobQueue; @@ -61,8 +62,6 @@ pub struct BuildConfig { pub release: bool, /// Whether we are running tests pub test: bool, - /// Whether we are building documentation - pub doc_all: bool, /// Whether to print std output in json format (for machine reading) pub json_messages: bool, } @@ -132,7 +131,6 @@ impl BuildConfig { target: target_config, release: false, test: false, - doc_all: false, json_messages: false, }) } @@ -304,14 +302,14 @@ fn compile<'a, 'cfg: 'a>( fingerprint::prepare_init(cx, unit)?; cx.links.validate(cx.resolve, unit)?; - let (dirty, fresh, freshness) = if unit.profile.run_custom_build { + let (dirty, fresh, freshness) = if unit.mode.is_run_custom_build() { custom_build::prepare(cx, unit)? - } else if unit.profile.doc && unit.profile.test { + } else if unit.mode == CompileMode::Doctest { // we run these targets later, so this is just a noop for now (Work::noop(), Work::noop(), Freshness::Fresh) } else { let (mut freshness, dirty, fresh) = fingerprint::prepare_target(cx, unit)?; - let work = if unit.profile.doc { + let work = if unit.mode.is_doc() { rustdoc(cx, unit)? } else { rustc(cx, unit, exec)? @@ -369,7 +367,7 @@ fn rustc<'a, 'cfg>( // If we are a binary and the package also contains a library, then we // don't pass the `-l` flags. let pass_l_flag = unit.target.is_lib() || !unit.pkg.targets().iter().any(|t| t.is_lib()); - let do_rename = unit.target.allows_underscores() && !unit.profile.test; + let do_rename = unit.target.allows_underscores() && !unit.mode.is_any_test(); let real_name = unit.target.name().to_string(); let crate_name = unit.target.crate_name(); @@ -561,7 +559,8 @@ fn link_targets<'a, 'cfg>( 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(); + let profile = unit.profile; + let unit_mode = unit.mode; let features = cx.resolve .features_sorted(&package_id) .into_iter() @@ -601,10 +600,18 @@ fn link_targets<'a, 'cfg>( } if json_messages { + let art_profile = machine_message::ArtifactProfile { + opt_level: profile.opt_level.as_str(), + debuginfo: profile.debuginfo, + debug_assertions: profile.debug_assertions, + overflow_checks: profile.overflow_checks, + test: unit_mode.is_any_test(), + }; + machine_message::emit(&machine_message::Artifact { package_id: &package_id, target: &target, - profile: &profile, + profile: art_profile, features, filenames: destinations, fresh, @@ -624,10 +631,10 @@ fn hardlink_or_copy(src: &Path, dst: &Path) -> CargoResult<()> { } 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(unix)] + use std::os::unix::fs::symlink; #[cfg(windows)] use std::os::windows::fs::symlink_dir as symlink; @@ -770,7 +777,7 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult rustdoc.arg(format!("--edition={}", &manifest.edition())); } - if let Some(ref args) = unit.profile.rustdoc_args { + if let Some(ref args) = cx.extra_args_for(unit) { rustdoc.args(args); } @@ -835,23 +842,20 @@ fn build_base_args<'a, 'cfg>( unit: &Unit<'a>, crate_types: &[&str], ) -> CargoResult<()> { + assert!(!unit.mode.is_run_custom_build()); + let Profile { ref opt_level, ref lto, codegen_units, - ref rustc_args, debuginfo, debug_assertions, overflow_checks, rpath, - test, - doc: _doc, - run_custom_build, ref panic, - check, .. - } = *unit.profile; - assert!(!run_custom_build); + } = unit.profile; + let test = unit.mode.is_any_test(); cmd.arg("--crate-name").arg(&unit.target.crate_name()); @@ -877,7 +881,7 @@ fn build_base_args<'a, 'cfg>( } } - if check { + if unit.mode.is_check() { cmd.arg("--emit=dep-info,metadata"); } else { cmd.arg("--emit=dep-info,link"); @@ -889,7 +893,7 @@ fn build_base_args<'a, 'cfg>( cmd.arg("-C").arg("prefer-dynamic"); } - if opt_level != "0" { + if opt_level.as_str() != "0" { cmd.arg("-C").arg(&format!("opt-level={}", opt_level)); } @@ -938,14 +942,14 @@ fn build_base_args<'a, 'cfg>( cmd.arg("-C").arg(format!("debuginfo={}", debuginfo)); } - if let Some(ref args) = *rustc_args { + if let Some(ref args) = cx.extra_args_for(unit) { cmd.args(args); } // -C overflow-checks is implied by the setting of -C debug-assertions, // so we only need to provide -C overflow-checks if it differs from // the value of -C debug-assertions we would provide. - if opt_level != "0" { + if opt_level.as_str() != "0" { if debug_assertions { cmd.args(&["-C", "debug-assertions=on"]); if !overflow_checks { @@ -1053,11 +1057,11 @@ fn build_deps_args<'a, 'cfg>( // error in the future, see PR #4797 if !dep_targets .iter() - .any(|u| !u.profile.doc && u.target.linkable()) + .any(|u| !u.mode.is_doc() && u.target.linkable()) { if let Some(u) = dep_targets .iter() - .find(|u| !u.profile.doc && u.target.is_lib()) + .find(|u| !u.mode.is_doc() && u.target.is_lib()) { cx.config.shell().warn(format!( "The package `{}` \ @@ -1072,10 +1076,10 @@ fn build_deps_args<'a, 'cfg>( } for dep in dep_targets { - if dep.profile.run_custom_build { + if dep.mode.is_run_custom_build() { cmd.env("OUT_DIR", &cx.files().build_script_out_dir(&dep)); } - if dep.target.linkable() && !dep.profile.doc { + if dep.target.linkable() && !dep.mode.is_doc() { link_to(cmd, cx, unit, &dep)?; } } diff --git a/src/cargo/core/compiler/output_depinfo.rs b/src/cargo/core/compiler/output_depinfo.rs index dcdbb95f8b7..a0a006a5d0a 100644 --- a/src/cargo/core/compiler/output_depinfo.rs +++ b/src/cargo/core/compiler/output_depinfo.rs @@ -1,11 +1,11 @@ use std::collections::{BTreeSet, HashSet}; -use std::io::{BufWriter, Write}; use std::fs::File; +use std::io::{BufWriter, Write}; use std::path::{Path, PathBuf}; use super::{fingerprint, Context, Unit}; -use util::{internal, CargoResult}; use util::paths; +use util::{internal, CargoResult}; fn render_filename>(path: P, basedir: Option<&str>) -> CargoResult { let path = path.as_ref(); @@ -34,7 +34,7 @@ fn add_deps_for_unit<'a, 'b>( // units representing the execution of a build script don't actually // generate a dep info file, so we just keep on going below - if !unit.profile.run_custom_build { + if !unit.mode.is_run_custom_build() { // Add dependencies from rustc dep-info output (stored in fingerprint directory) let dep_info_loc = fingerprint::dep_info_loc(context, unit); if let Some(paths) = fingerprint::parse_dep_info(unit.pkg, &dep_info_loc)? { @@ -43,9 +43,9 @@ fn add_deps_for_unit<'a, 'b>( } } else { debug!( - "can't find dep_info for {:?} {:?}", + "can't find dep_info for {:?} {}", unit.pkg.package_id(), - unit.profile + unit.target ); return Err(internal("dep_info missing")); } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index c9658a8aaed..b38c0950e9e 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -133,8 +133,12 @@ macro_rules! features { } macro_rules! stab { - (stable) => (Status::Stable); - (unstable) => (Status::Unstable); + (stable) => { + Status::Stable + }; + (unstable) => { + Status::Unstable + }; } /// A listing of all features in Cargo @@ -170,6 +174,9 @@ features! { // Whether a lock file is published with this crate [unstable] publish_lockfile: bool, + + // Overriding profiles for dependencies. + [unstable] profile_overrides: bool, } } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index b4b50fc90a9..5e42103862c 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -1,20 +1,21 @@ use std::collections::{BTreeMap, HashMap}; use std::fmt; +use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; use std::rc::Rc; -use std::hash::{Hash, Hasher}; use semver::Version; use serde::ser; use toml; use url::Url; +use core::interning::InternedString; +use core::profiles::Profiles; use core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; use core::{Edition, Feature, Features, WorkspaceConfig}; -use core::interning::InternedString; -use util::Config; -use util::toml::TomlManifest; use util::errors::*; +use util::toml::TomlManifest; +use util::Config; pub enum EitherManifest { Real(Manifest), @@ -152,59 +153,6 @@ impl ser::Serialize for TargetKind { } } -// Note that most of the fields here are skipped when serializing because we -// don't want to export them just yet (becomes a public API of Cargo). Others -// though are definitely needed! -#[derive(Clone, PartialEq, Eq, Debug, Hash, Serialize)] -pub struct Profile { - pub opt_level: String, - #[serde(skip_serializing)] - pub lto: Lto, - #[serde(skip_serializing)] - pub codegen_units: Option, // None = use rustc default - #[serde(skip_serializing)] - pub rustc_args: Option>, - #[serde(skip_serializing)] - pub rustdoc_args: Option>, - pub debuginfo: Option, - pub debug_assertions: bool, - pub overflow_checks: bool, - #[serde(skip_serializing)] - pub rpath: bool, - pub test: bool, - #[serde(skip_serializing)] - pub doc: bool, - #[serde(skip_serializing)] - pub run_custom_build: bool, - #[serde(skip_serializing)] - pub check: bool, - #[serde(skip_serializing)] - pub panic: Option, - #[serde(skip_serializing)] - pub incremental: bool, -} - -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub enum Lto { - Bool(bool), - Named(String), -} - -#[derive(Default, Clone, Debug, PartialEq, Eq)] -pub struct Profiles { - pub release: Profile, - pub dev: Profile, - pub test: Profile, - pub test_deps: Profile, - pub bench: Profile, - pub bench_deps: Profile, - pub doc: Profile, - pub custom_build: Profile, - pub check: Profile, - pub check_test: Profile, - pub doctest: Profile, -} - /// Information about a binary, a library, an example, etc. that is part of the /// package. #[derive(Clone, Hash, PartialEq, Eq, Debug)] @@ -524,6 +472,7 @@ impl Target { kind, name: name.to_string(), required_features, + tested: false, benched: false, ..Target::with_path(src_path) } @@ -726,112 +675,3 @@ impl fmt::Display for Target { } } } - -impl Profile { - pub fn default_dev() -> Profile { - Profile { - debuginfo: Some(2), - debug_assertions: true, - overflow_checks: true, - incremental: true, - ..Profile::default() - } - } - - pub fn default_release() -> Profile { - Profile { - opt_level: "3".to_string(), - debuginfo: None, - ..Profile::default() - } - } - - pub fn default_test() -> Profile { - Profile { - test: true, - ..Profile::default_dev() - } - } - - pub fn default_bench() -> Profile { - Profile { - test: true, - ..Profile::default_release() - } - } - - pub fn default_doc() -> Profile { - Profile { - doc: true, - ..Profile::default_dev() - } - } - - pub fn default_custom_build() -> Profile { - Profile { - run_custom_build: true, - ..Profile::default_dev() - } - } - - pub fn default_check() -> Profile { - Profile { - check: true, - ..Profile::default_dev() - } - } - - pub fn default_check_test() -> Profile { - Profile { - check: true, - test: true, - ..Profile::default_dev() - } - } - - pub fn default_doctest() -> Profile { - Profile { - doc: true, - test: true, - ..Profile::default_dev() - } - } -} - -impl Default for Profile { - fn default() -> Profile { - Profile { - opt_level: "0".to_string(), - lto: Lto::Bool(false), - codegen_units: None, - rustc_args: None, - rustdoc_args: None, - debuginfo: None, - debug_assertions: false, - overflow_checks: false, - rpath: false, - test: false, - doc: false, - run_custom_build: false, - check: false, - panic: None, - incremental: false, - } - } -} - -impl fmt::Display for Profile { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.test { - write!(f, "Profile(test)") - } else if self.doc { - write!(f, "Profile(doc)") - } else if self.run_custom_build { - write!(f, "Profile(run)") - } else if self.check { - write!(f, "Profile(check)") - } else { - write!(f, "Profile(build)") - } - } -} diff --git a/src/cargo/core/mod.rs b/src/cargo/core/mod.rs index d112050d08b..67578c8223c 100644 --- a/src/cargo/core/mod.rs +++ b/src/cargo/core/mod.rs @@ -1,7 +1,7 @@ pub use self::dependency::Dependency; pub use self::features::{CliUnstable, Edition, Feature, Features}; pub use self::manifest::{EitherManifest, VirtualManifest}; -pub use self::manifest::{LibKind, Manifest, Profile, Profiles, Target, TargetKind}; +pub use self::manifest::{LibKind, Manifest, Target, TargetKind}; pub use self::package::{Package, PackageSet}; pub use self::package_id::PackageId; pub use self::package_id_spec::PackageIdSpec; @@ -12,17 +12,18 @@ pub use self::source::{GitReference, Source, SourceId, SourceMap}; pub use self::summary::{FeatureMap, FeatureValue, Summary}; pub use self::workspace::{Members, Workspace, WorkspaceConfig, WorkspaceRootConfig}; -pub mod source; -pub mod package; -pub mod package_id; +pub mod compiler; pub mod dependency; +mod features; +mod interning; pub mod manifest; +pub mod package; +pub mod package_id; +mod package_id_spec; +pub mod profiles; +pub mod registry; pub mod resolver; -pub mod summary; pub mod shell; -pub mod registry; -pub mod compiler; -mod interning; -mod package_id_spec; +pub mod source; +pub mod summary; mod workspace; -mod features; diff --git a/src/cargo/core/package_id_spec.rs b/src/cargo/core/package_id_spec.rs index 0eef43800b7..798c746cc5b 100644 --- a/src/cargo/core/package_id_spec.rs +++ b/src/cargo/core/package_id_spec.rs @@ -8,7 +8,7 @@ use core::PackageId; use util::{ToSemver, ToUrl}; use util::errors::{CargoResult, CargoResultExt}; -/// Some or all of the data required to indentify a package: +/// Some or all of the data required to identify a package: /// /// 1. the package name (a `String`, required) /// 2. the package version (a `Version`, optional) diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs new file mode 100644 index 00000000000..fbca991d68f --- /dev/null +++ b/src/cargo/core/profiles.rs @@ -0,0 +1,400 @@ +use std::collections::HashSet; +use std::{cmp, fmt, hash}; + +use core::interning::InternedString; +use core::Shell; +use ops::CompileMode; +use util::lev_distance::lev_distance; +use util::toml::{StringOrBool, TomlProfile, U32OrBool}; +use util::CargoResult; + +/// Collection of all user profiles. +#[derive(Clone, Debug)] +pub struct Profiles { + dev: ProfileMaker, + release: ProfileMaker, + test: ProfileMaker, + bench: ProfileMaker, + doc: ProfileMaker, +} + +impl Profiles { + pub fn new( + dev: Option, + release: Option, + test: Option, + bench: Option, + doc: Option, + ) -> Profiles { + Profiles { + dev: ProfileMaker { + default: Profile::default_dev(), + toml: dev, + }, + release: ProfileMaker { + default: Profile::default_release(), + toml: release, + }, + test: ProfileMaker { + default: Profile::default_test(), + toml: test, + }, + bench: ProfileMaker { + default: Profile::default_bench(), + toml: bench, + }, + doc: ProfileMaker { + default: Profile::default_doc(), + toml: doc, + }, + } + } + + /// Retrieve the profile for a target. + /// `is_member` is whether or not this package is a member of the + /// workspace. + pub fn get_profile( + &self, + pkg_name: &str, + is_member: bool, + profile_for: ProfileFor, + mode: CompileMode, + release: bool, + ) -> Profile { + let maker = match mode { + CompileMode::Test => { + if release { + &self.bench + } else { + &self.test + } + } + CompileMode::Build + | CompileMode::Check { .. } + | CompileMode::Doctest + | CompileMode::RunCustomBuild => { + // Note: RunCustomBuild doesn't normally use this code path. + // `build_unit_profiles` normally ensures that it selects the + // ancestor's profile. However `cargo clean -p` can hit this + // path. + if release { + &self.release + } else { + &self.dev + } + } + CompileMode::Bench => &self.bench, + CompileMode::Doc { .. } => &self.doc, + }; + let mut profile = maker.profile_for(pkg_name, is_member, profile_for); + // `panic` should not be set for tests/benches, or any of their + // dependencies. + if profile_for == ProfileFor::TestDependency || mode.is_any_test() { + profile.panic = None; + } + profile + } + + /// The profile for *running* a `build.rs` script is only used for setting + /// a few environment variables. To ensure proper de-duplication of the + /// running `Unit`, this uses a stripped-down profile (so that unrelated + /// profile flags don't cause `build.rs` to needlessly run multiple + /// times). + pub fn get_profile_run_custom_build(&self, for_unit_profile: &Profile) -> Profile { + let mut result = Profile::default(); + result.debuginfo = for_unit_profile.debuginfo; + result.opt_level = for_unit_profile.opt_level; + result + } + + /// This returns a generic base profile. This is currently used for the + /// `[Finished]` line. It is not entirely accurate, since it doesn't + /// select for the package that was actually built. + pub fn base_profile(&self, release: bool) -> Profile { + if release { + self.release.profile_for("", true, ProfileFor::Any) + } else { + self.dev.profile_for("", true, ProfileFor::Any) + } + } + + /// Used to check for overrides for non-existing packages. + pub fn validate_packages( + &self, + shell: &mut Shell, + packages: &HashSet<&str>, + ) -> CargoResult<()> { + self.dev.validate_packages(shell, packages)?; + self.release.validate_packages(shell, packages)?; + self.test.validate_packages(shell, packages)?; + self.bench.validate_packages(shell, packages)?; + self.doc.validate_packages(shell, packages)?; + Ok(()) + } +} + +/// An object used for handling the profile override hierarchy. +/// +/// The precedence of profiles are (first one wins): +/// - [profile.dev.overrides.name] - A named package. +/// - [profile.dev.overrides."*"] - This cannot apply to workspace members. +/// - [profile.dev.build-override] - This can only apply to `build.rs` scripts +/// and their dependencies. +/// - [profile.dev] +/// - Default (hard-coded) values. +#[derive(Debug, Clone)] +struct ProfileMaker { + default: Profile, + toml: Option, +} + +impl ProfileMaker { + fn profile_for(&self, pkg_name: &str, is_member: bool, profile_for: ProfileFor) -> Profile { + let mut profile = self.default; + if let Some(ref toml) = self.toml { + merge_profile(&mut profile, toml); + if profile_for == ProfileFor::CustomBuild { + if let Some(ref build_override) = toml.build_override { + merge_profile(&mut profile, build_override); + } + } + if let Some(ref overrides) = toml.overrides { + if !is_member { + if let Some(star) = overrides.get("*") { + merge_profile(&mut profile, star); + } + } + if let Some(byname) = overrides.get(pkg_name) { + merge_profile(&mut profile, byname); + } + } + } + profile + } + + fn validate_packages(&self, shell: &mut Shell, packages: &HashSet<&str>) -> CargoResult<()> { + let toml = match self.toml { + Some(ref toml) => toml, + None => return Ok(()), + }; + let overrides = match toml.overrides { + Some(ref overrides) => overrides, + None => return Ok(()), + }; + for key in overrides.keys().filter(|k| k.as_str() != "*") { + if !packages.contains(key.as_str()) { + let suggestion = packages + .iter() + .map(|p| (lev_distance(key, p), p)) + .filter(|&(d, _)| d < 4) + .min_by_key(|p| p.0) + .map(|p| p.1); + match suggestion { + Some(p) => shell.warn(format!( + "package `{}` for profile override not found\n\nDid you mean `{}`?", + key, p + ))?, + None => { + shell.warn(format!("package `{}` for profile override not found", key))? + } + }; + } + } + Ok(()) + } +} + +fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { + if let Some(ref opt_level) = toml.opt_level { + profile.opt_level = InternedString::new(&opt_level.0); + } + match toml.lto { + Some(StringOrBool::Bool(b)) => profile.lto = Lto::Bool(b), + Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(InternedString::new(n)), + None => {} + } + if toml.codegen_units.is_some() { + profile.codegen_units = toml.codegen_units; + } + match toml.debug { + Some(U32OrBool::U32(debug)) => profile.debuginfo = Some(debug), + Some(U32OrBool::Bool(true)) => profile.debuginfo = Some(2), + Some(U32OrBool::Bool(false)) => profile.debuginfo = None, + None => {} + } + if let Some(debug_assertions) = toml.debug_assertions { + profile.debug_assertions = debug_assertions; + } + if let Some(rpath) = toml.rpath { + profile.rpath = rpath; + } + if let Some(ref panic) = toml.panic { + profile.panic = Some(InternedString::new(panic)); + } + if let Some(overflow_checks) = toml.overflow_checks { + profile.overflow_checks = overflow_checks; + } + if let Some(incremental) = toml.incremental { + profile.incremental = incremental; + } +} + +/// Profile settings used to determine which compiler flags to use for a +/// target. +#[derive(Debug, Clone, Copy, Eq)] +pub struct Profile { + pub name: &'static str, + pub opt_level: InternedString, + pub lto: Lto, + // None = use rustc default + pub codegen_units: Option, + pub debuginfo: Option, + pub debug_assertions: bool, + pub overflow_checks: bool, + pub rpath: bool, + pub incremental: bool, + pub panic: Option, +} + +impl Default for Profile { + fn default() -> Profile { + Profile { + name: "", + opt_level: InternedString::new("0"), + lto: Lto::Bool(false), + codegen_units: None, + debuginfo: None, + debug_assertions: false, + overflow_checks: false, + rpath: false, + incremental: false, + panic: None, + } + } +} + +impl fmt::Display for Profile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Profile({})", self.name) + } +} + +impl hash::Hash for Profile { + fn hash(&self, state: &mut H) + where + H: hash::Hasher, + { + self.comparable().hash(state); + } +} + +impl cmp::PartialEq for Profile { + fn eq(&self, other: &Self) -> bool { + self.comparable() == other.comparable() + } +} + +impl Profile { + fn default_dev() -> Profile { + Profile { + name: "dev", + debuginfo: Some(2), + debug_assertions: true, + overflow_checks: true, + incremental: true, + ..Profile::default() + } + } + + fn default_release() -> Profile { + Profile { + name: "release", + opt_level: InternedString::new("3"), + ..Profile::default() + } + } + + fn default_test() -> Profile { + Profile { + name: "test", + ..Profile::default_dev() + } + } + + fn default_bench() -> Profile { + Profile { + name: "bench", + ..Profile::default_release() + } + } + + fn default_doc() -> Profile { + Profile { + name: "doc", + ..Profile::default_dev() + } + } + + /// Compare all fields except `name`, which doesn't affect compilation. + /// This is necessary for `Unit` deduplication for things like "test" and + /// "dev" which are essentially the same. + fn comparable( + &self, + ) -> ( + &InternedString, + &Lto, + &Option, + &Option, + &bool, + &bool, + &bool, + &bool, + &Option, + ) { + ( + &self.opt_level, + &self.lto, + &self.codegen_units, + &self.debuginfo, + &self.debug_assertions, + &self.overflow_checks, + &self.rpath, + &self.incremental, + &self.panic, + ) + } +} + +/// The link-time-optimization setting. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum Lto { + /// False = no LTO + /// True = "Fat" LTO + Bool(bool), + /// Named LTO settings like "thin". + Named(InternedString), +} + +/// A flag used in `Unit` to indicate the purpose for the target. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum ProfileFor { + /// A general-purpose target. + Any, + /// A target for `build.rs` or any of its dependencies. This enables + /// `build-override` profiles for these targets. + CustomBuild, + /// A target that is a dependency of a test or benchmark. Currently this + /// enforces that the `panic` setting is not set. + TestDependency, +} + +impl ProfileFor { + pub fn all_values() -> &'static [ProfileFor] { + static ALL: [ProfileFor; 3] = [ + ProfileFor::Any, + ProfileFor::CustomBuild, + ProfileFor::TestDependency, + ]; + &ALL + } +} diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 72985eef3d5..cd5bc220a3c 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -1,14 +1,15 @@ use std::cell::RefCell; -use std::collections::BTreeMap; use std::collections::hash_map::{Entry, HashMap}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::slice; use glob::glob; use url::Url; +use core::profiles::Profiles; use core::registry::PackageRegistry; -use core::{Dependency, PackageIdSpec, Profile, Profiles}; +use core::{Dependency, PackageIdSpec}; use core::{EitherManifest, Package, SourceId, VirtualManifest}; use ops; use sources::PathSource; @@ -307,6 +308,11 @@ impl<'cfg> Workspace<'cfg> { } } + /// Returns true if the package is a member of the workspace. + pub fn is_member(&self, pkg: &Package) -> bool { + self.members().any(|p| p == pkg) + } + pub fn is_ephemeral(&self) -> bool { self.is_ephemeral } @@ -645,24 +651,10 @@ impl<'cfg> Workspace<'cfg> { } if let Some(ref root_manifest) = self.root_manifest { - let default_profiles = Profiles { - release: Profile::default_release(), - dev: Profile::default_dev(), - test: Profile::default_test(), - test_deps: Profile::default_dev(), - bench: Profile::default_bench(), - bench_deps: Profile::default_release(), - doc: Profile::default_doc(), - custom_build: Profile::default_custom_build(), - check: Profile::default_check(), - check_test: Profile::default_check_test(), - doctest: Profile::default_doctest(), - }; - for pkg in self.members() .filter(|p| p.manifest_path() != root_manifest) { - if pkg.manifest().profiles() != &default_profiles { + if pkg.manifest().original().has_profiles() { let message = &format!( "profiles for the non root package will be ignored, \ specify profiles at the workspace root:\n\ diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index a8c388752dd..72b1966990b 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -1,12 +1,13 @@ use std::fs; use std::path::Path; -use core::{Profiles, Workspace}; use core::compiler::{BuildConfig, Context, Kind, Unit}; -use util::Config; +use core::profiles::ProfileFor; +use core::Workspace; +use ops::{self, CompileMode}; use util::errors::{CargoResult, CargoResultExt}; use util::paths; -use ops; +use util::Config; pub struct CleanOptions<'a> { pub config: &'a Config, @@ -46,39 +47,33 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { // Generate all relevant `Unit` targets for this package for target in pkg.targets() { for kind in [Kind::Host, Kind::Target].iter() { - let Profiles { - ref release, - ref dev, - ref test, - ref bench, - ref doc, - ref custom_build, - ref test_deps, - ref bench_deps, - ref check, - ref check_test, - ref doctest, - } = *profiles; - let profiles = [ - release, - dev, - test, - bench, - doc, - custom_build, - test_deps, - bench_deps, - check, - check_test, - doctest, - ]; - for profile in profiles.iter() { - units.push(Unit { - pkg, - target, - profile, - kind: *kind, - }); + for mode in CompileMode::all_modes() { + for profile_for in ProfileFor::all_values() { + let profile = if mode.is_run_custom_build() { + profiles.get_profile_run_custom_build(&profiles.get_profile( + &pkg.name(), + ws.is_member(pkg), + *profile_for, + CompileMode::Build, + opts.release, + )) + } else { + profiles.get_profile( + &pkg.name(), + ws.is_member(pkg), + *profile_for, + *mode, + opts.release, + ) + }; + units.push(Unit { + pkg, + target, + profile, + kind: *kind, + mode: *mode, + }); + } } } } @@ -86,13 +81,21 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { let mut build_config = BuildConfig::new(config, Some(1), &opts.target, None)?; build_config.release = opts.release; - let mut cx = Context::new(ws, &resolve, &packages, opts.config, build_config, profiles)?; + let mut cx = Context::new( + ws, + &resolve, + &packages, + opts.config, + build_config, + profiles, + None, + )?; cx.prepare_units(None, &units)?; for unit in units.iter() { rm_rf(&cx.files().fingerprint_dir(unit), config)?; if unit.target.is_custom_build() { - if unit.profile.run_custom_build { + if unit.mode.is_run_custom_build() { rm_rf(&cx.files().build_script_out_dir(unit), config)?; } else { rm_rf(&cx.files().build_script_dir(unit), config)?; diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 85d58bd21fc..62465978471 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -26,14 +26,15 @@ use std::collections::HashSet; use std::path::{Path, PathBuf}; use std::sync::Arc; -use core::{Package, Source, Target}; -use core::{PackageId, PackageIdSpec, Profile, Profiles, TargetKind, Workspace}; use core::compiler::{BuildConfig, Compilation, Context, DefaultExecutor, Executor}; use core::compiler::{Kind, Unit}; +use core::profiles::{ProfileFor, Profiles}; use core::resolver::{Method, Resolve}; +use core::{Package, Source, Target}; +use core::{PackageId, PackageIdSpec, TargetKind, Workspace}; use ops; use util::config::Config; -use util::{profile, CargoResult, CargoResultExt}; +use util::{lev_distance, profile, CargoResult, CargoResultExt}; /// Contains information about how a package should be compiled. #[derive(Debug)] @@ -96,14 +97,88 @@ impl<'a> CompileOptions<'a> { } } -#[derive(Clone, Copy, PartialEq, Debug)] +/// The general "mode" of what to do. +/// This is used for two purposes. The commands themselves pass this in to +/// `compile_ws` to tell it the general execution strategy. This influences +/// the default targets selected. The other use is in the `Unit` struct +/// to indicate what is being done with a specific target. +#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)] pub enum CompileMode { + /// A target being built for a test. Test, + /// Building a target with `rustc` (lib or bin). Build, + /// Building a target with `rustc` to emit `rmeta` metadata only. If + /// `test` is true, then it is also compiled with `--test` to check it like + /// a test. Check { test: bool }, + /// Used to indicate benchmarks should be built. This is not used in + /// `Target` because it is essentially the same as `Test` (indicating + /// `--test` should be passed to rustc) and by using `Test` instead it + /// allows some de-duping of Units to occur. Bench, + /// A target that will be documented with `rustdoc`. + /// If `deps` is true, then it will also document all dependencies. Doc { deps: bool }, + /// A target that will be tested with `rustdoc`. Doctest, + /// A marker for Units that represent the execution of a `build.rs` + /// script. + RunCustomBuild, +} + +impl CompileMode { + /// Returns true if the unit is being checked. + pub fn is_check(&self) -> bool { + match *self { + CompileMode::Check { .. } => true, + _ => false, + } + } + + /// Returns true if this is a doc or doctest. Be careful using this. + /// Although both run rustdoc, the dependencies for those two modes are + /// very different. + pub fn is_doc(&self) -> bool { + match *self { + CompileMode::Doc { .. } | CompileMode::Doctest => true, + _ => false, + } + } + + /// Returns true if this is any type of test (test, benchmark, doctest, or + /// check-test). + pub fn is_any_test(&self) -> bool { + match *self { + CompileMode::Test + | CompileMode::Bench + | CompileMode::Check { test: true } + | CompileMode::Doctest => true, + _ => false, + } + } + + /// Returns true if this is the *execution* of a `build.rs` script. + pub fn is_run_custom_build(&self) -> bool { + *self == CompileMode::RunCustomBuild + } + + /// List of all modes (currently used by `cargo clean -p` for computing + /// all possible outputs). + pub fn all_modes() -> &'static [CompileMode] { + static ALL: [CompileMode; 9] = [ + CompileMode::Test, + CompileMode::Build, + CompileMode::Check { test: true }, + CompileMode::Check { test: false }, + CompileMode::Bench, + CompileMode::Doc { deps: true }, + CompileMode::Doc { deps: false }, + CompileMode::Doctest, + CompileMode::RunCustomBuild, + ]; + &ALL + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -269,11 +344,11 @@ pub fn compile_ws<'a>( build_config.release = release; build_config.test = mode == CompileMode::Test || mode == CompileMode::Bench; build_config.json_messages = message_format == MessageFormat::Json; - if let CompileMode::Doc { deps } = mode { - build_config.doc_all = deps; - } - - let profiles = ws.profiles(); + let default_arch_kind = if build_config.requested_target.is_some() { + Kind::Target + } else { + Kind::Host + }; let specs = spec.into_package_id_specs(ws)?; let features = Method::split_features(features); @@ -296,62 +371,51 @@ pub fn compile_ws<'a>( }) .collect::>>()?; - let mut general_targets = Vec::new(); - let mut package_targets = Vec::new(); - - match (target_rustc_args, target_rustdoc_args) { - (&Some(..), _) | (_, &Some(..)) if to_builds.len() != 1 => { - panic!("`rustc` and `rustdoc` should not accept multiple `-p` flags") - } - (&Some(ref args), _) => { - let all_features = - resolve_all_features(&resolve_with_overrides, to_builds[0].package_id()); - let targets = - generate_targets(to_builds[0], profiles, mode, filter, &all_features, release)?; - if targets.len() == 1 { - let (target, profile) = targets[0]; - let mut profile = profile.clone(); - profile.rustc_args = Some(args.to_vec()); - general_targets.push((target, profile)); - } else { - bail!( - "extra arguments to `rustc` can only be passed to one \ - target, consider filtering\nthe package by passing \ - e.g. `--lib` or `--bin NAME` to specify a single target" - ) - } - } - (&None, &Some(ref args)) => { - let all_features = - resolve_all_features(&resolve_with_overrides, to_builds[0].package_id()); - let targets = - generate_targets(to_builds[0], profiles, mode, filter, &all_features, release)?; - if targets.len() == 1 { - let (target, profile) = targets[0]; - let mut profile = profile.clone(); - profile.rustdoc_args = Some(args.to_vec()); - general_targets.push((target, profile)); - } else { - bail!( - "extra arguments to `rustdoc` can only be passed to one \ - target, consider filtering\nthe package by passing e.g. \ - `--lib` or `--bin NAME` to specify a single target" - ) - } - } - (&None, &None) => for &to_build in to_builds.iter() { - let all_features = resolve_all_features(&resolve_with_overrides, to_build.package_id()); - let targets = - generate_targets(to_build, profiles, mode, filter, &all_features, release)?; - package_targets.push((to_build, targets)); - }, + let (extra_args, extra_args_name) = match (target_rustc_args, target_rustdoc_args) { + (&Some(ref args), _) => (Some(args.clone()), "rustc"), + (_, &Some(ref args)) => (Some(args.clone()), "rustdoc"), + _ => (None, ""), }; - for &(target, ref profile) in &general_targets { - for &to_build in to_builds.iter() { - package_targets.push((to_build, vec![(target, profile)])); + if extra_args.is_some() && to_builds.len() != 1 { + panic!( + "`{}` should not accept multiple `-p` flags", + extra_args_name + ); + } + + let profiles = ws.profiles(); + let package_names = packages + .package_ids() + .map(|pid| pid.name().as_str()) + .collect::>(); + profiles.validate_packages(&mut config.shell(), &package_names)?; + + let mut extra_compiler_args = None; + + let units = generate_targets( + ws, + profiles, + &to_builds, + filter, + default_arch_kind, + mode, + &resolve_with_overrides, + release, + )?; + + if let Some(args) = extra_args { + if units.len() != 1 { + bail!( + "extra arguments to `{}` can only be passed to one \ + target, consider filtering\nthe package by passing \ + e.g. `--lib` or `--bin NAME` to specify a single target", + extra_args_name + ); } + extra_compiler_args = Some((units[0], args)); } + let mut ret = { let _p = profile::start("compiling"); let mut cx = Context::new( @@ -361,50 +425,14 @@ pub fn compile_ws<'a>( config, build_config, profiles, + extra_compiler_args, )?; - let units = package_targets - .iter() - .flat_map(|&(pkg, ref targets)| { - let default_kind = if cx.build_config.requested_target.is_some() { - Kind::Target - } else { - Kind::Host - }; - targets.iter().map(move |&(target, profile)| Unit { - pkg, - target, - profile, - kind: if target.for_host() { - Kind::Host - } else { - default_kind - }, - }) - }) - .collect::>(); cx.compile(&units, export_dir.clone(), &exec)? }; ret.to_doc_test = to_builds.into_iter().cloned().collect(); return Ok(ret); - - fn resolve_all_features( - resolve_with_overrides: &Resolve, - package_id: &PackageId, - ) -> HashSet { - let mut features = resolve_with_overrides.features(package_id).clone(); - - // Include features enabled for use by dependencies so targets can also use them with the - // required-features field when deciding whether to be built or skipped. - for (dep, _) in resolve_with_overrides.deps(package_id) { - for feature in resolve_with_overrides.features(dep) { - features.insert(dep.name().to_string() + "/" + feature); - } - } - - features - } } impl FilterRule { @@ -496,6 +524,7 @@ impl CompileFilter { .. } => examples.is_specific() || tests.is_specific() || benches.is_specific(), }, + CompileMode::RunCustomBuild => panic!("Invalid mode"), } } @@ -533,307 +562,332 @@ impl CompileFilter { } } -#[derive(Clone, Copy, Debug)] -struct BuildProposal<'a> { - target: &'a Target, - profile: &'a Profile, - required: bool, -} - -fn generate_default_targets<'a>( +/// Generates all the base targets for the packages the user has requested to +/// compile. Dependencies for these targets are computed later in +/// `unit_dependencies`. +fn generate_targets<'a>( + ws: &Workspace, + profiles: &Profiles, + packages: &[&'a Package], + filter: &CompileFilter, + default_arch_kind: Kind, mode: CompileMode, - targets: &'a [Target], - profile: &'a Profile, - dep: &'a Profile, - required_features_filterable: bool, -) -> Vec> { - match mode { - CompileMode::Bench => targets - .iter() - .filter(|t| t.benched()) - .map(|t| BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }) - .collect::>(), - CompileMode::Test => { - let mut base = targets - .iter() - .filter(|t| t.tested()) - .map(|t| BuildProposal { - target: t, - profile: if t.is_example() { dep } else { profile }, - required: !required_features_filterable, - }) - .collect::>(); - - // Always compile the library if we're testing everything as - // it'll be needed for doctests - if let Some(t) = targets.iter().find(|t| t.is_lib()) { - if t.doctested() { - base.push(BuildProposal { - target: t, - profile: dep, - required: !required_features_filterable, - }); + resolve: &Resolve, + release: bool, +) -> CargoResult>> { + let mut units = Vec::new(); + + // Helper for creating a Unit struct. + let new_unit = |pkg: &'a Package, target: &'a Target, target_mode: CompileMode| { + let profile_for = if mode.is_any_test() { + // NOTE: The ProfileFor here is subtle. If you have a profile + // with `panic` set, the `panic` flag is cleared for + // tests/benchmarks and their dependencies. If we left this + // as an "Any" profile, then the lib would get compiled three + // times (once with panic, once without, and once with + // --test). + // + // This would cause a problem for Doc tests, which would fail + // because `rustdoc` would attempt to link with both libraries + // at the same time. Also, it's probably not important (or + // even desirable?) for rustdoc to link with a lib with + // `panic` set. + // + // As a consequence, Examples and Binaries get compiled + // without `panic` set. This probably isn't a bad deal. + // + // Forcing the lib to be compiled three times during `cargo + // test` is probably also not desirable. + ProfileFor::TestDependency + } else { + ProfileFor::Any + }; + let target_mode = match target_mode { + CompileMode::Test => { + if target.is_example() { + // Examples are included as regular binaries to verify + // that they compile. + CompileMode::Build + } else { + CompileMode::Test } } - base + CompileMode::Build => match *target.kind() { + TargetKind::Test => CompileMode::Test, + TargetKind::Bench => CompileMode::Bench, + _ => CompileMode::Build, + }, + _ => target_mode, + }; + // Plugins or proc-macro should be built for the host. + let kind = if target.for_host() { + Kind::Host + } else { + default_arch_kind + }; + let profile = profiles.get_profile( + &pkg.name(), + ws.is_member(pkg), + profile_for, + target_mode, + release, + ); + // Once the profile has been selected for benchmarks, we don't need to + // distinguish between benches and tests. Switching the mode allows + // de-duplication of units that are essentially identical. For + // example, `cargo build --all-targets --release` creates the units + // (lib profile:bench, mode:test) and (lib profile:bench, mode:bench) + // and since these are the same, we want them to be de-duped in + // `unit_dependencies`. + let target_mode = match target_mode { + CompileMode::Bench => CompileMode::Test, + _ => target_mode, + }; + Unit { + pkg, + target, + profile, + kind, + mode: target_mode, } - CompileMode::Build | CompileMode::Check { .. } => targets - .iter() - .filter(|t| t.is_bin() || t.is_lib()) - .map(|t| BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }) - .collect(), - CompileMode::Doc { .. } => targets - .iter() - .filter(|t| { - t.documented() - && (!t.is_bin() || !targets.iter().any(|l| l.is_lib() && l.name() == t.name())) - }) - .map(|t| BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }) - .collect(), - CompileMode::Doctest => { - if let Some(t) = targets.iter().find(|t| t.is_lib()) { - if t.doctested() { - return vec![ - BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }, - ]; + }; + + for pkg in packages { + let features = resolve_all_features(resolve, pkg.package_id()); + // Create a list of proposed targets. The `bool` value indicates + // whether or not all required features *must* be present. If false, + // and the features are not available, then it will be silently + // skipped. Generally, targets specified by name (`--bin foo`) are + // required, all others can be silently skipped if features are + // missing. + let mut proposals: Vec<(Unit<'a>, bool)> = Vec::new(); + + match *filter { + CompileFilter::Default { + required_features_filterable, + } => { + let default_units = generate_default_targets(pkg.targets(), mode) + .iter() + .map(|t| (new_unit(pkg, t, mode), !required_features_filterable)) + .collect::>(); + proposals.extend(default_units); + if mode == CompileMode::Test { + // Include the lib as it will be required for doctests. + if let Some(t) = pkg.targets().iter().find(|t| t.is_lib() && t.doctested()) { + proposals.push((new_unit(pkg, t, CompileMode::Build), false)); + } } } + CompileFilter::Only { + all_targets, + lib, + ref bins, + ref examples, + ref tests, + ref benches, + } => { + if lib { + if let Some(target) = pkg.targets().iter().find(|t| t.is_lib()) { + proposals.push((new_unit(pkg, target, mode), false)); + } else if !all_targets { + bail!("no library targets found") + } + } + // If --tests was specified, add all targets that would be + // generated by `cargo test`. + let test_filter = match *tests { + FilterRule::All => Target::tested, + FilterRule::Just(_) => Target::is_test, + }; + let test_mode = match mode { + CompileMode::Build => CompileMode::Test, + CompileMode::Check { .. } => CompileMode::Check { test: true }, + _ => mode, + }; + // If --benches was specified, add all targets that would be + // generated by `cargo bench`. + let bench_filter = match *benches { + FilterRule::All => Target::benched, + FilterRule::Just(_) => Target::is_bench, + }; + let bench_mode = match mode { + CompileMode::Build => CompileMode::Bench, + CompileMode::Check { .. } => CompileMode::Check { test: true }, + _ => mode, + }; - Vec::new() + proposals.extend( + list_rule_targets(pkg, bins, "bin", Target::is_bin)? + .into_iter() + .map(|(t, required)| (new_unit(pkg, t, mode), required)) + .chain( + list_rule_targets(pkg, examples, "example", Target::is_example)? + .into_iter() + .map(|(t, required)| (new_unit(pkg, t, mode), required)), + ) + .chain( + list_rule_targets(pkg, tests, "test", test_filter)? + .into_iter() + .map(|(t, required)| (new_unit(pkg, t, test_mode), required)), + ) + .chain( + list_rule_targets(pkg, benches, "bench", bench_filter)? + .into_iter() + .map(|(t, required)| (new_unit(pkg, t, bench_mode), required)), + ) + .collect::>(), + ); + } } - } -} -/// Given a filter rule and some context, propose a list of targets -fn propose_indicated_targets<'a>( - pkg: &'a Package, - rule: &FilterRule, - desc: &'static str, - is_expected_kind: fn(&Target) -> bool, - profile: &'a Profile, -) -> CargoResult>> { - match *rule { - FilterRule::All => { - let result = pkg.targets() - .iter() - .filter(|t| is_expected_kind(t)) - .map(|t| BuildProposal { - target: t, - profile, - required: false, - }); - Ok(result.collect()) + // If any integration tests/benches are being run, make sure that + // binaries are built as well. + if !mode.is_check() && proposals.iter().any(|&(ref unit, _)| { + unit.mode.is_any_test() && (unit.target.is_test() || unit.target.is_bench()) + }) { + proposals.extend( + pkg.targets() + .iter() + .filter(|t| t.is_bin()) + .map(|t| (new_unit(pkg, t, CompileMode::Build), false)), + ); } - FilterRule::Just(ref names) => { - let mut targets = Vec::new(); - for name in names { - let target = pkg.targets() + + // Only include targets that are libraries or have all required + // features available. + for (unit, required) in proposals { + let unavailable_features = match unit.target.required_features() { + Some(rf) => rf.iter().filter(|f| !features.contains(*f)).collect(), + None => Vec::new(), + }; + if unit.target.is_lib() || unavailable_features.is_empty() { + units.push(unit); + } else if required { + let required_features = unit.target.required_features().unwrap(); + let quoted_required_features: Vec = required_features .iter() - .find(|t| t.name() == *name && is_expected_kind(t)); - let t = match target { - Some(t) => t, - None => { - let suggestion = pkg.find_closest_target(name, is_expected_kind); - match suggestion { - Some(s) => { - let suggested_name = s.name(); - bail!( - "no {} target named `{}`\n\nDid you mean `{}`?", - desc, - name, - suggested_name - ) - } - None => bail!("no {} target named `{}`", desc, name), - } - } - }; - debug!("found {} `{}`", desc, name); - targets.push(BuildProposal { - target: t, - profile, - required: true, - }); + .map(|s| format!("`{}`", s)) + .collect(); + bail!( + "target `{}` requires the features: {}\n\ + Consider enabling them by passing e.g. `--features=\"{}\"`", + unit.target.name(), + quoted_required_features.join(", "), + required_features.join(" ") + ); } - Ok(targets) + // else, silently skip target. } } + Ok(units) } -/// Collect the targets that are libraries or have all required features available. -fn filter_compatible_targets<'a>( - mut proposals: Vec>, - features: &HashSet, -) -> CargoResult> { - let mut compatible = Vec::with_capacity(proposals.len()); - for proposal in proposals.drain(..) { - let unavailable_features = match proposal.target.required_features() { - Some(rf) => rf.iter().filter(|f| !features.contains(*f)).collect(), - None => Vec::new(), - }; - if proposal.target.is_lib() || unavailable_features.is_empty() { - compatible.push((proposal.target, proposal.profile)); - } else if proposal.required { - let required_features = proposal.target.required_features().unwrap(); - let quoted_required_features: Vec = required_features +fn resolve_all_features( + resolve_with_overrides: &Resolve, + package_id: &PackageId, +) -> HashSet { + let mut features = resolve_with_overrides.features(package_id).clone(); + + // Include features enabled for use by dependencies so targets can also use them with the + // required-features field when deciding whether to be built or skipped. + for (dep, _) in resolve_with_overrides.deps(package_id) { + for feature in resolve_with_overrides.features(dep) { + features.insert(dep.name().to_string() + "/" + feature); + } + } + + features +} + +/// Given a list of all targets for a package, filters out only the targets +/// that are automatically included when the user doesn't specify any targets. +fn generate_default_targets(targets: &[Target], mode: CompileMode) -> Vec<&Target> { + match mode { + CompileMode::Bench => targets.iter().filter(|t| t.benched()).collect(), + CompileMode::Test => targets + .iter() + .filter(|t| t.tested() || t.is_example()) + .collect(), + CompileMode::Build | CompileMode::Check { .. } => targets + .iter() + .filter(|t| t.is_bin() || t.is_lib()) + .collect(), + CompileMode::Doc { .. } => { + // `doc` does lib and bins (bin with same name as lib is skipped). + targets .iter() - .map(|s| format!("`{}`", s)) - .collect(); - bail!( - "target `{}` requires the features: {}\n\ - Consider enabling them by passing e.g. `--features=\"{}\"`", - proposal.target.name(), - quoted_required_features.join(", "), - required_features.join(" ") - ); + .filter(|t| { + t.documented() + && (!t.is_bin() + || !targets.iter().any(|l| l.is_lib() && l.name() == t.name())) + }) + .collect() } + CompileMode::Doctest => { + // `test --doc`` + targets + .iter() + .find(|t| t.is_lib() && t.doctested()) + .into_iter() + .collect() + } + CompileMode::RunCustomBuild => panic!("Invalid mode"), } - Ok(compatible) } -/// Given the configuration for a build, this function will generate all -/// target/profile combinations needed to be built. -fn generate_targets<'a>( +/// Returns a list of targets based on command-line target selection flags. +/// The return value is a list of `(Target, bool)` pairs. The `bool` value +/// indicates whether or not all required features *must* be present. +fn list_rule_targets<'a>( pkg: &'a Package, - profiles: &'a Profiles, - mode: CompileMode, - filter: &CompileFilter, - features: &HashSet, - release: bool, -) -> CargoResult> { - let build = if release { - &profiles.release - } else { - &profiles.dev - }; - let test = if release { - &profiles.bench - } else { - &profiles.test - }; - let profile = match mode { - CompileMode::Test => test, - CompileMode::Bench => &profiles.bench, - CompileMode::Build => build, - CompileMode::Check { test: false } => &profiles.check, - CompileMode::Check { test: true } => &profiles.check_test, - CompileMode::Doc { .. } => &profiles.doc, - CompileMode::Doctest => &profiles.doctest, - }; - - let test_profile = if profile.check { - &profiles.check_test - } else if mode == CompileMode::Build { - test - } else { - profile - }; - - let bench_profile = if profile.check { - &profiles.check_test - } else if mode == CompileMode::Build { - &profiles.bench - } else { - profile - }; + rule: &FilterRule, + target_desc: &'static str, + is_expected_kind: fn(&Target) -> bool, +) -> CargoResult> { + match *rule { + FilterRule::All => Ok(pkg.targets() + .iter() + .filter(|t| is_expected_kind(t)) + .map(|t| (t, false)) + .collect()), + FilterRule::Just(ref names) => names + .iter() + .map(|name| find_target(pkg, name, target_desc, is_expected_kind)) + .collect(), + } +} - let targets = match *filter { - CompileFilter::Default { - required_features_filterable, - } => { - let deps = if release { - &profiles.bench_deps - } else { - &profiles.test_deps - }; - generate_default_targets( - mode, - pkg.targets(), - profile, - deps, - required_features_filterable, - ) - } - CompileFilter::Only { - all_targets, - lib, - ref bins, - ref examples, - ref tests, - ref benches, - } => { - let mut targets = Vec::new(); - - if lib { - if let Some(t) = pkg.targets().iter().find(|t| t.is_lib()) { - targets.push(BuildProposal { - target: t, - profile, - required: true, - }); - } else if !all_targets { - bail!("no library targets found") - } +/// Find the target for a specifically named target. +fn find_target<'a>( + pkg: &'a Package, + target_name: &str, + target_desc: &'static str, + is_expected_kind: fn(&Target) -> bool, +) -> CargoResult<(&'a Target, bool)> { + match pkg.targets() + .iter() + .find(|t| t.name() == target_name && is_expected_kind(t)) + { + // When a target is specified by name, required features *must* be + // available. + Some(t) => Ok((t, true)), + None => { + let suggestion = pkg.targets() + .iter() + .filter(|t| is_expected_kind(t)) + .map(|t| (lev_distance(target_name, t.name()), t)) + .filter(|&(d, _)| d < 4) + .min_by_key(|t| t.0) + .map(|t| t.1); + match suggestion { + Some(s) => bail!( + "no {} target named `{}`\n\nDid you mean `{}`?", + target_desc, + target_name, + s.name() + ), + None => bail!("no {} target named `{}`", target_desc, target_name), } - targets.append(&mut propose_indicated_targets( - pkg, - bins, - "bin", - Target::is_bin, - profile, - )?); - targets.append(&mut propose_indicated_targets( - pkg, - examples, - "example", - Target::is_example, - profile, - )?); - // If --tests was specified, add all targets that would be - // generated by `cargo test`. - let test_filter = match *tests { - FilterRule::All => Target::tested, - FilterRule::Just(_) => Target::is_test, - }; - targets.append(&mut propose_indicated_targets( - pkg, - tests, - "test", - test_filter, - test_profile, - )?); - // If --benches was specified, add all targets that would be - // generated by `cargo bench`. - let bench_filter = match *benches { - FilterRule::All => Target::benched, - FilterRule::Just(_) => Target::is_bench, - }; - targets.append(&mut propose_indicated_targets( - pkg, - benches, - "bench", - bench_filter, - bench_profile, - )?); - targets } - }; - - filter_compatible_targets(targets, features) + } } diff --git a/src/cargo/util/machine_message.rs b/src/cargo/util/machine_message.rs index d2225eacd0b..3104d4b6060 100644 --- a/src/cargo/util/machine_message.rs +++ b/src/cargo/util/machine_message.rs @@ -1,7 +1,7 @@ use serde::ser; use serde_json::{self, Value}; -use core::{PackageId, Profile, Target}; +use core::{PackageId, Target}; pub trait Message: ser::Serialize { fn reason(&self) -> &str; @@ -30,7 +30,7 @@ impl<'a> Message for FromCompiler<'a> { pub struct Artifact<'a> { pub package_id: &'a PackageId, pub target: &'a Target, - pub profile: &'a Profile, + pub profile: ArtifactProfile, pub features: Vec, pub filenames: Vec, pub fresh: bool, @@ -42,6 +42,18 @@ impl<'a> Message for Artifact<'a> { } } +/// This is different from the regular `Profile` to maintain backwards +/// compatibility (in particular, `test` is no longer in `Profile`, but we +/// still want it to be included here). +#[derive(Serialize)] +pub struct ArtifactProfile { + pub opt_level: &'static str, + pub debuginfo: Option, + pub debug_assertions: bool, + pub overflow_checks: bool, + pub test: bool, +} + #[derive(Serialize)] pub struct BuildScript<'a> { pub package_id: &'a PackageId, diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 635e7749396..28cebea35d3 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -6,21 +6,22 @@ use std::rc::Rc; use std::str; use semver::{self, VersionReq}; -use serde::ser; use serde::de::{self, Deserialize}; +use serde::ser; use serde_ignored; use toml; use url::Url; -use core::{GitReference, PackageIdSpec, Profiles, SourceId, WorkspaceConfig, WorkspaceRootConfig}; +use core::dependency::{Kind, Platform}; +use core::manifest::{LibKind, ManifestMetadata}; +use core::profiles::Profiles; use core::{Dependency, Manifest, PackageId, Summary, Target}; use core::{Edition, EitherManifest, Feature, Features, VirtualManifest}; -use core::dependency::{Kind, Platform}; -use core::manifest::{LibKind, Lto, ManifestMetadata, Profile}; +use core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig}; use sources::CRATES_IO; +use util::errors::{CargoError, CargoResult, CargoResultExt}; use util::paths; use util::{self, Config, ToUrl}; -use util::errors::{CargoError, CargoResult, CargoResultExt}; mod targets; use self::targets::targets; @@ -243,8 +244,29 @@ pub struct TomlProfiles { release: Option, } +impl TomlProfiles { + fn validate(&self, features: &Features, warnings: &mut Vec) -> CargoResult<()> { + if let Some(ref test) = self.test { + test.validate("test", features, warnings)?; + } + if let Some(ref doc) = self.doc { + doc.validate("doc", features, warnings)?; + } + if let Some(ref bench) = self.bench { + bench.validate("bench", features, warnings)?; + } + if let Some(ref dev) = self.dev { + dev.validate("dev", features, warnings)?; + } + if let Some(ref release) = self.release { + release.validate("release", features, warnings)?; + } + Ok(()) + } +} + #[derive(Clone, Debug)] -pub struct TomlOptLevel(String); +pub struct TomlOptLevel(pub String); impl<'de> de::Deserialize<'de> for TomlOptLevel { fn deserialize(d: D) -> Result @@ -347,20 +369,78 @@ impl<'de> de::Deserialize<'de> for U32OrBool { } #[derive(Deserialize, Serialize, Clone, Debug, Default)] +#[serde(rename_all = "kebab-case")] pub struct TomlProfile { - #[serde(rename = "opt-level")] - opt_level: Option, - lto: Option, - #[serde(rename = "codegen-units")] - codegen_units: Option, - debug: Option, - #[serde(rename = "debug-assertions")] - debug_assertions: Option, - rpath: Option, - panic: Option, - #[serde(rename = "overflow-checks")] - overflow_checks: Option, - incremental: Option, + pub opt_level: Option, + pub lto: Option, + pub codegen_units: Option, + pub debug: Option, + pub debug_assertions: Option, + pub rpath: Option, + pub panic: Option, + pub overflow_checks: Option, + pub incremental: Option, + pub overrides: Option>, + pub build_override: Option>, +} + +impl TomlProfile { + fn validate( + &self, + name: &str, + features: &Features, + warnings: &mut Vec, + ) -> CargoResult<()> { + if let Some(ref profile) = self.build_override { + features.require(Feature::profile_overrides())?; + profile.validate_override()?; + } + if let Some(ref override_map) = self.overrides { + features.require(Feature::profile_overrides())?; + for profile in override_map.values() { + profile.validate_override()?; + } + } + + match name { + "dev" | "release" => {} + _ => { + if self.overrides.is_some() || self.build_override.is_some() { + bail!( + "Profile overrides may only be specified for \ + `dev` or `release` profile, not `{}`.", + name + ); + } + } + } + + match name { + "test" | "bench" => { + if self.panic.is_some() { + warnings.push(format!("`panic` setting is ignored for `{}` profile", name)) + } + } + _ => {} + } + Ok(()) + } + + fn validate_override(&self) -> CargoResult<()> { + if self.overrides.is_some() || self.build_override.is_some() { + bail!("Profile overrides cannot be nested."); + } + if self.panic.is_some() { + bail!("`panic` may not be specified in a profile override.") + } + if self.lto.is_some() { + bail!("`lto` may not be specified in a profile override.") + } + if self.rpath.is_some() { + bail!("`rpath` may not be specified in a profile override.") + } + Ok(()) + } } #[derive(Clone, Debug, Serialize)] @@ -794,6 +874,9 @@ impl TomlManifest { `[workspace]`, only one can be specified" ), }; + if let Some(ref profiles) = me.profile { + profiles.validate(&features, &mut warnings)?; + } let profiles = build_profiles(&me.profile); let publish = match project.publish { Some(VecStringOrBool::VecString(ref vecstring)) => { @@ -1004,6 +1087,10 @@ impl TomlManifest { } } } + + pub fn has_profiles(&self) -> bool { + self.profile.is_some() + } } /// Will check a list of build targets, and make sure the target names are unique within a vector. @@ -1279,98 +1366,11 @@ impl fmt::Debug for PathValue { fn build_profiles(profiles: &Option) -> Profiles { let profiles = profiles.as_ref(); - let mut profiles = Profiles { - release: merge( - Profile::default_release(), - profiles.and_then(|p| p.release.as_ref()), - ), - dev: merge( - Profile::default_dev(), - profiles.and_then(|p| p.dev.as_ref()), - ), - test: merge( - Profile::default_test(), - profiles.and_then(|p| p.test.as_ref()), - ), - test_deps: merge( - Profile::default_dev(), - profiles.and_then(|p| p.dev.as_ref()), - ), - bench: merge( - Profile::default_bench(), - profiles.and_then(|p| p.bench.as_ref()), - ), - bench_deps: merge( - Profile::default_release(), - profiles.and_then(|p| p.release.as_ref()), - ), - doc: merge( - Profile::default_doc(), - profiles.and_then(|p| p.doc.as_ref()), - ), - custom_build: Profile::default_custom_build(), - check: merge( - Profile::default_check(), - profiles.and_then(|p| p.dev.as_ref()), - ), - check_test: merge( - Profile::default_check_test(), - profiles.and_then(|p| p.dev.as_ref()), - ), - doctest: Profile::default_doctest(), - }; - // The test/bench targets cannot have panic=abort because they'll all get - // compiled with --test which requires the unwind runtime currently - profiles.test.panic = None; - profiles.bench.panic = None; - profiles.test_deps.panic = None; - profiles.bench_deps.panic = None; - return profiles; - - fn merge(profile: Profile, toml: Option<&TomlProfile>) -> Profile { - let &TomlProfile { - ref opt_level, - ref lto, - codegen_units, - ref debug, - debug_assertions, - rpath, - ref panic, - ref overflow_checks, - ref incremental, - } = match toml { - Some(toml) => toml, - None => return profile, - }; - let debug = match *debug { - Some(U32OrBool::U32(debug)) => Some(Some(debug)), - Some(U32OrBool::Bool(true)) => Some(Some(2)), - Some(U32OrBool::Bool(false)) => Some(None), - None => None, - }; - Profile { - opt_level: opt_level - .clone() - .unwrap_or(TomlOptLevel(profile.opt_level)) - .0, - lto: match *lto { - Some(StringOrBool::Bool(b)) => Lto::Bool(b), - Some(StringOrBool::String(ref n)) => Lto::Named(n.clone()), - None => profile.lto, - }, - codegen_units, - rustc_args: None, - rustdoc_args: None, - debuginfo: debug.unwrap_or(profile.debuginfo), - debug_assertions: debug_assertions.unwrap_or(profile.debug_assertions), - overflow_checks: overflow_checks.unwrap_or(profile.overflow_checks), - rpath: rpath.unwrap_or(profile.rpath), - test: profile.test, - doc: profile.doc, - run_custom_build: profile.run_custom_build, - check: profile.check, - panic: panic.clone().or(profile.panic), - incremental: incremental.unwrap_or(profile.incremental), - } - } + Profiles::new( + profiles.and_then(|p| p.dev.clone()), + profiles.and_then(|p| p.release.clone()), + profiles.and_then(|p| p.test.clone()), + profiles.and_then(|p| p.bench.clone()), + profiles.and_then(|p| p.doc.clone()), + ) } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 1a305ba1fd9..483e5e2537e 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -192,3 +192,38 @@ cargo-features = ["edition"] ... rust = "2018" ``` + + +### Profile Overrides +* Tracking Issue: [rust-lang/rust#48683](https://github.com/rust-lang/rust/issues/48683) +* RFC: [#2282](https://github.com/rust-lang/rfcs/blob/master/text/2282-profile-dependencies.md) + +Profiles can be overridden for specific packages and custom build scripts. +The general format looks like this: + +```toml +cargo-features = ["profile-overrides"] + +[package] +... + +[profile.dev] +opt-level = 0 +debug = true + +# the `image` crate will be compiled with -Copt-level=3 +[profile.dev.overrides.image] +opt-level = 3 + +# All dependencies (but not this crate itself or any workspace member) +# will be compiled with -Copt-level=2 . This includes build dependencies. +[profile.dev.overrides."*"] +opt-level = 2 + +# Build scripts and their dependencies will be compiled with -Copt-level=3 +# By default, build scripts use the same rules as the rest of the profile +[profile.dev.build-override] +opt-level = 3 +``` + +Overrides can only be specified for dev and release profiles. diff --git a/tests/testsuite/cargotest/support/mod.rs b/tests/testsuite/cargotest/support/mod.rs index 115f5be47b6..4ae3aeabc51 100644 --- a/tests/testsuite/cargotest/support/mod.rs +++ b/tests/testsuite/cargotest/support/mod.rs @@ -9,26 +9,28 @@ use std::process::Output; use std::str; use std::usize; -use serde_json::{self, Value}; -use url::Url; -use hamcrest as ham; use cargo::util::ProcessBuilder; use cargo::util::ProcessError; +use hamcrest as ham; +use serde_json::{self, Value}; +use url::Url; use cargotest::support::paths::CargoPathExt; macro_rules! t { - ($e:expr) => (match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - }) + ($e:expr) => { + match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {}", stringify!($e), e), + } + }; } -pub mod paths; -pub mod git; -pub mod registry; pub mod cross_compile; +pub mod git; +pub mod paths; pub mod publish; +pub mod registry; /* * @@ -380,6 +382,7 @@ pub struct Execs { expect_stdout_contains_n: Vec<(String, usize)>, expect_stdout_not_contains: Vec, expect_stderr_not_contains: Vec, + expect_stderr_unordered: Vec, expect_neither_contains: Vec, expect_json: Option>, stream_output: bool, @@ -436,6 +439,11 @@ impl Execs { self } + pub fn with_stderr_unordered(mut self, expected: S) -> Execs { + self.expect_stderr_unordered.push(expected.to_string()); + self + } + pub fn with_json(mut self, expected: &str) -> Execs { self.expect_json = Some( expected @@ -526,6 +534,15 @@ impl Execs { MatchKind::NotPresent, )?; } + for expect in self.expect_stderr_unordered.iter() { + self.match_std( + Some(expect), + &actual.stderr, + "stderr", + &actual.stdout, + MatchKind::Unordered, + )?; + } for expect in self.expect_neither_contains.iter() { self.match_std( Some(expect), @@ -573,7 +590,10 @@ impl Execs { if let Some(ref objects) = self.expect_json { let stdout = str::from_utf8(&actual.stdout) .map_err(|_| "stdout was not utf8 encoded".to_owned())?; - let lines = stdout.lines().collect::>(); + let lines = stdout + .lines() + .filter(|line| line.starts_with("{")) + .collect::>(); if lines.len() != objects.len() { return Err(format!( "expected {} json lines, got {}, stdout:\n{}", @@ -709,6 +729,35 @@ impl Execs { Ok(()) } } + MatchKind::Unordered => { + let mut a = actual.lines().collect::>(); + let e = out.lines(); + + for e_line in e { + match a.iter().position(|a_line| lines_match(e_line, a_line)) { + Some(index) => a.remove(index), + None => { + return Err(format!( + "Did not find expected line:\n\ + {}\n\ + Remaining available output:\n\ + {}\n", + e_line, + a.join("\n") + )) + } + }; + } + if a.len() > 0 { + Err(format!( + "Output included extra lines:\n\ + {}\n", + a.join("\n") + )) + } else { + Ok(()) + } + } } } @@ -765,6 +814,7 @@ enum MatchKind { Partial, PartialN(usize), NotPresent, + Unordered, } pub fn lines_match(expected: &str, mut actual: &str) -> bool { @@ -943,6 +993,7 @@ pub fn execs() -> Execs { expect_stdout_contains_n: Vec::new(), expect_stdout_not_contains: Vec::new(), expect_stderr_not_contains: Vec::new(), + expect_stderr_unordered: Vec::new(), expect_neither_contains: Vec::new(), expect_json: None, stream_output: false, diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index 9c7097075f7..f4e49e711d4 100644 --- a/tests/testsuite/check.rs +++ b/tests/testsuite/check.rs @@ -747,16 +747,16 @@ fn check_filters() { .with_stderr_contains("[..] --crate-name foo src[/]lib.rs [..] --test [..]") .with_stderr_contains("[..] --crate-name foo src[/]lib.rs --crate-type lib [..]") .with_stderr_contains("[..] --crate-name foo src[/]main.rs [..] --test [..]") - .with_stderr_contains("[..] --crate-name foo src[/]main.rs --crate-type bin [..]") .with_stderr_contains("[..]unused_unit_lib[..]") .with_stderr_contains("[..]unused_unit_bin[..]") .with_stderr_contains("[..]unused_normal_lib[..]") .with_stderr_contains("[..]unused_normal_bin[..]") .with_stderr_contains("[..]unused_unit_t1[..]") - .with_stderr_contains("[..]unused_normal_ex1[..]") - .with_stderr_contains("[..]unused_unit_ex1[..]") + .with_stderr_does_not_contain("[..]unused_normal_ex1[..]") + .with_stderr_does_not_contain("[..]unused_unit_ex1[..]") .with_stderr_does_not_contain("[..]unused_normal_b1[..]") - .with_stderr_does_not_contain("[..]unused_unit_b1[..]"), + .with_stderr_does_not_contain("[..]unused_unit_b1[..]") + .with_stderr_does_not_contain("[..]--crate-type bin[..]"), ); p.root().join("target").rm_rf(); assert_that( @@ -764,9 +764,9 @@ fn check_filters() { execs() .with_status(0) .with_stderr_contains("[..]unused_normal_lib[..]") - .with_stderr_contains("[..]unused_normal_bin[..]") .with_stderr_contains("[..]unused_unit_t1[..]") .with_stderr_does_not_contain("[..]unused_unit_lib[..]") + .with_stderr_does_not_contain("[..]unused_normal_bin[..]") .with_stderr_does_not_contain("[..]unused_unit_bin[..]") .with_stderr_does_not_contain("[..]unused_normal_ex1[..]") .with_stderr_does_not_contain("[..]unused_normal_b1[..]") @@ -787,7 +787,7 @@ fn check_filters() { .with_stderr_contains("[..]unused_unit_t1[..]") .with_stderr_contains("[..]unused_unit_lib[..]") .with_stderr_contains("[..]unused_unit_bin[..]") - .with_stderr_contains("[..]unused_unit_ex1[..]"), + .with_stderr_does_not_contain("[..]unused_unit_ex1[..]"), ); } diff --git a/tests/testsuite/doc.rs b/tests/testsuite/doc.rs index 82638b0408c..ac9d269d3b1 100644 --- a/tests/testsuite/doc.rs +++ b/tests/testsuite/doc.rs @@ -8,6 +8,7 @@ use cargotest::support::{execs, project, path2url}; use cargotest::support::registry::Package; use hamcrest::{assert_that, existing_dir, existing_file, is_not}; use cargo::util::ProcessError; +use glob::glob; #[test] fn simple() { @@ -163,6 +164,20 @@ fn doc_deps() { assert_that(&p.root().join("target/doc/foo/index.html"), existing_file()); assert_that(&p.root().join("target/doc/bar/index.html"), existing_file()); + // Verify that it only emits rmeta for the dependency. + assert_eq!( + glob(&p.root().join("target/debug/**/*.rlib").to_str().unwrap()) + .unwrap() + .count(), + 0 + ); + assert_eq!( + glob(&p.root().join("target/debug/deps/libbar-*.rmeta").to_str().unwrap()) + .unwrap() + .count(), + 1 + ); + assert_that( p.cargo("doc") .env("RUST_LOG", "cargo::ops::cargo_rustc::fingerprint"), diff --git a/tests/testsuite/freshness.rs b/tests/testsuite/freshness.rs index 3b94f5f05d6..9ff31d66afe 100644 --- a/tests/testsuite/freshness.rs +++ b/tests/testsuite/freshness.rs @@ -268,9 +268,6 @@ fn changing_profiles_caches_targets() { [profile.dev] panic = "abort" - - [profile.test] - panic = "unwind" "#, ) .file("src/lib.rs", "") diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index e1d3181cd08..413f0463d3f 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -70,6 +70,8 @@ mod path; mod plugins; mod proc_macro; mod profiles; +mod profile_overrides; +mod profile_targets; mod publish; mod read_manifest; mod registry; diff --git a/tests/testsuite/profile_overrides.rs b/tests/testsuite/profile_overrides.rs new file mode 100644 index 00000000000..59a61616dd4 --- /dev/null +++ b/tests/testsuite/profile_overrides.rs @@ -0,0 +1,345 @@ +use cargotest::support::{basic_lib_manifest, execs, project}; +use cargotest::ChannelChanger; +use hamcrest::assert_that; + +#[test] +fn profile_override_gated() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [profile.dev.build-override] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `profile-overrides` is required + +consider adding `cargo-features = [\"profile-overrides\"]` to the manifest +", + ), + ); + + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [profile.dev.overrides."*"] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `profile-overrides` is required + +consider adding `cargo-features = [\"profile-overrides\"]` to the manifest +", + ), + ); +} + +#[test] +fn profile_override_basic() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = {path = "bar"} + + [profile.dev] + opt-level = 1 + + [profile.dev.overrides.bar] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build -v").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr( + "[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar [..] -C opt-level=3 [..]` +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name foo [..] -C opt-level=1 [..]` +[FINISHED] dev [optimized + debuginfo] target(s) in [..]", + ), + ); +} + +#[test] +fn profile_override_bad_name() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = {path = "bar"} + + [profile.dev.overrides.bart] + opt-level = 3 + + [profile.dev.overrides.no-suggestion] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr_contains( + "\ +[WARNING] package `bart` for profile override not found + +Did you mean `bar`? +[WARNING] package `no-suggestion` for profile override not found +[COMPILING] [..] +", + ), + ); +} + +#[test] +fn profile_override_dev_release_only() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = {path = "bar"} + + [profile.test.overrides.bar] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr_contains( + "\ +Caused by: + Profile overrides may only be specified for `dev` or `release` profile, not `test`. +", + ), + ); +} + +#[test] +fn profile_override_bad_settings() { + let bad_values = [ + ( + "panic = \"abort\"", + "`panic` may not be specified in a profile override.", + ), + ( + "lto = true", + "`lto` may not be specified in a profile override.", + ), + ( + "rpath = true", + "`rpath` may not be specified in a profile override.", + ), + ("overrides = {}", "Profile overrides cannot be nested."), + ]; + for &(ref snippet, ref expected) in bad_values.iter() { + let p = project("foo") + .file( + "Cargo.toml", + &format!( + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = {{path = "bar"}} + + [profile.dev.overrides.bar] + {} + "#, + snippet + ), + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs() + .with_status(101) + .with_stderr_contains(format!("Caused by:\n {}", expected)), + ); + } +} + +#[test] +fn profile_override_hierarchy() { + // Test that the precedence rules are correct for different types. + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [workspace] + members = ["m1", "m2", "m3"] + + [profile.dev] + codegen-units = 1 + + [profile.dev.overrides.m2] + codegen-units = 2 + + [profile.dev.overrides."*"] + codegen-units = 3 + + [profile.dev.build-override] + codegen-units = 4 + "#) + + // m1 + .file("m1/Cargo.toml", + r#" + [package] + name = "m1" + version = "0.0.1" + + [dependencies] + m2 = { path = "../m2" } + dep = { path = "../../dep" } + "#) + .file("m1/src/lib.rs", + r#" + extern crate m2; + extern crate dep; + "#) + .file("m1/build.rs", + r#"fn main() {}"#) + + // m2 + .file("m2/Cargo.toml", + r#" + [package] + name = "m2" + version = "0.0.1" + + [dependencies] + m3 = { path = "../m3" } + + [build-dependencies] + m3 = { path = "../m3" } + dep = { path = "../../dep" } + "#) + .file("m2/src/lib.rs", + r#" + extern crate m3; + "#) + .file("m2/build.rs", + r#" + extern crate m3; + extern crate dep; + fn main() {} + "#) + + // m3 + .file("m3/Cargo.toml", &basic_lib_manifest("m3")) + .file("m3/src/lib.rs", "") + .build(); + + // dep (outside of workspace) + let _dep = project("dep") + .file("Cargo.toml", &basic_lib_manifest("dep")) + .file("src/lib.rs", "") + .build(); + + // Profiles should be: + // m3: 4 (as build.rs dependency) + // m3: 1 (as [profile.dev] as workspace member) + // dep: 3 (as [profile.dev.overrides."*"] as non-workspace member) + // m1 build.rs: 4 (as [profile.dev.build-override]) + // m2 build.rs: 2 (as [profile.dev.overrides.m2]) + // m2: 2 (as [profile.dev.overrides.m2]) + // m1: 1 (as [profile.dev]) + + assert_that( + p.cargo("build -v").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr_unordered("\ +[COMPILING] m3 [..] +[COMPILING] dep [..] +[RUNNING] `rustc --crate-name m3 m3[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=4 [..] +[RUNNING] `rustc --crate-name dep [..]dep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=3 [..] +[RUNNING] `rustc --crate-name m3 m3[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 [..] +[RUNNING] `rustc --crate-name build_script_build m1[/]build.rs --crate-type bin --emit=dep-info,link -C codegen-units=4 [..] +[COMPILING] m2 [..] +[RUNNING] `rustc --crate-name build_script_build m2[/]build.rs --crate-type bin --emit=dep-info,link -C codegen-units=2 [..] +[RUNNING] `[..][/]m1-[..][/]build-script-build` +[RUNNING] `[..][/]m2-[..][/]build-script-build` +[RUNNING] `rustc --crate-name m2 m2[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=2 [..] +[COMPILING] m1 [..] +[RUNNING] `rustc --crate-name m1 m1[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +", + ), + ); +} diff --git a/tests/testsuite/profile_targets.rs b/tests/testsuite/profile_targets.rs new file mode 100644 index 00000000000..7a245fe8a95 --- /dev/null +++ b/tests/testsuite/profile_targets.rs @@ -0,0 +1,703 @@ +use cargotest::is_nightly; +use cargotest::support::{execs, project, Project}; +use hamcrest::assert_that; + +// These tests try to exercise exactly which profiles are selected for every +// target. + +fn all_target_project() -> Project { + // This abuses the `codegen-units` setting so that we can verify exactly + // which profile is used for each compiler invocation. + project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "bar" } + + [build-dependencies] + bdep = { path = "bdep" } + + [profile.dev] + codegen-units = 1 + panic = "abort" + [profile.release] + codegen-units = 2 + panic = "abort" + [profile.test] + codegen-units = 3 + [profile.bench] + codegen-units = 4 + [profile.doc] + codegen-units = 5 + "#, + ) + .file("src/lib.rs", "extern crate bar;") + .file("src/main.rs", r#" + extern crate foo; + fn main() {} + "#) + .file("examples/ex1.rs", r#" + extern crate foo; + fn main() {} + "#) + .file("tests/test1.rs", "extern crate foo;") + .file("benches/bench1.rs", "extern crate foo;") + .file("build.rs", r#" + extern crate bdep; + fn main() { + eprintln!("foo custom build PROFILE={} DEBUG={} OPT_LEVEL={}", + std::env::var("PROFILE").unwrap(), + std::env::var("DEBUG").unwrap(), + std::env::var("OPT_LEVEL").unwrap(), + ); + } + "#) + + // bar package + .file("bar/Cargo.toml", r#" + [package] + name = "bar" + version = "0.0.1" + "#) + .file("bar/src/lib.rs", "") + + // bdep package + .file("bdep/Cargo.toml", r#" + [package] + name = "bdep" + version = "0.0.1" + + [dependencies] + bar = { path = "../bar" } + "#) + .file("bdep/src/lib.rs", "extern crate bar;") + .build() +} + +#[test] +fn profile_selection_build() { + let p = all_target_project(); + + // Build default targets. + // NOTES: + // - bdep `panic` is not set because it thinks `build.rs` is a plugin. + // - bar `panic` is not set because it is shared with `bdep`. + // - build_script_build is built without panic because it thinks `build.rs` is a plugin. + assert_that(p.cargo("build -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `[..][/]target[/]debug[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +")); + assert_that( + p.cargo("build -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +", + ), + ); +} + +#[test] +fn profile_selection_build_release() { + let p = all_target_project(); + + // Build default targets, release. + assert_that(p.cargo("build --release -vv"), + execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `[..][/]target[/]release[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C panic=abort -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C panic=abort -C codegen-units=2 [..] +[FINISHED] release [optimized] [..] +")); + assert_that( + p.cargo("build --release -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] release [optimized] [..] +", + ), + ); +} + +#[test] +fn profile_selection_build_all_targets() { + let p = all_target_project(); + // Build all explicit targets. + // NOTES + // - bdep `panic` is not set because it thinks `build.rs` is a plugin. + // - bar compiled twice. It tries with and without panic, but the "is a + // plugin" logic is forcing it to be cleared. + // - build_script_build is built without panic because it thinks + // `build.rs` is a plugin. + // - build_script_build is being run two times. Once for the `dev` and + // `test` targets, once for the `bench` targets. + // TODO: "PROFILE" says debug both times, though! + // - Benchmark dependencies are compiled in `dev` mode, which may be + // surprising. See https://github.com/rust-lang/cargo/issues/4929. + // + // - Dependency profiles: + // Pkg Target Profile Reason + // --- ------ ------- ------ + // bar lib dev* For bdep and foo + // bar lib dev-panic For tests/benches + // bdep lib dev* For foo build.rs + // foo custom dev* + // + // `*` = wants panic, but it is cleared when args are built. + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib dev+panic build (a normal lib target) + // lib dev-panic build (used by tests/benches) + // lib test test + // lib bench test(bench) + // test test test + // bench bench test(bench) + // bin test test + // bin bench test(bench) + // bin dev build + // example dev build + assert_that(p.cargo("build --all-targets -vv"), + execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `[..][/]target[/]debug[/]build[/]foo-[..][/]build-script-build` +[RUNNING] `[..][/]target[/]debug[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=debug DEBUG=false OPT_LEVEL=3 +foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C panic=abort -C codegen-units=1 -C debuginfo=2 [..]` +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,link -C codegen-units=3 -C debuginfo=2 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..]` +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,link -C codegen-units=3 -C debuginfo=2 --test [..]` +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,link -C codegen-units=3 -C debuginfo=2 --test [..]` +[RUNNING] `rustc --crate-name bench1 benches[/]bench1.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C panic=abort -C codegen-units=1 -C debuginfo=2 [..]` +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --crate-type bin --emit=dep-info,link -C panic=abort -C codegen-units=1 -C debuginfo=2 [..]` +[FINISHED] dev [unoptimized + debuginfo] [..] +")); + assert_that( + p.cargo("build -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +", + ), + ); +} + +#[test] +fn profile_selection_build_all_targets_release() { + let p = all_target_project(); + // Build all explicit targets, release. + // NOTES + // - bdep `panic` is not set because it thinks `build.rs` is a plugin. + // - bar compiled twice. It tries with and without panic, but the "is a + // plugin" logic is forcing it to be cleared. + // - build_script_build is built without panic because it thinks + // `build.rs` is a plugin. + // - build_script_build is being run two times. Once for the `dev` and + // `test` targets, once for the `bench` targets. + // TODO: "PROFILE" says debug both times, though! + // + // - Dependency profiles: + // Pkg Target Profile Reason + // --- ------ ------- ------ + // bar lib release* For bdep and foo + // bar lib release-panic For tests/benches + // bdep lib release* For foo build.rs + // foo custom release* + // + // `*` = wants panic, but it is cleared when args are built. + // + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib release+panic build (a normal lib target) + // lib release-panic build (used by tests/benches) + // lib bench test (bench/test de-duped) + // test bench test + // bench bench test + // bin bench test (bench/test de-duped) + // bin release build + // example release build + assert_that(p.cargo("build --all-targets --release -vv"), + execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `[..][/]target[/]release[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C panic=abort -C codegen-units=2 [..]` +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..]` +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name bench1 benches[/]bench1.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..]` +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C panic=abort -C codegen-units=2 [..]` +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C panic=abort -C codegen-units=2 [..]` +[FINISHED] release [optimized] [..] +")); + assert_that( + p.cargo("build --all-targets --release -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] release [optimized] [..] +", + ), + ); +} + +#[test] +fn profile_selection_test() { + let p = all_target_project(); + // Test default. + // NOTES: + // - Dependency profiles: + // Pkg Target Profile Reason + // --- ------ ------- ------ + // bar lib dev* For bdep and foo + // bar lib dev-panic For tests/benches + // bdep lib dev* For foo build.rs + // foo custom dev* + // + // `*` = wants panic, but it is cleared when args are built. + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib dev-panic build + // lib test test + // test test test + // example dev-panic build + // bin test test + // bin dev-panic build + // + assert_that(p.cargo("test -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `[..][/]target[/]debug[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,link -C codegen-units=3 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,link -C codegen-units=3 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,link -C codegen-units=3 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]test1[..]` +[DOCTEST] foo +[RUNNING] `rustdoc --test [..] +")); + assert_that( + p.cargo("test -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]test1[..]` +[DOCTEST] foo +[RUNNING] `rustdoc --test [..] +", + ), + ); +} + +#[test] +fn profile_selection_test_release() { + let p = all_target_project(); + // Test default release. + // NOTES: + // - Dependency profiles: + // Pkg Target Profile Reason + // --- ------ ------- ------ + // bar lib release* For bdep and foo + // bar lib release-panic For tests/benches + // bdep lib release* For foo build.rs + // foo custom release* + // + // `*` = wants panic, but it is cleared when args are built. + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib release-panic build + // lib bench test + // test bench test + // example release-panic build + // bin bench test + // bin release-panic build + // + assert_that(p.cargo("test --release -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `[..][/]target[/]release[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..] +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..] +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[FINISHED] release [optimized] [..] +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]test1[..]` +[DOCTEST] foo +[RUNNING] `rustdoc --test [..]` +")); + assert_that( + p.cargo("test --release -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] release [optimized] [..] +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]foo[..]` +[RUNNING] `[..]test1[..]` +[DOCTEST] foo +[RUNNING] `rustdoc --test [..] +", + ), + ); +} + +#[test] +fn profile_selection_bench() { + let p = all_target_project(); + + // Bench default. + // NOTES: + // - Dependency profiles: + // Pkg Target Profile Reason + // --- ------ ------- ------ + // bar lib release* For bdep and foo + // bar lib release-panic For tests/benches + // bdep lib release* For foo build.rs + // foo custom release* + // + // `*` = wants panic, but it is cleared when args are built. + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib release-panic build + // lib bench test(bench) + // bench bench test(bench) + // bin bench test(bench) + // bin release-panic build + // + assert_that(p.cargo("bench -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `[..]target[/]release[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..] +[RUNNING] `rustc --crate-name bench1 benches[/]bench1.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,link -C opt-level=3 -C codegen-units=4 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[FINISHED] release [optimized] [..] +[RUNNING] `[..]foo[..] --bench` +[RUNNING] `[..]foo[..] --bench` +[RUNNING] `[..]bench1[..] --bench` +")); + assert_that( + p.cargo("bench -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] release [optimized] [..] +[RUNNING] `[..]foo[..] --bench` +[RUNNING] `[..]foo[..] --bench` +[RUNNING] `[..]bench1[..] --bench` +", + ), + ); +} + +#[test] +fn profile_selection_check_all_targets() { + if !is_nightly() { + // This can be removed once 1.27 is stable, see below. + return; + } + + let p = all_target_project(); + // check + // NOTES: + // - Dependency profiles: + // Pkg Target Profile Action Reason + // --- ------ ------- ------ ------ + // bar lib dev* link For bdep + // bar lib dev-panic metadata For tests/benches + // bar lib dev metadata For lib/bins + // bdep lib dev* link For foo build.rs + // foo custom dev* link For build.rs + // + // `*` = wants panic, but it is cleared when args are built. + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib dev check + // lib dev-panic check (for tests/benches) + // lib dev-panic check-test (checking lib as a unittest) + // example dev check + // test dev-panic check-test + // bench dev-panic check-test + // bin dev check + // bin dev-panic check-test (checking bin as a unittest) + // + assert_that(p.cargo("check --all-targets -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] bdep[..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `[..]target[/]debug[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name bench1 benches[/]bench1.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --crate-type bin --emit=dep-info,metadata -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,metadata -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +")); + // Starting with Rust 1.27, rustc emits `rmeta` files for bins, so + // everything should be completely fresh. Previously, bins were being + // rechecked. + // See https://github.com/rust-lang/rust/pull/49289 and + // https://github.com/rust-lang/cargo/issues/3624 + assert_that( + p.cargo("check --all-targets -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +", + ), + ); +} + +#[test] +fn profile_selection_check_all_targets_release() { + if !is_nightly() { + // See note in profile_selection_check_all_targets. + return; + } + + let p = all_target_project(); + // check --release + // https://github.com/rust-lang/cargo/issues/5218 + // This is a pretty straightforward variant of + // `profile_selection_check_all_targets` that uses `release` instead of + // `dev` for all targets. + assert_that(p.cargo("check --all-targets --release -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C opt-level=3 -C panic=abort -C codegen-units=2 [..] +[COMPILING] bdep[..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `[..]target[/]release[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C opt-level=3 -C panic=abort -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C opt-level=3 -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,metadata -C opt-level=3 -C codegen-units=2 --test [..] +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,metadata -C opt-level=3 -C codegen-units=2 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,metadata -C opt-level=3 -C codegen-units=2 --test [..] +[RUNNING] `rustc --crate-name bench1 benches[/]bench1.rs --emit=dep-info,metadata -C opt-level=3 -C codegen-units=2 --test [..] +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --crate-type bin --emit=dep-info,metadata -C opt-level=3 -C panic=abort -C codegen-units=2 [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --crate-type bin --emit=dep-info,metadata -C opt-level=3 -C panic=abort -C codegen-units=2 [..] +[FINISHED] release [optimized] [..] +")); + + assert_that( + p.cargo("check --all-targets --release -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] release [optimized] [..] +", + ), + ); +} + +#[test] +fn profile_selection_check_all_targets_test() { + if !is_nightly() { + // See note in profile_selection_check_all_targets. + return; + } + + let p = all_target_project(); + // check --profile=test + // NOTES: + // - This doesn't actually use the "test" profile. Everything uses dev. + // It probably should use "test"??? Probably doesn't really matter. + // - Dependency profiles: + // Pkg Target Profile Action Reason + // --- ------ ------- ------ ------ + // bar lib dev* link For bdep + // bar lib dev-panic metadata For tests/benches + // bdep lib dev* link For foo build.rs + // foo custom dev* link For build.rs + // + // `*` = wants panic, but it is cleared when args are built. + // + // - foo target list is: + // Target Profile Mode + // ------ ------- ---- + // lib dev-panic check-test (for tests/benches) + // lib dev-panic check-test (checking lib as a unittest) + // example dev-panic check-test + // test dev-panic check-test + // bench dev-panic check-test + // bin dev-panic check-test + // + assert_that(p.cargo("check --all-targets --profile=test -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] bdep[..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `[..]target[/]debug[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0 +[RUNNING] `rustc --crate-name foo src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustc --crate-name foo src[/]lib.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name test1 tests[/]test1.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name foo src[/]main.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name bench1 benches[/]bench1.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[RUNNING] `rustc --crate-name ex1 examples[/]ex1.rs --emit=dep-info,metadata -C codegen-units=1 -C debuginfo=2 --test [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +")); + + assert_that( + p.cargo("check --all-targets --profile=test -vv"), + execs().with_status(0).with_stderr_unordered( + "\ +[FRESH] bar [..] +[FRESH] bdep [..] +[FRESH] foo [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +", + ), + ); +} + +#[test] +fn profile_selection_doc() { + let p = all_target_project(); + // doc + // NOTES: + // - Dependency profiles: + // Pkg Target Profile Action Reason + // --- ------ ------- ------ ------ + // bar lib dev* link For bdep + // bar lib dev metadata For rustdoc + // bdep lib dev* link For foo build.rs + // foo custom dev* link For build.rs + // + // `*` = wants panic, but it is cleared when args are built. + assert_that(p.cargo("doc -vv"), execs().with_status(0).with_stderr_unordered("\ +[COMPILING] bar [..] +[DOCUMENTING] bar [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `rustdoc --crate-name bar bar[/]src[/]lib.rs [..] +[RUNNING] `rustc --crate-name bar bar[/]src[/]lib.rs --crate-type lib --emit=dep-info,metadata -C panic=abort -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] bdep [..] +[RUNNING] `rustc --crate-name bdep bdep[/]src[/]lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[COMPILING] foo [..] +[RUNNING] `rustc --crate-name build_script_build build.rs --crate-type bin --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 [..] +[RUNNING] `[..]target[/]debug[/]build[/]foo-[..][/]build-script-build` +foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0 +[DOCUMENTING] foo [..] +[RUNNING] `rustdoc --crate-name foo src[/]lib.rs [..] +[FINISHED] dev [unoptimized + debuginfo] [..] +")); +} diff --git a/tests/testsuite/profiles.rs b/tests/testsuite/profiles.rs index 999de518cee..8d1478ced24 100644 --- a/tests/testsuite/profiles.rs +++ b/tests/testsuite/profiles.rs @@ -340,3 +340,34 @@ fn profile_in_virtual_manifest_works() { ), ); } + +#[test] +fn profile_panic_test_bench() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.test] + panic = "abort" + + [profile.bench] + panic = "abort" + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build"), + execs().with_status(0).with_stderr_contains( + "\ +[WARNING] `panic` setting is ignored for `test` profile +[WARNING] `panic` setting is ignored for `bench` profile +", + ), + ); +} diff --git a/tests/testsuite/rustc.rs b/tests/testsuite/rustc.rs index dd7d51b50a2..95691b8dfb1 100644 --- a/tests/testsuite/rustc.rs +++ b/tests/testsuite/rustc.rs @@ -1,4 +1,4 @@ -use cargotest::support::{execs, project}; +use cargotest::support::{basic_lib_manifest, execs, project}; use hamcrest::assert_that; const CARGO_RUSTC_ERROR: &'static str = @@ -595,3 +595,57 @@ fn rustc_with_other_profile() { execs().with_status(0), ); } + +#[test] +fn rustc_fingerprint() { + // Verify that the fingerprint includes the rustc args. + let p = project("foo") + .file("Cargo.toml", &basic_lib_manifest("foo")) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("rustc -v -- -C debug-assertions"), + execs().with_status(0).with_stderr( + "\ +[COMPILING] foo [..] +[RUNNING] `rustc [..]-C debug-assertions [..] +[FINISHED] [..] +", + ), + ); + + assert_that( + p.cargo("rustc -v -- -C debug-assertions"), + execs().with_status(0).with_stderr( + "\ +[FRESH] foo [..] +[FINISHED] [..] +", + ), + ); + + assert_that( + p.cargo("rustc -v"), + execs() + .with_status(0) + .with_stderr_does_not_contain("-C debug-assertions") + .with_stderr( + "\ +[COMPILING] foo [..] +[RUNNING] `rustc [..] +[FINISHED] [..] +", + ), + ); + + assert_that( + p.cargo("rustc -v"), + execs().with_status(0).with_stderr( + "\ +[FRESH] foo [..] +[FINISHED] [..] +", + ), + ); +} diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index a1953003fde..315e886c506 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -3,12 +3,12 @@ use std::io::prelude::*; use std::str; use cargo; -use cargotest::{is_nightly, rustc_host, sleep_ms}; -use cargotest::support::{basic_bin_manifest, basic_lib_manifest, cargo_exe, execs, project}; +use cargo::util::process; use cargotest::support::paths::CargoPathExt; use cargotest::support::registry::Package; +use cargotest::support::{basic_bin_manifest, basic_lib_manifest, cargo_exe, execs, project}; +use cargotest::{is_nightly, rustc_host, sleep_ms}; use hamcrest::{assert_that, existing_file, is_not}; -use cargo::util::process; #[test] fn cargo_test_simple() { @@ -1644,8 +1644,7 @@ fn test_run_implicit_test_target() { .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm.rs", - "#[test] fn test_in_exm() { } - fn main() { panic!(\"Don't execute me!\"); }", + "fn main() { compile_error!(\"Don't build me!\"); }", ) .build(); @@ -1658,8 +1657,7 @@ fn test_run_implicit_test_target() { [COMPILING] foo v0.0.1 ({dir}) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [RUNNING] target[/]debug[/]deps[/]mybin-[..][EXE] -[RUNNING] target[/]debug[/]deps[/]mytest-[..][EXE] -[RUNNING] target[/]debug[/]examples[/]myexm-[..][EXE]", +[RUNNING] target[/]debug[/]deps[/]mytest-[..][EXE]", dir = prj.url() )) .with_stdout_contains("test test_in_test ... ok"), @@ -1691,8 +1689,7 @@ fn test_run_implicit_bench_target() { .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm.rs", - "#[test] fn test_in_exm() { } - fn main() { panic!(\"Don't execute me!\"); }", + "fn main() { compile_error!(\"Don't build me!\"); }", ) .build(); @@ -1737,8 +1734,8 @@ fn test_run_implicit_example_target() { .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm.rs", - "#[test] fn test_in_exm() { } - fn main() { panic!(\"Don't execute me!\"); }", + r#"#[test] fn test_in_exm() { panic!("Don't even test me."); } + fn main() { panic!("Don't execute me!"); }"#, ) .build(); @@ -1747,8 +1744,7 @@ fn test_run_implicit_example_target() { execs().with_status(0).with_stderr(format!( "\ [COMPILING] foo v0.0.1 ({dir}) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] -[RUNNING] target[/]debug[/]examples[/]myexm-[..][EXE]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", dir = prj.url() )), ); @@ -3979,3 +3975,72 @@ fn test_hint_workspace() { .with_status(101), ); } + +#[test] +fn json_artifact_includes_test_flag() { + // Verify that the JSON artifact output includes `test` flag. + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [profile.test] + opt-level = 1 + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("test -v --message-format=json"), + execs().with_status(0).with_json( + r#" + { + "reason":"compiler-artifact", + "profile": { + "debug_assertions": true, + "debuginfo": 2, + "opt_level": "0", + "overflow_checks": true, + "test": false + }, + "features": [], + "package_id":"foo 0.0.1 ([..])", + "target":{ + "kind":["lib"], + "crate_types":["lib"], + "name":"foo", + "src_path":"[..]lib.rs" + }, + "filenames":["[..].rlib"], + "fresh": false + } + + { + "reason":"compiler-artifact", + "profile": { + "debug_assertions": true, + "debuginfo": 2, + "opt_level": "1", + "overflow_checks": true, + "test": true + }, + "features": [], + "package_id":"foo 0.0.1 ([..])", + "target":{ + "kind":["lib"], + "crate_types":["lib"], + "name":"foo", + "src_path":"[..]lib.rs" + }, + "filenames":["[..][/]foo-[..]"], + "fresh": false + } +"#, + ), + ); +}