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