#
cargo/core/compiler/build_context/
target_info.rs

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