cargo/core/compiler/build_context/target_info.rs
1//! This modules contains types storing information of target platforms.
2//!
3//! Normally, call [`RustcTargetData::new`] to construct all the target
4//! platform once, and then query info on your demand. For example,
5//!
6//! * [`RustcTargetData::dep_platform_activated`] to check if platform is activated.
7//! * [`RustcTargetData::info`] to get a [`TargetInfo`] for an in-depth query.
8//! * [`TargetInfo::rustc_outputs`] to get a list of supported file types.
9
10use crate::core::compiler::apply_env_config;
11use crate::core::compiler::{BuildRunner, CompileKind, CompileMode, CompileTarget, CrateType};
12use crate::core::{Dependency, Package, Target, TargetKind, Workspace};
13use crate::util::context::{GlobalContext, StringList, TargetConfig};
14use crate::util::interning::InternedString;
15use crate::util::{CargoResult, Rustc};
16use anyhow::Context as _;
17use cargo_platform::{Cfg, CfgExpr};
18use cargo_util::{paths, ProcessBuilder};
19use serde::{Deserialize, Serialize};
20use std::cell::RefCell;
21use std::collections::hash_map::{Entry, HashMap};
22use std::path::{Path, PathBuf};
23use std::rc::Rc;
24use std::str::{self, FromStr};
25
26/// Information about the platform target gleaned from querying rustc.
27///
28/// [`RustcTargetData`] keeps several of these, one for the host and the others
29/// for other specified targets. If no target is specified, it uses a clone from
30/// the host.
31#[derive(Clone)]
32pub struct TargetInfo {
33 /// A base process builder for discovering crate type information. In
34 /// particular, this is used to determine the output filename prefix and
35 /// suffix for a crate type.
36 crate_type_process: ProcessBuilder,
37 /// Cache of output filename prefixes and suffixes.
38 ///
39 /// The key is the crate type name (like `cdylib`) and the value is
40 /// `Some((prefix, suffix))`, for example `libcargo.so` would be
41 /// `Some(("lib", ".so"))`. The value is `None` if the crate type is not
42 /// supported.
43 crate_types: RefCell<HashMap<CrateType, Option<(String, String)>>>,
44 /// `cfg` information extracted from `rustc --print=cfg`.
45 cfg: Vec<Cfg>,
46 /// `supports_std` information extracted from `rustc --print=target-spec-json`
47 pub supports_std: Option<bool>,
48 /// Supported values for `-Csplit-debuginfo=` flag, queried from rustc
49 support_split_debuginfo: Vec<String>,
50 /// Path to the sysroot.
51 pub sysroot: PathBuf,
52 /// Path to the "lib" directory in the sysroot which rustc uses for linking
53 /// target libraries.
54 pub sysroot_target_libdir: PathBuf,
55 /// Extra flags to pass to `rustc`, see [`extra_args`].
56 pub rustflags: Rc<[String]>,
57 /// Extra flags to pass to `rustdoc`, see [`extra_args`].
58 pub rustdocflags: Rc<[String]>,
59}
60
61/// Kind of each file generated by a Unit, part of `FileType`.
62#[derive(Clone, PartialEq, Eq, Debug)]
63pub enum FileFlavor {
64 /// Not a special file type.
65 Normal,
66 /// Like `Normal`, but not directly executable.
67 /// For example, a `.wasm` file paired with the "normal" `.js` file.
68 Auxiliary,
69 /// Something you can link against (e.g., a library).
70 Linkable,
71 /// An `.rmeta` Rust metadata file.
72 Rmeta,
73 /// Piece of external debug information (e.g., `.dSYM`/`.pdb` file).
74 DebugInfo,
75 /// SBOM (Software Bill of Materials pre-cursor) file (e.g. cargo-sbon.json).
76 Sbom,
77}
78
79/// Type of each file generated by a Unit.
80#[derive(Debug)]
81pub struct FileType {
82 /// The kind of file.
83 pub flavor: FileFlavor,
84 /// The crate-type that generates this file.
85 ///
86 /// `None` for things that aren't associated with a specific crate type,
87 /// for example `rmeta` files.
88 pub crate_type: Option<CrateType>,
89 /// The suffix for the file (for example, `.rlib`).
90 /// This is an empty string for executables on Unix-like platforms.
91 suffix: String,
92 /// The prefix for the file (for example, `lib`).
93 /// This is an empty string for things like executables.
94 prefix: String,
95 /// Flag to convert hyphen to underscore when uplifting.
96 should_replace_hyphens: bool,
97}
98
99impl FileType {
100 /// The filename for this `FileType` created by rustc.
101 pub fn output_filename(&self, target: &Target, metadata: Option<&str>) -> String {
102 match metadata {
103 Some(metadata) => format!(
104 "{}{}-{}{}",
105 self.prefix,
106 target.crate_name(),
107 metadata,
108 self.suffix
109 ),
110 None => format!("{}{}{}", self.prefix, target.crate_name(), self.suffix),
111 }
112 }
113
114 /// The filename for this `FileType` that Cargo should use when "uplifting"
115 /// it to the destination directory.
116 pub fn uplift_filename(&self, target: &Target) -> String {
117 let name = match target.binary_filename() {
118 Some(name) => name,
119 None => {
120 // For binary crate type, `should_replace_hyphens` will always be false.
121 if self.should_replace_hyphens {
122 target.crate_name()
123 } else {
124 target.name().to_string()
125 }
126 }
127 };
128
129 format!("{}{}{}", self.prefix, name, self.suffix)
130 }
131
132 /// Creates a new instance representing a `.rmeta` file.
133 pub fn new_rmeta() -> FileType {
134 // Note that even binaries use the `lib` prefix.
135 FileType {
136 flavor: FileFlavor::Rmeta,
137 crate_type: None,
138 suffix: ".rmeta".to_string(),
139 prefix: "lib".to_string(),
140 should_replace_hyphens: true,
141 }
142 }
143}
144
145impl TargetInfo {
146 /// Learns the information of target platform from `rustc` invocation(s).
147 ///
148 /// Generally, the first time calling this function is expensive, as it may
149 /// query `rustc` several times. To reduce the cost, output of each `rustc`
150 /// invocation is cached by [`Rustc::cached_output`].
151 ///
152 /// Search `Tricky` to learn why querying `rustc` several times is needed.
153 #[tracing::instrument(skip_all)]
154 pub fn new(
155 gctx: &GlobalContext,
156 requested_kinds: &[CompileKind],
157 rustc: &Rustc,
158 kind: CompileKind,
159 ) -> CargoResult<TargetInfo> {
160 let mut rustflags =
161 extra_args(gctx, requested_kinds, &rustc.host, None, kind, Flags::Rust)?;
162 let mut turn = 0;
163 loop {
164 let extra_fingerprint = kind.fingerprint_hash();
165
166 // Query rustc for several kinds of info from each line of output:
167 // 0) file-names (to determine output file prefix/suffix for given crate type)
168 // 1) sysroot
169 // 2) split-debuginfo
170 // 3) cfg
171 //
172 // Search `--print` to see what we query so far.
173 let mut process = rustc.workspace_process();
174 apply_env_config(gctx, &mut process)?;
175 process
176 .arg("-")
177 .arg("--crate-name")
178 .arg("___")
179 .arg("--print=file-names")
180 .args(&rustflags)
181 .env_remove("RUSTC_LOG");
182
183 // Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver
184 // as environment variables specify.
185 if let Some(client) = gctx.jobserver_from_env() {
186 process.inherit_jobserver(client);
187 }
188
189 if let CompileKind::Target(target) = kind {
190 process.arg("--target").arg(target.rustc_target());
191 }
192
193 let crate_type_process = process.clone();
194 const KNOWN_CRATE_TYPES: &[CrateType] = &[
195 CrateType::Bin,
196 CrateType::Rlib,
197 CrateType::Dylib,
198 CrateType::Cdylib,
199 CrateType::Staticlib,
200 CrateType::ProcMacro,
201 ];
202 for crate_type in KNOWN_CRATE_TYPES.iter() {
203 process.arg("--crate-type").arg(crate_type.as_str());
204 }
205
206 process.arg("--print=sysroot");
207 process.arg("--print=split-debuginfo");
208 process.arg("--print=crate-name"); // `___` as a delimiter.
209 process.arg("--print=cfg");
210
211 // parse_crate_type() relies on "unsupported/unknown crate type" error message,
212 // so make warnings always emitted as warnings.
213 process.arg("-Wwarnings");
214
215 let (output, error) = rustc
216 .cached_output(&process, extra_fingerprint)
217 .with_context(|| {
218 "failed to run `rustc` to learn about target-specific information"
219 })?;
220
221 let mut lines = output.lines();
222 let mut map = HashMap::new();
223 for crate_type in KNOWN_CRATE_TYPES {
224 let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?;
225 map.insert(crate_type.clone(), out);
226 }
227
228 let Some(line) = lines.next() else {
229 return error_missing_print_output("sysroot", &process, &output, &error);
230 };
231 let sysroot = PathBuf::from(line);
232 let sysroot_target_libdir = {
233 let mut libdir = sysroot.clone();
234 libdir.push("lib");
235 libdir.push("rustlib");
236 libdir.push(match &kind {
237 CompileKind::Host => rustc.host.as_str(),
238 CompileKind::Target(target) => target.short_name(),
239 });
240 libdir.push("lib");
241 libdir
242 };
243
244 let support_split_debuginfo = {
245 // HACK: abuse `--print=crate-name` to use `___` as a delimiter.
246 let mut res = Vec::new();
247 loop {
248 match lines.next() {
249 Some(line) if line == "___" => break,
250 Some(line) => res.push(line.into()),
251 None => {
252 return error_missing_print_output(
253 "split-debuginfo",
254 &process,
255 &output,
256 &error,
257 )
258 }
259 }
260 }
261 res
262 };
263
264 let cfg = lines
265 .map(|line| Ok(Cfg::from_str(line)?))
266 .filter(TargetInfo::not_user_specific_cfg)
267 .collect::<CargoResult<Vec<_>>>()
268 .with_context(|| {
269 format!(
270 "failed to parse the cfg from `rustc --print=cfg`, got:\n{}",
271 output
272 )
273 })?;
274
275 // recalculate `rustflags` from above now that we have `cfg`
276 // information
277 let new_flags = extra_args(
278 gctx,
279 requested_kinds,
280 &rustc.host,
281 Some(&cfg),
282 kind,
283 Flags::Rust,
284 )?;
285
286 // Tricky: `RUSTFLAGS` defines the set of active `cfg` flags, active
287 // `cfg` flags define which `.cargo/config` sections apply, and they
288 // in turn can affect `RUSTFLAGS`! This is a bona fide mutual
289 // dependency, and it can even diverge (see `cfg_paradox` test).
290 //
291 // So what we do here is running at most *two* iterations of
292 // fixed-point iteration, which should be enough to cover
293 // practically useful cases, and warn if that's not enough for
294 // convergence.
295 let reached_fixed_point = new_flags == rustflags;
296 if !reached_fixed_point && turn == 0 {
297 turn += 1;
298 rustflags = new_flags;
299 continue;
300 }
301 if !reached_fixed_point {
302 gctx.shell().warn("non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")?;
303 }
304
305 let mut supports_std: Option<bool> = None;
306
307 // The '--print=target-spec-json' is an unstable option of rustc, therefore only
308 // try to fetch this information if rustc allows nightly features. Additionally,
309 // to avoid making two rustc queries when not required, only try to fetch the
310 // target-spec when the '-Zbuild-std' option is passed.
311 if gctx.cli_unstable().build_std.is_some() {
312 let mut target_spec_process = rustc.workspace_process();
313 apply_env_config(gctx, &mut target_spec_process)?;
314 target_spec_process
315 .arg("--print=target-spec-json")
316 .arg("-Zunstable-options")
317 .args(&rustflags)
318 .env_remove("RUSTC_LOG");
319
320 if let CompileKind::Target(target) = kind {
321 target_spec_process
322 .arg("--target")
323 .arg(target.rustc_target());
324 }
325
326 #[derive(Deserialize)]
327 struct Metadata {
328 pub std: Option<bool>,
329 }
330
331 #[derive(Deserialize)]
332 struct TargetSpec {
333 pub metadata: Metadata,
334 }
335
336 if let Ok(output) = target_spec_process.output() {
337 if let Ok(spec) = serde_json::from_slice::<TargetSpec>(&output.stdout) {
338 supports_std = spec.metadata.std;
339 }
340 }
341 }
342
343 return Ok(TargetInfo {
344 crate_type_process,
345 crate_types: RefCell::new(map),
346 sysroot,
347 sysroot_target_libdir,
348 rustflags: rustflags.into(),
349 rustdocflags: extra_args(
350 gctx,
351 requested_kinds,
352 &rustc.host,
353 Some(&cfg),
354 kind,
355 Flags::Rustdoc,
356 )?
357 .into(),
358 cfg,
359 supports_std,
360 support_split_debuginfo,
361 });
362 }
363 }
364
365 fn not_user_specific_cfg(cfg: &CargoResult<Cfg>) -> bool {
366 if let Ok(Cfg::Name(cfg_name)) = cfg {
367 // This should also include "debug_assertions", but it causes
368 // regressions. Maybe some day in the distant future it can be
369 // added (and possibly change the warning to an error).
370 if cfg_name == "proc_macro" {
371 return false;
372 }
373 }
374 true
375 }
376
377 /// All the target [`Cfg`] settings.
378 pub fn cfg(&self) -> &[Cfg] {
379 &self.cfg
380 }
381
382 /// Returns the list of file types generated by the given crate type.
383 ///
384 /// Returns `None` if the target does not support the given crate type.
385 fn file_types(
386 &self,
387 crate_type: &CrateType,
388 flavor: FileFlavor,
389 target_triple: &str,
390 ) -> CargoResult<Option<Vec<FileType>>> {
391 let crate_type = if *crate_type == CrateType::Lib {
392 CrateType::Rlib
393 } else {
394 crate_type.clone()
395 };
396
397 let mut crate_types = self.crate_types.borrow_mut();
398 let entry = crate_types.entry(crate_type.clone());
399 let crate_type_info = match entry {
400 Entry::Occupied(o) => &*o.into_mut(),
401 Entry::Vacant(v) => {
402 let value = self.discover_crate_type(v.key())?;
403 &*v.insert(value)
404 }
405 };
406 let Some((prefix, suffix)) = crate_type_info else {
407 return Ok(None);
408 };
409 let mut ret = vec![FileType {
410 suffix: suffix.clone(),
411 prefix: prefix.clone(),
412 flavor,
413 crate_type: Some(crate_type.clone()),
414 should_replace_hyphens: crate_type != CrateType::Bin,
415 }];
416
417 // Window shared library import/export files.
418 if crate_type.is_dynamic() {
419 // Note: Custom JSON specs can alter the suffix. For now, we'll
420 // just ignore non-DLL suffixes.
421 if target_triple.ends_with("-windows-msvc") && suffix == ".dll" {
422 // See https://docs.microsoft.com/en-us/cpp/build/reference/working-with-import-libraries-and-export-files
423 // for more information about DLL import/export files.
424 ret.push(FileType {
425 suffix: ".dll.lib".to_string(),
426 prefix: prefix.clone(),
427 flavor: FileFlavor::Auxiliary,
428 crate_type: Some(crate_type.clone()),
429 should_replace_hyphens: true,
430 });
431 // NOTE: lld does not produce these
432 ret.push(FileType {
433 suffix: ".dll.exp".to_string(),
434 prefix: prefix.clone(),
435 flavor: FileFlavor::Auxiliary,
436 crate_type: Some(crate_type.clone()),
437 should_replace_hyphens: true,
438 });
439 } else if suffix == ".dll"
440 && (target_triple.ends_with("windows-gnu")
441 || target_triple.ends_with("windows-gnullvm"))
442 {
443 // See https://cygwin.com/cygwin-ug-net/dll.html for more
444 // information about GNU import libraries.
445 // LD can link DLL directly, but LLD requires the import library.
446 ret.push(FileType {
447 suffix: ".dll.a".to_string(),
448 prefix: "lib".to_string(),
449 flavor: FileFlavor::Auxiliary,
450 crate_type: Some(crate_type.clone()),
451 should_replace_hyphens: true,
452 })
453 }
454 }
455
456 if target_triple.starts_with("wasm32-") && crate_type == CrateType::Bin && suffix == ".js" {
457 // emscripten binaries generate a .js file, which loads a .wasm
458 // file.
459 ret.push(FileType {
460 suffix: ".wasm".to_string(),
461 prefix: prefix.clone(),
462 flavor: FileFlavor::Auxiliary,
463 crate_type: Some(crate_type.clone()),
464 // Name `foo-bar` will generate a `foo_bar.js` and
465 // `foo_bar.wasm`. Cargo will translate the underscore and
466 // copy `foo_bar.js` to `foo-bar.js`. However, the wasm
467 // filename is embedded in the .js file with an underscore, so
468 // it should not contain hyphens.
469 should_replace_hyphens: true,
470 });
471 // And a map file for debugging. This is only emitted with debug=2
472 // (-g4 for emcc).
473 ret.push(FileType {
474 suffix: ".wasm.map".to_string(),
475 prefix: prefix.clone(),
476 flavor: FileFlavor::DebugInfo,
477 crate_type: Some(crate_type.clone()),
478 should_replace_hyphens: true,
479 });
480 }
481
482 // Handle separate debug files.
483 let is_apple = target_triple.contains("-apple-");
484 if matches!(
485 crate_type,
486 CrateType::Bin | CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro
487 ) {
488 if is_apple {
489 let suffix = if crate_type == CrateType::Bin {
490 ".dSYM".to_string()
491 } else {
492 ".dylib.dSYM".to_string()
493 };
494 ret.push(FileType {
495 suffix,
496 prefix: prefix.clone(),
497 flavor: FileFlavor::DebugInfo,
498 crate_type: Some(crate_type),
499 // macOS tools like lldb use all sorts of magic to locate
500 // dSYM files. See https://lldb.llvm.org/use/symbols.html
501 // for some details. It seems like a `.dSYM` located next
502 // to the executable with the same name is one method. The
503 // dSYM should have the same hyphens as the executable for
504 // the names to match.
505 should_replace_hyphens: false,
506 })
507 } else if target_triple.ends_with("-msvc") || target_triple.ends_with("-uefi") {
508 ret.push(FileType {
509 suffix: ".pdb".to_string(),
510 prefix: prefix.clone(),
511 flavor: FileFlavor::DebugInfo,
512 crate_type: Some(crate_type),
513 // The absolute path to the pdb file is embedded in the
514 // executable. If the exe/pdb pair is moved to another
515 // machine, then debuggers will look in the same directory
516 // of the exe with the original pdb filename. Since the
517 // original name contains underscores, they need to be
518 // preserved.
519 should_replace_hyphens: true,
520 })
521 } else {
522 // Because DWARF Package (dwp) files are produced after the
523 // fact by another tool, there is nothing in the binary that
524 // provides a means to locate them. By convention, debuggers
525 // take the binary filename and append ".dwp" (including to
526 // binaries that already have an extension such as shared libs)
527 // to find the dwp.
528 ret.push(FileType {
529 // It is important to preserve the existing suffix for
530 // e.g. shared libraries, where the dwp for libfoo.so is
531 // expected to be at libfoo.so.dwp.
532 suffix: format!("{suffix}.dwp"),
533 prefix: prefix.clone(),
534 flavor: FileFlavor::DebugInfo,
535 crate_type: Some(crate_type.clone()),
536 // Likewise, the dwp needs to match the primary artifact's
537 // hyphenation exactly.
538 should_replace_hyphens: crate_type != CrateType::Bin,
539 })
540 }
541 }
542
543 Ok(Some(ret))
544 }
545
546 fn discover_crate_type(&self, crate_type: &CrateType) -> CargoResult<Option<(String, String)>> {
547 let mut process = self.crate_type_process.clone();
548
549 process.arg("--crate-type").arg(crate_type.as_str());
550
551 let output = process.exec_with_output().with_context(|| {
552 format!(
553 "failed to run `rustc` to learn about crate-type {} information",
554 crate_type
555 )
556 })?;
557
558 let error = str::from_utf8(&output.stderr).unwrap();
559 let output = str::from_utf8(&output.stdout).unwrap();
560 parse_crate_type(crate_type, &process, output, error, &mut output.lines())
561 }
562
563 /// Returns all the file types generated by rustc for the given `mode`/`target_kind`.
564 ///
565 /// The first value is a Vec of file types generated, the second value is
566 /// a list of `CrateTypes` that are not supported by the given target.
567 pub fn rustc_outputs(
568 &self,
569 mode: CompileMode,
570 target_kind: &TargetKind,
571 target_triple: &str,
572 ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
573 match mode {
574 CompileMode::Build => self.calc_rustc_outputs(target_kind, target_triple),
575 CompileMode::Test | CompileMode::Bench => {
576 match self.file_types(&CrateType::Bin, FileFlavor::Normal, target_triple)? {
577 Some(fts) => Ok((fts, Vec::new())),
578 None => Ok((Vec::new(), vec![CrateType::Bin])),
579 }
580 }
581 CompileMode::Check { .. } => Ok((vec![FileType::new_rmeta()], Vec::new())),
582 CompileMode::Doc { .. }
583 | CompileMode::Doctest
584 | CompileMode::Docscrape
585 | CompileMode::RunCustomBuild => {
586 panic!("asked for rustc output for non-rustc mode")
587 }
588 }
589 }
590
591 fn calc_rustc_outputs(
592 &self,
593 target_kind: &TargetKind,
594 target_triple: &str,
595 ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
596 let mut unsupported = Vec::new();
597 let mut result = Vec::new();
598 let crate_types = target_kind.rustc_crate_types();
599 for crate_type in &crate_types {
600 let flavor = if crate_type.is_linkable() {
601 FileFlavor::Linkable
602 } else {
603 FileFlavor::Normal
604 };
605 let file_types = self.file_types(crate_type, flavor, target_triple)?;
606 match file_types {
607 Some(types) => {
608 result.extend(types);
609 }
610 None => {
611 unsupported.push(crate_type.clone());
612 }
613 }
614 }
615 if !result.is_empty() && !crate_types.iter().any(|ct| ct.requires_upstream_objects()) {
616 // Only add rmeta if pipelining.
617 result.push(FileType::new_rmeta());
618 }
619 Ok((result, unsupported))
620 }
621
622 /// Checks if the debuginfo-split value is supported by this target
623 pub fn supports_debuginfo_split(&self, split: InternedString) -> bool {
624 self.support_split_debuginfo
625 .iter()
626 .any(|sup| sup.as_str() == split.as_str())
627 }
628
629 /// Checks if a target maybe support std.
630 ///
631 /// If no explicitly stated in target spec json, we treat it as "maybe support".
632 ///
633 /// This is only useful for `-Zbuild-std` to determine the default set of
634 /// crates it is going to build.
635 pub fn maybe_support_std(&self) -> bool {
636 matches!(self.supports_std, Some(true) | None)
637 }
638}
639
640/// Takes rustc output (using specialized command line args), and calculates the file prefix and
641/// suffix for the given crate type, or returns `None` if the type is not supported. (e.g., for a
642/// Rust library like `libcargo.rlib`, we have prefix "lib" and suffix "rlib").
643///
644/// The caller needs to ensure that the lines object is at the correct line for the given crate
645/// type: this is not checked.
646///
647/// This function can not handle more than one file per type (with wasm32-unknown-emscripten, there
648/// are two files for bin (`.wasm` and `.js`)).
649fn parse_crate_type(
650 crate_type: &CrateType,
651 cmd: &ProcessBuilder,
652 output: &str,
653 error: &str,
654 lines: &mut str::Lines<'_>,
655) -> CargoResult<Option<(String, String)>> {
656 let not_supported = error.lines().any(|line| {
657 (line.contains("unsupported crate type") || line.contains("unknown crate type"))
658 && line.contains(&format!("crate type `{}`", crate_type))
659 });
660 if not_supported {
661 return Ok(None);
662 }
663 let Some(line) = lines.next() else {
664 anyhow::bail!(
665 "malformed output when learning about crate-type {} information\n{}",
666 crate_type,
667 output_err_info(cmd, output, error)
668 )
669 };
670 let mut parts = line.trim().split("___");
671 let prefix = parts.next().unwrap();
672 let Some(suffix) = parts.next() else {
673 return error_missing_print_output("file-names", cmd, output, error);
674 };
675
676 Ok(Some((prefix.to_string(), suffix.to_string())))
677}
678
679/// Helper for creating an error message for missing output from a certain `--print` request.
680fn error_missing_print_output<T>(
681 request: &str,
682 cmd: &ProcessBuilder,
683 stdout: &str,
684 stderr: &str,
685) -> CargoResult<T> {
686 let err_info = output_err_info(cmd, stdout, stderr);
687 anyhow::bail!(
688 "output of --print={request} missing when learning about \
689 target-specific information from rustc\n{err_info}",
690 )
691}
692
693/// Helper for creating an error message when parsing rustc output fails.
694fn output_err_info(cmd: &ProcessBuilder, stdout: &str, stderr: &str) -> String {
695 let mut result = format!("command was: {}\n", cmd);
696 if !stdout.is_empty() {
697 result.push_str("\n--- stdout\n");
698 result.push_str(stdout);
699 }
700 if !stderr.is_empty() {
701 result.push_str("\n--- stderr\n");
702 result.push_str(stderr);
703 }
704 if stdout.is_empty() && stderr.is_empty() {
705 result.push_str("(no output received)");
706 }
707 result
708}
709
710/// Compiler flags for either rustc or rustdoc.
711#[derive(Debug, Copy, Clone)]
712enum Flags {
713 Rust,
714 Rustdoc,
715}
716
717impl Flags {
718 fn as_key(self) -> &'static str {
719 match self {
720 Flags::Rust => "rustflags",
721 Flags::Rustdoc => "rustdocflags",
722 }
723 }
724
725 fn as_env(self) -> &'static str {
726 match self {
727 Flags::Rust => "RUSTFLAGS",
728 Flags::Rustdoc => "RUSTDOCFLAGS",
729 }
730 }
731}
732
733/// Acquire extra flags to pass to the compiler from various locations.
734///
735/// The locations are:
736///
737/// - the `CARGO_ENCODED_RUSTFLAGS` environment variable
738/// - the `RUSTFLAGS` environment variable
739///
740/// then if none of those were found
741///
742/// - `target.*.rustflags` from the config (.cargo/config)
743/// - `target.cfg(..).rustflags` from the config
744/// - `host.*.rustflags` from the config if compiling a host artifact or without `--target`
745/// (requires `-Zhost-config`)
746///
747/// then if none of those were found
748///
749/// - `build.rustflags` from the config
750///
751/// The behavior differs slightly when cross-compiling (or, specifically, when `--target` is
752/// provided) for artifacts that are always built for the host (plugins, build scripts, ...).
753/// For those artifacts, _only_ `host.*.rustflags` is respected, and no other configuration
754/// sources, _regardless of the value of `target-applies-to-host`_. This is counterintuitive, but
755/// necessary to retain backwards compatibility with older versions of Cargo.
756///
757/// Rules above also applies to rustdoc. Just the key would be `rustdocflags`/`RUSTDOCFLAGS`.
758fn extra_args(
759 gctx: &GlobalContext,
760 requested_kinds: &[CompileKind],
761 host_triple: &str,
762 target_cfg: Option<&[Cfg]>,
763 kind: CompileKind,
764 flags: Flags,
765) -> CargoResult<Vec<String>> {
766 let target_applies_to_host = gctx.target_applies_to_host()?;
767
768 // Host artifacts should not generally pick up rustflags from anywhere except [host].
769 //
770 // The one exception to this is if `target-applies-to-host = true`, which opts into a
771 // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags
772 // set elsewhere when `--target` isn't passed.
773 if kind.is_host() {
774 if target_applies_to_host && requested_kinds == [CompileKind::Host] {
775 // This is the past Cargo behavior where we fall back to the same logic as for other
776 // artifacts without --target.
777 } else {
778 // In all other cases, host artifacts just get flags from [host], regardless of
779 // --target. Or, phrased differently, no `--target` behaves the same as `--target
780 // <host>`, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for
781 // example).
782 return Ok(rustflags_from_host(gctx, flags, host_triple)?.unwrap_or_else(Vec::new));
783 }
784 }
785
786 // All other artifacts pick up the RUSTFLAGS, [target.*], and [build], in that order.
787 // NOTE: It is impossible to have a [host] section and reach this logic with kind.is_host(),
788 // since [host] implies `target-applies-to-host = false`, which always early-returns above.
789
790 if let Some(rustflags) = rustflags_from_env(gctx, flags) {
791 Ok(rustflags)
792 } else if let Some(rustflags) =
793 rustflags_from_target(gctx, host_triple, target_cfg, kind, flags)?
794 {
795 Ok(rustflags)
796 } else if let Some(rustflags) = rustflags_from_build(gctx, flags)? {
797 Ok(rustflags)
798 } else {
799 Ok(Vec::new())
800 }
801}
802
803/// Gets compiler flags from environment variables.
804/// See [`extra_args`] for more.
805fn rustflags_from_env(gctx: &GlobalContext, flags: Flags) -> Option<Vec<String>> {
806 // First try CARGO_ENCODED_RUSTFLAGS from the environment.
807 // Prefer this over RUSTFLAGS since it's less prone to encoding errors.
808 if let Ok(a) = gctx.get_env(format!("CARGO_ENCODED_{}", flags.as_env())) {
809 if a.is_empty() {
810 return Some(Vec::new());
811 }
812 return Some(a.split('\x1f').map(str::to_string).collect());
813 }
814
815 // Then try RUSTFLAGS from the environment
816 if let Ok(a) = gctx.get_env(flags.as_env()) {
817 let args = a
818 .split(' ')
819 .map(str::trim)
820 .filter(|s| !s.is_empty())
821 .map(str::to_string);
822 return Some(args.collect());
823 }
824
825 // No rustflags to be collected from the environment
826 None
827}
828
829/// Gets compiler flags from `[target]` section in the config.
830/// See [`extra_args`] for more.
831fn rustflags_from_target(
832 gctx: &GlobalContext,
833 host_triple: &str,
834 target_cfg: Option<&[Cfg]>,
835 kind: CompileKind,
836 flag: Flags,
837) -> CargoResult<Option<Vec<String>>> {
838 let mut rustflags = Vec::new();
839
840 // Then the target.*.rustflags value...
841 let target = match &kind {
842 CompileKind::Host => host_triple,
843 CompileKind::Target(target) => target.short_name(),
844 };
845 let key = format!("target.{}.{}", target, flag.as_key());
846 if let Some(args) = gctx.get::<Option<StringList>>(&key)? {
847 rustflags.extend(args.as_slice().iter().cloned());
848 }
849 // ...including target.'cfg(...)'.rustflags
850 if let Some(target_cfg) = target_cfg {
851 gctx.target_cfgs()?
852 .iter()
853 .filter_map(|(key, cfg)| {
854 match flag {
855 Flags::Rust => cfg
856 .rustflags
857 .as_ref()
858 .map(|rustflags| (key, &rustflags.val)),
859 // `target.cfg(…).rustdocflags` is currently not supported.
860 Flags::Rustdoc => None,
861 }
862 })
863 .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg))
864 .for_each(|(_key, cfg_rustflags)| {
865 rustflags.extend(cfg_rustflags.as_slice().iter().cloned());
866 });
867 }
868
869 if rustflags.is_empty() {
870 Ok(None)
871 } else {
872 Ok(Some(rustflags))
873 }
874}
875
876/// Gets compiler flags from `[host]` section in the config.
877/// See [`extra_args`] for more.
878fn rustflags_from_host(
879 gctx: &GlobalContext,
880 flag: Flags,
881 host_triple: &str,
882) -> CargoResult<Option<Vec<String>>> {
883 let target_cfg = gctx.host_cfg_triple(host_triple)?;
884 let list = match flag {
885 Flags::Rust => &target_cfg.rustflags,
886 Flags::Rustdoc => {
887 // host.rustdocflags is not a thing, since it does not make sense
888 return Ok(None);
889 }
890 };
891 Ok(list.as_ref().map(|l| l.val.as_slice().to_vec()))
892}
893
894/// Gets compiler flags from `[build]` section in the config.
895/// See [`extra_args`] for more.
896fn rustflags_from_build(gctx: &GlobalContext, flag: Flags) -> CargoResult<Option<Vec<String>>> {
897 // Then the `build.rustflags` value.
898 let build = gctx.build_config()?;
899 let list = match flag {
900 Flags::Rust => &build.rustflags,
901 Flags::Rustdoc => &build.rustdocflags,
902 };
903 Ok(list.as_ref().map(|l| l.as_slice().to_vec()))
904}
905
906/// Collection of information about `rustc` and the host and target.
907pub struct RustcTargetData<'gctx> {
908 /// Information about `rustc` itself.
909 pub rustc: Rustc,
910
911 /// Config
912 pub gctx: &'gctx GlobalContext,
913 requested_kinds: Vec<CompileKind>,
914
915 /// Build information for the "host", which is information about when
916 /// `rustc` is invoked without a `--target` flag. This is used for
917 /// selecting a linker, and applying link overrides.
918 ///
919 /// The configuration read into this depends on whether or not
920 /// `target-applies-to-host=true`.
921 host_config: TargetConfig,
922 /// Information about the host platform.
923 host_info: TargetInfo,
924
925 /// Build information for targets that we're building for.
926 target_config: HashMap<CompileTarget, TargetConfig>,
927 /// Information about the target platform that we're building for.
928 target_info: HashMap<CompileTarget, TargetInfo>,
929}
930
931impl<'gctx> RustcTargetData<'gctx> {
932 #[tracing::instrument(skip_all)]
933 pub fn new(
934 ws: &Workspace<'gctx>,
935 requested_kinds: &[CompileKind],
936 ) -> CargoResult<RustcTargetData<'gctx>> {
937 let gctx = ws.gctx();
938 let rustc = gctx.load_global_rustc(Some(ws))?;
939 let mut target_config = HashMap::new();
940 let mut target_info = HashMap::new();
941 let target_applies_to_host = gctx.target_applies_to_host()?;
942 let host_target = CompileTarget::new(&rustc.host)?;
943 let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?;
944
945 // This config is used for link overrides and choosing a linker.
946 let host_config = if target_applies_to_host {
947 gctx.target_cfg_triple(&rustc.host)?
948 } else {
949 gctx.host_cfg_triple(&rustc.host)?
950 };
951
952 // This is a hack. The unit_dependency graph builder "pretends" that
953 // `CompileKind::Host` is `CompileKind::Target(host)` if the
954 // `--target` flag is not specified. Since the unit_dependency code
955 // needs access to the target config data, create a copy so that it
956 // can be found. See `rebuild_unit_graph_shared` for why this is done.
957 if requested_kinds.iter().any(CompileKind::is_host) {
958 target_config.insert(host_target, gctx.target_cfg_triple(&rustc.host)?);
959
960 // If target_applies_to_host is true, the host_info is the target info,
961 // otherwise we need to build target info for the target.
962 if target_applies_to_host {
963 target_info.insert(host_target, host_info.clone());
964 } else {
965 let host_target_info = TargetInfo::new(
966 gctx,
967 requested_kinds,
968 &rustc,
969 CompileKind::Target(host_target),
970 )?;
971 target_info.insert(host_target, host_target_info);
972 }
973 };
974
975 let mut res = RustcTargetData {
976 rustc,
977 gctx,
978 requested_kinds: requested_kinds.into(),
979 host_config,
980 host_info,
981 target_config,
982 target_info,
983 };
984
985 // Get all kinds we currently know about.
986 //
987 // For now, targets can only ever come from the root workspace
988 // units and artifact dependencies, so this
989 // correctly represents all the kinds that can happen. When we have
990 // other ways for targets to appear at places that are not the root units,
991 // we may have to revisit this.
992 fn artifact_targets(package: &Package) -> impl Iterator<Item = CompileKind> + '_ {
993 package
994 .manifest()
995 .dependencies()
996 .iter()
997 .filter_map(|d| d.artifact()?.target()?.to_compile_kind())
998 }
999 let all_kinds = requested_kinds
1000 .iter()
1001 .copied()
1002 .chain(ws.members().flat_map(|p| {
1003 p.manifest()
1004 .default_kind()
1005 .into_iter()
1006 .chain(p.manifest().forced_kind())
1007 .chain(artifact_targets(p))
1008 }));
1009 for kind in all_kinds {
1010 res.merge_compile_kind(kind)?;
1011 }
1012
1013 Ok(res)
1014 }
1015
1016 /// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet.
1017 pub fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> {
1018 if let CompileKind::Target(target) = kind {
1019 if !self.target_config.contains_key(&target) {
1020 self.target_config
1021 .insert(target, self.gctx.target_cfg_triple(target.short_name())?);
1022 }
1023 if !self.target_info.contains_key(&target) {
1024 self.target_info.insert(
1025 target,
1026 TargetInfo::new(self.gctx, &self.requested_kinds, &self.rustc, kind)?,
1027 );
1028 }
1029 }
1030 Ok(())
1031 }
1032
1033 /// Returns a "short" name for the given kind, suitable for keying off
1034 /// configuration in Cargo or presenting to users.
1035 pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str {
1036 match kind {
1037 CompileKind::Host => &self.rustc.host,
1038 CompileKind::Target(target) => target.short_name(),
1039 }
1040 }
1041
1042 /// Whether a dependency should be compiled for the host or target platform,
1043 /// specified by `CompileKind`.
1044 pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool {
1045 // If this dependency is only available for certain platforms,
1046 // make sure we're only enabling it for that platform.
1047 let Some(platform) = dep.platform() else {
1048 return true;
1049 };
1050 let name = self.short_name(&kind);
1051 platform.matches(name, self.cfg(kind))
1052 }
1053
1054 /// Gets the list of `cfg`s printed out from the compiler for the specified kind.
1055 pub fn cfg(&self, kind: CompileKind) -> &[Cfg] {
1056 self.info(kind).cfg()
1057 }
1058
1059 /// Information about the given target platform, learned by querying rustc.
1060 ///
1061 /// # Panics
1062 ///
1063 /// Panics, if the target platform described by `kind` can't be found.
1064 /// See [`get_info`](Self::get_info) for a non-panicking alternative.
1065 pub fn info(&self, kind: CompileKind) -> &TargetInfo {
1066 self.get_info(kind).unwrap()
1067 }
1068
1069 /// Information about the given target platform, learned by querying rustc.
1070 ///
1071 /// Returns `None` if the target platform described by `kind` can't be found.
1072 pub fn get_info(&self, kind: CompileKind) -> Option<&TargetInfo> {
1073 match kind {
1074 CompileKind::Host => Some(&self.host_info),
1075 CompileKind::Target(s) => self.target_info.get(&s),
1076 }
1077 }
1078
1079 /// Gets the target configuration for a particular host or target.
1080 pub fn target_config(&self, kind: CompileKind) -> &TargetConfig {
1081 match kind {
1082 CompileKind::Host => &self.host_config,
1083 CompileKind::Target(s) => &self.target_config[&s],
1084 }
1085 }
1086
1087 pub fn get_unsupported_std_targets(&self) -> Vec<&str> {
1088 let mut unsupported = Vec::new();
1089 for (target, target_info) in &self.target_info {
1090 if target_info.supports_std == Some(false) {
1091 unsupported.push(target.short_name());
1092 }
1093 }
1094 unsupported
1095 }
1096}
1097
1098/// Structure used to deal with Rustdoc fingerprinting
1099#[derive(Debug, Serialize, Deserialize)]
1100pub struct RustDocFingerprint {
1101 pub rustc_vv: String,
1102}
1103
1104impl RustDocFingerprint {
1105 /// This function checks whether the latest version of `Rustc` used to compile this
1106 /// `Workspace`'s docs was the same as the one is currently being used in this `cargo doc`
1107 /// call.
1108 ///
1109 /// In case it's not, it takes care of removing the `doc/` folder as well as overwriting
1110 /// the rustdoc fingerprint info in order to guarantee that we won't end up with mixed
1111 /// versions of the `js/html/css` files that `rustdoc` autogenerates which do not have
1112 /// any versioning.
1113 pub fn check_rustdoc_fingerprint(build_runner: &BuildRunner<'_, '_>) -> CargoResult<()> {
1114 if build_runner
1115 .bcx
1116 .gctx
1117 .cli_unstable()
1118 .skip_rustdoc_fingerprint
1119 {
1120 return Ok(());
1121 }
1122 let actual_rustdoc_target_data = RustDocFingerprint {
1123 rustc_vv: build_runner.bcx.rustc().verbose_version.clone(),
1124 };
1125
1126 let fingerprint_path = build_runner
1127 .files()
1128 .host_root()
1129 .join(".rustdoc_fingerprint.json");
1130 let write_fingerprint = || -> CargoResult<()> {
1131 paths::write(
1132 &fingerprint_path,
1133 serde_json::to_string(&actual_rustdoc_target_data)?,
1134 )
1135 };
1136 let Ok(rustdoc_data) = paths::read(&fingerprint_path) else {
1137 // If the fingerprint does not exist, do not clear out the doc
1138 // directories. Otherwise this ran into problems where projects
1139 // like bootstrap were creating the doc directory before running
1140 // `cargo doc` in a way that deleting it would break it.
1141 return write_fingerprint();
1142 };
1143 match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) {
1144 Ok(fingerprint) => {
1145 if fingerprint.rustc_vv == actual_rustdoc_target_data.rustc_vv {
1146 return Ok(());
1147 } else {
1148 tracing::debug!(
1149 "doc fingerprint changed:\noriginal:\n{}\nnew:\n{}",
1150 fingerprint.rustc_vv,
1151 actual_rustdoc_target_data.rustc_vv
1152 );
1153 }
1154 }
1155 Err(e) => {
1156 tracing::debug!("could not deserialize {:?}: {}", fingerprint_path, e);
1157 }
1158 };
1159 // Fingerprint does not match, delete the doc directories and write a new fingerprint.
1160 tracing::debug!(
1161 "fingerprint {:?} mismatch, clearing doc directories",
1162 fingerprint_path
1163 );
1164 build_runner
1165 .bcx
1166 .all_kinds
1167 .iter()
1168 .map(|kind| build_runner.files().layout(*kind).doc())
1169 .filter(|path| path.exists())
1170 .try_for_each(|path| clean_doc(path))?;
1171 write_fingerprint()?;
1172 return Ok(());
1173
1174 fn clean_doc(path: &Path) -> CargoResult<()> {
1175 let entries = path
1176 .read_dir()
1177 .with_context(|| format!("failed to read directory `{}`", path.display()))?;
1178 for entry in entries {
1179 let entry = entry?;
1180 // Don't remove hidden files. Rustdoc does not create them,
1181 // but the user might have.
1182 if entry
1183 .file_name()
1184 .to_str()
1185 .map_or(false, |name| name.starts_with('.'))
1186 {
1187 continue;
1188 }
1189 let path = entry.path();
1190 if entry.file_type()?.is_dir() {
1191 paths::remove_dir_all(path)?;
1192 } else {
1193 paths::remove_file(path)?;
1194 }
1195 }
1196 Ok(())
1197 }
1198 }
1199}