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