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