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