cargo/core/compiler/
build_plan.rs

1//! A graph-like structure used to represent the rustc commands to build the package 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
9use std::collections::BTreeMap;
10use std::path::{Path, PathBuf};
11
12use serde::Serialize;
13
14use super::build_runner::OutputFile;
15use super::{BuildRunner, CompileKind, CompileMode, Unit};
16use crate::core::TargetKind;
17use crate::util::{internal, CargoResult, GlobalContext};
18use cargo_util::ProcessBuilder;
19
20#[derive(Debug, Serialize)]
21struct Invocation {
22    package_name: String,
23    package_version: semver::Version,
24    target_kind: TargetKind,
25    kind: CompileKind,
26    compile_mode: CompileMode,
27    deps: Vec<usize>,
28    outputs: Vec<PathBuf>,
29    links: BTreeMap<PathBuf, PathBuf>,
30    program: String,
31    args: Vec<String>,
32    env: BTreeMap<String, String>,
33    cwd: Option<PathBuf>,
34}
35
36#[derive(Debug)]
37pub struct BuildPlan {
38    invocation_map: BTreeMap<String, usize>,
39    plan: SerializedBuildPlan,
40}
41
42#[derive(Debug, Serialize)]
43struct SerializedBuildPlan {
44    invocations: Vec<Invocation>,
45    inputs: Vec<PathBuf>,
46}
47
48impl Invocation {
49    pub fn new(unit: &Unit, deps: Vec<usize>) -> Invocation {
50        let id = unit.pkg.package_id();
51        Invocation {
52            package_name: id.name().to_string(),
53            package_version: id.version().clone(),
54            kind: unit.kind,
55            target_kind: unit.target.kind().clone(),
56            compile_mode: unit.mode,
57            deps,
58            outputs: Vec::new(),
59            links: BTreeMap::new(),
60            program: String::new(),
61            args: Vec::new(),
62            env: BTreeMap::new(),
63            cwd: None,
64        }
65    }
66
67    pub fn add_output(&mut self, path: &Path, link: &Option<PathBuf>) {
68        self.outputs.push(path.to_path_buf());
69        if let Some(ref link) = *link {
70            self.links.insert(link.clone(), path.to_path_buf());
71        }
72    }
73
74    pub fn update_cmd(&mut self, cmd: &ProcessBuilder) -> CargoResult<()> {
75        self.program = cmd
76            .get_program()
77            .to_str()
78            .ok_or_else(|| anyhow::format_err!("unicode program string required"))?
79            .to_string();
80        self.cwd = Some(cmd.get_cwd().unwrap().to_path_buf());
81        for arg in cmd.get_args() {
82            self.args.push(
83                arg.to_str()
84                    .ok_or_else(|| anyhow::format_err!("unicode argument string required"))?
85                    .to_string(),
86            );
87        }
88        for (var, value) in cmd.get_envs() {
89            let Some(value) = value else { continue };
90            self.env.insert(
91                var.clone(),
92                value
93                    .to_str()
94                    .ok_or_else(|| anyhow::format_err!("unicode environment value required"))?
95                    .to_string(),
96            );
97        }
98        Ok(())
99    }
100}
101
102impl 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, build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<()> {
111        let id = self.plan.invocations.len();
112        self.invocation_map.insert(unit.buildkey(), id);
113        let deps = build_runner
114            .unit_deps(unit)
115            .iter()
116            .map(|dep| self.invocation_map[&dep.unit.buildkey()])
117            .collect();
118        let invocation = Invocation::new(unit, deps);
119        self.plan.invocations.push(invocation);
120        Ok(())
121    }
122
123    pub fn update(
124        &mut self,
125        invocation_name: &str,
126        cmd: &ProcessBuilder,
127        outputs: &[OutputFile],
128    ) -> CargoResult<()> {
129        let id = self.invocation_map[invocation_name];
130        let invocation =
131            self.plan.invocations.get_mut(id).ok_or_else(|| {
132                internal(format!("couldn't find invocation for {}", invocation_name))
133            })?;
134
135        invocation.update_cmd(cmd)?;
136        for output in outputs.iter() {
137            invocation.add_output(&output.path, &output.hardlink);
138        }
139
140        Ok(())
141    }
142
143    pub fn set_inputs(&mut self, inputs: Vec<PathBuf>) {
144        self.plan.inputs = inputs;
145    }
146
147    pub fn output_plan(self, gctx: &GlobalContext) {
148        let encoded = serde_json::to_string(&self.plan).unwrap();
149        crate::drop_println!(gctx, "{}", encoded);
150    }
151}
152
153impl SerializedBuildPlan {
154    pub fn new() -> SerializedBuildPlan {
155        SerializedBuildPlan {
156            invocations: Vec::new(),
157            inputs: Vec::new(),
158        }
159    }
160}