#
cargo/core/compiler/
compile_kind.rs

    1//! Type definitions for cross-compilation.
    2
    3use crate::core::Target;
    4use crate::util::errors::CargoResult;
    5use crate::util::interning::InternedString;
    6use crate::util::{GlobalContext, StableHasher, try_canonicalize};
    7use anyhow::Context as _;
    8use serde::Serialize;
    9use std::collections::BTreeSet;
    10use std::fs;
    11use std::hash::{Hash, Hasher};
    12use std::path::Path;
    13
    14/// Indicator for how a unit is being compiled.
    15///
    16/// This is used primarily for organizing cross compilations vs host
    17/// compilations, where cross compilations happen at the request of `--target`
    18/// and host compilations happen for things like build scripts and procedural
    19/// macros.
    20#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
    21pub enum CompileKind {
    22    /// Attached to a unit that is compiled for the "host" system or otherwise
    23    /// is compiled without a `--target` flag. This is used for procedural
    24    /// macros and build scripts, or if the `--target` flag isn't passed.
    25    Host,
    26
    27    /// Attached to a unit to be compiled for a particular target. This is used
    28    /// for units when the `--target` flag is passed.
    29    Target(CompileTarget),
    30}
    31
    32/// Fallback behavior in the
    33/// [`CompileKind::from_requested_targets_with_fallback`] function when
    34/// no targets are specified.
    35pub enum CompileKindFallback {
    36    /// The build configuration is consulted to find the default target, such as
    37    /// `$CARGO_BUILD_TARGET` or reading `build.target`.
    38    BuildConfig,
    39
    40    /// Only the host should be returned when targets aren't explicitly
    41    /// specified. This is used by `cargo metadata` for example where "only
    42    /// host" has a special meaning in terms of the returned metadata.
    43    JustHost,
    44}
    45
    46impl CompileKind {
    47    pub fn is_host(&self) -> bool {
    48        matches!(self, CompileKind::Host)
    49    }
    50
    51    pub fn for_target(self, target: &Target) -> CompileKind {
    52        // Once we start compiling for the `Host` kind we continue doing so, but
    53        // if we are a `Target` kind and then we start compiling for a target
    54        // that needs to be on the host we lift ourselves up to `Host`.
    55        match self {
    56            CompileKind::Host => CompileKind::Host,
    57            CompileKind::Target(_) if target.for_host() => CompileKind::Host,
    58            CompileKind::Target(n) => CompileKind::Target(n),
    59        }
    60    }
    61
    62    /// Creates a new list of `CompileKind` based on the requested list of
    63    /// targets.
    64    ///
    65    /// If no targets are given then this returns a single-element vector with
    66    /// `CompileKind::Host`.
    67    pub fn from_requested_targets(
    68        gctx: &GlobalContext,
    69        targets: &[String],
    70    ) -> CargoResult<Vec<CompileKind>> {
    71        CompileKind::from_requested_targets_with_fallback(
    72            gctx,
    73            targets,
    74            CompileKindFallback::BuildConfig,
    75        )
    76    }
    77
    78    /// Same as [`CompileKind::from_requested_targets`] except that if `targets`
    79    /// doesn't explicitly mention anything the behavior of what to return is
    80    /// controlled by the `fallback` argument.
    81    pub fn from_requested_targets_with_fallback(
    82        gctx: &GlobalContext,
    83        targets: &[String],
    84        fallback: CompileKindFallback,
    85    ) -> CargoResult<Vec<CompileKind>> {
    86        let dedup = |targets: &[String]| {
    87            let deduplicated_targets = targets
    88                .iter()
    89                .map(|value| {
    90                    // This neatly substitutes the manually-specified `host-tuple` target directive
    91                    // with the compiling machine's target triple.
    92
    93                    if value.as_str() == "host-tuple" {
    94                        let host_triple = env!("RUST_HOST_TARGET");
    95                        Ok(CompileKind::Target(CompileTarget::new(host_triple)?))
    96                    } else {
    97                        Ok(CompileKind::Target(CompileTarget::new(value.as_str())?))
    98                    }
    99                })
    100                // First collect into a set to deduplicate any `--target` passed
    101                // more than once...
    102                .collect::<CargoResult<BTreeSet<_>>>()?
    103                // ... then generate a flat list for everything else to use.
    104                .into_iter()
    105                .collect();
    106
    107            Ok(deduplicated_targets)
    108        };
    109
    110        if !targets.is_empty() {
    111            return dedup(targets);
    112        }
    113
    114        let kinds = match (fallback, &gctx.build_config()?.target) {
    115            (_, None) | (CompileKindFallback::JustHost, _) => Ok(vec![CompileKind::Host]),
    116            (CompileKindFallback::BuildConfig, Some(build_target_config)) => {
    117                dedup(&build_target_config.values(gctx)?)
    118            }
    119        };
    120
    121        kinds
    122    }
    123
    124    /// Hash used for fingerprinting.
    125    ///
    126    /// Metadata hashing uses the normal Hash trait, which does not
    127    /// differentiate on `.json` file contents. The fingerprint hash does
    128    /// check the contents.
    129    pub fn fingerprint_hash(&self) -> u64 {
    130        match self {
    131            CompileKind::Host => 0,
    132            CompileKind::Target(target) => target.fingerprint_hash(),
    133        }
    134    }
    135}
    136
    137impl serde::ser::Serialize for CompileKind {
    138    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    139    where
    140        S: serde::ser::Serializer,
    141    {
    142        match self {
    143            CompileKind::Host => None::<&str>.serialize(s),
    144            CompileKind::Target(t) => Some(t.name).serialize(s),
    145        }
    146    }
    147}
    148
    149/// Abstraction for the representation of a compilation target that Cargo has.
    150///
    151/// Compilation targets are one of two things right now:
    152///
    153/// 1. A raw target string, like `x86_64-unknown-linux-gnu`.
    154/// 2. The path to a JSON file, such as `/path/to/my-target.json`.
    155///
    156/// Raw target strings are typically dictated by `rustc` itself and represent
    157/// built-in targets. Custom JSON files are somewhat unstable, but supported
    158/// here in Cargo. Note that for JSON target files this `CompileTarget` stores a
    159/// full canonicalized path to the target.
    160///
    161/// The main reason for this existence is to handle JSON target files where when
    162/// we call rustc we pass full paths but when we use it for Cargo's purposes
    163/// like naming directories or looking up configuration keys we only check the
    164/// file stem of JSON target files. For built-in rustc targets this is just an
    165/// uninterpreted string basically.
    166#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord, Serialize)]
    167pub struct CompileTarget {
    168    name: InternedString,
    169}
    170
    171impl CompileTarget {
    172    pub fn new(name: &str) -> CargoResult<CompileTarget> {
    173        let name = name.trim();
    174        if name.is_empty() {
    175            anyhow::bail!("target was empty");
    176        }
    177        if !name.ends_with(".json") {
    178            return Ok(CompileTarget { name: name.into() });
    179        }
    180
    181        // If `name` ends in `.json` then it's likely a custom target
    182        // specification. Canonicalize the path to ensure that different builds
    183        // with different paths always produce the same result.
    184        let path = try_canonicalize(Path::new(name))
    185            .with_context(|| format!("target path {:?} is not a valid file", name))?;
    186
    187        let name = path
    188            .into_os_string()
    189            .into_string()
    190            .map_err(|_| anyhow::format_err!("target path is not valid unicode"))?;
    191        Ok(CompileTarget { name: name.into() })
    192    }
    193
    194    /// Returns the full unqualified name of this target, suitable for passing
    195    /// to `rustc` directly.
    196    ///
    197    /// Typically this is pretty much the same as `short_name`, but for the case
    198    /// of JSON target files this will be a full canonicalized path name for the
    199    /// current filesystem.
    200    pub fn rustc_target(&self) -> InternedString {
    201        self.name
    202    }
    203
    204    /// Returns a "short" version of the target name suitable for usage within
    205    /// Cargo for configuration and such.
    206    ///
    207    /// This is typically the same as `rustc_target`, or the full name, but for
    208    /// JSON target files this returns just the file stem (e.g. `foo` out of
    209    /// `foo.json`) instead of the full path.
    210    pub fn short_name(&self) -> &str {
    211        // Flexible target specifications often point at json files, so if it
    212        // looks like we've got one of those just use the file stem (the file
    213        // name without ".json") as a short name for this target. Note that the
    214        // `unwrap()` here should never trigger since we have a nonempty name
    215        // and it starts as utf-8 so it's always utf-8
    216        if self.name.ends_with(".json") {
    217            Path::new(&self.name).file_stem().unwrap().to_str().unwrap()
    218        } else {
    219            &self.name
    220        }
    221    }
    222
    223    /// See [`CompileKind::fingerprint_hash`].
    224    pub fn fingerprint_hash(&self) -> u64 {
    225        let mut hasher = StableHasher::new();
    226        match self
    227            .name
    228            .ends_with(".json")
    229            .then(|| fs::read_to_string(self.name))
    230        {
    231            Some(Ok(contents)) => {
    232                // This may have some performance concerns, since it is called
    233                // fairly often. If that ever seems worth fixing, consider
    234                // embedding this in `CompileTarget`.
    235                contents.hash(&mut hasher);
    236            }
    237            _ => {
    238                self.name.hash(&mut hasher);
    239            }
    240        }
    241        Hasher::finish(&hasher)
    242    }
    243}