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