| //! Type definitions for cross-compilation. | |
| use crate::util::errors::CargoResult; | |
| use crate::util::interning::InternedString; | |
| use crate::util::{GlobalContext, StableHasher, try_canonicalize}; | |
| use crate::workspace::Target; | |
| use anyhow::Context as _; | |
| use anyhow::bail; | |
| use cargo_util::ProcessBuilder; | |
| 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), | |
| } | |
| /// Fallback behavior in the | |
| /// [`CompileKind::from_requested_targets_with_fallback`] function when | |
| /// no targets are specified. | |
| pub enum CompileKindFallback { | |
| /// The build configuration is consulted to find the default target, such as | |
| /// `$CARGO_BUILD_TARGET` or reading `build.target`. | |
| BuildConfig, | |
| /// Only the host should be returned when targets aren't explicitly | |
| /// specified. This is used by `cargo metadata` for example where "only | |
| /// host" has a special meaning in terms of the returned metadata. | |
| JustHost, | |
| } | |
| 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>> { | |
| CompileKind::from_requested_targets_with_fallback( | |
| gctx, | |
| targets, | |
| CompileKindFallback::BuildConfig, | |
| ) | |
| } | |
| /// Same as [`CompileKind::from_requested_targets`] except that if `targets` | |
| /// doesn't explicitly mention anything the behavior of what to return is | |
| /// controlled by the `fallback` argument. | |
| pub fn from_requested_targets_with_fallback( | |
| gctx: &GlobalContext, | |
| targets: &[String], | |
| fallback: CompileKindFallback, | |
| ) -> CargoResult<Vec<CompileKind>> { | |
| let dedup = |targets: &[String]| { | |
| let deduplicated_targets = targets | |
| .iter() | |
| .map(|value| { | |
| // This neatly substitutes the manually-specified `host-tuple` target directive | |
| // with the compiling machine's target tuple. | |
| if value.as_str() == "host-tuple" { | |
| let host_triple = env!("RUST_HOST_TARGET"); | |
| Ok(CompileKind::Target(CompileTarget::new( | |
| host_triple, | |
| gctx.cli_unstable().json_target_spec, | |
| )?)) | |
| } else { | |
| Ok(CompileKind::Target(CompileTarget::new( | |
| value.as_str(), | |
| gctx.cli_unstable().json_target_spec, | |
| )?)) | |
| } | |
| }) | |
| // 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(); | |
| Ok(deduplicated_targets) | |
| }; | |
| if !targets.is_empty() { | |
| return dedup(targets); | |
| } | |
| let kinds = match (fallback, &gctx.build_config()?.target) { | |
| (_, None) | (CompileKindFallback::JustHost, _) => Ok(vec![CompileKind::Host]), | |
| (CompileKindFallback::BuildConfig, Some(build_target_config)) => { | |
| dedup(&build_target_config.values(gctx.cwd())?) | |
| } | |
| }; | |
| 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(), | |
| } | |
| } | |
| /// Adds the `--target` flag to the given [`ProcessBuilder`] if this is a | |
| /// non-host build. | |
| pub fn add_target_arg(&self, builder: &mut ProcessBuilder) { | |
| if let CompileKind::Target(target) = self { | |
| builder.arg("--target").arg(target.rustc_target()); | |
| if matches!(target, CompileTarget::Json { .. }) { | |
| builder.arg("-Zunstable-options"); | |
| } | |
| } | |
| } | |
| } | |
| 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.rustc_target()).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 enum CompileTarget { | |
| Tuple(InternedString), | |
| Json { | |
| short: InternedString, | |
| path: InternedString, | |
| }, | |
| } | |
| impl CompileTarget { | |
| pub fn new(name: &str, unstable_json: bool) -> CargoResult<CompileTarget> { | |
| let name = name.trim(); | |
| if name.is_empty() { | |
| bail!("target was empty"); | |
| } | |
| if !name.ends_with(".json") { | |
| return Ok(CompileTarget::Tuple(name.into())); | |
| } | |
| if !unstable_json { | |
| bail!( | |
| "`.json` target specs require -Zjson-target-spec to be added to the cargo invocation" | |
| ); | |
| } | |
| // 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 p = try_canonicalize(Path::new(name)) | |
| .with_context(|| format!("target path `{name}` is not a valid file"))?; | |
| let path = p | |
| .to_str() | |
| .ok_or_else(|| anyhow::format_err!("target path `{name}` is not valid unicode"))? | |
| .into(); | |
| let short = p.file_stem().unwrap().to_str().unwrap().into(); | |
| Ok(CompileTarget::Json { short, path }) | |
| } | |
| /// 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 { | |
| match self { | |
| CompileTarget::Tuple(name) => *name, | |
| CompileTarget::Json { path, .. } => *path, | |
| } | |
| } | |
| /// 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 { | |
| match self { | |
| CompileTarget::Tuple(name) => name, | |
| CompileTarget::Json { short, .. } => short, | |
| } | |
| } | |
| /// See [`CompileKind::fingerprint_hash`]. | |
| pub fn fingerprint_hash(&self) -> u64 { | |
| let mut hasher = StableHasher::new(); | |
| match self { | |
| CompileTarget::Tuple(name) => name.hash(&mut hasher), | |
| CompileTarget::Json { path, .. } => { | |
| // This may have some performance concerns, since it is called | |
| // fairly often. If that ever seems worth fixing, consider | |
| // embedding this in `CompileTarget`. | |
| match fs::read_to_string(path) { | |
| Ok(contents) => contents.hash(&mut hasher), | |
| Err(_) => path.hash(&mut hasher), | |
| } | |
| } | |
| } | |
| Hasher::finish(&hasher) | |
| } | |
| } |