Skip to content

Commit 13c258e

Browse files
committed
Auto merge of #5301 - mshal:build-plan, r=matklad
Add --build-plan for 'cargo build' With 'cargo build --build-plan', cargo does not actually run any commands, but instead prints out what it would have done in the form of a JSON data structure. Fixes #3815
2 parents ef719bc + a071c3a commit 13c258e

File tree

11 files changed

+548
-75
lines changed

11 files changed

+548
-75
lines changed

src/bin/cargo/command_prelude.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ pub trait AppExt: Sized {
132132
)
133133
}
134134

135+
fn arg_build_plan(self) -> Self {
136+
self._arg(opt("build-plan", "Output the build plan in JSON"))
137+
}
138+
135139
fn arg_new_opts(self) -> Self {
136140
self._arg(
137141
opt(
@@ -275,6 +279,12 @@ pub trait ArgMatchesExt {
275279
let mut build_config = BuildConfig::new(config, self.jobs()?, &self.target(), mode)?;
276280
build_config.message_format = message_format;
277281
build_config.release = self._is_present("release");
282+
build_config.build_plan = self._is_present("build-plan");
283+
if build_config.build_plan && !config.cli_unstable().unstable_options {
284+
Err(format_err!(
285+
"`--build-plan` flag is unstable, pass `-Z unstable-options` to enable it"
286+
))?;
287+
};
278288

279289
let opts = CompileOptions {
280290
config,

src/bin/cargo/commands/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub fn cli() -> App {
3131
.arg(opt("out-dir", "Copy final artifacts to this directory").value_name("PATH"))
3232
.arg_manifest_path()
3333
.arg_message_format()
34+
.arg_build_plan()
3435
.after_help(
3536
"\
3637
If the --package argument is given, then SPEC is a package id specification

src/cargo/core/compiler/build_config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub struct BuildConfig {
1414
pub mode: CompileMode,
1515
/// Whether to print std output in json format (for machine reading)
1616
pub message_format: MessageFormat,
17+
/// Output a build plan to stdout instead of actually compiling.
18+
pub build_plan: bool,
1719
}
1820

1921
impl BuildConfig {
@@ -87,6 +89,7 @@ impl BuildConfig {
8789
release: false,
8890
mode,
8991
message_format: MessageFormat::Human,
92+
build_plan: false,
9093
})
9194
}
9295

src/cargo/core/compiler/build_context/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> {
229229
}
230230
None
231231
}
232+
233+
/// Return the list of filenames read by cargo to generate the BuildContext
234+
/// (all Cargo.toml, etc).
235+
pub fn inputs(&self) -> CargoResult<Vec<PathBuf>> {
236+
let mut inputs = Vec::new();
237+
for id in self.packages.package_ids() {
238+
let pkg = self.get_package(id)?;
239+
inputs.push(pkg.manifest_path().to_path_buf());
240+
}
241+
inputs.sort();
242+
Ok(inputs)
243+
}
232244
}
233245

234246
/// Information required to build for a target

src/cargo/core/compiler/build_plan.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//! A graph-like structure used to represent the rustc commands to build the project and the
2+
//! interdependencies between them.
3+
//!
4+
//! The BuildPlan structure is used to store the dependency graph of a dry run so that it can be
5+
//! shared with an external build system. Each Invocation in the BuildPlan comprises a single
6+
//! subprocess and defines the build environment, the outputs produced by the subprocess, and the
7+
//! dependencies on other Invocations.
8+
9+
use std::collections::BTreeMap;
10+
11+
use core::TargetKind;
12+
use super::{Context, Kind, Unit};
13+
use super::context::OutputFile;
14+
use util::{internal, CargoResult, ProcessBuilder};
15+
use std::sync::Arc;
16+
use std::path::PathBuf;
17+
use serde_json;
18+
use semver;
19+
20+
#[derive(Debug, Serialize)]
21+
struct Invocation {
22+
package_name: String,
23+
package_version: semver::Version,
24+
target_kind: TargetKind,
25+
kind: Kind,
26+
deps: Vec<usize>,
27+
outputs: Vec<PathBuf>,
28+
links: BTreeMap<PathBuf, PathBuf>,
29+
program: String,
30+
args: Vec<String>,
31+
env: BTreeMap<String, String>,
32+
cwd: Option<PathBuf>,
33+
}
34+
35+
#[derive(Debug)]
36+
pub struct BuildPlan {
37+
invocation_map: BTreeMap<String, usize>,
38+
plan: SerializedBuildPlan,
39+
}
40+
41+
#[derive(Debug, Serialize)]
42+
struct SerializedBuildPlan {
43+
invocations: Vec<Invocation>,
44+
inputs: Vec<PathBuf>,
45+
}
46+
47+
impl Invocation {
48+
pub fn new(unit: &Unit, deps: Vec<usize>) -> Invocation {
49+
let id = unit.pkg.package_id();
50+
Invocation {
51+
package_name: id.name().to_string(),
52+
package_version: id.version().clone(),
53+
kind: unit.kind,
54+
target_kind: unit.target.kind().clone(),
55+
deps: deps,
56+
outputs: Vec::new(),
57+
links: BTreeMap::new(),
58+
program: String::new(),
59+
args: Vec::new(),
60+
env: BTreeMap::new(),
61+
cwd: None,
62+
}
63+
}
64+
65+
pub fn add_output(&mut self, path: &PathBuf, link: &Option<PathBuf>) {
66+
self.outputs.push(path.clone());
67+
if let Some(ref link) = *link {
68+
self.links.insert(link.clone(), path.clone());
69+
}
70+
}
71+
72+
pub fn update_cmd(&mut self, cmd: ProcessBuilder) -> CargoResult<()> {
73+
self.program = cmd.get_program()
74+
.to_str()
75+
.ok_or_else(|| format_err!("unicode program string required"))?
76+
.to_string();
77+
self.cwd = Some(cmd.get_cwd().unwrap().to_path_buf());
78+
for arg in cmd.get_args().iter() {
79+
self.args.push(
80+
arg.to_str()
81+
.ok_or_else(|| format_err!("unicode argument string required"))?
82+
.to_string(),
83+
);
84+
}
85+
for (var, value) in cmd.get_envs() {
86+
let value = match value {
87+
Some(s) => s,
88+
None => continue,
89+
};
90+
self.env.insert(
91+
var.clone(),
92+
value
93+
.to_str()
94+
.ok_or_else(|| format_err!("unicode environment value required"))?
95+
.to_string(),
96+
);
97+
}
98+
Ok(())
99+
}
100+
}
101+
102+
impl BuildPlan {
103+
pub fn new() -> BuildPlan {
104+
BuildPlan {
105+
invocation_map: BTreeMap::new(),
106+
plan: SerializedBuildPlan::new(),
107+
}
108+
}
109+
110+
pub fn add(&mut self, cx: &Context, unit: &Unit) -> CargoResult<()> {
111+
let id = self.plan.invocations.len();
112+
self.invocation_map.insert(unit.buildkey(), id);
113+
let deps = cx.dep_targets(&unit)
114+
.iter()
115+
.map(|dep| self.invocation_map[&dep.buildkey()])
116+
.collect();
117+
let invocation = Invocation::new(unit, deps);
118+
self.plan.invocations.push(invocation);
119+
Ok(())
120+
}
121+
122+
pub fn update(
123+
&mut self,
124+
invocation_name: String,
125+
cmd: ProcessBuilder,
126+
outputs: Arc<Vec<OutputFile>>,
127+
) -> CargoResult<()> {
128+
let id = self.invocation_map[&invocation_name];
129+
let invocation = self.plan
130+
.invocations
131+
.get_mut(id)
132+
.ok_or_else(|| internal(format!("couldn't find invocation for {}", invocation_name)))?;
133+
134+
invocation.update_cmd(cmd)?;
135+
for output in outputs.iter() {
136+
invocation.add_output(&output.path, &output.hardlink);
137+
}
138+
139+
Ok(())
140+
}
141+
142+
pub fn set_inputs(&mut self, inputs: Vec<PathBuf>) {
143+
self.plan.inputs = inputs;
144+
}
145+
146+
pub fn output_plan(self) {
147+
let encoded = serde_json::to_string(&self.plan).unwrap();
148+
println!("{}", encoded);
149+
}
150+
}
151+
152+
impl SerializedBuildPlan {
153+
pub fn new() -> SerializedBuildPlan {
154+
SerializedBuildPlan {
155+
invocations: Vec::new(),
156+
inputs: Vec::new(),
157+
}
158+
}
159+
}

src/cargo/core/compiler/context/mod.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,28 @@ use std::collections::{HashMap, HashSet};
33
use std::fmt::Write;
44
use std::path::PathBuf;
55
use std::sync::Arc;
6+
use std::cmp::Ordering;
67

78
use jobserver::Client;
89

910
use core::{Package, PackageId, Resolve, Target};
1011
use core::profiles::Profile;
1112
use util::errors::{CargoResult, CargoResultExt};
12-
use util::{internal, profile, Config};
13+
use util::{internal, profile, Config, short_hash};
1314

1415
use super::custom_build::{self, BuildDeps, BuildScripts, BuildState};
1516
use super::fingerprint::Fingerprint;
1617
use super::job_queue::JobQueue;
1718
use super::layout::Layout;
1819
use super::{BuildContext, Compilation, CompileMode, Executor, FileFlavor, Kind};
20+
use super::build_plan::BuildPlan;
1921

2022
mod unit_dependencies;
2123
use self::unit_dependencies::build_unit_dependencies;
2224

2325
mod compilation_files;
24-
pub use self::compilation_files::Metadata;
25-
use self::compilation_files::{CompilationFiles, OutputFile};
26+
pub use self::compilation_files::{Metadata, OutputFile};
27+
use self::compilation_files::CompilationFiles;
2628

2729
/// All information needed to define a Unit.
2830
///
@@ -62,6 +64,24 @@ pub struct Unit<'a> {
6264
pub mode: CompileMode,
6365
}
6466

67+
impl<'a> Unit<'a> {
68+
pub fn buildkey(&self) -> String {
69+
format!("{}-{}", self.pkg.name(), short_hash(self))
70+
}
71+
}
72+
73+
impl<'a> Ord for Unit<'a> {
74+
fn cmp(&self, other: &Unit) -> Ordering {
75+
self.buildkey().cmp(&other.buildkey())
76+
}
77+
}
78+
79+
impl<'a> PartialOrd for Unit<'a> {
80+
fn partial_cmp(&self, other: &Unit) -> Option<Ordering> {
81+
Some(self.cmp(other))
82+
}
83+
}
84+
6585
pub struct Context<'a, 'cfg: 'a> {
6686
pub bcx: &'a BuildContext<'a, 'cfg>,
6787
pub compilation: Compilation<'cfg>,
@@ -121,6 +141,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
121141
exec: &Arc<Executor>,
122142
) -> CargoResult<Compilation<'cfg>> {
123143
let mut queue = JobQueue::new(self.bcx);
144+
let mut plan = BuildPlan::new();
145+
let build_plan = self.bcx.build_config.build_plan;
124146
self.prepare_units(export_dir, units)?;
125147
self.prepare()?;
126148
custom_build::build_map(&mut self, units)?;
@@ -131,11 +153,16 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
131153
// part of this, that's all done next as part of the `execute`
132154
// function which will run everything in order with proper
133155
// parallelism.
134-
super::compile(&mut self, &mut queue, unit, exec)?;
156+
super::compile(&mut self, &mut queue, &mut plan, unit, exec)?;
135157
}
136158

137159
// Now that we've figured out everything that we're going to do, do it!
138-
queue.execute(&mut self)?;
160+
queue.execute(&mut self, &mut plan)?;
161+
162+
if build_plan {
163+
plan.set_inputs(self.bcx.inputs()?);
164+
plan.output_plan();
165+
}
139166

140167
for unit in units.iter() {
141168
for output in self.outputs(unit)?.iter() {
@@ -366,7 +393,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
366393
return Vec::new();
367394
}
368395
}
369-
self.unit_dependencies[unit].clone()
396+
let mut deps = self.unit_dependencies[unit].clone();
397+
deps.sort();
398+
deps
370399
}
371400

372401
pub fn incremental_args(&self, unit: &Unit) -> CargoResult<Vec<String>> {

0 commit comments

Comments
 (0)