diff --git a/src/bin/bench.rs b/src/bin/bench.rs index eaf4c0d6b86..4d5c8c10400 100644 --- a/src/bin/bench.rs +++ b/src/bin/bench.rs @@ -19,6 +19,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_lib: bool, flag_bin: Vec, flag_bins: bool, @@ -72,6 +73,7 @@ Options: --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] --no-fail-fast Run all benchmarks regardless of failure + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -134,6 +136,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &options.flag_bench, options.flag_benches, options.flag_all_targets), message_format: options.flag_message_format, + build_plan: options.flag_build_plan, target_rustdoc_args: None, target_rustc_args: None, }, diff --git a/src/bin/build.rs b/src/bin/build.rs index 889052068e4..704051a7ac0 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -18,6 +18,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_release: bool, flag_lib: bool, flag_bin: Vec, @@ -69,6 +70,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -121,6 +123,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &options.flag_bench, options.flag_benches, options.flag_all_targets), message_format: options.flag_message_format, + build_plan: options.flag_build_plan, target_rustdoc_args: None, target_rustc_args: None, }; diff --git a/src/bin/check.rs b/src/bin/check.rs index 98172a28c1b..0713eb7d54c 100644 --- a/src/bin/check.rs +++ b/src/bin/check.rs @@ -38,6 +38,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -72,6 +73,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_release: bool, flag_lib: bool, flag_bin: Vec, @@ -137,6 +139,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &options.flag_bench, options.flag_benches, options.flag_all_targets), message_format: options.flag_message_format, + build_plan: options.flag_build_plan, target_rustdoc_args: None, target_rustc_args: None, }; diff --git a/src/bin/doc.rs b/src/bin/doc.rs index 6e17836963c..ee8f3c7f0a4 100644 --- a/src/bin/doc.rs +++ b/src/bin/doc.rs @@ -20,6 +20,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_package: Vec, flag_lib: bool, flag_bin: Vec, @@ -57,6 +58,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -112,6 +114,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &empty, false, false), message_format: options.flag_message_format, + build_plan: options.flag_build_plan, release: options.flag_release, mode: ops::CompileMode::Doc { deps: !options.flag_no_deps, diff --git a/src/bin/install.rs b/src/bin/install.rs index a0c000202fc..193e73b170f 100644 --- a/src/bin/install.rs +++ b/src/bin/install.rs @@ -134,6 +134,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &[], false, false), message_format: ops::MessageFormat::Human, + build_plan: false, target_rustc_args: None, target_rustdoc_args: None, }; diff --git a/src/bin/run.rs b/src/bin/run.rs index 48939d6547e..d1a2cd72eb7 100644 --- a/src/bin/run.rs +++ b/src/bin/run.rs @@ -20,6 +20,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_release: bool, flag_frozen: bool, flag_locked: bool, @@ -50,6 +51,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -106,6 +108,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { false) }, message_format: options.flag_message_format, + build_plan: options.flag_build_plan, target_rustdoc_args: None, target_rustc_args: None, }; diff --git a/src/bin/rustc.rs b/src/bin/rustc.rs index 98d7c410de4..63868988ca1 100644 --- a/src/bin/rustc.rs +++ b/src/bin/rustc.rs @@ -19,6 +19,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_release: bool, flag_lib: bool, flag_bin: Vec, @@ -68,6 +69,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -129,6 +131,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &options.flag_bench, options.flag_benches, options.flag_all_targets), message_format: options.flag_message_format, + build_plan: options.flag_build_plan, target_rustdoc_args: None, target_rustc_args: options.arg_opts.as_ref().map(|a| &a[..]), }; diff --git a/src/bin/rustdoc.rs b/src/bin/rustdoc.rs index 156a6b86796..cc944b5bffe 100644 --- a/src/bin/rustdoc.rs +++ b/src/bin/rustdoc.rs @@ -18,6 +18,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_package: Option, flag_lib: bool, flag_bin: Vec, @@ -66,6 +67,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date -Z FLAG ... Unstable (nightly-only) flags to Cargo @@ -114,6 +116,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { &options.flag_bench, options.flag_benches, options.flag_all_targets), message_format: options.flag_message_format, + build_plan: options.flag_build_plan, mode: ops::CompileMode::Doc { deps: false }, target_rustdoc_args: Some(&options.arg_opts), target_rustc_args: None, diff --git a/src/bin/test.rs b/src/bin/test.rs index c4c2bac3daa..bc9de31360f 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -31,6 +31,7 @@ pub struct Options { flag_quiet: Option, flag_color: Option, flag_message_format: MessageFormat, + flag_build_plan: bool, flag_release: bool, flag_no_fail_fast: bool, flag_frozen: bool, @@ -75,6 +76,7 @@ Options: -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never --message-format FMT Error format: human, json [default: human] + --build-plan Generate a build plan --no-fail-fast Run all tests regardless of failure --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -167,6 +169,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult { mode: mode, filter: filter, message_format: options.flag_message_format, + build_plan: options.flag_build_plan, target_rustdoc_args: None, target_rustc_args: None, }, diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index cc3400268f9..637fffd2abe 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -59,6 +59,8 @@ pub struct CompileOptions<'a> { pub mode: CompileMode, /// `--error_format` flag for the compiler. pub message_format: MessageFormat, + /// Output a build plan to stdout instead of actually compiling. + pub build_plan: bool, /// Extra arguments to be passed to rustdoc (for main crate and dependencies) pub target_rustdoc_args: Option<&'a [String]>, /// The specified target will be compiled with all the available arguments, @@ -81,6 +83,7 @@ impl<'a> CompileOptions<'a> { release: false, filter: CompileFilter::Default { required_features_filterable: false }, message_format: MessageFormat::Human, + build_plan: false, target_rustdoc_args: None, target_rustc_args: None, } @@ -213,6 +216,7 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>, let CompileOptions { config, jobs, target, spec, features, all_features, no_default_features, release, mode, message_format, + build_plan, ref filter, ref target_rustdoc_args, ref target_rustc_args } = *options; @@ -310,6 +314,7 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>, build_config.release = release; build_config.test = mode == CompileMode::Test || mode == CompileMode::Bench; build_config.json_messages = message_format == MessageFormat::Json; + build_config.build_plan = build_plan; if let CompileMode::Doc { deps } = mode { build_config.doc_all = deps; } diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index b72793ae60d..196e1889781 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -308,6 +308,7 @@ fn run_verify(ws: &Workspace, tar: &FileLock, opts: &PackageOpts) -> CargoResult filter: ops::CompileFilter::Default { required_features_filterable: true }, release: false, message_format: ops::MessageFormat::Human, + build_plan: false, mode: ops::CompileMode::Build, target_rustdoc_args: None, target_rustc_args: None, diff --git a/src/cargo/ops/cargo_rustc/build_plan.rs b/src/cargo/ops/cargo_rustc/build_plan.rs new file mode 100644 index 00000000000..16a61deea44 --- /dev/null +++ b/src/cargo/ops/cargo_rustc/build_plan.rs @@ -0,0 +1,109 @@ +//! A graph-like structure used to represent the rustc commands to build the project and the +//! interdependencies between them. +//! +//! The BuildPlan structure is used to store the dependency graph of a dry run so that it can be +//! shared with an external build system. Each Module in the BuildPlan comprises a single +//! subprocess and defines the build environment, the outputs produced by the subprocess, and the +//! dependencies on other Modules. + +use std::collections::{HashMap}; + +use super::{Context, Unit}; +use super::context::TargetFileType; +use util::{internal, CargoResult, ProcessBuilder}; +use std::sync::Arc; +use std::path::PathBuf; +use serde_json; + +#[derive(Debug, Serialize)] +struct Module { + deps: Vec, + outputs: Vec, + links: HashMap, + program: String, + args: Vec, + env: HashMap, + cwd: String, +} + +#[derive(Debug, Serialize)] +pub struct BuildPlan { + modules: HashMap, +} + +impl Module { + pub fn new(deps: Vec) -> Module { + Module { + deps: deps.iter().map(|dep| buildkey(dep)).collect(), + outputs: Vec::new(), + links: HashMap::new(), + program: String::new(), + args: Vec::new(), + env: HashMap::new(), + cwd: String::new(), + } + } + + pub fn add_output(&mut self, path: &PathBuf, link: &Option) { + self.outputs.push(path.clone()); + if link.is_some() { + self.links.insert(link.as_ref().unwrap().clone(), path.clone()); + } + } + + pub fn update_cmd(&mut self, cmd: ProcessBuilder) { + self.program = cmd.get_program().to_str().expect("unicode program string required").to_string().clone(); + self.cwd = cmd.get_cwd().expect("cwd is required").to_str().expect("unicode cwd string required").to_string().clone(); + for arg in cmd.get_args().iter() { + self.args.push(arg.to_str().expect("unicode argument string required").to_string().clone()); + } + for (var, value) in cmd.get_envs() { + self.env.insert(var.clone(), value.as_ref().expect("environment value required").to_str().expect("unicode environment value required").to_string().clone()); + } + } +} + +pub fn buildkey(unit: &Unit) -> String { + format!("{} {} {} {:?}", unit.pkg, unit.target, unit.profile, unit.kind) +} + +impl BuildPlan { + pub fn new() -> BuildPlan { + BuildPlan { + modules: HashMap::new(), + } + } + + pub fn add(&mut self, + cx: &Context, + unit: &Unit, + ) -> CargoResult<()> { + let k = buildkey(unit); + let deps = cx.dep_targets(&unit)?; + let module = Module::new(deps); + self.modules.insert(k, module); + Ok(()) + } + + pub fn update(&mut self, + module_name: String, + cmd: ProcessBuilder, + filenames: Arc, TargetFileType)>>, + ) -> CargoResult<()> { + let module = self.modules.get_mut(&module_name).ok_or_else(|| { + internal(format!("couldn't find module for {}", module_name)) + })?; + + module.update_cmd(cmd); + for &(ref dst, ref link_dst, _) in filenames.iter() { + module.add_output(dst, link_dst); + } + + Ok(()) + } + + pub fn output_plan(self) { + let encoded = serde_json::to_string_pretty(&self).unwrap(); + println!("{}", encoded); + } +} diff --git a/src/cargo/ops/cargo_rustc/custom_build.rs b/src/cargo/ops/cargo_rustc/custom_build.rs index 95940bfcb13..439bcc0d4dd 100644 --- a/src/cargo/ops/cargo_rustc/custom_build.rs +++ b/src/cargo/ops/cargo_rustc/custom_build.rs @@ -12,6 +12,7 @@ use util::machine_message; use super::job::Work; use super::{fingerprint, Kind, Context, Unit}; +use super::build_plan::buildkey; /// Contains the parsed output of a custom build script. #[derive(Clone, Debug, Hash)] @@ -88,12 +89,16 @@ pub fn prepare<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) build_work(cx, unit)? }; - // Now that we've prep'd our work, build the work needed to manage the - // fingerprint and then start returning that upwards. - let (freshness, dirty, fresh) = + if cx.build_config.build_plan { + Ok((work_dirty, work_fresh, Freshness::Dirty)) + } else { + // Now that we've prep'd our work, build the work needed to manage the + // fingerprint and then start returning that upwards. + let (freshness, dirty, fresh) = fingerprint::prepare_build_cmd(cx, unit)?; - Ok((work_dirty.then(dirty), work_fresh.then(fresh), freshness)) + Ok((work_dirty.then(dirty), work_fresh.then(fresh), freshness)) + } } fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) @@ -104,6 +109,8 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) }).expect("running a script not depending on an actual script"); let script_output = cx.build_script_dir(build_script_unit); let build_output = cx.build_script_out_dir(unit); + let build_plan = cx.build_config.build_plan; + let module_name = buildkey(unit); // Building the command to execute let to_exec = script_output.join(unit.target.name()); @@ -230,7 +237,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) // 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 { let build_state = build_state.outputs.lock().unwrap(); for (name, id) in lib_deps { let key = (id.clone(), kind); @@ -252,49 +259,53 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) } // And now finally, run the build command itself! - state.running(&cmd); - let output = cmd.exec_with_streaming( - &mut |out_line| { state.stdout(out_line); Ok(()) }, - &mut |err_line| { state.stderr(err_line); Ok(()) }, - true, - ).map_err(|e| { - format_err!("failed to run custom build command for `{}`\n{}", - pkg_name, e) + if build_plan { + state.build_plan(module_name, cmd.clone(), Arc::new(Vec::new())); + } else { + state.running(&cmd); + let output = cmd.exec_with_streaming( + &mut |out_line| { state.stdout(out_line); Ok(()) }, + &mut |err_line| { state.stderr(err_line); Ok(()) }, + true, + ).map_err(|e| { + format_err!("failed to run custom build command for `{}`\n{}", + pkg_name, e) + + })?; - })?; + // After the build command has finished running, we need to be sure to + // remember all of its output so we can later discover precisely what it + // was, even if we don't run the build command again (due to freshness). + // + // This is also the location where we provide feedback into the build + // state informing what variables were discovered via our script as + // well. + paths::write(&output_file, &output.stdout)?; + paths::write(&err_file, &output.stderr)?; + paths::write(&root_output_file, &util::path2bytes(&root_output)?)?; + let parsed_output = BuildOutput::parse( + &output.stdout, + &pkg_name, + &root_output, + &root_output, + )?; + + if json_messages { + let library_paths = parsed_output.library_paths.iter().map(|l| { + l.display().to_string() + }).collect::>(); + machine_message::emit(&machine_message::BuildScript { + package_id: &id, + linked_libs: &parsed_output.library_links, + linked_paths: &library_paths, + cfgs: &parsed_output.cfgs, + env: &parsed_output.env, + }); + } - // After the build command has finished running, we need to be sure to - // remember all of its output so we can later discover precisely what it - // was, even if we don't run the build command again (due to freshness). - // - // This is also the location where we provide feedback into the build - // state informing what variables were discovered via our script as - // well. - paths::write(&output_file, &output.stdout)?; - paths::write(&err_file, &output.stderr)?; - paths::write(&root_output_file, &util::path2bytes(&root_output)?)?; - let parsed_output = BuildOutput::parse( - &output.stdout, - &pkg_name, - &root_output, - &root_output, - )?; - - if json_messages { - let library_paths = parsed_output.library_paths.iter().map(|l| { - l.display().to_string() - }).collect::>(); - machine_message::emit(&machine_message::BuildScript { - package_id: &id, - linked_libs: &parsed_output.library_links, - linked_paths: &library_paths, - cfgs: &parsed_output.cfgs, - env: &parsed_output.env, - }); + build_state.insert(id, kind, parsed_output); } - - build_state.insert(id, kind, parsed_output); Ok(()) }); diff --git a/src/cargo/ops/cargo_rustc/job_queue.rs b/src/cargo/ops/cargo_rustc/job_queue.rs index 281f057aba1..345ef89ac65 100644 --- a/src/cargo/ops/cargo_rustc/job_queue.rs +++ b/src/cargo/ops/cargo_rustc/job_queue.rs @@ -3,6 +3,8 @@ use std::collections::hash_map::HashMap; use std::fmt; use std::io; use std::mem; +use std::sync::Arc; +use std::path::PathBuf; use std::sync::mpsc::{channel, Sender, Receiver}; use crossbeam::{self, Scope}; @@ -13,7 +15,8 @@ use util::{Config, DependencyQueue, Fresh, Dirty, Freshness}; use util::{CargoResult, ProcessBuilder, profile, internal, CargoResultExt}; use {handle_error}; -use super::{Context, Kind, Unit}; +use super::{Context, Kind, Unit, BuildPlan}; +use super::context::TargetFileType; use super::job::Job; /// A management structure of the entire dependency graph to compile. @@ -56,6 +59,7 @@ pub struct JobState<'a> { enum Message<'a> { Run(String), + BuildPlanMsg(String, ProcessBuilder, Arc, TargetFileType)>>), Stdout(String), Stderr(String), Token(io::Result), @@ -67,6 +71,10 @@ impl<'a> JobState<'a> { let _ = self.tx.send(Message::Run(cmd.to_string())); } + pub fn build_plan(&self, module_name: String, cmd: ProcessBuilder, filenames: Arc, TargetFileType)>>) { + let _ = self.tx.send(Message::BuildPlanMsg(module_name, cmd, filenames)); + } + pub fn stdout(&self, out: &str) { let _ = self.tx.send(Message::Stdout(out.to_string())); } @@ -109,7 +117,7 @@ impl<'a> JobQueue<'a> { /// This function will spawn off `config.jobs()` workers to build all of the /// necessary dependencies, in order. Freshness is propagated as far as /// possible along each dependency chain. - pub fn execute(&mut self, cx: &mut Context) -> CargoResult<()> { + pub fn execute(&mut self, cx: &mut Context, plan: &mut BuildPlan) -> CargoResult<()> { let _p = profile::start("executing the job graph"); // We need to give a handle to the send half of our message queue to the @@ -136,12 +144,13 @@ impl<'a> JobQueue<'a> { })?; crossbeam::scope(|scope| { - self.drain_the_queue(cx, scope, &helper) + self.drain_the_queue(cx, plan, scope, &helper) }) } fn drain_the_queue(&mut self, cx: &mut Context, + plan: &mut BuildPlan, scope: &Scope<'a>, jobserver_helper: &HelperThread) -> CargoResult<()> { @@ -149,6 +158,7 @@ impl<'a> JobQueue<'a> { let mut tokens = Vec::new(); let mut queue = Vec::new(); + let build_plan = cx.build_config.build_plan; trace!("queue: {:#?}", self.queue); // Iteratively execute the entire dependency graph. Each turn of the @@ -189,7 +199,7 @@ impl<'a> JobQueue<'a> { // we're able to perform some parallel work. while error.is_none() && self.active < tokens.len() + 1 && !queue.is_empty() { let (key, job, fresh) = queue.remove(0); - self.run(key, fresh, job, cx.config, scope)?; + self.run(key, fresh, job, cx.config, scope, build_plan)?; } // If after all that we're not actually running anything then we're @@ -209,6 +219,9 @@ impl<'a> JobQueue<'a> { Message::Run(cmd) => { cx.config.shell().verbose(|c| c.status("Running", &cmd))?; } + Message::BuildPlanMsg(module_name, cmd, filenames) => { + plan.update(module_name, cmd, filenames)?; + } Message::Stdout(out) => { if cx.config.extra_verbose() { println!("{}", out); @@ -268,7 +281,9 @@ impl<'a> JobQueue<'a> { build_type, opt_type, time_elapsed); - cx.config.shell().status("Finished", message)?; + if !build_plan { + cx.config.shell().status("Finished", message)?; + } Ok(()) } else if let Some(e) = error { Err(e) @@ -285,7 +300,8 @@ impl<'a> JobQueue<'a> { fresh: Freshness, job: Job, config: &Config, - scope: &Scope<'a>) -> CargoResult<()> { + scope: &Scope<'a>, + build_plan: bool) -> CargoResult<()> { info!("start: {:?}", key); self.active += 1; @@ -303,8 +319,10 @@ impl<'a> JobQueue<'a> { Freshness::Dirty => { scope.spawn(doit); } } - // Print out some nice progress information - self.note_working_on(config, &key, fresh)?; + if !build_plan { + // Print out some nice progress information + self.note_working_on(config, &key, fresh)?; + } Ok(()) } diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index ea9294fd06b..85a94a664cf 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -17,6 +17,7 @@ use util::{Config, internal, profile, join_paths}; use util::errors::{CargoResult, CargoResultExt, Internal}; use util::Freshness; +use self::build_plan::{BuildPlan, buildkey}; use self::job::{Job, Work}; use self::job_queue::JobQueue; @@ -27,6 +28,7 @@ pub use self::context::{Context, Unit, TargetFileType}; pub use self::custom_build::{BuildOutput, BuildMap, BuildScripts}; pub use self::layout::is_bad_artifact_name; +mod build_plan; mod compilation; mod context; mod custom_build; @@ -69,6 +71,7 @@ pub struct BuildConfig { pub doc_all: bool, /// Whether to print std output in json format (for machine reading) pub json_messages: bool, + pub build_plan: bool, } /// Information required to build for a target @@ -160,6 +163,8 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, build_config, profiles)?; let mut queue = JobQueue::new(&cx); + let mut plan = BuildPlan::new(); + let build_plan = cx.build_config.build_plan; cx.prepare()?; cx.probe_target_info(&units)?; @@ -172,11 +177,15 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, // part of this, that's all done next as part of the `execute` // function which will run everything in order with proper // parallelism. - compile(&mut cx, &mut queue, unit, Arc::clone(&exec))?; + compile(&mut cx, &mut queue, &mut plan, unit, Arc::clone(&exec))?; } // Now that we've figured out everything that we're going to do, do it! - queue.execute(&mut cx)?; + queue.execute(&mut cx, &mut plan)?; + + if build_plan { + plan.output_plan(); + } for unit in units.iter() { for &(ref dst, ref link_dst, file_type) in cx.target_filenames(unit)?.iter() { @@ -259,8 +268,10 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, jobs: &mut JobQueue<'a>, + plan: &mut BuildPlan, unit: &Unit<'a>, exec: Arc) -> CargoResult<()> { + let build_plan = cx.build_config.build_plan; if !cx.compiled.insert(*unit) { return Ok(()) } @@ -277,6 +288,8 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, } else if unit.profile.doc && unit.profile.test { // we run these targets later, so this is just a noop for now (Work::noop(), Work::noop(), Freshness::Fresh) + } else if build_plan { + (rustc(cx, unit, exec.clone())?, Work::noop(), Freshness::Dirty) } else { let (mut freshness, dirty, fresh) = fingerprint::prepare_target(cx, unit)?; let work = if unit.profile.doc { @@ -295,11 +308,14 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, (dirty, fresh, freshness) }; jobs.enqueue(cx, unit, Job::new(dirty, fresh), freshness)?; + if build_plan { + plan.add(cx, unit)?; + } drop(p); // Be sure to compile all dependencies of this target as well. for unit in cx.dep_targets(unit)?.iter() { - compile(cx, jobs, unit, exec.clone())?; + compile(cx, jobs, plan, unit, exec.clone())?; } Ok(()) @@ -309,8 +325,10 @@ fn rustc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>, exec: Arc) -> CargoResult { let mut rustc = prepare_rustc(cx, &unit.target.rustc_crate_types(), unit)?; + let build_plan = cx.build_config.build_plan; let name = unit.pkg.name().to_string(); + let module_name = buildkey(unit); // If this is an upstream dep we don't want warnings from, turn off all // lints. @@ -370,10 +388,12 @@ fn rustc<'a, 'cfg>(cx: &mut Context<'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(); - add_native_deps(&mut rustc, &build_state, &build_deps, - pass_l_flag, ¤t_id)?; - add_plugin_deps(&mut rustc, &build_state, &build_deps, - &root_output)?; + if !build_plan { + add_native_deps(&mut rustc, &build_state, &build_deps, + pass_l_flag, ¤t_id)?; + add_plugin_deps(&mut rustc, &build_state, &build_deps, + &root_output)?; + } add_custom_env(&mut rustc, &build_state, ¤t_id, kind)?; } @@ -421,6 +441,8 @@ fn rustc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, ).chain_err(|| { format!("Could not compile `{}`.", name) })?; + } else if build_plan { + state.build_plan(module_name, rustc.clone(), filenames.clone()); } else { exec.exec(rustc, &package_id, &target) .map_err(Internal::new)