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 || target_triple.ends_with("cygwin"))
443 {
444 // See https://cygwin.com/cygwin-ug-net/dll.html for more
445 // information about GNU import libraries.
446 // LD can link DLL directly, but LLD requires the import library.
447 ret.push(FileType {
448 suffix: ".dll.a".to_string(),
449 prefix: "lib".to_string(),
450 flavor: FileFlavor::Auxiliary,
451 crate_type: Some(crate_type.clone()),
452 should_replace_hyphens: true,
453 })
454 }
455 }
456
457 if target_triple.starts_with("wasm32-") && crate_type == CrateType::Bin && suffix == ".js" {
458 // emscripten binaries generate a .js file, which loads a .wasm
459 // file.
460 ret.push(FileType {
461 suffix: ".wasm".to_string(),
462 prefix: prefix.clone(),
463 flavor: FileFlavor::Auxiliary,
464 crate_type: Some(crate_type.clone()),
465 // Name `foo-bar` will generate a `foo_bar.js` and
466 // `foo_bar.wasm`. Cargo will translate the underscore and
467 // copy `foo_bar.js` to `foo-bar.js`. However, the wasm
468 // filename is embedded in the .js file with an underscore, so
469 // it should not contain hyphens.
470 should_replace_hyphens: true,
471 });
472 // And a map file for debugging. This is only emitted with debug=2
473 // (-g4 for emcc).
474 ret.push(FileType {
475 suffix: ".wasm.map".to_string(),
476 prefix: prefix.clone(),
477 flavor: FileFlavor::DebugInfo,
478 crate_type: Some(crate_type.clone()),
479 should_replace_hyphens: true,
480 });
481 }
482
483 // Handle separate debug files.
484 let is_apple = target_triple.contains("-apple-");
485 if matches!(
486 crate_type,
487 CrateType::Bin | CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro
488 ) {
489 if is_apple {
490 let suffix = if crate_type == CrateType::Bin {
491 ".dSYM".to_string()
492 } else {
493 ".dylib.dSYM".to_string()
494 };
495 ret.push(FileType {
496 suffix,
497 prefix: prefix.clone(),
498 flavor: FileFlavor::DebugInfo,
499 crate_type: Some(crate_type),
500 // macOS tools like lldb use all sorts of magic to locate
501 // dSYM files. See https://lldb.llvm.org/use/symbols.html
502 // for some details. It seems like a `.dSYM` located next
503 // to the executable with the same name is one method. The
504 // dSYM should have the same hyphens as the executable for
505 // the names to match.
506 should_replace_hyphens: false,
507 })
508 } else if target_triple.ends_with("-msvc") || target_triple.ends_with("-uefi") {
509 ret.push(FileType {
510 suffix: ".pdb".to_string(),
511 prefix: prefix.clone(),
512 flavor: FileFlavor::DebugInfo,
513 crate_type: Some(crate_type),
514 // The absolute path to the pdb file is embedded in the
515 // executable. If the exe/pdb pair is moved to another
516 // machine, then debuggers will look in the same directory
517 // of the exe with the original pdb filename. Since the
518 // original name contains underscores, they need to be
519 // preserved.
520 should_replace_hyphens: true,
521 })
522 } else {
523 // Because DWARF Package (dwp) files are produced after the
524 // fact by another tool, there is nothing in the binary that
525 // provides a means to locate them. By convention, debuggers
526 // take the binary filename and append ".dwp" (including to
527 // binaries that already have an extension such as shared libs)
528 // to find the dwp.
529 ret.push(FileType {
530 // It is important to preserve the existing suffix for
531 // e.g. shared libraries, where the dwp for libfoo.so is
532 // expected to be at libfoo.so.dwp.
533 suffix: format!("{suffix}.dwp"),
534 prefix: prefix.clone(),
535 flavor: FileFlavor::DebugInfo,
536 crate_type: Some(crate_type.clone()),
537 // Likewise, the dwp needs to match the primary artifact's
538 // hyphenation exactly.
539 should_replace_hyphens: crate_type != CrateType::Bin,
540 })
541 }
542 }
543
544 Ok(Some(ret))
545 }
546
547 fn discover_crate_type(&self, crate_type: &CrateType) -> CargoResult<Option<(String, String)>> {
548 let mut process = self.crate_type_process.clone();
549
550 process.arg("--crate-type").arg(crate_type.as_str());
551
552 let output = process.exec_with_output().with_context(|| {
553 format!(
554 "failed to run `rustc` to learn about crate-type {} information",
555 crate_type
556 )
557 })?;
558
559 let error = str::from_utf8(&output.stderr).unwrap();
560 let output = str::from_utf8(&output.stdout).unwrap();
561 parse_crate_type(crate_type, &process, output, error, &mut output.lines())
562 }
563
564 /// Returns all the file types generated by rustc for the given `mode`/`target_kind`.
565 ///
566 /// The first value is a Vec of file types generated, the second value is
567 /// a list of `CrateTypes` that are not supported by the given target.
568 pub fn rustc_outputs(
569 &self,
570 mode: CompileMode,
571 target_kind: &TargetKind,
572 target_triple: &str,
573 ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
574 match mode {
575 CompileMode::Build => self.calc_rustc_outputs(target_kind, target_triple),
576 CompileMode::Test | CompileMode::Bench => {
577 match self.file_types(&CrateType::Bin, FileFlavor::Normal, target_triple)? {
578 Some(fts) => Ok((fts, Vec::new())),
579 None => Ok((Vec::new(), vec![CrateType::Bin])),
580 }
581 }
582 CompileMode::Check { .. } => Ok((vec![FileType::new_rmeta()], Vec::new())),
583 CompileMode::Doc { .. }
584 | CompileMode::Doctest
585 | CompileMode::Docscrape
586 | CompileMode::RunCustomBuild => {
587 panic!("asked for rustc output for non-rustc mode")
588 }
589 }
590 }
591
592 fn calc_rustc_outputs(
593 &self,
594 target_kind: &TargetKind,
595 target_triple: &str,
596 ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
597 let mut unsupported = Vec::new();
598 let mut result = Vec::new();
599 let crate_types = target_kind.rustc_crate_types();
600 for crate_type in &crate_types {
601 let flavor = if crate_type.is_linkable() {
602 FileFlavor::Linkable
603 } else {
604 FileFlavor::Normal
605 };
606 let file_types = self.file_types(crate_type, flavor, target_triple)?;
607 match file_types {
608 Some(types) => {
609 result.extend(types);
610 }
611 None => {
612 unsupported.push(crate_type.clone());
613 }
614 }
615 }
616 if !result.is_empty() && !crate_types.iter().any(|ct| ct.requires_upstream_objects()) {
617 // Only add rmeta if pipelining.
618 result.push(FileType::new_rmeta());
619 }
620 Ok((result, unsupported))
621 }
622
623 /// Checks if the debuginfo-split value is supported by this target
624 pub fn supports_debuginfo_split(&self, split: InternedString) -> bool {
625 self.support_split_debuginfo
626 .iter()
627 .any(|sup| sup.as_str() == split.as_str())
628 }
629
630 /// Checks if a target maybe support std.
631 ///
632 /// If no explicitly stated in target spec json, we treat it as "maybe support".
633 ///
634 /// This is only useful for `-Zbuild-std` to determine the default set of
635 /// crates it is going to build.
636 pub fn maybe_support_std(&self) -> bool {
637 matches!(self.supports_std, Some(true) | None)
638 }
639}
640
641/// Takes rustc output (using specialized command line args), and calculates the file prefix and
642/// suffix for the given crate type, or returns `None` if the type is not supported. (e.g., for a
643/// Rust library like `libcargo.rlib`, we have prefix "lib" and suffix "rlib").
644///
645/// The caller needs to ensure that the lines object is at the correct line for the given crate
646/// type: this is not checked.
647///
648/// This function can not handle more than one file per type (with wasm32-unknown-emscripten, there
649/// are two files for bin (`.wasm` and `.js`)).
650fn parse_crate_type(
651 crate_type: &CrateType,
652 cmd: &ProcessBuilder,
653 output: &str,
654 error: &str,
655 lines: &mut str::Lines<'_>,
656) -> CargoResult<Option<(String, String)>> {
657 let not_supported = error.lines().any(|line| {
658 (line.contains("unsupported crate type") || line.contains("unknown crate type"))
659 && line.contains(&format!("crate type `{}`", crate_type))
660 });
661 if not_supported {
662 return Ok(None);
663 }
664 let Some(line) = lines.next() else {
665 anyhow::bail!(
666 "malformed output when learning about crate-type {} information\n{}",
667 crate_type,
668 output_err_info(cmd, output, error)
669 )
670 };
671 let mut parts = line.trim().split("___");
672 let prefix = parts.next().unwrap();
673 let Some(suffix) = parts.next() else {
674 return error_missing_print_output("file-names", cmd, output, error);
675 };
676
677 Ok(Some((prefix.to_string(), suffix.to_string())))
678}
679
680/// Helper for creating an error message for missing output from a certain `--print` request.
681fn error_missing_print_output<T>(
682 request: &str,
683 cmd: &ProcessBuilder,
684 stdout: &str,
685 stderr: &str,
686) -> CargoResult<T> {
687 let err_info = output_err_info(cmd, stdout, stderr);
688 anyhow::bail!(
689 "output of --print={request} missing when learning about \
690 target-specific information from rustc\n{err_info}",
691 )
692}
693
694/// Helper for creating an error message when parsing rustc output fails.
695fn output_err_info(cmd: &ProcessBuilder, stdout: &str, stderr: &str) -> String {
696 let mut result = format!("command was: {}\n", cmd);
697 if !stdout.is_empty() {
698 result.push_str("\n--- stdout\n");
699 result.push_str(stdout);
700 }
701 if !stderr.is_empty() {
702 result.push_str("\n--- stderr\n");
703 result.push_str(stderr);
704 }
705 if stdout.is_empty() && stderr.is_empty() {
706 result.push_str("(no output received)");
707 }
708 result
709}
710
711/// Compiler flags for either rustc or rustdoc.
712#[derive(Debug, Copy, Clone)]
713enum Flags {
714 Rust,
715 Rustdoc,
716}
717
718impl Flags {
719 fn as_key(self) -> &'static str {
720 match self {
721 Flags::Rust => "rustflags",
722 Flags::Rustdoc => "rustdocflags",
723 }
724 }
725
726 fn as_env(self) -> &'static str {
727 match self {
728 Flags::Rust => "RUSTFLAGS",
729 Flags::Rustdoc => "RUSTDOCFLAGS",
730 }
731 }
732}
733
734/// Acquire extra flags to pass to the compiler from various locations.
735///
736/// The locations are:
737///
738/// - the `CARGO_ENCODED_RUSTFLAGS` environment variable
739/// - the `RUSTFLAGS` environment variable
740///
741/// then if none of those were found
742///
743/// - `target.*.rustflags` from the config (.cargo/config)
744/// - `target.cfg(..).rustflags` from the config
745/// - `host.*.rustflags` from the config if compiling a host artifact or without `--target`
746/// (requires `-Zhost-config`)
747///
748/// then if none of those were found
749///
750/// - `build.rustflags` from the config
751///
752/// The behavior differs slightly when cross-compiling (or, specifically, when `--target` is
753/// provided) for artifacts that are always built for the host (plugins, build scripts, ...).
754/// For those artifacts, _only_ `host.*.rustflags` is respected, and no other configuration
755/// sources, _regardless of the value of `target-applies-to-host`_. This is counterintuitive, but
756/// necessary to retain backwards compatibility with older versions of Cargo.
757///
758/// Rules above also applies to rustdoc. Just the key would be `rustdocflags`/`RUSTDOCFLAGS`.
759fn extra_args(
760 gctx: &GlobalContext,
761 requested_kinds: &[CompileKind],
762 host_triple: &str,
763 target_cfg: Option<&[Cfg]>,
764 kind: CompileKind,
765 flags: Flags,
766) -> CargoResult<Vec<String>> {
767 let target_applies_to_host = gctx.target_applies_to_host()?;
768
769 // Host artifacts should not generally pick up rustflags from anywhere except [host].
770 //
771 // The one exception to this is if `target-applies-to-host = true`, which opts into a
772 // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags
773 // set elsewhere when `--target` isn't passed.
774 if kind.is_host() {
775 if target_applies_to_host && requested_kinds == [CompileKind::Host] {
776 // This is the past Cargo behavior where we fall back to the same logic as for other
777 // artifacts without --target.
778 } else {
779 // In all other cases, host artifacts just get flags from [host], regardless of
780 // --target. Or, phrased differently, no `--target` behaves the same as `--target
781 // <host>`, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for
782 // example).
783 return Ok(rustflags_from_host(gctx, flags, host_triple)?.unwrap_or_else(Vec::new));
784 }
785 }
786
787 // All other artifacts pick up the RUSTFLAGS, [target.*], and [build], in that order.
788 // NOTE: It is impossible to have a [host] section and reach this logic with kind.is_host(),
789 // since [host] implies `target-applies-to-host = false`, which always early-returns above.
790
791 if let Some(rustflags) = rustflags_from_env(gctx, flags) {
792 Ok(rustflags)
793 } else if let Some(rustflags) =
794 rustflags_from_target(gctx, host_triple, target_cfg, kind, flags)?
795 {
796 Ok(rustflags)
797 } else if let Some(rustflags) = rustflags_from_build(gctx, flags)? {
798 Ok(rustflags)
799 } else {
800 Ok(Vec::new())
801 }
802}
803
804/// Gets compiler flags from environment variables.
805/// See [`extra_args`] for more.
806fn rustflags_from_env(gctx: &GlobalContext, flags: Flags) -> Option<Vec<String>> {
807 // First try CARGO_ENCODED_RUSTFLAGS from the environment.
808 // Prefer this over RUSTFLAGS since it's less prone to encoding errors.
809 if let Ok(a) = gctx.get_env(format!("CARGO_ENCODED_{}", flags.as_env())) {
810 if a.is_empty() {
811 return Some(Vec::new());
812 }
813 return Some(a.split('\x1f').map(str::to_string).collect());
814 }
815
816 // Then try RUSTFLAGS from the environment
817 if let Ok(a) = gctx.get_env(flags.as_env()) {
818 let args = a
819 .split(' ')
820 .map(str::trim)
821 .filter(|s| !s.is_empty())
822 .map(str::to_string);
823 return Some(args.collect());
824 }
825
826 // No rustflags to be collected from the environment
827 None
828}
829
830/// Gets compiler flags from `[target]` section in the config.
831/// See [`extra_args`] for more.
832fn rustflags_from_target(
833 gctx: &GlobalContext,
834 host_triple: &str,
835 target_cfg: Option<&[Cfg]>,
836 kind: CompileKind,
837 flag: Flags,
838) -> CargoResult<Option<Vec<String>>> {
839 let mut rustflags = Vec::new();
840
841 // Then the target.*.rustflags value...
842 let target = match &kind {
843 CompileKind::Host => host_triple,
844 CompileKind::Target(target) => target.short_name(),
845 };
846 let key = format!("target.{}.{}", target, flag.as_key());
847 if let Some(args) = gctx.get::<Option<StringList>>(&key)? {
848 rustflags.extend(args.as_slice().iter().cloned());
849 }
850 // ...including target.'cfg(...)'.rustflags
851 if let Some(target_cfg) = target_cfg {
852 gctx.target_cfgs()?
853 .iter()
854 .filter_map(|(key, cfg)| {
855 match flag {
856 Flags::Rust => cfg
857 .rustflags
858 .as_ref()
859 .map(|rustflags| (key, &rustflags.val)),
860 // `target.cfg(…).rustdocflags` is currently not supported.
861 Flags::Rustdoc => None,
862 }
863 })
864 .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg))
865 .for_each(|(_key, cfg_rustflags)| {
866 rustflags.extend(cfg_rustflags.as_slice().iter().cloned());
867 });
868 }
869
870 if rustflags.is_empty() {
871 Ok(None)
872 } else {
873 Ok(Some(rustflags))
874 }
875}
876
877/// Gets compiler flags from `[host]` section in the config.
878/// See [`extra_args`] for more.
879fn rustflags_from_host(
880 gctx: &GlobalContext,
881 flag: Flags,
882 host_triple: &str,
883) -> CargoResult<Option<Vec<String>>> {
884 let target_cfg = gctx.host_cfg_triple(host_triple)?;
885 let list = match flag {
886 Flags::Rust => &target_cfg.rustflags,
887 Flags::Rustdoc => {
888 // host.rustdocflags is not a thing, since it does not make sense
889 return Ok(None);
890 }
891 };
892 Ok(list.as_ref().map(|l| l.val.as_slice().to_vec()))
893}
894
895/// Gets compiler flags from `[build]` section in the config.
896/// See [`extra_args`] for more.
897fn rustflags_from_build(gctx: &GlobalContext, flag: Flags) -> CargoResult<Option<Vec<String>>> {
898 // Then the `build.rustflags` value.
899 let build = gctx.build_config()?;
900 let list = match flag {
901 Flags::Rust => &build.rustflags,
902 Flags::Rustdoc => &build.rustdocflags,
903 };
904 Ok(list.as_ref().map(|l| l.as_slice().to_vec()))
905}
906
907/// Collection of information about `rustc` and the host and target.
908pub struct RustcTargetData<'gctx> {
909 /// Information about `rustc` itself.
910 pub rustc: Rustc,
911
912 /// Config
913 pub gctx: &'gctx GlobalContext,
914 requested_kinds: Vec<CompileKind>,
915
916 /// Build information for the "host", which is information about when
917 /// `rustc` is invoked without a `--target` flag. This is used for
918 /// selecting a linker, and applying link overrides.
919 ///
920 /// The configuration read into this depends on whether or not
921 /// `target-applies-to-host=true`.
922 host_config: TargetConfig,
923 /// Information about the host platform.
924 host_info: TargetInfo,
925
926 /// Build information for targets that we're building for.
927 target_config: HashMap<CompileTarget, TargetConfig>,
928 /// Information about the target platform that we're building for.
929 target_info: HashMap<CompileTarget, TargetInfo>,
930}
931
932impl<'gctx> RustcTargetData<'gctx> {
933 #[tracing::instrument(skip_all)]
934 pub fn new(
935 ws: &Workspace<'gctx>,
936 requested_kinds: &[CompileKind],
937 ) -> CargoResult<RustcTargetData<'gctx>> {
938 let gctx = ws.gctx();
939 let rustc = gctx.load_global_rustc(Some(ws))?;
940 let mut target_config = HashMap::new();
941 let mut target_info = HashMap::new();
942 let target_applies_to_host = gctx.target_applies_to_host()?;
943 let host_target = CompileTarget::new(&rustc.host)?;
944 let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?;
945
946 // This config is used for link overrides and choosing a linker.
947 let host_config = if target_applies_to_host {
948 gctx.target_cfg_triple(&rustc.host)?
949 } else {
950 gctx.host_cfg_triple(&rustc.host)?
951 };
952
953 // This is a hack. The unit_dependency graph builder "pretends" that
954 // `CompileKind::Host` is `CompileKind::Target(host)` if the
955 // `--target` flag is not specified. Since the unit_dependency code
956 // needs access to the target config data, create a copy so that it
957 // can be found. See `rebuild_unit_graph_shared` for why this is done.
958 if requested_kinds.iter().any(CompileKind::is_host) {
959 target_config.insert(host_target, gctx.target_cfg_triple(&rustc.host)?);
960
961 // If target_applies_to_host is true, the host_info is the target info,
962 // otherwise we need to build target info for the target.
963 if target_applies_to_host {
964 target_info.insert(host_target, host_info.clone());
965 } else {
966 let host_target_info = TargetInfo::new(
967 gctx,
968 requested_kinds,
969 &rustc,
970 CompileKind::Target(host_target),
971 )?;
972 target_info.insert(host_target, host_target_info);
973 }
974 };
975
976 let mut res = RustcTargetData {
977 rustc,
978 gctx,
979 requested_kinds: requested_kinds.into(),
980 host_config,
981 host_info,
982 target_config,
983 target_info,
984 };
985
986 // Get all kinds we currently know about.
987 //
988 // For now, targets can only ever come from the root workspace
989 // units and artifact dependencies, so this
990 // correctly represents all the kinds that can happen. When we have
991 // other ways for targets to appear at places that are not the root units,
992 // we may have to revisit this.
993 fn artifact_targets(package: &Package) -> impl Iterator<Item = CompileKind> + '_ {
994 package
995 .manifest()
996 .dependencies()
997 .iter()
998 .filter_map(|d| d.artifact()?.target()?.to_compile_kind())
999 }
1000 let all_kinds = requested_kinds
1001 .iter()
1002 .copied()
1003 .chain(ws.members().flat_map(|p| {
1004 p.manifest()
1005 .default_kind()
1006 .into_iter()
1007 .chain(p.manifest().forced_kind())
1008 .chain(artifact_targets(p))
1009 }));
1010 for kind in all_kinds {
1011 res.merge_compile_kind(kind)?;
1012 }
1013
1014 Ok(res)
1015 }
1016
1017 /// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet.
1018 pub fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> {
1019 if let CompileKind::Target(target) = kind {
1020 if !self.target_config.contains_key(&target) {
1021 self.target_config
1022 .insert(target, self.gctx.target_cfg_triple(target.short_name())?);
1023 }
1024 if !self.target_info.contains_key(&target) {
1025 self.target_info.insert(
1026 target,
1027 TargetInfo::new(self.gctx, &self.requested_kinds, &self.rustc, kind)?,
1028 );
1029 }
1030 }
1031 Ok(())
1032 }
1033
1034 /// Returns a "short" name for the given kind, suitable for keying off
1035 /// configuration in Cargo or presenting to users.
1036 pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str {
1037 match kind {
1038 CompileKind::Host => &self.rustc.host,
1039 CompileKind::Target(target) => target.short_name(),
1040 }
1041 }
1042
1043 /// Whether a dependency should be compiled for the host or target platform,
1044 /// specified by `CompileKind`.
1045 pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool {
1046 // If this dependency is only available for certain platforms,
1047 // make sure we're only enabling it for that platform.
1048 let Some(platform) = dep.platform() else {
1049 return true;
1050 };
1051 let name = self.short_name(&kind);
1052 platform.matches(name, self.cfg(kind))
1053 }
1054
1055 /// Gets the list of `cfg`s printed out from the compiler for the specified kind.
1056 pub fn cfg(&self, kind: CompileKind) -> &[Cfg] {
1057 self.info(kind).cfg()
1058 }
1059
1060 /// Information about the given target platform, learned by querying rustc.
1061 ///
1062 /// # Panics
1063 ///
1064 /// Panics, if the target platform described by `kind` can't be found.
1065 /// See [`get_info`](Self::get_info) for a non-panicking alternative.
1066 pub fn info(&self, kind: CompileKind) -> &TargetInfo {
1067 self.get_info(kind).unwrap()
1068 }
1069
1070 /// Information about the given target platform, learned by querying rustc.
1071 ///
1072 /// Returns `None` if the target platform described by `kind` can't be found.
1073 pub fn get_info(&self, kind: CompileKind) -> Option<&TargetInfo> {
1074 match kind {
1075 CompileKind::Host => Some(&self.host_info),
1076 CompileKind::Target(s) => self.target_info.get(&s),
1077 }
1078 }
1079
1080 /// Gets the target configuration for a particular host or target.
1081 pub fn target_config(&self, kind: CompileKind) -> &TargetConfig {
1082 match kind {
1083 CompileKind::Host => &self.host_config,
1084 CompileKind::Target(s) => &self.target_config[&s],
1085 }
1086 }
1087
1088 pub fn get_unsupported_std_targets(&self) -> Vec<&str> {
1089 let mut unsupported = Vec::new();
1090 for (target, target_info) in &self.target_info {
1091 if target_info.supports_std == Some(false) {
1092 unsupported.push(target.short_name());
1093 }
1094 }
1095 unsupported
1096 }
1097}
1098
1099/// Structure used to deal with Rustdoc fingerprinting
1100#[derive(Debug, Serialize, Deserialize)]
1101pub struct RustDocFingerprint {
1102 pub rustc_vv: String,
1103}
1104
1105impl RustDocFingerprint {
1106 /// This function checks whether the latest version of `Rustc` used to compile this
1107 /// `Workspace`'s docs was the same as the one is currently being used in this `cargo doc`
1108 /// call.
1109 ///
1110 /// In case it's not, it takes care of removing the `doc/` folder as well as overwriting
1111 /// the rustdoc fingerprint info in order to guarantee that we won't end up with mixed
1112 /// versions of the `js/html/css` files that `rustdoc` autogenerates which do not have
1113 /// any versioning.
1114 pub fn check_rustdoc_fingerprint(build_runner: &BuildRunner<'_, '_>) -> CargoResult<()> {
1115 if build_runner
1116 .bcx
1117 .gctx
1118 .cli_unstable()
1119 .skip_rustdoc_fingerprint
1120 {
1121 return Ok(());
1122 }
1123 let actual_rustdoc_target_data = RustDocFingerprint {
1124 rustc_vv: build_runner.bcx.rustc().verbose_version.clone(),
1125 };
1126
1127 let fingerprint_path = build_runner
1128 .files()
1129 .host_root()
1130 .join(".rustdoc_fingerprint.json");
1131 let write_fingerprint = || -> CargoResult<()> {
1132 paths::write(
1133 &fingerprint_path,
1134 serde_json::to_string(&actual_rustdoc_target_data)?,
1135 )
1136 };
1137 let Ok(rustdoc_data) = paths::read(&fingerprint_path) else {
1138 // If the fingerprint does not exist, do not clear out the doc
1139 // directories. Otherwise this ran into problems where projects
1140 // like bootstrap were creating the doc directory before running
1141 // `cargo doc` in a way that deleting it would break it.
1142 return write_fingerprint();
1143 };
1144 match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) {
1145 Ok(fingerprint) => {
1146 if fingerprint.rustc_vv == actual_rustdoc_target_data.rustc_vv {
1147 return Ok(());
1148 } else {
1149 tracing::debug!(
1150 "doc fingerprint changed:\noriginal:\n{}\nnew:\n{}",
1151 fingerprint.rustc_vv,
1152 actual_rustdoc_target_data.rustc_vv
1153 );
1154 }
1155 }
1156 Err(e) => {
1157 tracing::debug!("could not deserialize {:?}: {}", fingerprint_path, e);
1158 }
1159 };
1160 // Fingerprint does not match, delete the doc directories and write a new fingerprint.
1161 tracing::debug!(
1162 "fingerprint {:?} mismatch, clearing doc directories",
1163 fingerprint_path
1164 );
1165 build_runner
1166 .bcx
1167 .all_kinds
1168 .iter()
1169 .map(|kind| build_runner.files().layout(*kind).doc())
1170 .filter(|path| path.exists())
1171 .try_for_each(|path| clean_doc(path))?;
1172 write_fingerprint()?;
1173 return Ok(());
1174
1175 fn clean_doc(path: &Path) -> CargoResult<()> {
1176 let entries = path
1177 .read_dir()
1178 .with_context(|| format!("failed to read directory `{}`", path.display()))?;
1179 for entry in entries {
1180 let entry = entry?;
1181 // Don't remove hidden files. Rustdoc does not create them,
1182 // but the user might have.
1183 if entry
1184 .file_name()
1185 .to_str()
1186 .map_or(false, |name| name.starts_with('.'))
1187 {
1188 continue;
1189 }
1190 let path = entry.path();
1191 if entry.file_type()?.is_dir() {
1192 paths::remove_dir_all(path)?;
1193 } else {
1194 paths::remove_file(path)?;
1195 }
1196 }
1197 Ok(())
1198 }
1199 }
1200}