    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200

    //! Type definitions for cross-compilation.

    use crate::core::Target;
    use crate::util::errors::CargoResult;
    use crate::util::interning::InternedString;
    use crate::util::{try_canonicalize, GlobalContext, StableHasher};
    use anyhow::Context as _;
    use serde::Serialize;
    use std::collections::BTreeSet;
    use std::fs;
    use std::hash::{Hash, Hasher};
    use std::path::Path;

    /// Indicator for how a unit is being compiled.
    ///
    /// This is used primarily for organizing cross compilations vs host
    /// compilations, where cross compilations happen at the request of `--target`
    /// and host compilations happen for things like build scripts and procedural
    /// macros.
    #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
    pub enum CompileKind {
        /// Attached to a unit that is compiled for the "host" system or otherwise
        /// is compiled without a `--target` flag. This is used for procedural
        /// macros and build scripts, or if the `--target` flag isn't passed.
        Host,

        /// Attached to a unit to be compiled for a particular target. This is used
        /// for units when the `--target` flag is passed.
        Target(CompileTarget),
    }

    impl CompileKind {
        pub fn is_host(&self) -> bool {
            matches!(self, CompileKind::Host)
        }

        pub fn for_target(self, target: &Target) -> CompileKind {
            // Once we start compiling for the `Host` kind we continue doing so, but
            // if we are a `Target` kind and then we start compiling for a target
            // that needs to be on the host we lift ourselves up to `Host`.
            match self {
                CompileKind::Host => CompileKind::Host,
                CompileKind::Target(_) if target.for_host() => CompileKind::Host,
                CompileKind::Target(n) => CompileKind::Target(n),
            }
        }

        /// Creates a new list of `CompileKind` based on the requested list of
        /// targets.
        ///
        /// If no targets are given then this returns a single-element vector with
        /// `CompileKind::Host`.
        pub fn from_requested_targets(
            gctx: &GlobalContext,
            targets: &[String],
        ) -> CargoResult<Vec<CompileKind>> {
            let dedup = |targets: &[String]| {
                Ok(targets
                    .iter()
                    .map(|value| Ok(CompileKind::Target(CompileTarget::new(value)?)))
                    // First collect into a set to deduplicate any `--target` passed
                    // more than once...
                    .collect::<CargoResult<BTreeSet<_>>>()?
                    // ... then generate a flat list for everything else to use.
                    .into_iter()
                    .collect())
            };

            if !targets.is_empty() {
                return dedup(targets);
            }

            let kinds = match &gctx.build_config()?.target {
                None => Ok(vec![CompileKind::Host]),
                Some(build_target_config) => dedup(&build_target_config.values(gctx)?),
            };

            kinds
        }

        /// Hash used for fingerprinting.
        ///
        /// Metadata hashing uses the normal Hash trait, which does not
        /// differentiate on `.json` file contents. The fingerprint hash does
        /// check the contents.
        pub fn fingerprint_hash(&self) -> u64 {
            match self {
                CompileKind::Host => 0,
                CompileKind::Target(target) => target.fingerprint_hash(),
            }
        }
    }

    impl serde::ser::Serialize for CompileKind {
        fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
        where
            S: serde::ser::Serializer,
        {
            match self {
                CompileKind::Host => None::<&str>.serialize(s),
                CompileKind::Target(t) => Some(t.name).serialize(s),
            }
        }
    }

    /// Abstraction for the representation of a compilation target that Cargo has.
    ///
    /// Compilation targets are one of two things right now:
    ///
    /// 1. A raw target string, like `x86_64-unknown-linux-gnu`.
    /// 2. The path to a JSON file, such as `/path/to/my-target.json`.
    ///
    /// Raw target strings are typically dictated by `rustc` itself and represent
    /// built-in targets. Custom JSON files are somewhat unstable, but supported
    /// here in Cargo. Note that for JSON target files this `CompileTarget` stores a
    /// full canonicalized path to the target.
    ///
    /// The main reason for this existence is to handle JSON target files where when
    /// we call rustc we pass full paths but when we use it for Cargo's purposes
    /// like naming directories or looking up configuration keys we only check the
    /// file stem of JSON target files. For built-in rustc targets this is just an
    /// uninterpreted string basically.
    #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord, Serialize)]
    pub struct CompileTarget {
        name: InternedString,
    }

    impl CompileTarget {
        pub fn new(name: &str) -> CargoResult<CompileTarget> {
            let name = name.trim();
            if name.is_empty() {
                anyhow::bail!("target was empty");
            }
            if !name.ends_with(".json") {
                return Ok(CompileTarget { name: name.into() });
            }

            // If `name` ends in `.json` then it's likely a custom target
            // specification. Canonicalize the path to ensure that different builds
            // with different paths always produce the same result.
            let path = try_canonicalize(Path::new(name))
                .with_context(|| format!("target path {:?} is not a valid file", name))?;

            let name = path
                .into_os_string()
                .into_string()
                .map_err(|_| anyhow::format_err!("target path is not valid unicode"))?;
            Ok(CompileTarget { name: name.into() })
        }

        /// Returns the full unqualified name of this target, suitable for passing
        /// to `rustc` directly.
        ///
        /// Typically this is pretty much the same as `short_name`, but for the case
        /// of JSON target files this will be a full canonicalized path name for the
        /// current filesystem.
        pub fn rustc_target(&self) -> InternedString {
            self.name
        }

        /// Returns a "short" version of the target name suitable for usage within
        /// Cargo for configuration and such.
        ///
        /// This is typically the same as `rustc_target`, or the full name, but for
        /// JSON target files this returns just the file stem (e.g. `foo` out of
        /// `foo.json`) instead of the full path.
        pub fn short_name(&self) -> &str {
            // Flexible target specifications often point at json files, so if it
            // looks like we've got one of those just use the file stem (the file
            // name without ".json") as a short name for this target. Note that the
            // `unwrap()` here should never trigger since we have a nonempty name
            // and it starts as utf-8 so it's always utf-8
            if self.name.ends_with(".json") {
                Path::new(&self.name).file_stem().unwrap().to_str().unwrap()
            } else {
                &self.name
            }
        }

        /// See [`CompileKind::fingerprint_hash`].
        pub fn fingerprint_hash(&self) -> u64 {
            let mut hasher = StableHasher::new();
            match self
                .name
                .ends_with(".json")
                .then(|| fs::read_to_string(self.name))
            {
                Some(Ok(contents)) => {
                    // This may have some performance concerns, since it is called
                    // fairly often. If that ever seems worth fixing, consider
                    // embedding this in `CompileTarget`.
                    contents.hash(&mut hasher);
                }
                _ => {
                    self.name.hash(&mut hasher);
                }
            }
            hasher.finish()
        }
    }