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}