use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PlatformSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub container_platform: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub native: bool,
}
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Platform {
pub host: String,
pub target: Option<String>,
pub container_platform: Option<String>,
}
impl Platform {
pub fn compose(
host: impl Into<String>,
declared: Option<&PlatformSpec>,
triple_field: Option<&str>,
) -> Self {
let target = declared
.and_then(|d| d.target.clone())
.or_else(|| triple_field.map(str::to_string));
let container_platform = declared.and_then(|d| d.container_platform.clone());
Platform {
host: host.into(),
target,
container_platform,
}
}
pub fn is_cross_arch(&self) -> bool {
match &self.target {
None => false,
Some(t) => arch_of(t) != arch_of(&self.host),
}
}
pub fn container_is_foreign_arch(&self) -> bool {
match &self.container_platform {
None => false,
Some(p) => docker_platform_arch(p) != Some(arch_of(&self.host)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Resolution {
NativeCross,
CrossDocker,
Emulate { docker_platform: String },
Offload { target: String },
Skip { reason: String },
}
impl Resolution {
pub fn label(&self) -> String {
match self {
Resolution::NativeCross => "NativeCross (cargo-zigbuild)".into(),
Resolution::CrossDocker => "CrossDocker (cross-rs container)".into(),
Resolution::Emulate { docker_platform } => {
format!("Emulate (QEMU {docker_platform}, slow)")
}
Resolution::Offload { target } => format!("Offload (needs {target} runner)"),
Resolution::Skip { reason } => format!("Skip ({reason})"),
}
}
pub fn is_slow_or_unsatisfiable(&self) -> bool {
matches!(
self,
Resolution::Emulate { .. } | Resolution::Offload { .. } | Resolution::Skip { .. }
)
}
}
pub fn preflight_line(name: &str, platform: &Platform, resolution: &Resolution) -> String {
let what = match (&platform.target, &platform.container_platform) {
(Some(t), _) => format!("targets {t}"),
(None, Some(c)) => format!("builds {c} image"),
(None, None) => "host-native".to_string(),
};
format!(
"{name} · {what} · host {host} · resolution = {res}",
host = platform.host,
res = resolution.label(),
)
}
pub fn resolve(host: &str, target: Option<&str>, container_platform: Option<&str>) -> Resolution {
let host_arch = arch_of(host);
let foreign_target = target
.map(str::trim)
.filter(|t| !t.is_empty())
.filter(|t| arch_of(t) != host_arch);
if let Some(t) = foreign_target {
if host_native_crossable(host, t) {
return Resolution::NativeCross;
}
}
if let Some(p) = container_platform {
if docker_platform_arch(p) != Some(host_arch) {
return Resolution::Emulate {
docker_platform: p.to_string(),
};
}
}
if let Some(t) = foreign_target {
if container_platform.is_some() {
return Resolution::CrossDocker;
}
if is_known_arch(arch_of(t)) {
return Resolution::Offload {
target: t.to_string(),
};
}
return Resolution::Skip {
reason: format!(
"cannot build `{t}` on host `{host}`: target arch is unrecognized, \
no host-native cross path, and no toolchain container declared"
),
};
}
Resolution::NativeCross
}
pub fn resolve_placement(
host: &str,
target: Option<&str>,
container_platform: Option<&str>,
native: bool,
capability: &crate::nativecross::ToolAvailability,
) -> Resolution {
let derived = derive_placement(host, target, container_platform, native);
match capability_demotion(host, target, container_platform, &derived, capability) {
Some(gap) => Resolution::Offload { target: gap.target },
None => derived,
}
}
pub fn derive_placement(
host: &str,
target: Option<&str>,
container_platform: Option<&str>,
native: bool,
) -> Resolution {
if !native {
return resolve(host, target, container_platform);
}
let host_arch = arch_of(host);
let os_constrained = container_platform.is_none();
match target.map(str::trim).filter(|t| !t.is_empty()) {
Some(t) if arch_of(t) != host_arch || (os_constrained && foreign_os(host, t)) => {
Resolution::Offload {
target: t.to_string(),
}
}
_ => Resolution::NativeCross,
}
}
fn capability_gap(
host: &str,
target: Option<&str>,
resolution: &Resolution,
capability: &crate::nativecross::ToolAvailability,
) -> Option<crate::nativecross::CrossToolUnavailable> {
if !matches!(resolution, Resolution::NativeCross) {
return None;
}
let target = target.map(str::trim).filter(|t| !t.is_empty())?;
if !crate::nativecross::is_native_cross_target(host, target) {
return None;
}
crate::nativecross::select_cross_tool(host, Some(target), capability).err()
}
pub fn capability_demotion(
host: &str,
target: Option<&str>,
container_platform: Option<&str>,
derived: &Resolution,
capability: &crate::nativecross::ToolAvailability,
) -> Option<crate::nativecross::CrossToolUnavailable> {
if container_platform.is_some() {
return None;
}
capability_gap(host, target, derived, capability)
.filter(|gap| is_known_arch(arch_of(&gap.target)))
}
fn foreign_os(host: &str, target: &str) -> bool {
let (host_os, target_os) = (os_tag_of(host), os_tag_of(target));
host_os != "unknown" && target_os != "unknown" && host_os != target_os
}
pub fn host_native_crossable(host: &str, target: &str) -> bool {
match target_os(target) {
TargetOs::Linux => true,
TargetOs::Windows { msvc } => !msvc,
TargetOs::Darwin => matches!(target_os(host), TargetOs::Darwin),
TargetOs::Unknown => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TargetOs {
Linux,
Windows { msvc: bool },
Darwin,
Unknown,
}
pub fn target_is_msvc(triple: &str) -> bool {
triple.contains("windows") && triple.ends_with("msvc")
}
fn target_os(triple: &str) -> TargetOs {
if triple.contains("linux") {
TargetOs::Linux
} else if triple.contains("windows") {
TargetOs::Windows {
msvc: target_is_msvc(triple),
}
} else if triple.contains("darwin") || triple.contains("apple") {
TargetOs::Darwin
} else {
TargetOs::Unknown
}
}
pub fn os_tag_of(triple: &str) -> &'static str {
match target_os(triple) {
TargetOs::Linux => "linux",
TargetOs::Windows { .. } => "windows",
TargetOs::Darwin => "darwin",
TargetOs::Unknown => "unknown",
}
}
fn is_known_arch(arch: &str) -> bool {
matches!(
arch,
"x86_64" | "aarch64" | "arm64" | "x86" | "i686" | "arm" | "riscv64" | "powerpc64" | "s390x"
)
}
pub fn docker_platform_arch(platform: &str) -> Option<&'static str> {
let arch = platform.split('/').nth(1).unwrap_or(platform);
match arch {
"amd64" | "x86_64" => Some("x86_64"),
"arm64" | "aarch64" => Some("aarch64"),
"386" | "x86" => Some("x86"),
"arm" => Some("arm"),
_ => None,
}
}
pub fn detect_host_triple() -> String {
let arch = std::env::consts::ARCH;
let tail = match std::env::consts::OS {
"macos" => "apple-darwin",
"linux" => "unknown-linux-gnu",
"windows" => "pc-windows-msvc",
other => return format!("{arch}-unknown-{other}"),
};
format!("{arch}-{tail}")
}
pub fn gha_runner_arch(arch: &str) -> String {
match arch {
"x86_64" => "X64".into(),
"aarch64" | "arm64" => "ARM64".into(),
"x86" | "i686" => "X86".into(),
"arm" => "ARM".into(),
other => other.to_ascii_uppercase(),
}
}
pub fn arch_of(triple: &str) -> &str {
triple.split('-').next().unwrap_or(triple)
}
pub fn build_worker_mesh_tags(arch: &str, os: &str) -> Vec<String> {
let os_tag = format!("os:{os}");
let arch_tag = match arch {
"x86_64" | "x86" | "i686" | "amd64" => "arch:x86",
"aarch64" | "arm64" | "arm" => "arch:arm",
_ => return vec!["tag:build-worker".into(), os_tag],
};
vec!["tag:build-worker".into(), arch_tag.into(), os_tag]
}
#[cfg(test)]
mod build_worker_tag_tests {
use super::build_worker_mesh_tags;
#[test]
fn amd64_selects_x86_build_worker() {
assert_eq!(
build_worker_mesh_tags("x86_64", "linux"),
vec![
"tag:build-worker".to_string(),
"arch:x86".to_string(),
"os:linux".to_string()
]
);
}
#[test]
fn arm64_selects_arm_build_worker() {
assert_eq!(
build_worker_mesh_tags("aarch64", "linux"),
vec![
"tag:build-worker".to_string(),
"arch:arm".to_string(),
"os:linux".to_string()
]
);
}
#[test]
fn arm64_darwin_does_not_collide_with_arm64_linux() {
let darwin = build_worker_mesh_tags("aarch64", "darwin");
let linux = build_worker_mesh_tags("aarch64", "linux");
assert_ne!(darwin, linux);
assert!(darwin.contains(&"os:darwin".to_string()));
assert!(linux.contains(&"os:linux".to_string()));
}
#[test]
fn unknown_arch_falls_back_to_os_pinned_pool() {
assert_eq!(
build_worker_mesh_tags("riscv64", "linux"),
vec!["tag:build-worker".to_string(), "os:linux".to_string()]
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_host_triple_is_a_wellformed_triple() {
let t = detect_host_triple();
let segs: Vec<&str> = t.split('-').collect();
assert!(
segs.len() >= 3,
"host triple should have >=3 segments: {t:?}"
);
assert_eq!(segs[0], std::env::consts::ARCH, "arch segment leads: {t:?}");
}
#[test]
fn detect_host_triple_matches_known_os_tails() {
let t = detect_host_triple();
match std::env::consts::OS {
"macos" => assert!(t.ends_with("-apple-darwin"), "{t:?}"),
"linux" => assert!(t.ends_with("-unknown-linux-gnu"), "{t:?}"),
"windows" => assert!(t.ends_with("-pc-windows-msvc"), "{t:?}"),
_ => {} }
}
#[test]
fn gha_runner_arch_maps_the_rust_vocabulary() {
assert_eq!(gha_runner_arch("x86_64"), "X64");
assert_eq!(gha_runner_arch("aarch64"), "ARM64");
assert_eq!(gha_runner_arch("arm64"), "ARM64");
assert_eq!(gha_runner_arch("x86"), "X86");
assert_eq!(gha_runner_arch("i686"), "X86");
assert_eq!(gha_runner_arch("arm"), "ARM");
}
#[test]
fn gha_runner_arch_upcases_unknown_arch() {
assert_eq!(gha_runner_arch("riscv64"), "RISCV64");
}
#[test]
fn arch_of_takes_the_first_segment() {
assert_eq!(arch_of("aarch64-apple-darwin"), "aarch64");
assert_eq!(arch_of("x86_64-unknown-linux-musl"), "x86_64");
assert_eq!(arch_of("nodashes"), "nodashes");
}
#[test]
fn compose_prefers_declared_target_over_triple_field() {
let spec = PlatformSpec {
target: Some("x86_64-unknown-linux-musl".into()),
container_platform: None,
native: false,
};
let p = Platform::compose("aarch64-apple-darwin", Some(&spec), Some("legacy-triple"));
assert_eq!(p.target.as_deref(), Some("x86_64-unknown-linux-musl"));
assert_eq!(p.host, "aarch64-apple-darwin");
}
#[test]
fn compose_falls_back_to_triple_field_when_undeclared() {
let p = Platform::compose(
"aarch64-apple-darwin",
None,
Some("x86_64-unknown-linux-musl"),
);
assert_eq!(p.target.as_deref(), Some("x86_64-unknown-linux-musl"));
assert!(p.container_platform.is_none());
}
#[test]
fn compose_host_native_when_nothing_declared() {
let p = Platform::compose("aarch64-apple-darwin", None, None);
assert!(p.target.is_none());
assert!(!p.is_cross_arch(), "no target is never cross-arch");
}
#[test]
fn is_cross_arch_compares_arch_segment_only() {
let same_arch = Platform::compose(
"x86_64-unknown-linux-gnu",
None,
Some("x86_64-apple-darwin"),
);
assert!(!same_arch.is_cross_arch());
let cross = Platform::compose(
"aarch64-apple-darwin",
None,
Some("x86_64-unknown-linux-musl"),
);
assert!(cross.is_cross_arch());
}
#[test]
fn container_foreign_arch_catches_the_mesofact_case() {
let spec = PlatformSpec {
target: Some("x86_64-unknown-linux-musl".into()),
container_platform: Some("linux/amd64".into()),
native: false,
};
let p = Platform::compose("aarch64-apple-darwin", Some(&spec), None);
assert!(p.container_is_foreign_arch());
let native = Platform::compose("x86_64-unknown-linux-gnu", Some(&spec), None);
assert!(!native.container_is_foreign_arch());
}
#[test]
fn docker_platform_arch_normalizes_os_arch_variant() {
assert_eq!(docker_platform_arch("linux/amd64"), Some("x86_64"));
assert_eq!(docker_platform_arch("linux/arm64/v8"), Some("aarch64"));
assert_eq!(docker_platform_arch("arm64"), Some("aarch64"));
assert_eq!(docker_platform_arch("linux/riscv64"), None);
}
#[test]
fn platform_spec_round_trips_through_toml() {
let spec = PlatformSpec {
target: Some("x86_64-unknown-linux-musl".into()),
container_platform: Some("linux/amd64".into()),
native: false,
};
let toml = toml::to_string(&spec).unwrap();
let back: PlatformSpec = toml::from_str(&toml).unwrap();
assert_eq!(spec, back);
}
#[test]
fn empty_platform_spec_serializes_to_nothing() {
let spec = PlatformSpec::default();
assert_eq!(toml::to_string(&spec).unwrap(), "");
}
const ARM_MAC: &str = "aarch64-apple-darwin";
const X64_LINUX: &str = "x86_64-unknown-linux-gnu";
#[test]
fn resolve_no_target_is_native() {
assert_eq!(resolve(ARM_MAC, None, None), Resolution::NativeCross);
}
#[test]
fn resolve_host_arch_target_is_native() {
assert_eq!(
resolve(ARM_MAC, Some("aarch64-unknown-linux-musl"), None),
Resolution::NativeCross
);
}
#[test]
fn resolve_foreign_linux_target_cross_compiles_natively() {
assert_eq!(
resolve(ARM_MAC, Some("x86_64-unknown-linux-musl"), None),
Resolution::NativeCross
);
}
#[test]
fn resolve_crossable_target_wins_over_foreign_container() {
assert_eq!(
resolve(
ARM_MAC,
Some("x86_64-unknown-linux-musl"),
Some("linux/amd64")
),
Resolution::NativeCross
);
}
#[test]
fn resolve_foreign_container_with_non_crossable_emulates() {
let r = resolve(ARM_MAC, Some("x86_64-pc-windows-msvc"), Some("linux/amd64"));
assert_eq!(
r,
Resolution::Emulate {
docker_platform: "linux/amd64".into()
}
);
}
#[test]
fn resolve_multiarch_image_build_with_no_target_emulates() {
assert_eq!(
resolve(ARM_MAC, None, Some("linux/amd64")),
Resolution::Emulate {
docker_platform: "linux/amd64".into()
}
);
}
#[test]
fn resolve_host_arch_container_is_not_emulation() {
assert_eq!(
resolve(ARM_MAC, None, Some("linux/arm64")),
Resolution::NativeCross
);
}
#[test]
fn resolve_darwin_target_from_linux_host_offloads() {
assert_eq!(
resolve(X64_LINUX, Some("aarch64-apple-darwin"), None),
Resolution::Offload {
target: "aarch64-apple-darwin".into()
}
);
}
#[test]
fn resolve_non_crossable_with_host_arch_container_uses_cross_docker() {
assert_eq!(
resolve(X64_LINUX, Some("aarch64-apple-darwin"), Some("linux/amd64")),
Resolution::CrossDocker
);
}
#[test]
fn resolve_windows_msvc_off_linux_offloads_but_gnu_cross_compiles() {
assert_eq!(
resolve(X64_LINUX, Some("aarch64-pc-windows-msvc"), None),
Resolution::Offload {
target: "aarch64-pc-windows-msvc".into()
}
);
assert_eq!(
resolve(X64_LINUX, Some("aarch64-pc-windows-gnu"), None),
Resolution::NativeCross
);
}
#[test]
fn resolve_unknown_foreign_arch_with_no_container_skips() {
let r = resolve(X64_LINUX, Some("sparc64-unknown-linux-gnu"), None);
assert_eq!(r, Resolution::NativeCross);
let skip = resolve(X64_LINUX, Some("mos-unknown-none"), None);
match skip {
Resolution::Skip { reason } => {
assert!(reason.contains("mos-unknown-none"), "reason: {reason}");
}
other => panic!("expected Skip, got {other:?}"),
}
}
#[test]
fn resolve_is_total_over_the_class_space() {
let hosts = [ARM_MAC, X64_LINUX, "x86_64-pc-windows-msvc"];
let targets = [
None,
Some("x86_64-unknown-linux-musl"),
Some("aarch64-unknown-linux-gnu"),
Some("aarch64-apple-darwin"),
Some("x86_64-pc-windows-msvc"),
Some("mos-unknown-none"),
Some(""),
];
let containers = [
None,
Some("linux/amd64"),
Some("linux/arm64"),
Some("linux/riscv64"),
];
for h in hosts {
for t in targets {
for c in containers {
let _r = resolve(h, t, c);
}
}
}
}
#[test]
fn native_false_defers_to_the_full_decision_table() {
assert_eq!(
derive_placement(ARM_MAC, Some("x86_64-unknown-linux-musl"), None, false),
resolve(ARM_MAC, Some("x86_64-unknown-linux-musl"), None),
);
assert_eq!(
derive_placement(ARM_MAC, Some("x86_64-pc-windows-msvc"), Some("linux/amd64"), false),
resolve(ARM_MAC, Some("x86_64-pc-windows-msvc"), Some("linux/amd64")),
);
}
#[test]
fn native_foreign_arch_offloads_instead_of_cross_compiling() {
assert_eq!(
derive_placement(ARM_MAC, Some("x86_64-unknown-linux-musl"), None, true),
Resolution::Offload {
target: "x86_64-unknown-linux-musl".into()
}
);
}
#[test]
fn native_forces_past_the_foreign_container_emulate_branch() {
assert_eq!(
derive_placement(
ARM_MAC,
Some("x86_64-unknown-linux-musl"),
Some("linux/amd64"),
true
),
Resolution::Offload {
target: "x86_64-unknown-linux-musl".into()
}
);
}
#[test]
fn native_host_platform_target_builds_locally() {
assert_eq!(
derive_placement(ARM_MAC, Some("aarch64-apple-darwin"), None, true),
Resolution::NativeCross
);
assert_eq!(
derive_placement(X64_LINUX, Some("x86_64-unknown-linux-musl"), None, true),
Resolution::NativeCross
);
}
#[test]
fn native_same_arch_foreign_os_offloads() {
assert_eq!(
derive_placement(ARM_MAC, Some("aarch64-unknown-linux-gnu"), None, true),
Resolution::Offload {
target: "aarch64-unknown-linux-gnu".into()
}
);
assert_eq!(
derive_placement(ARM_MAC, Some("aarch64-unknown-linux-musl"), None, true),
Resolution::Offload {
target: "aarch64-unknown-linux-musl".into()
}
);
assert_eq!(
derive_placement(
"aarch64-unknown-linux-gnu",
Some("aarch64-apple-darwin"),
None,
true
),
Resolution::Offload {
target: "aarch64-apple-darwin".into()
}
);
}
#[test]
fn native_container_step_is_not_os_constrained() {
assert_eq!(
derive_placement(
ARM_MAC,
Some("aarch64-unknown-linux-musl"),
Some("linux/arm64"),
true
),
Resolution::NativeCross
);
}
#[test]
fn native_unknown_os_target_does_not_offload_on_os_alone() {
assert_eq!(
derive_placement(X64_LINUX, Some("x86_64-unknown-none"), None, true),
Resolution::NativeCross
);
}
#[test]
fn native_absent_or_empty_target_builds_locally() {
assert_eq!(
derive_placement(ARM_MAC, None, None, true),
Resolution::NativeCross
);
assert_eq!(
derive_placement(ARM_MAC, Some(" "), None, true),
Resolution::NativeCross
);
}
use crate::nativecross::ToolAvailability;
const NO_ZIG: ToolAvailability = ToolAvailability {
zigbuild: false,
musl_cross: false,
};
#[test]
fn capability_full_is_pure_derivation() {
for (host, target, container, native) in [
(ARM_MAC, Some("x86_64-unknown-linux-musl"), None, false),
(ARM_MAC, Some("x86_64-unknown-linux-gnu"), None, false),
(ARM_MAC, Some("aarch64-unknown-linux-gnu"), None, false),
(ARM_MAC, Some("x86_64-unknown-linux-musl"), None, true),
(ARM_MAC, Some("aarch64-apple-darwin"), None, true),
(X64_LINUX, Some("aarch64-apple-darwin"), None, false),
(ARM_MAC, None, None, false),
(
ARM_MAC,
Some("x86_64-pc-windows-msvc"),
Some("linux/amd64"),
false,
),
] {
assert_eq!(
resolve_placement(host, target, container, native, &ToolAvailability::FULL),
derive_placement(host, target, container, native),
"FULL capability must not perturb {host} → {target:?}"
);
}
}
#[test]
fn missing_zigbuild_demotes_native_cross_to_offload() {
for target in ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] {
assert_eq!(
derive_placement(ARM_MAC, Some(target), None, false),
Resolution::NativeCross,
"{target} derives NativeCross (that is the defect's premise)"
);
assert_eq!(
resolve_placement(ARM_MAC, Some(target), None, false, &NO_ZIG),
Resolution::Offload {
target: target.into()
}
);
}
}
#[test]
fn musl_cross_fallback_is_not_a_capability_gap() {
let musl_only = ToolAvailability {
zigbuild: false,
musl_cross: true,
};
assert_eq!(
resolve_placement(
ARM_MAC,
Some("x86_64-unknown-linux-musl"),
None,
false,
&musl_only
),
Resolution::NativeCross
);
assert_eq!(
resolve_placement(
ARM_MAC,
Some("x86_64-unknown-linux-gnu"),
None,
false,
&musl_only
),
Resolution::Offload {
target: "x86_64-unknown-linux-gnu".into()
}
);
}
#[test]
fn host_platform_target_never_demotes() {
for (host, target) in [
(ARM_MAC, Some("aarch64-apple-darwin")),
(ARM_MAC, None),
(ARM_MAC, Some(" ")),
(X64_LINUX, Some("x86_64-unknown-linux-gnu")),
(ARM_MAC, Some("x86_64-apple-darwin")),
] {
assert_eq!(
resolve_placement(host, target, None, false, &NO_ZIG),
Resolution::NativeCross,
"{host} → {target:?} must not demote"
);
}
}
#[test]
fn container_step_is_never_demoted() {
assert_eq!(
resolve_placement(
ARM_MAC,
Some("aarch64-unknown-linux-musl"),
Some("linux/arm64"),
true,
&NO_ZIG
),
Resolution::NativeCross
);
}
#[test]
fn other_tiers_are_not_demotable() {
assert!(matches!(
resolve_placement(
ARM_MAC,
Some("x86_64-pc-windows-msvc"),
Some("linux/amd64"),
false,
&NO_ZIG
),
Resolution::Emulate { .. }
));
assert_eq!(
resolve_placement(X64_LINUX, Some("aarch64-apple-darwin"), None, false, &NO_ZIG),
Resolution::Offload {
target: "aarch64-apple-darwin".into()
}
);
assert!(matches!(
resolve_placement(ARM_MAC, Some("mos-unknown-none"), None, false, &NO_ZIG),
Resolution::Skip { .. }
));
}
#[test]
fn capability_demotion_names_the_missing_tool_and_matches_the_verdict() {
let target = "x86_64-unknown-linux-gnu";
let derived = derive_placement(ARM_MAC, Some(target), None, false);
let gap = capability_demotion(ARM_MAC, Some(target), None, &derived, &NO_ZIG)
.expect("a zigbuild-less mac cannot carry the gnu leg");
assert_eq!(gap.target, target);
assert_eq!(gap.preferred, crate::nativecross::CrossTool::CargoZigbuild);
let rendered = gap.to_string();
assert!(
rendered.contains("cargo install cargo-zigbuild"),
"the demotion must name the install command, got: {rendered}"
);
assert_eq!(
resolve_placement(ARM_MAC, Some(target), None, false, &NO_ZIG),
Resolution::Offload {
target: target.into()
}
);
assert!(
capability_demotion(
ARM_MAC,
Some(target),
None,
&derived,
&ToolAvailability::FULL
)
.is_none()
);
}
#[test]
fn unknown_arch_is_not_offloaded_into_the_void() {
assert_eq!(
resolve_placement(ARM_MAC, Some("powerpc64-unknown-linux-gnu"), None, false, &NO_ZIG),
Resolution::Offload {
target: "powerpc64-unknown-linux-gnu".into()
}
);
assert_eq!(
resolve_placement(ARM_MAC, Some("fictional-unknown-linux-gnu"), None, false, &NO_ZIG),
Resolution::NativeCross
);
}
#[test]
fn native_spec_round_trips_through_toml() {
let spec = PlatformSpec {
target: Some("x86_64-unknown-linux-musl".into()),
container_platform: None,
native: true,
};
let toml = toml::to_string(&spec).unwrap();
assert!(toml.contains("native = true"), "toml: {toml:?}");
let back: PlatformSpec = toml::from_str(&toml).unwrap();
assert_eq!(spec, back);
assert!(back.native);
}
#[test]
fn resolution_labels_carry_the_cost_parenthetical() {
assert_eq!(
Resolution::NativeCross.label(),
"NativeCross (cargo-zigbuild)"
);
assert_eq!(
Resolution::CrossDocker.label(),
"CrossDocker (cross-rs container)"
);
assert_eq!(
Resolution::Emulate {
docker_platform: "linux/amd64".into()
}
.label(),
"Emulate (QEMU linux/amd64, slow)"
);
assert_eq!(
Resolution::Offload {
target: "aarch64-apple-darwin".into()
}
.label(),
"Offload (needs aarch64-apple-darwin runner)"
);
}
#[test]
fn is_slow_or_unsatisfiable_partitions_the_tiers() {
assert!(!Resolution::NativeCross.is_slow_or_unsatisfiable());
assert!(!Resolution::CrossDocker.is_slow_or_unsatisfiable());
assert!(Resolution::Emulate {
docker_platform: "linux/amd64".into()
}
.is_slow_or_unsatisfiable());
assert!(Resolution::Offload { target: "x".into() }.is_slow_or_unsatisfiable());
assert!(Resolution::Skip { reason: "x".into() }.is_slow_or_unsatisfiable());
}
#[test]
fn preflight_line_matches_w222_format() {
let p = Platform::compose(ARM_MAC, None, Some("x86_64-unknown-linux-musl"));
let r = resolve(
&p.host,
p.target.as_deref(),
p.container_platform.as_deref(),
);
let line = preflight_line("mesofact-dev-build", &p, &r);
assert_eq!(
line,
"mesofact-dev-build · targets x86_64-unknown-linux-musl · \
host aarch64-apple-darwin · resolution = NativeCross (cargo-zigbuild)"
);
}
#[test]
fn preflight_line_describes_image_builds_and_host_native() {
let img = Platform {
host: ARM_MAC.into(),
target: None,
container_platform: Some("linux/amd64".into()),
};
let r = resolve(&img.host, None, img.container_platform.as_deref());
assert!(preflight_line("image-yah-base", &img, &r).contains("builds linux/amd64 image"),);
let native = Platform::compose(ARM_MAC, None, None);
let rn = resolve(&native.host, None, None);
assert!(preflight_line("check", &native, &rn).contains("· host-native ·"));
}
#[test]
fn host_native_crossable_spec() {
assert!(host_native_crossable(ARM_MAC, "x86_64-unknown-linux-musl"));
assert!(host_native_crossable(
X64_LINUX,
"aarch64-unknown-linux-gnu"
));
assert!(host_native_crossable(X64_LINUX, "x86_64-pc-windows-gnu"));
assert!(!host_native_crossable(X64_LINUX, "x86_64-pc-windows-msvc"));
assert!(host_native_crossable(ARM_MAC, "x86_64-apple-darwin"));
assert!(!host_native_crossable(X64_LINUX, "aarch64-apple-darwin"));
assert!(!host_native_crossable(X64_LINUX, "mos-unknown-none"));
}
#[test]
fn host_arch_round_trips_through_gha_vocabulary() {
let host = detect_host_triple();
let gha = gha_runner_arch(arch_of(&host));
assert!(!gha.is_empty(), "host {host:?} → gha arch {gha:?}");
}
}