#
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::{bail, Context as _};
    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!(
    100			    format!("could not parse `{j}`. Number of parallel jobs should be `default` or a number."))
    101                    }
    102                },
    103            },
    104        };
    105
    106        // If sbom flag is set, it requires the unstable feature
    107        let sbom = match (cfg.sbom, gctx.cli_unstable().sbom) {
    108            (Some(sbom), true) => sbom,
    109            (Some(_), false) => {
    110                gctx.shell()
    111                    .warn("ignoring 'sbom' config, pass `-Zsbom` to enable it")?;
    112                false
    113            }
    114            (None, _) => false,
    115        };
    116
    117        Ok(BuildConfig {
    118            requested_kinds,
    119            jobs,
    120            keep_going,
    121            requested_profile: "dev".into(),
    122            intent,
    123            message_format: MessageFormat::Human,
    124            force_rebuild: false,
    125            build_plan: false,
    126            unit_graph: false,
    127            dry_run: false,
    128            primary_unit_rustc: None,
    129            rustfix_diagnostic_server: Rc::new(RefCell::new(None)),
    130            export_dir: None,
    131            future_incompat_report: false,
    132            timing_outputs: Vec::new(),
    133            sbom,
    134            compile_time_deps_only: false,
    135        })
    136    }
    137
    138    /// Whether or not the *user* wants JSON output. Whether or not rustc
    139    /// actually uses JSON is decided in `add_error_format`.
    140    pub fn emit_json(&self) -> bool {
    141        matches!(self.message_format, MessageFormat::Json { .. })
    142    }
    143
    144    pub fn single_requested_kind(&self) -> CargoResult<CompileKind> {
    145        match self.requested_kinds.len() {
    146            1 => Ok(self.requested_kinds[0]),
    147            _ => bail!("only one `--target` argument is supported"),
    148        }
    149    }
    150}
    151
    152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
    153pub enum MessageFormat {
    154    Human,
    155    Json {
    156        /// Whether rustc diagnostics are rendered by cargo or included into the
    157        /// output stream.
    158        render_diagnostics: bool,
    159        /// Whether the `rendered` field of rustc diagnostics are using the
    160        /// "short" rendering.
    161        short: bool,
    162        /// Whether the `rendered` field of rustc diagnostics embed ansi color
    163        /// codes.
    164        ansi: bool,
    165    },
    166    Short,
    167}
    168
    169/// The specific action to be performed on each `Unit` of work.
    170#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
    171pub enum CompileMode {
    172    /// Test with `rustc`.
    173    Test,
    174    /// Compile with `rustc`.
    175    Build,
    176    /// Type-check with `rustc` by emitting `rmeta` metadata only.
    177    ///
    178    /// If `test` is true, then it is also compiled with `--test` to check it like
    179    /// a test.
    180    Check { test: bool },
    181    /// Document with `rustdoc`.
    182    Doc,
    183    /// Test with `rustdoc`.
    184    Doctest,
    185    /// Scrape for function calls by `rustdoc`.
    186    Docscrape,
    187    /// Execute the binary built from the `build.rs` script.
    188    RunCustomBuild,
    189}
    190
    191impl ser::Serialize for CompileMode {
    192    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    193    where
    194        S: ser::Serializer,
    195    {
    196        use self::CompileMode::*;
    197        match *self {
    198            Test => "test".serialize(s),
    199            Build => "build".serialize(s),
    200            Check { .. } => "check".serialize(s),
    201            Doc { .. } => "doc".serialize(s),
    202            Doctest => "doctest".serialize(s),
    203            Docscrape => "docscrape".serialize(s),
    204            RunCustomBuild => "run-custom-build".serialize(s),
    205        }
    206    }
    207}
    208
    209impl CompileMode {
    210    /// Returns `true` if the unit is being checked.
    211    pub fn is_check(self) -> bool {
    212        matches!(self, CompileMode::Check { .. })
    213    }
    214
    215    /// Returns `true` if this is generating documentation.
    216    pub fn is_doc(self) -> bool {
    217        matches!(self, CompileMode::Doc { .. })
    218    }
    219
    220    /// Returns `true` if this a doc test.
    221    pub fn is_doc_test(self) -> bool {
    222        self == CompileMode::Doctest
    223    }
    224
    225    /// Returns `true` if this is scraping examples for documentation.
    226    pub fn is_doc_scrape(self) -> bool {
    227        self == CompileMode::Docscrape
    228    }
    229
    230    /// Returns `true` if this is any type of test (test, benchmark, doc test, or
    231    /// check test).
    232    pub fn is_any_test(self) -> bool {
    233        matches!(
    234            self,
    235            CompileMode::Test | CompileMode::Check { test: true } | CompileMode::Doctest
    236        )
    237    }
    238
    239    /// Returns `true` if this is something that passes `--test` to rustc.
    240    pub fn is_rustc_test(self) -> bool {
    241        matches!(self, CompileMode::Test | CompileMode::Check { test: true })
    242    }
    243
    244    /// Returns `true` if this is the *execution* of a `build.rs` script.
    245    pub fn is_run_custom_build(self) -> bool {
    246        self == CompileMode::RunCustomBuild
    247    }
    248
    249    /// Returns `true` if this mode may generate an executable.
    250    ///
    251    /// Note that this also returns `true` for building libraries, so you also
    252    /// have to check the target.
    253    pub fn generates_executable(self) -> bool {
    254        matches!(self, CompileMode::Test | CompileMode::Build)
    255    }
    256}
    257
    258/// Represents the high-level operation requested by the user.
    259///
    260/// It determines which "Cargo targets" are selected by default and influences
    261/// how they will be processed. This is derived from the Cargo command the user
    262/// invoked (like `cargo build` or `cargo test`).
    263///
    264/// Unlike [`CompileMode`], which describes the specific compilation steps for
    265/// individual units, [`UserIntent`] represents the overall goal of the build
    266/// process as specified by the user.
    267///
    268/// For example, when a user runs `cargo test`, the intent is [`UserIntent::Test`],
    269/// but this might result in multiple [`CompileMode`]s for different units.
    270#[derive(Clone, Copy, Debug)]
    271pub enum UserIntent {
    272    /// Build benchmark binaries, e.g., `cargo bench`
    273    Bench,
    274    /// Build binaries and libraries, e.g., `cargo run`, `cargo install`, `cargo build`.
    275    Build,
    276    /// Perform type-check, e.g., `cargo check`.
    277    Check { test: bool },
    278    /// Document packages.
    279    ///
    280    /// If `deps` is true, then it will also document all dependencies.
    281    /// if `json` is true, the documentation output is in json format.
    282    Doc { deps: bool, json: bool },
    283    /// Build doctest binaries, e.g., `cargo test --doc`
    284    Doctest,
    285    /// Build test binaries, e.g., `cargo test`
    286    Test,
    287}
    288
    289impl UserIntent {
    290    /// Returns `true` if this is generating documentation.
    291    pub fn is_doc(self) -> bool {
    292        matches!(self, UserIntent::Doc { .. })
    293    }
    294
    295    /// User wants rustdoc output in JSON format.
    296    pub fn wants_doc_json_output(self) -> bool {
    297        matches!(self, UserIntent::Doc { json: true, .. })
    298    }
    299
    300    /// User wants to document also for dependencies.
    301    pub fn wants_deps_docs(self) -> bool {
    302        matches!(self, UserIntent::Doc { deps: true, .. })
    303    }
    304
    305    /// Returns `true` if this is any type of test (test, benchmark, doc test, or
    306    /// check test).
    307    pub fn is_any_test(self) -> bool {
    308        matches!(
    309            self,
    310            UserIntent::Test
    311                | UserIntent::Bench
    312                | UserIntent::Check { test: true }
    313                | UserIntent::Doctest
    314        )
    315    }
    316
    317    /// Returns `true` if this is something that passes `--test` to rustc.
    318    pub fn is_rustc_test(self) -> bool {
    319        matches!(
    320            self,
    321            UserIntent::Test | UserIntent::Bench | UserIntent::Check { test: true }
    322        )
    323    }
    324}
    325
    326/// Kinds of build timings we can output.
    327#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
    328pub enum TimingOutput {
    329    /// Human-readable HTML report
    330    Html,
    331    /// Machine-readable JSON (unstable)
    332    Json,
    333}