Skip to main content

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