diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs index f5fbe119354..af908080343 100644 --- a/src/cargo/core/compiler/build_config.rs +++ b/src/cargo/core/compiler/build_config.rs @@ -23,7 +23,7 @@ pub struct BuildConfig { /// Force Cargo to do a full rebuild and treat each target as changed. pub force_rebuild: bool, /// Output a build plan to stdout instead of actually compiling. - pub build_plan: bool, + pub build_plan: BuildPlanConfig, /// An optional wrapper, if any, used to wrap rustc invocations pub rustc_wrapper: Option, pub rustfix_diagnostic_server: RefCell>, @@ -97,7 +97,7 @@ impl BuildConfig { mode, message_format: MessageFormat::Human, force_rebuild: false, - build_plan: false, + build_plan: BuildPlanConfig(None), rustc_wrapper: None, rustfix_diagnostic_server: RefCell::new(None), cache_messages: config.cli_unstable().cache_messages, @@ -127,6 +127,73 @@ pub enum MessageFormat { Short, } +/// Configuration structure for the `--build-plan` option, mainly a wrapper +/// for the `BuildPlanStage` selected (`None` if we're not generating a build +/// plan, then compile all units as usual). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BuildPlanConfig(pub Option); + +/// Stage in the build process at which to generate the build plan. At later +/// stages more units will be compiled and less will be outputted to the build +/// plan. In one extreme, `Init` is the earliest possible stage where all units +/// are routed to the build plan; the `None` of this `Option` in +/// `BuildPlanConfig` can be thought of as the other extreme at the end, where +/// all units are already compiled so the build plan is empty (not even +/// generated). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BuildPlanStage { + /// Initial stage before anything is compiled (default option), all units + /// will be outputted as `Invocation`s in the build plan instead of calling + /// `rustc` for them (actually compiling). Ideally (but this is not + /// guaranteed), generating a build plan at the `Init` stage shouldn't + /// change anything in the working environment (e.g., it shouldn't compile, + /// update fingerprints, remove an old `rlib` file). + Init, + /// In contrast to `Init`, this stage signals to output the build plan after + /// *all* custom build scripts (and their dependencies) have been compiled + /// and executed. + /// + /// At this stage in the build process, the remaining units (which will be + /// outputted to the build plan instead of being compiled) are the ones + /// actually linked inside the final binary/library target, whereas the + /// compiled build scripts (and their dependencies) were used only for + /// their side effects to modify the `rustc` invocations (which will be + /// reflected in the build plan, something that doesn't happen at the + /// `Init` stage when the build script haven't been executed yet). + PostBuildScripts, +} + +impl BuildPlanConfig { + /// Signal if the `--build-plan` option was requested (independent of the + /// particular stage selected). + pub fn requested(&self) -> bool { + self.0.is_some() + } + + /// Signal if the `PostBuildScripts` stage was selected for the build plan + /// (implies `requested`). + pub fn post_build_scripts(&self) -> bool { + self.0 == Some(BuildPlanStage::PostBuildScripts) + } + + /// Determine (based on the build plan stage selected, if any) whether the + /// current unit should be compiled, or included in the build plan instead + /// (returning `false`, we use a `bool` because there's no other + /// alternative of what can happen to a unit). The result is usually stored + /// in `Unit::to_be_compiled`. Based on these rules: + /// 1. If we didn't request a build plan, compile *any* unit. + /// 2. If we requested it at the `Init` stage, compile *nothing*. + /// 3. If we requested it at the `PostBuildScripts` stage, compile *only* a + /// `unit_used_for_build_script` (usually determined by `UnitFor::build`). + pub fn should_compile_unit(&self, unit_used_for_build_script: bool) -> bool { + match self.0 { + None => true, + Some(BuildPlanStage::Init) => false, + Some(BuildPlanStage::PostBuildScripts) => unit_used_for_build_script, + } + } +} + /// The general "mode" for 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 diff --git a/src/cargo/core/compiler/build_plan.rs b/src/cargo/core/compiler/build_plan.rs index cfdd1a01523..06740bb80f1 100644 --- a/src/cargo/core/compiler/build_plan.rs +++ b/src/cargo/core/compiler/build_plan.rs @@ -115,7 +115,12 @@ impl BuildPlan { let deps = cx .dep_targets(unit) .iter() - .map(|dep| self.invocation_map[&dep.buildkey()]) + .filter_map(|&dep| { + self.invocation_map.get(&dep.buildkey()).copied() + // In case `BuildPlanStage::PostBuildScripts` was selected some + // dependencies (custom build scripts and related) will be + // compiled and won't be present in the `invocation_map`. + }) .collect(); let invocation = Invocation::new(unit, deps); self.plan.invocations.push(invocation); diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index 96a544759a0..f1560daa154 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -108,7 +108,6 @@ impl<'a, 'cfg> Context<'a, 'cfg> { ) -> CargoResult> { let mut queue = JobQueue::new(self.bcx); let mut plan = BuildPlan::new(); - let build_plan = self.bcx.build_config.build_plan; self.prepare_units(export_dir, units)?; self.prepare()?; custom_build::build_map(&mut self, units)?; @@ -127,7 +126,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { // Now that we've figured out everything that we're going to do, do it! queue.execute(&mut self, &mut plan)?; - if build_plan { + if self.bcx.build_config.build_plan.requested() { plan.set_inputs(self.build_plan_inputs()?); plan.output_plan(); } @@ -437,7 +436,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { let mut keys = self .unit_dependencies .keys() - .filter(|unit| !unit.mode.is_run_custom_build()) + .filter(|unit| !unit.mode.is_run_custom_build() && unit.to_be_compiled) .collect::>(); // Sort for consistent error messages. keys.sort_unstable(); diff --git a/src/cargo/core/compiler/context/unit_dependencies.rs b/src/cargo/core/compiler/context/unit_dependencies.rs index db0744476c7..e3d5960e843 100644 --- a/src/cargo/core/compiler/context/unit_dependencies.rs +++ b/src/cargo/core/compiler/context/unit_dependencies.rs @@ -382,6 +382,7 @@ fn dep_build_script<'a>( bcx.profiles.get_profile_run_custom_build(&unit.profile), unit.kind, CompileMode::RunCustomBuild, + bcx.build_config.build_plan.should_compile_unit(true), ); (unit, UnitFor::new_build()) @@ -421,8 +422,12 @@ fn new_unit<'a>( mode, bcx.build_config.release, ); - - bcx.units.intern(pkg, target, profile, kind, mode) + let to_be_compiled = bcx + .build_config + .build_plan + .should_compile_unit(unit_for.is_build()); + bcx.units + .intern(pkg, target, profile, kind, mode, to_be_compiled) } /// Fill in missing dependencies for units of the `RunCustomBuild` diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 7978d1d480e..0b0aeb27c57 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -126,7 +126,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes let script_dir = cx.files().build_script_dir(build_script_unit); let script_out_dir = cx.files().build_script_out_dir(unit); let script_run_dir = cx.files().build_script_run_dir(unit); - let build_plan = bcx.build_config.build_plan; + let unit_is_to_be_compiled = unit.to_be_compiled; let invocation_name = unit.buildkey(); if let Some(deps) = unit.pkg.manifest().metabuild() { @@ -278,7 +278,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes // along to this custom build command. We're also careful to augment our // dynamic library search path in case the build script depended on any // native dynamic libraries. - if !build_plan { + if unit_is_to_be_compiled { let build_state = build_state.outputs.lock().unwrap(); for (name, id) in lib_deps { let key = (id, kind); @@ -300,9 +300,11 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes if let Some(build_scripts) = build_scripts { super::add_plugin_deps(&mut cmd, &build_state, &build_scripts, &host_target_root)?; } + // Release `build_state.outputs` lock in this inner scope, otherwise + // it will deadlock with the `build_state.insert` call below. } - if build_plan { + if !unit_is_to_be_compiled { state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new())); return Ok(()); } @@ -373,7 +375,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes Ok(()) }); - let mut job = if cx.bcx.build_config.build_plan { + let mut job = if !unit.to_be_compiled { Job::new(Work::noop(), Freshness::Dirty) } else { fingerprint::prepare_target(cx, unit, false)? diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 1b999be7f42..42b0dd0d69c 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -422,7 +422,7 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> { "{} [{}] target(s) in {}", build_type, opt_type, time_elapsed ); - if !cx.bcx.build_config.build_plan { + if !cx.bcx.build_config.build_plan.requested() { cx.bcx.config.shell().status("Finished", message)?; } Ok(()) @@ -513,7 +513,7 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> { my_tx.send(Message::Finish(id, Artifact::All, res)).unwrap(); }; - if !cx.bcx.build_config.build_plan { + if !cx.bcx.build_config.build_plan.requested() { // Print out some nice progress information. self.note_working_on(cx.bcx.config, unit, fresh)?; } diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 628ac8bbff8..45accc18cb7 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -24,7 +24,9 @@ use log::debug; use same_file::is_same_file; use serde::Serialize; -pub use self::build_config::{BuildConfig, CompileMode, MessageFormat}; +pub use self::build_config::{ + BuildConfig, BuildPlanConfig, BuildPlanStage, CompileMode, MessageFormat, +}; pub use self::build_context::{BuildContext, FileFlavor, TargetConfig, TargetInfo}; use self::build_plan::BuildPlan; pub use self::compilation::{Compilation, Doctest}; @@ -112,7 +114,6 @@ fn compile<'a, 'cfg: 'a>( force_rebuild: bool, ) -> CargoResult<()> { let bcx = cx.bcx; - let build_plan = bcx.build_config.build_plan; if !cx.compiled.insert(*unit) { return Ok(()); } @@ -128,7 +129,7 @@ fn compile<'a, 'cfg: 'a>( } else if unit.mode.is_doc_test() { // We run these targets later, so this is just a no-op for now. Job::new(Work::noop(), Freshness::Fresh) - } else if build_plan { + } else if !unit.to_be_compiled { Job::new(rustc(cx, unit, &exec.clone())?, Freshness::Dirty) } else { let force = exec.force_rebuild(unit) || force_rebuild; @@ -167,7 +168,7 @@ fn compile<'a, 'cfg: 'a>( for unit in cx.dep_targets(unit).iter() { compile(cx, jobs, plan, unit, exec, false)?; } - if build_plan { + if !unit.to_be_compiled { plan.add(cx, unit)?; } @@ -183,7 +184,8 @@ fn rustc<'a, 'cfg>( if cx.is_primary_package(unit) { rustc.env("CARGO_PRIMARY_PACKAGE", "1"); } - let build_plan = cx.bcx.build_config.build_plan; + let unit_is_to_be_compiled = unit.to_be_compiled; + let post_build_scripts = cx.bcx.build_config.build_plan.post_build_scripts(); let name = unit.pkg.name().to_string(); let buildkey = unit.buildkey(); @@ -243,7 +245,11 @@ fn rustc<'a, 'cfg>( // previous build scripts, we include them in the rustc invocation. if let Some(build_deps) = build_deps { let build_state = build_state.outputs.lock().unwrap(); - if !build_plan { + if unit_is_to_be_compiled || post_build_scripts { + // Even if we're not compiling this unit, if a build plan was + // requested *after* the custom build scripts are executed this + // information generated by them will be available (and is + // exactly what we want to include in the build plan). add_native_deps( &mut rustc, &build_state, @@ -257,14 +263,16 @@ fn rustc<'a, 'cfg>( add_custom_env(&mut rustc, &build_state, current_id, kind)?; } - for output in outputs.iter() { - // If there is both an rmeta and rlib, rustc will prefer to use the - // rlib, even if it is older. Therefore, we must delete the rlib to - // force using the new rmeta. - if output.path.extension() == Some(OsStr::new("rmeta")) { - let dst = root.join(&output.path).with_extension("rlib"); - if dst.exists() { - paths::remove_file(&dst)?; + if unit_is_to_be_compiled { + for output in outputs.iter() { + // If there is both an rmeta and rlib, rustc will prefer to use the + // rlib, even if it is older. Therefore, we must delete the rlib to + // force using the new rmeta. + if output.path.extension() == Some(OsStr::new("rmeta")) { + let dst = root.join(&output.path).with_extension("rlib"); + if dst.exists() { + paths::remove_file(&dst)?; + } } } } @@ -284,7 +292,7 @@ fn rustc<'a, 'cfg>( state.running(&rustc); let timestamp = paths::set_invocation_time(&fingerprint_dir)?; - if build_plan { + if !unit_is_to_be_compiled { state.build_plan(buildkey, rustc.clone(), outputs.clone()); } else { exec.exec( diff --git a/src/cargo/core/compiler/unit.rs b/src/cargo/core/compiler/unit.rs index 00c9841cc14..aab7d076017 100644 --- a/src/cargo/core/compiler/unit.rs +++ b/src/cargo/core/compiler/unit.rs @@ -48,6 +48,19 @@ pub struct UnitInner<'a> { pub kind: Kind, /// The "mode" this unit is being compiled for. See [`CompileMode`] for more details. pub mode: CompileMode, + /// Used only for the `--build-plan` option, this flag indicates whether this unit is intended + /// to be passed to a `rustc` call (roughly speaking, to be "compiled"), the usual case, or if + /// instead such invocation will be registered in a build plan (`false`). + pub to_be_compiled: bool, + // This flag adds a new dimension to allow for the (otherwise) same unit to exist as both, a + // compiled build script dependency and a "normal" dependency that will be outputted to the + // build plan when requesting the `post-build-scripts` stage, which departs from the basic + // all/none scenario of routing all units either to `rustc` or to the build plan. + // Without this flag our internal bookkeeping structures like `Context::unit_dependencies` + // and `Context::compiled` would confuse the two and ignore one of them. However, we do *not* + // want to add this attribute to the metadata or fingerprint (hence it exists as standalone + // here and not inside other structures like `CompileMode` or `Profile`), since even if they + // may have different purposes they should be able to be reused. } impl UnitInner<'_> { @@ -94,13 +107,18 @@ impl<'a> Deref for Unit<'a> { impl<'a> fmt::Debug for Unit<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Unit") + let mut debug_struct = f.debug_struct("Unit"); + debug_struct .field("pkg", &self.pkg) .field("target", &self.target) .field("profile", &self.profile) .field("kind", &self.kind) - .field("mode", &self.mode) - .finish() + .field("mode", &self.mode); + if !self.to_be_compiled { + // Only print in the (rare) case of use of using `--build-plan`. + debug_struct.field("to_be_compiled", &false); + } + debug_struct.finish() } } @@ -139,6 +157,7 @@ impl<'a> UnitInterner<'a> { profile: Profile, kind: Kind, mode: CompileMode, + to_be_compiled: bool, ) -> Unit<'a> { let inner = self.intern_inner(&UnitInner { pkg, @@ -146,6 +165,7 @@ impl<'a> UnitInterner<'a> { profile, kind, mode, + to_be_compiled, }); Unit { inner } } diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index af15ab26525..85a0b70a445 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -93,7 +93,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { opts.release, ) }; - units.push(bcx.units.intern(pkg, target, profile, *kind, *mode)); + units.push(bcx.units.intern(pkg, target, profile, *kind, *mode, true)); } } } diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 826c3472e8e..b7e0e383fc7 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -653,7 +653,9 @@ fn generate_targets<'a>( target_mode, bcx.build_config.release, ); - bcx.units.intern(pkg, target, profile, kind, target_mode) + let to_be_compiled = bcx.build_config.build_plan.should_compile_unit(false); + bcx.units + .intern(pkg, target, profile, kind, target_mode, to_be_compiled) }; // Create a list of proposed targets. diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index b8f208ea3e3..a7cb03a98fe 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -2,7 +2,7 @@ use std::ffi::{OsStr, OsString}; use std::fs; use std::path::PathBuf; -use crate::core::compiler::{BuildConfig, MessageFormat}; +use crate::core::compiler::{BuildConfig, BuildPlanConfig, BuildPlanStage, MessageFormat}; use crate::core::Workspace; use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl}; use crate::sources::CRATES_IO_REGISTRY; @@ -142,10 +142,21 @@ pub trait AppExt: Sized { } fn arg_build_plan(self) -> Self { - self._arg(opt( - "build-plan", - "Output the build plan in JSON (unstable)", - )) + self._arg( + opt("build-plan", "Output the build plan in JSON (unstable)") + .long_help( + "\ + Output the build plan in JSON (unstable) at a STAGE in the build \ + process: `init`, before compiling/running anything (default) or \ + `post-build-scripts`, after *all* the build scripts have been run \ + (and their dependencies compiled).", + ) + .value_name("STAGE") + .possible_values(&["init", "post-build-scripts"]) + .default_value("init") + .hide_default_value(true) + .hide_possible_values(true), + ) } fn arg_new_opts(self) -> Self { @@ -318,8 +329,28 @@ pub trait ArgMatchesExt { let mut build_config = BuildConfig::new(config, self.jobs()?, &self.target(), mode)?; build_config.message_format = message_format; build_config.release = self._is_present("release"); - build_config.build_plan = self._is_present("build-plan"); - if build_config.build_plan { + + if self._occurrences_of("build-plan") > 0 { + build_config.build_plan = BuildPlanConfig(self._value_of("build-plan").map(|t| { + if t.eq_ignore_ascii_case("init") { + BuildPlanStage::Init + } else if t.eq_ignore_ascii_case("post-build-scripts") { + BuildPlanStage::PostBuildScripts + } else { + panic!("Impossible build plan type: {:?}", t) + } + })) + }; + // We need to check if the `--build-plan` option was actually specified + // because we're allowing its use without the user indicating a value + // (by setting `init` as the default value), to be backwards compatible + // with the previous implementation (this no-value case is now + // superseded by explicitly setting the `init` value of this option). + // (This is not ideal but the "option with empty named value" use case + // doesn't seem to be supported by `clap`, which would otherwise + // trigger the "requires a value but none was supplied" error.) + + if build_config.build_plan.requested() { config .cli_unstable() .fail_if_stable_opt("--build-plan", 5579)?; @@ -469,6 +500,8 @@ about this warning."; fn _values_of_os(&self, name: &str) -> Vec; fn _is_present(&self, name: &str) -> bool; + + fn _occurrences_of(&self, name: &str) -> u64; } impl<'a> ArgMatchesExt for ArgMatches<'a> { @@ -497,6 +530,10 @@ impl<'a> ArgMatchesExt for ArgMatches<'a> { fn _is_present(&self, name: &str) -> bool { self.is_present(name) } + + fn _occurrences_of(&self, name: &str) -> u64 { + self.occurrences_of(name) + } } pub fn values(args: &ArgMatches<'_>, name: &str) -> Vec { diff --git a/tests/testsuite/build_plan.rs b/tests/testsuite/build_plan.rs index f23078e4ba3..9202ff24a14 100644 --- a/tests/testsuite/build_plan.rs +++ b/tests/testsuite/build_plan.rs @@ -8,7 +8,7 @@ fn cargo_build_plan_simple() { .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); - p.cargo("build --build-plan -Zunstable-options") + p.cargo("build --build-plan init -Zunstable-options") .masquerade_as_nightly_cargo() .with_json( r#" @@ -196,8 +196,199 @@ fn cargo_build_plan_build_script() { .run(); } +// Check that (in contrast with `cargo_build_plan_build_script`) the custom +// build script invocations are gone (they've been compiled instead), as +// `foo`'s dependency on them also, and that their emitted information is passed +// to `foo`'s invocation: `-L bar` argument and `FOO=foo` environmental variable. +// +// FIXME: The JSON matching pattern in this test is very brittle, we just care +// about the above *extra* items in the `"args"` and `"env"` arrays, but we need +// to add *all* their elements for the pattern to match, there doesn't seem to +// be a more flexible wildcard of the sort `{... "extra-item"}`. +#[cargo_test] +fn cargo_build_plan_post_build_script() { + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + "#, + ) + .file("src/main.rs", r#"fn main() {}"#) + .file( + "build.rs", + r#" + fn main() { + println!("cargo:rustc-flags=-L bar"); + println!("cargo:rustc-env=FOO=foo"); + } + "#, + ) + .build(); + + p.cargo("build --build-plan post-build-scripts -Z unstable-options") + .masquerade_as_nightly_cargo() + .with_json( + r#" + { + "inputs": [ + "[..]/foo/Cargo.toml" + ], + "invocations": [ + { + "args": [ + "--crate-name", + "foo", + "src/main.rs", + "--color", + "never", + "--crate-type", + "bin", + "--emit=[..]", + "-C", + "debuginfo=[..]", + "-C", + "metadata=[..]", + "-C", + "extra-filename=[..]", + "--out-dir", + "[..]", + "-L", + "dependency=[..]", + "-L", + "bar" + ], + "cwd": "[..]/cit/[..]/foo", + "deps": [], + "env": { + "CARGO": "[..]", + "CARGO_MANIFEST_DIR": "[..]", + "CARGO_PKG_AUTHORS": "[..]", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_NAME": "foo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION": "0.5.0", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_PKG_VERSION_MINOR": "5", + "CARGO_PKG_VERSION_PATCH": "0", + "CARGO_PKG_VERSION_PRE": "", + "CARGO_PRIMARY_PACKAGE": "1", + "FOO": "foo", + "LD_LIBRARY_PATH": "[..]", + "OUT_DIR": "[..]" + }, + "kind": "Host", + "links": "{...}", + "outputs": "{...}", + "package_name": "foo", + "package_version": "0.5.0", + "program": "rustc", + "target_kind": ["bin"], + "compile_mode": "build" + } + ] + } + "#, + ) + .run(); +} + +// Similar to `cargo_build_plan_single_dep`, this test adds `bar` *also* as a +// build dependency. The two `bar` dependencies should be processed separately +// while having the same metadata and fingerprint. Using the `post-build-scripts` +// option, `bar` should still be listed since we're only consuming the build +// dependency (the expected JSON is an exact copy of the other test). +// +// Note that the `bar` invocation is listed even though it *won't* be compiled +// (since it's already available from the compiled build dependency), the same +// way that in the default `init` option a dependency is listed even though it +// may be already fresh and not chosen for compilation. +#[cargo_test] +fn cargo_build_plan_post_build_script_with_build_dep() { + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + [dependencies] + bar = { path = "bar" } + [build-dependencies] + bar = { path = "bar" } + "#, + ) + .file( + "src/lib.rs", + r#" + pub fn foo() { bar::bar(); } + "#, + ) + .file("build.rs", r#"fn main() {}"#) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("build --build-plan post-build-scripts -Z unstable-options") + .masquerade_as_nightly_cargo() + .with_json( + r#" + { + "inputs": [ + "[..]/foo/Cargo.toml", + "[..]/foo/bar/Cargo.toml" + ], + "invocations": [ + { + "args": "{...}", + "cwd": "[..]/cit/[..]/foo", + "deps": [], + "env": "{...}", + "kind": "Host", + "links": "{...}", + "outputs": [ + "[..]/foo/target/debug/deps/libbar-[..].rlib", + "[..]/foo/target/debug/deps/libbar-[..].rmeta" + ], + "package_name": "bar", + "package_version": "0.0.1", + "program": "rustc", + "target_kind": ["lib"], + "compile_mode": "build" + }, + { + "args": "{...}", + "cwd": "[..]/cit/[..]/foo", + "deps": [0], + "env": "{...}", + "kind": "Host", + "links": "{...}", + "outputs": [ + "[..]/foo/target/debug/deps/libfoo-[..].rlib", + "[..]/foo/target/debug/deps/libfoo-[..].rmeta" + ], + "package_name": "foo", + "package_version": "0.5.0", + "program": "rustc", + "target_kind": ["lib"], + "compile_mode": "build" + } + ] + } + "#, + ) + .run(); +} + #[cargo_test] -fn build_plan_with_dev_dep() { +fn cargo_build_plan_with_dev_dep() { Package::new("bar", "0.1.0").publish(); let p = project()