blob: ab580d517543daec68eede91bf9eaf3aec32f9a2 [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Maps Rust targets to Chromium targets.
use std::collections::HashSet;
use std::iter::Iterator;
use cargo_platform::Cfg;
use once_cell::sync::OnceCell;
pub use cargo_platform::Platform;
/// A set of platforms: either the set of all platforms, or a finite set of
/// platform configurations.
#[derive(Clone, Debug)]
pub enum PlatformSet {
/// Matches any platform configuration.
All,
/// Matches a finite set of configurations.
Platforms(HashSet<Platform>),
}
impl PlatformSet {
/// A `PlatformSet` that matches no platforms. Useful as a starting point
/// when iteratively adding platforms with `add`.
pub fn empty() -> Self {
Self::Platforms(HashSet::new())
}
/// Add a single platform filter to `self`. The resulting set is superset of
/// the original. If `filter` is `None`, `self` becomes `PlatformSet::All`.
pub fn add(&mut self, filter: Option<Platform>) {
let set = match self {
// If the set is already all platforms, no need to add `filter`.
Self::All => return,
Self::Platforms(set) => set,
};
match filter {
None => *self = Self::All,
Some(platform) => {
set.insert(platform);
}
}
}
}
/// Whether `platform`, either an explicit rustc target triple or a `cfg(...)`
/// expression, matches any build target supported by Chromium.
pub fn matches_supported_target(platform: &Platform) -> bool {
match platform {
Platform::Name(name) => SUPPORTED_NAMED_PLATFORMS.iter().any(|p| *p == name),
Platform::Cfg(cfg_expr) => {
supported_os_cfgs().iter().any(|c| cfg_expr.matches(std::slice::from_ref(c)))
}
}
}
/// Remove terms containing unsupported platforms from `platform`, assuming
/// `matches_supported_target(&platform)` is true.
///
/// `platform` may contain a cfg(...) expression referencing platforms we don't
/// support: for example, `cfg(any(unix, target_os = "wasi"))`. However, such an
/// expression may still be true on configurations we do support.
///
/// `filter_unsupported_platform_terms` returns a new platform filter without
/// unsupported terms that is logically equivalent for the set of platforms we
/// do support, or `None` if the new filter would be true for all supported
/// platforms. This is useful when generating conditional expressions in build
/// files from such a cfg(...) expression.
///
/// Assumes `matches_supported_target(&platform)` is true. If not, the function
/// may return an invalid result or panic.
pub fn filter_unsupported_platform_terms(platform: Platform) -> Option<Platform> {
use ExprValidity::*;
match platform {
// If it's a target name, do nothing since `is_supported` is true.
x @ Platform::Name(_) => Some(x),
// Rewrite `cfg_expr` to be valid.
Platform::Cfg(mut cfg_expr) => match cfg_expr_filter_visitor(&mut cfg_expr) {
Valid => Some(Platform::Cfg(cfg_expr)),
AlwaysTrue => None,
AlwaysFalse => unreachable!("cfg would be false on all supported platforms"),
},
}
}
pub fn supported_os_cfgs_for_testing() -> &'static [Cfg] {
supported_os_cfgs()
}
pub fn supported_named_platforms_for_testing() -> &'static [&'static str] {
SUPPORTED_NAMED_PLATFORMS
}
// The validity of a cfg expr for our set of supported platforms.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ExprValidity {
// Contains only terms for supported platforms.
Valid,
// Contains terms for unsupported platforms, and would evaluate to true on
// all supported platforms.
AlwaysTrue,
// Contains terms for unsupported platforms, and would evaluate to false on
// all supported platforms.
AlwaysFalse,
}
// Rewrites `cfg_expr` to exclude unsupported terms. `ExprValidity::Valid` if
// the rewritten expr is valid: it contains no unsupported terms. Otherwise
// returns `AlwaysTrue` or `AlwaysFalse`.
fn cfg_expr_filter_visitor(cfg_expr: &mut cargo_platform::CfgExpr) -> ExprValidity {
use ExprValidity::*;
// Any logical operation on a set of valid expressions also yields a valid
// expression. If any of the set is invalid, we must apply special handling
// to remove the invalid term or decide the expression is always true or
// false.
match cfg_expr {
// A not(...) expr inverts the truth value of an invalid expr.
cargo_platform::CfgExpr::Not(sub_expr) => match cfg_expr_filter_visitor(sub_expr) {
Valid => Valid,
AlwaysTrue => AlwaysFalse,
AlwaysFalse => AlwaysTrue,
},
// An all(...) expr is always false if any term is always false. If any
// term is always true, it can be removed.
cargo_platform::CfgExpr::All(sub_exprs) => {
let mut validity = Valid;
sub_exprs.retain_mut(|e| match cfg_expr_filter_visitor(e) {
// Keep valid terms.
Valid => true,
// Remove always-true terms.
AlwaysTrue => false,
// If a term is always false, it doesn't matter; we will discard
// this expr.
AlwaysFalse => {
validity = AlwaysFalse;
true
}
});
if validity == AlwaysFalse {
AlwaysFalse
} else if sub_exprs.is_empty() {
// We only reach this if all the terms we removed were always
// true, in which case the expression is always true.
AlwaysTrue
} else if sub_exprs.len() == 1 {
// If only one term remains, we can simplify by replacing
// all(<term>) with <term>.
let new_expr = sub_exprs.drain(..).next().unwrap();
*cfg_expr = new_expr;
Valid
} else {
Valid
}
}
// An any(...) expr is always true if any term is always true. If any
// term is always false, it can be removed.
cargo_platform::CfgExpr::Any(sub_exprs) => {
let mut validity = Valid;
sub_exprs.retain_mut(|e| match cfg_expr_filter_visitor(e) {
// Keep valid terms.
Valid => true,
// If a term is always true, it doesn't matter; we will discard
// this expr.
AlwaysTrue => {
validity = AlwaysTrue;
true
}
// Remove always-false terms.
AlwaysFalse => false,
});
if validity == AlwaysTrue {
AlwaysTrue
} else if sub_exprs.is_empty() {
// We only reach this if all the terms we removed were always
// false, in which case the expression is always false.
AlwaysFalse
} else if sub_exprs.len() == 1 {
// If only one term remains, we can simplify by replacing
// any(<term>) with <term>.
let new_expr = sub_exprs.drain(..).next().unwrap();
*cfg_expr = new_expr;
Valid
} else {
Valid
}
}
cargo_platform::CfgExpr::Value(cfg) => {
if supported_os_cfgs().iter().any(|c| c == cfg) {
Valid
} else {
AlwaysFalse
}
}
}
}
fn supported_os_cfgs() -> &'static [Cfg] {
static CFG_SET: OnceCell<Vec<Cfg>> = OnceCell::new();
CFG_SET.get_or_init(|| {
let mut cfg_set: Vec<Cfg> = [
// Set of supported OSes for `cfg(target_os = ...)`.
"android", "darwin", "fuchsia", "ios", "linux", "windows",
]
.into_iter()
.map(|os| Cfg::KeyPair("target_os".to_string(), os.to_string()))
.collect();
cfg_set.extend(
// Alternative syntax `cfg(unix)` or `cfg(windows)`.
["unix", "windows"].into_iter().map(|os| Cfg::Name(os.to_string())),
);
cfg_set
})
}
static SUPPORTED_NAMED_PLATFORMS: &'static [&'static str] = &[
"i686-linux-android",
"x86_64-linux-android",
"armv7-linux-android",
"aarch64-linux-android",
"aarch64-fuchsia",
"x86_64-fuchsia",
"aarch64-apple-ios",
"armv7-apple-ios",
"x86_64-apple-ios",
"i386-apple-ios",
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
];