#
cargo/core/compiler/
build_config.rs

    1use crate::core::compiler::CompileKind;
    2use crate::util::context::JobsConfig;
    3use crate::util::interning::InternedString;
    4use crate::util::{CargoResult, GlobalContext, RustfixDiagnosticServer};
    5use anyhow::{Context as _, bail};
    6use cargo_util::ProcessBuilder;
    7use serde::ser;
    8use std::cell::RefCell;
    9use std::path::PathBuf;
    10use std::rc::Rc;
    11use std::thread::available_parallelism;
    12
    13/// Configuration information for a rustc build.
    14#[derive(Debug, Clone)]
    15pub struct BuildConfig {
    16    /// The requested kind of compilation for this session
    17    pub requested_kinds: Vec<CompileKind>,
    18    /// Number of rustc jobs to run in parallel.
    19    pub jobs: u32,
    20    /// Do not abort the build as soon as there is an error.
    21    pub keep_going: bool,
    22    /// Build profile
    23    pub requested_profile: InternedString,
    24    /// The intent we are compiling in.
    25    pub intent: UserIntent,
    26    /// `true` to print stdout in JSON format (for machine reading).
    27    pub message_format: MessageFormat,
    28    /// Force Cargo to do a full rebuild and treat each target as changed.
    29    pub force_rebuild: bool,
    30    /// Output a build plan to stdout instead of actually compiling.
    31    pub build_plan: bool,
    32    /// Output the unit graph to stdout instead of actually compiling.
    33    pub unit_graph: bool,
    34    /// `true` to avoid really compiling.
    35    pub dry_run: bool,
    36    /// An optional override of the rustc process for primary units
    37    pub primary_unit_rustc: Option<ProcessBuilder>,
    38    /// A thread used by `cargo fix` to receive messages on a socket regarding
    39    /// the success/failure of applying fixes.
    40    pub rustfix_diagnostic_server: Rc<RefCell<Option<RustfixDiagnosticServer>>>,
    41    /// The directory to copy final artifacts to. Note that even if
    42    /// `artifact-dir` is set, a copy of artifacts still can be found at
    43    /// `target/(debug\release)` as usual.
    44    /// Named `export_dir` to avoid confusion with
    45    /// `CompilationFiles::artifact_dir`.
    46    pub export_dir: Option<PathBuf>,
    47    /// `true` to output a future incompatibility report at the end of the build
    48    pub future_incompat_report: bool,
    49    /// Which kinds of build timings to output (empty if none).
    50    pub timing_outputs: Vec<TimingOutput>,
    51    /// Output SBOM precursor files.
    52    pub sbom: bool,
    53    /// Build compile time dependencies only, e.g., build scripts and proc macros
    54    pub compile_time_deps_only: bool,
    55}
    56
    57fn default_parallelism() -> CargoResult<u32> {
    58    Ok(available_parallelism()
    59        .context("failed to determine the amount of parallelism available")?
    60        .get() as u32)
    61}
    62
    63impl BuildConfig {
    64    /// Parses all config files to learn about build configuration. Currently
    65    /// configured options are:
    66    ///
    67    /// * `build.jobs`
    68    /// * `build.target`
    69    /// * `target.$target.ar`
    70    /// * `target.$target.linker`
    71    /// * `target.$target.libfoo.metadata`
    72    pub fn new(
    73        gctx: &GlobalContext,
    74        jobs: Option<JobsConfig>,
    75        keep_going: bool,
    76        requested_targets: &[String],
    77        intent: UserIntent,
    78    ) -> CargoResult<BuildConfig> {
    79        let cfg = gctx.build_config()?;
    80        let requested_kinds = CompileKind::from_requested_targets(gctx, requested_targets)?;
    81        if jobs.is_some() && gctx.jobserver_from_env().is_some() {
    82            gctx.shell().warn(
    83                "a `-j` argument was passed to Cargo but Cargo is \
    84                 also configured with an external jobserver in \
    85                 its environment, ignoring the `-j` parameter",
    86            )?;
    87        }
    88        let jobs = match jobs.or(cfg.jobs.clone()) {
    89            None => default_parallelism()?,
    90            Some(value) => match value {
    91                JobsConfig::Integer(j) => match j {
    92                    0 => anyhow::bail!("jobs may not be 0"),
    93                    j if j < 0 => (default_parallelism()? as i32 + j).max(1) as u32,
    94                    j => j as u32,
    95                },
    96                JobsConfig::String(j) => match j.as_str() {
    97                    "default" => default_parallelism()?,
    98                    _ => {
    99                        anyhow::bail!(format!(
    100                            "could not parse `{j}`. Number of parallel jobs should be `default` or a number."
    101                        ))
    102                    }
    103                },
    104            },
    105        };
    106
    107        // If sbom flag is set, it requires the unstable feature
    108        let sbom = match (cfg.sbom, gctx.cli_unstable().sbom) {
    109            (Some(sbom), true) => sbom,
    110            (Some(_), false) => {
    111                gctx.shell()
    112                    .warn("ignoring 'sbom' config, pass `-Zsbom` to enable it")?;
    113                false
    114            }
    115            (None, _) => false,
    116        };
    117
    118        let timing_outputs = match (cfg.analysis.as_ref(), gctx.cli_unstable().build_analysis) {
    119            // Enable HTML output to pretend we are persisting timing data for now.
    120            (Some(analysis), true) if analysis.enabled => vec![TimingOutput::Html],
    121            (Some(_), false) => {
    122                gctx.shell().warn(
    123                    "ignoring 'build.analysis' config, pass `-Zbuild-analysis` to enable it",
    124                )?;
    125                Vec::new()
    126            }
    127            _ => Vec::new(),
    128        };
    129
    130        Ok(BuildConfig {
    131            requested_kinds,
    132            jobs,
    133            keep_going,
    134            requested_profile: "dev".into(),
    135            intent,
    136            message_format: MessageFormat::Human,
    137            force_rebuild: false,
    138            build_plan: false,
    139            unit_graph: false,
    140            dry_run: false,
    141            primary_unit_rustc: None,
    142            rustfix_diagnostic_server: Rc::new(RefCell::new(None)),
    143            export_dir: None,
    144            future_incompat_report: false,
    145            timing_outputs,
    146            sbom,
    147            compile_time_deps_only: false,
    148        })
    149    }
    150
    151    /// Whether or not the *user* wants JSON output. Whether or not rustc
    152    /// actually uses JSON is decided in `add_error_format`.
    153    pub fn emit_json(&self) -> bool {
    154        matches!(self.message_format, MessageFormat::Json { .. })
    155    }
    156
    157    pub fn single_requested_kind(&self) -> CargoResult<CompileKind> {
    158        match self.requested_kinds.len() {
    159            1 => Ok(self.requested_kinds[0]),
    160            _ => bail!("only one `--target` argument is supported"),
    161        }
    162    }
    163}
    164
    165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
    166pub enum MessageFormat {
    167    Human,
    168    Json {
    169        /// Whether rustc diagnostics are rendered by cargo or included into the
    170        /// output stream.
    171        render_diagnostics: bool,
    172        /// Whether the `rendered` field of rustc diagnostics are using the
    173        /// "short" rendering.
    174        short: bool,
    175        /// Whether the `rendered` field of rustc diagnostics embed ansi color
    176        /// codes.
    177        ansi: bool,
    178    },
    179    Short,
    180}
    181
    182/// The specific action to be performed on each `Unit` of work.
    183#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
    184pub enum CompileMode {
    185    /// Test with `rustc`.
    186    Test,
    187    /// Compile with `rustc`.
    188    Build,
    189    /// Type-check with `rustc` by emitting `rmeta` metadata only.
    190    ///
    191    /// If `test` is true, then it is also compiled with `--test` to check it like
    192    /// a test.
    193    Check { test: bool },
    194    /// Document with `rustdoc`.
    195    Doc,
    196    /// Test with `rustdoc`.
    197    Doctest,
    198    /// Scrape for function calls by `rustdoc`.
    199    Docscrape,
    200    /// Execute the binary built from the `build.rs` script.
    201    RunCustomBuild,
    202}
    203
    204impl ser::Serialize for CompileMode {
    205    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    206    where
    207        S: ser::Serializer,
    208    {
    209        use self::CompileMode::*;
    210        match *self {
    211            Test => "test".serialize(s),
    212            Build => "build".serialize(s),
    213            Check { .. } => "check".serialize(s),
    214            Doc { .. } => "doc".serialize(s),
    215            Doctest => "doctest".serialize(s),
    216            Docscrape => "docscrape".serialize(s),
    217            RunCustomBuild => "run-custom-build".serialize(s),
    218        }
    219    }
    220}
    221
    222impl CompileMode {
    223    /// Returns `true` if the unit is being checked.
    224    pub fn is_check(self) -> bool {
    225        matches!(self, CompileMode::Check { .. })
    226    }
    227
    228    /// Returns `true` if this is generating documentation.
    229    pub fn is_doc(self) -> bool {
    230        matches!(self, CompileMode::Doc { .. })
    231    }
    232
    233    /// Returns `true` if this a doc test.
    234    pub fn is_doc_test(self) -> bool {
    235        self == CompileMode::Doctest
    236    }
    237
    238    /// Returns `true` if this is scraping examples for documentation.
    239    pub fn is_doc_scrape(self) -> bool {
    240        self == CompileMode::Docscrape
    241    }
    242
    243    /// Returns `true` if this is any type of test (test, benchmark, doc test, or
    244    /// check test).
    245    pub fn is_any_test(self) -> bool {
    246        matches!(
    247            self,
    248            CompileMode::Test | CompileMode::Check { test: true } | CompileMode::Doctest
    249        )
    250    }
    251
    252    /// Returns `true` if this is something that passes `--test` to rustc.
    253    pub fn is_rustc_test(self) -> bool {
    254        matches!(self, CompileMode::Test | CompileMode::Check { test: true })
    255    }
    256
    257    /// Returns `true` if this is the *execution* of a `build.rs` script.
    258    pub fn is_run_custom_build(self) -> bool {
    259        self == CompileMode::RunCustomBuild
    260    }
    261
    262    /// Returns `true` if this mode may generate an executable.
    263    ///
    264    /// Note that this also returns `true` for building libraries, so you also
    265    /// have to check the target.
    266    pub fn generates_executable(self) -> bool {
    267        matches!(self, CompileMode::Test | CompileMode::Build)
    268    }
    269}
    270
    271/// Represents the high-level operation requested by the user.
    272///
    273/// It determines which "Cargo targets" are selected by default and influences
    274/// how they will be processed. This is derived from the Cargo command the user
    275/// invoked (like `cargo build` or `cargo test`).
    276///
    277/// Unlike [`CompileMode`], which describes the specific compilation steps for
    278/// individual units, [`UserIntent`] represents the overall goal of the build
    279/// process as specified by the user.
    280///
    281/// For example, when a user runs `cargo test`, the intent is [`UserIntent::Test`],
    282/// but this might result in multiple [`CompileMode`]s for different units.
    283#[derive(Clone, Copy, Debug)]
    284pub enum UserIntent {
    285    /// Build benchmark binaries, e.g., `cargo bench`
    286    Bench,
    287    /// Build binaries and libraries, e.g., `cargo run`, `cargo install`, `cargo build`.
    288    Build,
    289    /// Perform type-check, e.g., `cargo check`.
    290    Check { test: bool },
    291    /// Document packages.
    292    ///
    293    /// If `deps` is true, then it will also document all dependencies.
    294    /// if `json` is true, the documentation output is in json format.
    295    Doc { deps: bool, json: bool },
    296    /// Build doctest binaries, e.g., `cargo test --doc`
    297    Doctest,
    298    /// Build test binaries, e.g., `cargo test`
    299    Test,
    300}
    301
    302impl UserIntent {
    303    /// Returns `true` if this is generating documentation.
    304    pub fn is_doc(self) -> bool {
    305        matches!(self, UserIntent::Doc { .. })
    306    }
    307
    308    /// User wants rustdoc output in JSON format.
    309    pub fn wants_doc_json_output(self) -> bool {
    310        matches!(self, UserIntent::Doc { json: true, .. })
    311    }
    312
    313    /// User wants to document also for dependencies.
    314    pub fn wants_deps_docs(self) -> bool {
    315        matches!(self, UserIntent::Doc { deps: true, .. })
    316    }
    317
    318    /// Returns `true` if this is any type of test (test, benchmark, doc test, or
    319    /// check test).
    320    pub fn is_any_test(self) -> bool {
    321        matches!(
    322            self,
    323            UserIntent::Test
    324                | UserIntent::Bench
    325                | UserIntent::Check { test: true }
    326                | UserIntent::Doctest
    327        )
    328    }
    329
    330    /// Returns `true` if this is something that passes `--test` to rustc.
    331    pub fn is_rustc_test(self) -> bool {
    332        matches!(
    333            self,
    334            UserIntent::Test | UserIntent::Bench | UserIntent::Check { test: true }
    335        )
    336    }
    337}
    338
    339/// Kinds of build timings we can output.
    340#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
    341pub enum TimingOutput {
    342    /// Human-readable HTML report
    343    Html,
    344    /// Machine-readable JSON (unstable)
    345    Json,
    346}