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