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