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