use crate::compat::{format, String};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TargetSpec {
pub profile: &'static str,
pub triple: &'static str,
}
impl TargetSpec {
pub const fn current() -> Self {
Self { profile: crate::platform::profile::profile_name(), triple: current_triple() }
}
#[cfg(not(widgets_unstripped))]
pub const fn current_stripped() -> Self {
Self { profile: "unknown", triple: current_triple() }
}
}
const fn current_triple() -> &'static str {
if cfg!(target_triple = "aarch64-apple-darwin") {
return "aarch64-apple-darwin";
}
if cfg!(target_triple = "x86_64-apple-darwin") {
return "x86_64-apple-darwin";
}
if cfg!(target_triple = "x86_64-unknown-linux-gnu") {
return "x86_64-unknown-linux-gnu";
}
if cfg!(target_triple = "aarch64-unknown-linux-gnu") {
return "aarch64-unknown-linux-gnu";
}
if cfg!(target_triple = "x86_64-pc-windows-msvc") {
return "x86_64-pc-windows-msvc";
}
if cfg!(target_triple = "aarch64-apple-ios") {
return "aarch64-apple-ios";
}
if cfg!(target_triple = "aarch64-apple-ios-sim") {
return "aarch64-apple-ios-sim";
}
if cfg!(target_triple = "aarch64-unknown-linux-ohos") {
return "aarch64-unknown-linux-ohos";
}
if cfg!(target_triple = "armv7-unknown-linux-ohos") {
return "armv7-unknown-linux-ohos";
}
if cfg!(target_triple = "x86_64-unknown-linux-ohos") {
return "x86_64-unknown-linux-ohos";
}
if cfg!(target_triple = "aarch64-linux-android") {
return "aarch64-linux-android";
}
if cfg!(target_triple = "x86_64-linux-android") {
return "x86_64-linux-android";
}
if cfg!(target_triple = "wasm32-unknown-unknown") {
return "wasm32-unknown-unknown";
}
"unknown"
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum VerificationLevel {
Unknown,
Describable,
BuiltVerified,
}
impl VerificationLevel {
pub const fn token(self) -> &'static str {
match self {
Self::BuiltVerified => "built-verified",
Self::Describable => "describable",
Self::Unknown => "unknown",
}
}
pub const fn from_token(token: &str) -> Option<Self> {
match token.as_bytes() {
b"built-verified" => Some(Self::BuiltVerified),
b"describable" => Some(Self::Describable),
b"unknown" => Some(Self::Unknown),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Availability {
Available {
level: VerificationLevel,
},
Unsupported {
reason: &'static str,
},
Unverified {
reason: &'static str,
},
}
impl Availability {
pub const fn is_usable(self) -> bool {
matches!(self, Self::Available { .. })
}
pub const fn is_unsupported(self) -> bool {
matches!(self, Self::Unsupported { .. })
}
pub const fn level(self) -> Option<VerificationLevel> {
match self {
Self::Available { level } => Some(level),
Self::Unsupported { .. } | Self::Unverified { .. } => None,
}
}
pub fn describe(self) -> String {
match self {
Self::Available { level } => format!("available ({})", level.token()),
Self::Unsupported { reason } => format!("unsupported: {reason}"),
Self::Unverified { reason } => format!("unverified: {reason}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TargetProbe {
pub target: TargetSpec,
pub availability: Availability,
}
impl TargetProbe {
pub fn describe(&self) -> String {
format!(
"{} on {} ({}): {}",
"capability",
self.target.profile,
self.target.triple,
self.availability.describe()
)
}
pub const fn is_conclusive(self) -> bool {
matches!(
self.availability,
Availability::Available { level: VerificationLevel::BuiltVerified }
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurrentBuildVerdict {
Usable,
Unbuildable,
Unknown,
}
pub fn probe_capability(
name: &str,
target: TargetSpec,
verdict: CurrentBuildVerdict,
) -> TargetProbe {
let current = TargetSpec::current();
let availability = match verdict {
CurrentBuildVerdict::Usable => {
if current.profile == target.profile && current.triple == target.triple {
Availability::Available { level: VerificationLevel::BuiltVerified }
} else {
Availability::Available { level: VerificationLevel::Describable }
}
}
CurrentBuildVerdict::Unbuildable => Availability::Unsupported {
reason: "the running build's constructors gate this capability out of the target's \
profile, and no verified target build enables it",
},
CurrentBuildVerdict::Unknown => Availability::Unverified {
reason: "no constructor or capability is registered for this name in the running \
build, and the target has not been built with it",
},
};
let _ = name;
TargetProbe { target, availability }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_spec_is_concrete_on_a_device_profile() {
let spec = TargetSpec::current();
assert_ne!(spec.profile, "unknown", "a device profile must name itself");
assert_ne!(spec.triple, "unknown", "build.rs must have captured the TARGET triple");
assert!(spec.triple.split('-').count() >= 3, "`{}` is not a target triple", spec.triple);
}
#[test]
fn the_captured_triple_agrees_with_the_reader() {
assert_eq!(
cfg!(target_triple_captured),
current_triple() != "unknown",
"build.rs captured `{}` but the reader answered `{}`; the two triple lists disagree",
if cfg!(target_triple_captured) { "a declared triple" } else { "an undeclared triple" },
current_triple()
);
}
#[test]
fn verification_levels_round_trip() {
for level in [
VerificationLevel::BuiltVerified,
VerificationLevel::Describable,
VerificationLevel::Unknown,
] {
assert_eq!(
VerificationLevel::from_token(level.token()),
Some(level),
"`{}` did not round-trip",
level.token()
);
}
}
#[test]
fn unknown_level_token_is_rejected() {
assert_eq!(VerificationLevel::from_token("maybe"), None);
assert_eq!(VerificationLevel::from_token(""), None);
}
#[test]
fn only_verified_availability_is_usable() {
assert!(Availability::Available { level: VerificationLevel::BuiltVerified }.is_usable());
assert!(Availability::Available { level: VerificationLevel::Describable }.is_usable());
assert!(!Availability::Unverified { reason: "not built" }.is_usable());
assert!(!Availability::Unsupported { reason: "no gpu" }.is_usable());
}
#[test]
fn verification_levels_are_ordered_strongest_first() {
assert!(VerificationLevel::BuiltVerified > VerificationLevel::Describable);
assert!(VerificationLevel::Describable > VerificationLevel::Unknown);
}
#[test]
fn every_availability_describes_itself() {
assert!(Availability::Available { level: VerificationLevel::Describable }
.describe()
.contains("describable"));
assert!(Availability::Unsupported { reason: "no gpu" }.describe().contains("no gpu"));
assert!(Availability::Unverified { reason: "not built" }.describe().contains("not built"));
}
#[test]
fn a_usable_capability_on_the_current_target_is_built_verified() {
let probe = probe_capability("button", TargetSpec::current(), CurrentBuildVerdict::Usable);
assert!(probe.is_conclusive());
assert_eq!(probe.availability.level(), Some(VerificationLevel::BuiltVerified));
}
#[test]
fn a_usable_capability_on_a_different_target_is_only_describable() {
let other = TargetSpec { profile: "mini", triple: "thumbv7em-none-eabihf" };
let probe = probe_capability("button", other, CurrentBuildVerdict::Usable);
assert!(!probe.is_conclusive());
assert_eq!(probe.availability.level(), Some(VerificationLevel::Describable));
assert!(probe.availability.is_usable(), "a describable answer is still usable");
}
#[test]
fn an_unbuildable_capability_is_unsupported() {
let probe = probe_capability(
"video_codec",
TargetSpec::current(),
CurrentBuildVerdict::Unbuildable,
);
assert!(probe.availability.is_unsupported());
assert!(!probe.availability.is_usable());
assert!(probe.describe().contains("unsupported"));
}
#[test]
fn an_unknown_name_is_unverified_not_unsupported() {
let probe = probe_capability(
"no_such_control",
TargetSpec::current(),
CurrentBuildVerdict::Unknown,
);
assert!(!probe.availability.is_unsupported());
assert!(!probe.availability.is_usable());
assert!(probe.describe().contains("unverified"));
}
#[test]
fn every_probe_names_its_target() {
let probe = probe_capability("button", TargetSpec::current(), CurrentBuildVerdict::Usable);
let line = probe.describe();
assert!(line.contains(probe.target.profile), "{line}");
assert!(line.contains(probe.target.triple), "{line}");
}
}