#
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::{CargoResult, GlobalContext, internal};
    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}