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::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.jsonl`).
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    let key = format!("target.{}.{}", target, flag.as_key());
876    if let Some(args) = gctx.get::<Option<StringList>>(&key)? {
877        rustflags.extend(args.as_slice().iter().cloned());
878    }
879    // ...including target.'cfg(...)'.rustflags
880    if let Some(target_cfg) = target_cfg {
881        gctx.target_cfgs()?
882            .iter()
883            .filter_map(|(key, cfg)| match flag {
884                Flags::Rust => cfg
885                    .rustflags
886                    .as_ref()
887                    .map(|rustflags| (key, &rustflags.val)),
888                Flags::Rustdoc => cfg
889                    .rustdocflags
890                    .as_ref()
891                    .map(|rustdocflags| (key, &rustdocflags.val)),
892            })
893            .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg))
894            .for_each(|(_key, cfg_rustflags)| {
895                rustflags.extend(cfg_rustflags.as_slice().iter().cloned());
896            });
897    }
898
899    if rustflags.is_empty() {
900        Ok(None)
901    } else {
902        Ok(Some(rustflags))
903    }
904}
905
906/// Gets compiler flags from `[host]` section in the config.
907/// See [`extra_args`] for more.
908fn rustflags_from_host(
909    gctx: &GlobalContext,
910    flag: Flags,
911    host_triple: &str,
912) -> CargoResult<Option<Vec<String>>> {
913    let target_cfg = gctx.host_cfg_triple(host_triple)?;
914    let list = match flag {
915        Flags::Rust => &target_cfg.rustflags,
916        Flags::Rustdoc => {
917            // host.rustdocflags is not a thing, since it does not make sense
918            return Ok(None);
919        }
920    };
921    Ok(list.as_ref().map(|l| l.val.as_slice().to_vec()))
922}
923
924/// Gets compiler flags from `[build]` section in the config.
925/// See [`extra_args`] for more.
926fn rustflags_from_build(gctx: &GlobalContext, flag: Flags) -> CargoResult<Option<Vec<String>>> {
927    // Then the `build.rustflags` value.
928    let build = gctx.build_config()?;
929    let list = match flag {
930        Flags::Rust => &build.rustflags,
931        Flags::Rustdoc => &build.rustdocflags,
932    };
933    Ok(list.as_ref().map(|l| l.as_slice().to_vec()))
934}
935
936/// Whether a host artifact must take its configuration solely from `[host]` and ignore `[target]`.
937fn host_artifact_uses_only_host_config(
938    gctx: &GlobalContext,
939    requested_kinds: &[CompileKind],
940    kind: CompileKind,
941) -> CargoResult<bool> {
942    let target_applies_to_host = gctx.target_applies_to_host()?;
943
944    // Host artifacts should not generally pick up rustflags from anywhere except [host].
945    //
946    // The one exception to this is if `target-applies-to-host = true`, which opts into a
947    // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags
948    // set elsewhere when `--target` isn't passed.
949    if kind.is_host() {
950        if target_applies_to_host && requested_kinds == [CompileKind::Host] {
951            // This is the past Cargo behavior where we fall back to the same logic as for other
952            // artifacts without --target.
953        } else {
954            // In all other cases, host artifacts just get flags from [host], regardless of
955            // --target. Or, phrased differently, no `--target` behaves the same as `--target
956            // <host>`, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for
957            // example).
958            return Ok(true);
959        }
960    }
961
962    Ok(false)
963}
964
965/// Collection of information about `rustc` and the host and target.
966pub struct RustcTargetData<'gctx> {
967    /// Information about `rustc` itself.
968    pub rustc: Rustc,
969
970    /// Config
971    pub gctx: &'gctx GlobalContext,
972    requested_kinds: Vec<CompileKind>,
973
974    /// Build information for the "host", which is information about when
975    /// `rustc` is invoked without a `--target` flag. This is used for
976    /// selecting a linker, and applying link overrides.
977    ///
978    /// The configuration read into this depends on whether or not
979    /// `target-applies-to-host=true`.
980    host_config: TargetConfig,
981    /// Information about the host platform.
982    host_info: TargetInfo,
983
984    /// Build information for targets that we're building for.
985    target_config: HashMap<CompileTarget, TargetConfig>,
986    /// Information about the target platform that we're building for.
987    target_info: HashMap<CompileTarget, TargetInfo>,
988}
989
990impl<'gctx> RustcTargetData<'gctx> {
991    #[tracing::instrument(skip_all)]
992    pub fn new(
993        ws: &Workspace<'gctx>,
994        requested_kinds: &[CompileKind],
995    ) -> CargoResult<RustcTargetData<'gctx>> {
996        let gctx = ws.gctx();
997        let rustc = gctx.load_global_rustc(Some(ws))?;
998        let mut target_config = HashMap::default();
999        let mut target_info = HashMap::default();
1000        let target_applies_to_host = gctx.target_applies_to_host()?;
1001        let host_target = CompileTarget::new(&rustc.host, gctx.cli_unstable().json_target_spec)?;
1002        let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?;
1003
1004        // This config is used for link overrides and choosing a linker.
1005        let host_config = if target_applies_to_host {
1006            gctx.target_cfg_triple(&rustc.host)?
1007        } else {
1008            gctx.host_cfg_triple(&rustc.host)?
1009        };
1010
1011        // This is a hack. The unit_dependency graph builder "pretends" that
1012        // `CompileKind::Host` is `CompileKind::Target(host)` if the
1013        // `--target` flag is not specified. Since the unit_dependency code
1014        // needs access to the target config data, create a copy so that it
1015        // can be found. See `rebuild_unit_graph_shared` for why this is done.
1016        if requested_kinds.iter().any(CompileKind::is_host) {
1017            target_config.insert(host_target, gctx.target_cfg_triple(&rustc.host)?);
1018
1019            // If target_applies_to_host is true, the host_info is the target info,
1020            // otherwise we need to build target info for the target.
1021            if target_applies_to_host {
1022                target_info.insert(host_target, host_info.clone());
1023            } else {
1024                let host_target_info = TargetInfo::new(
1025                    gctx,
1026                    requested_kinds,
1027                    &rustc,
1028                    CompileKind::Target(host_target),
1029                )?;
1030                target_info.insert(host_target, host_target_info);
1031            }
1032        };
1033
1034        let mut res = RustcTargetData {
1035            rustc,
1036            gctx,
1037            requested_kinds: requested_kinds.into(),
1038            host_config,
1039            host_info,
1040            target_config,
1041            target_info,
1042        };
1043
1044        // Get all kinds we currently know about.
1045        //
1046        // For now, targets can only ever come from the root workspace
1047        // units and artifact dependencies, so this
1048        // correctly represents all the kinds that can happen. When we have
1049        // other ways for targets to appear at places that are not the root units,
1050        // we may have to revisit this.
1051        fn artifact_targets(package: &Package) -> impl Iterator<Item = CompileKind> + '_ {
1052            package
1053                .manifest()
1054                .dependencies()
1055                .iter()
1056                .filter_map(|d| d.artifact()?.target()?.to_compile_kind())
1057        }
1058        let all_kinds = requested_kinds
1059            .iter()
1060            .copied()
1061            .chain(ws.members().flat_map(|p| {
1062                p.manifest()
1063                    .default_kind()
1064                    .into_iter()
1065                    .chain(p.manifest().forced_kind())
1066                    .chain(artifact_targets(p))
1067            }));
1068        for kind in all_kinds {
1069            res.merge_compile_kind(kind)?;
1070        }
1071
1072        Ok(res)
1073    }
1074
1075    /// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet.
1076    pub fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> {
1077        if let CompileKind::Target(target) = kind {
1078            if !self.target_config.contains_key(&target) {
1079                self.target_config
1080                    .insert(target, self.gctx.target_cfg_triple(target.short_name())?);
1081            }
1082            if !self.target_info.contains_key(&target) {
1083                self.target_info.insert(
1084                    target,
1085                    TargetInfo::new(self.gctx, &self.requested_kinds, &self.rustc, kind)?,
1086                );
1087            }
1088        }
1089        Ok(())
1090    }
1091
1092    /// Returns a "short" name for the given kind, suitable for keying off
1093    /// configuration in Cargo or presenting to users.
1094    pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str {
1095        match kind {
1096            CompileKind::Host => &self.rustc.host,
1097            CompileKind::Target(target) => target.short_name(),
1098        }
1099    }
1100
1101    /// Whether a dependency should be compiled for the host or target platform,
1102    /// specified by `CompileKind`.
1103    pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool {
1104        // If this dependency is only available for certain platforms,
1105        // make sure we're only enabling it for that platform.
1106        let Some(platform) = dep.platform() else {
1107            return true;
1108        };
1109        let name = self.short_name(&kind);
1110        platform.matches(name, self.cfg(kind))
1111    }
1112
1113    /// Gets the list of `cfg`s printed out from the compiler for the specified kind.
1114    pub fn cfg(&self, kind: CompileKind) -> &[Cfg] {
1115        self.info(kind).cfg()
1116    }
1117
1118    /// Information about the given target platform, learned by querying rustc.
1119    ///
1120    /// # Panics
1121    ///
1122    /// Panics, if the target platform described by `kind` can't be found.
1123    /// See [`get_info`](Self::get_info) for a non-panicking alternative.
1124    pub fn info(&self, kind: CompileKind) -> &TargetInfo {
1125        self.get_info(kind).unwrap()
1126    }
1127
1128    /// Information about the given target platform, learned by querying rustc.
1129    ///
1130    /// Returns `None` if the target platform described by `kind` can't be found.
1131    pub fn get_info(&self, kind: CompileKind) -> Option<&TargetInfo> {
1132        match kind {
1133            CompileKind::Host => Some(&self.host_info),
1134            CompileKind::Target(s) => self.target_info.get(&s),
1135        }
1136    }
1137
1138    /// Gets the target configuration for a particular host or target.
1139    pub fn target_config(&self, kind: CompileKind) -> &TargetConfig {
1140        match kind {
1141            CompileKind::Host => &self.host_config,
1142            CompileKind::Target(s) => &self.target_config[&s],
1143        }
1144    }
1145
1146    pub fn get_unsupported_std_targets(&self) -> Vec<&str> {
1147        let mut unsupported = Vec::new();
1148        for (target, target_info) in &self.target_info {
1149            if target_info.supports_std == Some(false) {
1150                unsupported.push(target.short_name());
1151            }
1152        }
1153        unsupported
1154    }
1155
1156    pub fn requested_kinds(&self) -> &[CompileKind] {
1157        &self.requested_kinds
1158    }
1159}