-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add --build-plan for 'cargo build' #5301
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
//! 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 Invocation in the BuildPlan comprises a single | ||
//! subprocess and defines the build environment, the outputs produced by the subprocess, and the | ||
//! dependencies on other Invocations. | ||
|
||
use std::collections::BTreeMap; | ||
|
||
use core::TargetKind; | ||
use super::{Context, Kind, Unit}; | ||
use super::context::OutputFile; | ||
use util::{internal, CargoResult, ProcessBuilder}; | ||
use std::sync::Arc; | ||
use std::path::PathBuf; | ||
use serde_json; | ||
use semver; | ||
|
||
#[derive(Debug, Serialize)] | ||
struct Invocation { | ||
package_name: String, | ||
package_version: semver::Version, | ||
target_kind: TargetKind, | ||
kind: Kind, | ||
deps: Vec<usize>, | ||
outputs: Vec<PathBuf>, | ||
links: BTreeMap<PathBuf, PathBuf>, | ||
program: String, | ||
args: Vec<String>, | ||
env: BTreeMap<String, String>, | ||
cwd: Option<PathBuf>, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct BuildPlan { | ||
invocation_map: BTreeMap<String, usize>, | ||
plan: SerializedBuildPlan, | ||
} | ||
|
||
#[derive(Debug, Serialize)] | ||
struct SerializedBuildPlan { | ||
invocations: Vec<Invocation>, | ||
inputs: Vec<PathBuf>, | ||
} | ||
|
||
impl Invocation { | ||
pub fn new(unit: &Unit, deps: Vec<usize>) -> Invocation { | ||
let id = unit.pkg.package_id(); | ||
Invocation { | ||
package_name: id.name().to_string(), | ||
package_version: id.version().clone(), | ||
kind: unit.kind, | ||
target_kind: unit.target.kind().clone(), | ||
deps: deps, | ||
outputs: Vec::new(), | ||
links: BTreeMap::new(), | ||
program: String::new(), | ||
args: Vec::new(), | ||
env: BTreeMap::new(), | ||
cwd: None, | ||
} | ||
} | ||
|
||
pub fn add_output(&mut self, path: &PathBuf, link: &Option<PathBuf>) { | ||
self.outputs.push(path.clone()); | ||
if let Some(ref link) = *link { | ||
self.links.insert(link.clone(), path.clone()); | ||
} | ||
} | ||
|
||
pub fn update_cmd(&mut self, cmd: ProcessBuilder) -> CargoResult<()> { | ||
self.program = cmd.get_program() | ||
.to_str() | ||
.ok_or_else(|| format_err!("unicode program string required"))? | ||
.to_string() | ||
.clone(); | ||
self.cwd = Some(cmd.get_cwd().unwrap().to_path_buf()); | ||
for arg in cmd.get_args().iter() { | ||
self.args.push( | ||
arg.to_str() | ||
.ok_or_else(|| format_err!("unicode argument string required"))? | ||
.to_string() | ||
.clone(), | ||
); | ||
} | ||
for var in cmd.get_envs().keys() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could be written as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, how does that avoid the unwrap_or_default? I still get an Option from cmd.get_envs() for the value. |
||
let value = cmd.get_env(var).unwrap_or_default(); | ||
self.env.insert( | ||
var.clone(), | ||
value | ||
.to_str() | ||
.ok_or_else(|| format_err!("unicode environment value required"))? | ||
.to_string(), | ||
); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl BuildPlan { | ||
pub fn new() -> BuildPlan { | ||
BuildPlan { | ||
invocation_map: BTreeMap::new(), | ||
plan: SerializedBuildPlan::new(), | ||
} | ||
} | ||
|
||
pub fn add(&mut self, cx: &Context, unit: &Unit) -> CargoResult<()> { | ||
let id = self.plan.invocations.len(); | ||
self.invocation_map.insert(unit.buildkey(), id); | ||
let deps = cx.dep_targets(&unit) | ||
.iter() | ||
.map(|dep| self.invocation_map[&dep.buildkey()]) | ||
.collect(); | ||
let invocation = Invocation::new(unit, deps); | ||
self.plan.invocations.push(invocation); | ||
Ok(()) | ||
} | ||
|
||
pub fn update( | ||
&mut self, | ||
invocation_name: String, | ||
cmd: ProcessBuilder, | ||
outputs: Arc<Vec<OutputFile>>, | ||
) -> CargoResult<()> { | ||
let id = self.invocation_map[&invocation_name]; | ||
let invocation = self.plan | ||
.invocations | ||
.get_mut(id) | ||
.ok_or_else(|| internal(format!("couldn't find invocation for {}", invocation_name)))?; | ||
|
||
invocation.update_cmd(cmd)?; | ||
for output in outputs.iter() { | ||
invocation.add_output(&output.path, &output.hardlink); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn set_inputs(&mut self, inputs: Vec<PathBuf>) { | ||
self.plan.inputs = inputs; | ||
} | ||
|
||
pub fn output_plan(self) { | ||
let encoded = serde_json::to_string(&self.plan).unwrap(); | ||
println!("{}", encoded); | ||
} | ||
} | ||
|
||
impl SerializedBuildPlan { | ||
pub fn new() -> SerializedBuildPlan { | ||
SerializedBuildPlan { | ||
invocations: Vec::new(), | ||
inputs: Vec::new(), | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
.clone
is not necessary I think: we already get an ownedString
afterto_string
. The same applies to aclone
below as well.