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    /// Which kinds of build timings to output (empty if none).
48    pub timing_outputs: Vec<TimingOutput>,
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_outputs: Vec::new(),
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 CompileMode {
208    /// Returns `true` if the unit is being checked.
209    pub fn is_check(self) -> bool {
210        matches!(self, CompileMode::Check { .. })
211    }
212
213    /// Returns `true` if this is generating documentation.
214    pub fn is_doc(self) -> bool {
215        matches!(self, CompileMode::Doc { .. })
216    }
217
218    /// Returns `true` if this a doc test.
219    pub fn is_doc_test(self) -> bool {
220        self == CompileMode::Doctest
221    }
222
223    /// Returns `true` if this is scraping examples for documentation.
224    pub fn is_doc_scrape(self) -> bool {
225        self == CompileMode::Docscrape
226    }
227
228    /// Returns `true` if this is any type of test (test, benchmark, doc test, or
229    /// check test).
230    pub fn is_any_test(self) -> bool {
231        matches!(
232            self,
233            CompileMode::Test | CompileMode::Check { test: true } | CompileMode::Doctest
234        )
235    }
236
237    /// Returns `true` if this is something that passes `--test` to rustc.
238    pub fn is_rustc_test(self) -> bool {
239        matches!(self, CompileMode::Test | CompileMode::Check { test: true })
240    }
241
242    /// Returns `true` if this is the *execution* of a `build.rs` script.
243    pub fn is_run_custom_build(self) -> bool {
244        self == CompileMode::RunCustomBuild
245    }
246
247    /// Returns `true` if this mode may generate an executable.
248    ///
249    /// Note that this also returns `true` for building libraries, so you also
250    /// have to check the target.
251    pub fn generates_executable(self) -> bool {
252        matches!(self, CompileMode::Test | CompileMode::Build)
253    }
254}
255
256/// Represents the high-level operation requested by the user.
257///
258/// It determines which "Cargo targets" are selected by default and influences
259/// how they will be processed. This is derived from the Cargo command the user
260/// invoked (like `cargo build` or `cargo test`).
261///
262/// Unlike [`CompileMode`], which describes the specific compilation steps for
263/// individual units, [`UserIntent`] represents the overall goal of the build
264/// process as specified by the user.
265///
266/// For example, when a user runs `cargo test`, the intent is [`UserIntent::Test`],
267/// but this might result in multiple [`CompileMode`]s for different units.
268#[derive(Clone, Copy, Debug)]
269pub enum UserIntent {
270    /// Build benchmark binaries, e.g., `cargo bench`
271    Bench,
272    /// Build binaries and libraries, e.g., `cargo run`, `cargo install`, `cargo build`.
273    Build,
274    /// Perform type-check, e.g., `cargo check`.
275    Check { test: bool },
276    /// Document packages.
277    ///
278    /// If `deps` is true, then it will also document all dependencies.
279    /// if `json` is true, the documentation output is in json format.
280    Doc { deps: bool, json: bool },
281    /// Build doctest binaries, e.g., `cargo test --doc`
282    Doctest,
283    /// Build test binaries, e.g., `cargo test`
284    Test,
285}
286
287impl UserIntent {
288    /// Returns `true` if this is generating documentation.
289    pub fn is_doc(self) -> bool {
290        matches!(self, UserIntent::Doc { .. })
291    }
292
293    /// User wants rustdoc output in JSON format.
294    pub fn wants_doc_json_output(self) -> bool {
295        matches!(self, UserIntent::Doc { json: true, .. })
296    }
297
298    /// User wants to document also for dependencies.
299    pub fn wants_deps_docs(self) -> bool {
300        matches!(self, UserIntent::Doc { deps: true, .. })
301    }
302
303    /// Returns `true` if this is any type of test (test, benchmark, doc test, or
304    /// check test).
305    pub fn is_any_test(self) -> bool {
306        matches!(
307            self,
308            UserIntent::Test
309                | UserIntent::Bench
310                | UserIntent::Check { test: true }
311                | UserIntent::Doctest
312        )
313    }
314
315    /// Returns `true` if this is something that passes `--test` to rustc.
316    pub fn is_rustc_test(self) -> bool {
317        matches!(
318            self,
319            UserIntent::Test | UserIntent::Bench | UserIntent::Check { test: true }
320        )
321    }
322}
323
324/// Kinds of build timings we can output.
325#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
326pub enum TimingOutput {
327    /// Human-readable HTML report
328    Html,
329    /// Machine-readable JSON (unstable)
330    Json,
331}