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