Skip to main content

crossbuild_core/
platform.rs

1//! Host and target platform detection, capability assessment, and toolchain routing.
2
3use std::collections::BTreeMap;
4
5use crate::model::{
6    Abi, HostDetectError, HostInfo, LinkerHint, OperatingSystem, SysrootHint,
7    TargetFamily, TargetInfo, TargetSupport, TargetTriple, ToolchainHint,
8};
9
10/// Detects the host platform information.
11pub fn detect_host() -> Result<HostInfo, HostDetectError> {
12    HostInfo::detect()
13}
14
15/// Assesses target support tier and provides toolchain hints.
16pub fn assess_target(target: &TargetTriple, host: &HostInfo) -> TargetInfo {
17    let is_native = target.triple == host.host_triple.triple;
18    let requires_cross = !is_native;
19
20    let supported = determine_support_tier(target);
21    let toolchain_hint = suggest_toolchain_provider(target, host, &supported);
22    let sysroot_hint = suggest_sysroot_provider(target, host, &supported);
23    let linker_hint = suggest_linker(target, host);
24
25    TargetInfo {
26        triple: target.clone(),
27        is_native,
28        requires_cross,
29        supported,
30        toolchain_hint,
31        sysroot_hint,
32        linker_hint,
33    }
34}
35
36/// Determines the Rust support tier for a target.
37fn determine_support_tier(target: &TargetTriple) -> TargetSupport {
38    // Tier 1 targets (guaranteed to build and pass tests)
39    const TIER1: &[&str] = &[
40        "x86_64-unknown-linux-gnu",
41        "x86_64-pc-windows-msvc",
42        "aarch64-unknown-linux-gnu",
43        "aarch64-apple-darwin",
44        "x86_64-apple-darwin",
45    ];
46
47    // Tier 2 targets (guaranteed to build, tests may not run)
48    const TIER2: &[&str] = &[
49        "x86_64-unknown-linux-musl",
50        "aarch64-unknown-linux-musl",
51        "x86_64-unknown-freebsd",
52        "aarch64-unknown-freebsd",
53        "x86_64-unknown-netbsd",
54        "aarch64-unknown-netbsd",
55        "x86_64-unknown-openbsd",
56        "x86_64-unknown-illumos",
57        "powerpc64le-unknown-linux-gnu",
58        "s390x-unknown-linux-gnu",
59        "riscv64gc-unknown-linux-gnu",
60        "x86_64-pc-windows-gnu",
61        "i686-pc-windows-msvc",
62        "i686-pc-windows-gnu",
63        "aarch64-pc-windows-msvc",
64        "wasm32-wasi",
65        "wasm32-unknown-unknown",
66        "wasm32-unknown-emscripten",
67    ];
68
69    if TIER1.contains(&target.triple.as_str()) {
70        TargetSupport::Tier1
71    } else if TIER2.contains(&target.triple.as_str()) {
72        TargetSupport::Tier2
73    } else if is_known_tier3(target) {
74        TargetSupport::Tier3
75    } else {
76        TargetSupport::Unsupported
77    }
78}
79
80/// Checks if a target is a known tier 3 target.
81fn is_known_tier3(target: &TargetTriple) -> bool {
82    let triple = target.triple.as_str();
83
84    // Known tier 3 patterns
85    const TIER3_PATTERNS: &[&str] = &[
86        "mips",
87        "mips64",
88        "sparc",
89        "sparc64",
90        "hexagon",
91        "avr",
92        "xtensa",
93        "csky",
94        "loongarch",
95        "wasm64",
96        "nvptx",
97        "spirv",
98        "bpf",
99    ];
100
101    TIER3_PATTERNS.iter().any(|p| triple.contains(p))
102}
103
104/// Suggests the best toolchain provider for a target.
105fn suggest_toolchain_provider(
106    target: &TargetTriple,
107    host: &HostInfo,
108    support: &TargetSupport,
109) -> ToolchainHint {
110    // Native builds use host toolchain
111    if target.triple == host.host_triple.triple {
112        return ToolchainHint::Rustup;
113    }
114
115    match support {
116        TargetSupport::Tier1 | TargetSupport::Tier2 => {
117            // For tier 1/2, prefer rustup if available
118            if rustup_target_available(target) {
119                ToolchainHint::Rustup
120            } else if target.is_wasm() {
121                ToolchainHint::Rustup
122            } else if target.is_bare_metal() {
123                ToolchainHint::Rustup
124            } else {
125                ToolchainHint::Zig
126            }
127        }
128        TargetSupport::Tier3 => ToolchainHint::Zig,
129        TargetSupport::Unsupported => ToolchainHint::Custom,
130    }
131}
132
133/// Checks if a target is available via rustup.
134pub fn rustup_target_available(target: &TargetTriple) -> bool {
135    // Check common rustup targets
136    const RUSTUP_TARGETS: &[&str] = &[
137        "x86_64-unknown-linux-gnu",
138        "x86_64-unknown-linux-musl",
139        "aarch64-unknown-linux-gnu",
140        "aarch64-unknown-linux-musl",
141        "x86_64-pc-windows-msvc",
142        "x86_64-pc-windows-gnu",
143        "aarch64-pc-windows-msvc",
144        "i686-pc-windows-msvc",
145        "i686-pc-windows-gnu",
146        "x86_64-apple-darwin",
147        "aarch64-apple-darwin",
148        "wasm32-wasi",
149        "wasm32-unknown-unknown",
150        "wasm32-unknown-emscripten",
151        "x86_64-unknown-freebsd",
152        "aarch64-unknown-freebsd",
153        "powerpc64le-unknown-linux-gnu",
154        "s390x-unknown-linux-gnu",
155        "riscv64gc-unknown-linux-gnu",
156    ];
157
158    RUSTUP_TARGETS.contains(&target.triple.as_str())
159}
160
161/// Suggests the best sysroot provider for a target.
162fn suggest_sysroot_provider(
163    target: &TargetTriple,
164    host: &HostInfo,
165    support: &TargetSupport,
166) -> SysrootHint {
167    if target.triple == host.host_triple.triple {
168        return SysrootHint::None;
169    }
170
171    match support {
172        TargetSupport::Tier1 | TargetSupport::Tier2 => {
173            if target.is_wasm() {
174                SysrootHint::None // wasm doesn't need sysroot
175            } else if rustup_target_available(target) {
176                SysrootHint::Rustup
177            } else if target.is_bare_metal() {
178                SysrootHint::None
179            } else {
180                SysrootHint::Zig
181            }
182        }
183        TargetSupport::Tier3 => SysrootHint::Zig,
184        TargetSupport::Unsupported => SysrootHint::Custom,
185    }
186}
187
188/// Suggests the best linker for a target.
189fn suggest_linker(target: &TargetTriple, host: &HostInfo) -> LinkerHint {
190    match (target.family(), target.os.clone(), target.abi.clone()) {
191        // Windows targets
192        (TargetFamily::Windows, _, Abi::Msvc) => LinkerHint::MSVC,
193        (TargetFamily::Windows, _, Abi::Gnu) => LinkerHint::Lld,
194
195        // Linux targets
196        (TargetFamily::Linux, OperatingSystem::Linux, Abi::Gnu) => {
197            if host.os == OperatingSystem::Linux {
198                LinkerHint::SystemDefault
199            } else {
200                LinkerHint::Lld
201            }
202        }
203        (TargetFamily::Linux, OperatingSystem::Linux, Abi::Musl) => LinkerHint::Lld,
204        (TargetFamily::Linux, OperatingSystem::Android, _) => LinkerHint::Lld,
205
206        // macOS targets
207        (TargetFamily::MacOs, OperatingSystem::MacOs, _) => LinkerHint::Lld,
208        (TargetFamily::MacOs, OperatingSystem::Ios | OperatingSystem::TvOS | OperatingSystem::WatchOS, _) => {
209            LinkerHint::Lld
210        }
211
212        // WASM targets
213        (TargetFamily::Wasm, _, _) => LinkerHint::Lld,
214
215        // BSD targets
216        (TargetFamily::Other, OperatingSystem::FreeBSD, _) => LinkerHint::Lld,
217        (TargetFamily::Other, OperatingSystem::NetBSD, _) => LinkerHint::Lld,
218        (TargetFamily::Other, OperatingSystem::OpenBSD, _) => LinkerHint::Lld,
219
220        // Bare metal
221        (TargetFamily::BareMetal, _, _) => LinkerHint::Lld,
222
223        // Default to LLD for cross-compilation
224        _ => LinkerHint::Lld,
225    }
226}
227
228/// Capability matrix for host/target combinations.
229#[derive(Debug, Clone)]
230pub struct CapabilityMatrix {
231    pub host: HostInfo,
232    pub targets: BTreeMap<TargetTriple, TargetInfo>,
233}
234
235impl CapabilityMatrix {
236    /// Creates a new capability matrix for the current host.
237    pub fn new() -> Result<Self, HostDetectError> {
238        let host = detect_host()?;
239        Ok(Self {
240            host,
241            targets: BTreeMap::new(),
242        })
243    }
244
245    /// Assesses a target and adds it to the matrix.
246    pub fn assess(&mut self, target: &TargetTriple) -> &TargetInfo {
247        let info = assess_target(target, &self.host);
248        self.targets.insert(target.clone(), info);
249        self.targets.get(target).unwrap()
250    }
251
252    /// Gets the assessment for a target, computing it if necessary.
253    pub fn get(&mut self, target: &TargetTriple) -> &TargetInfo {
254        if !self.targets.contains_key(target) {
255            self.assess(target);
256        }
257        self.targets.get(target).unwrap()
258    }
259
260    /// Returns all assessed targets.
261    pub fn all_targets(&self) -> &BTreeMap<TargetTriple, TargetInfo> {
262        &self.targets
263    }
264
265    /// Checks if a target can be built natively.
266    pub fn is_native(&self, target: &TargetTriple) -> bool {
267        target.triple == self.host.host_triple.triple
268    }
269
270    /// Checks if cross-compilation is required.
271    pub fn requires_cross(&self, target: &TargetTriple) -> bool {
272        !self.is_native(target)
273    }
274
275    /// Gets the recommended toolchain provider for a target.
276    pub fn toolchain_hint(&mut self, target: &TargetTriple) -> ToolchainHint {
277        self.get(target).toolchain_hint
278    }
279
280    /// Gets the recommended sysroot provider for a target.
281    pub fn sysroot_hint(&mut self, target: &TargetTriple) -> SysrootHint {
282        self.get(target).sysroot_hint
283    }
284
285    /// Gets the recommended linker for a target.
286    pub fn linker_hint(&mut self, target: &TargetTriple) -> LinkerHint {
287        self.get(target).linker_hint
288    }
289}
290
291impl Default for CapabilityMatrix {
292    fn default() -> Self {
293        Self::new().expect("failed to detect host")
294    }
295}
296
297/// Known target triples organized by tier.
298pub struct KnownTargets;
299
300impl KnownTargets {
301    /// Returns all tier 1 target triples.
302    pub fn tier1() -> &'static [&'static str] {
303        &[
304            "x86_64-unknown-linux-gnu",
305            "x86_64-pc-windows-msvc",
306            "aarch64-unknown-linux-gnu",
307            "aarch64-apple-darwin",
308            "x86_64-apple-darwin",
309        ]
310    }
311
312    /// Returns all tier 2 target triples.
313    pub fn tier2() -> &'static [&'static str] {
314        &[
315            "x86_64-unknown-linux-musl",
316            "aarch64-unknown-linux-musl",
317            "x86_64-unknown-freebsd",
318            "aarch64-unknown-freebsd",
319            "x86_64-unknown-netbsd",
320            "aarch64-unknown-netbsd",
321            "x86_64-unknown-openbsd",
322            "x86_64-unknown-illumos",
323            "powerpc64le-unknown-linux-gnu",
324            "s390x-unknown-linux-gnu",
325            "riscv64gc-unknown-linux-gnu",
326            "x86_64-pc-windows-gnu",
327            "i686-pc-windows-msvc",
328            "i686-pc-windows-gnu",
329            "aarch64-pc-windows-msvc",
330            "wasm32-wasi",
331            "wasm32-unknown-unknown",
332            "wasm32-unknown-emscripten",
333        ]
334    }
335
336    /// Checks if a target is a known tier 1 target.
337    pub fn is_tier1(target: &str) -> bool {
338        Self::tier1().contains(&target)
339    }
340
341    /// Checks if a target is a known tier 2 target.
342    pub fn is_tier2(target: &str) -> bool {
343        Self::tier2().contains(&target)
344    }
345
346    /// Returns all known targets (tier 1 + tier 2).
347    pub fn all_known() -> Vec<&'static str> {
348        let mut targets = Vec::new();
349        targets.extend_from_slice(Self::tier1());
350        targets.extend_from_slice(Self::tier2());
351        targets
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn detects_host() {
361        let host = detect_host().expect("host detection should work");
362        assert!(!host.host_triple.triple.is_empty());
363        assert!(host.rustc_version.is_some());
364        assert!(host.cargo_version.is_some());
365    }
366
367    #[test]
368    fn assesses_tier1_targets() {
369        let host = detect_host().unwrap();
370        let targets = KnownTargets::tier1();
371        for t in targets {
372            let target = TargetTriple::parse(t).unwrap();
373            let info = assess_target(&target, &host);
374            assert_eq!(info.supported, TargetSupport::Tier1);
375        }
376    }
377
378    #[test]
379    fn assesses_tier2_targets() {
380        let host = detect_host().unwrap();
381        let targets = KnownTargets::tier2();
382        for t in targets {
383            let target = TargetTriple::parse(t).unwrap();
384            let info = assess_target(&target, &host);
385            assert_eq!(info.supported, TargetSupport::Tier2);
386        }
387    }
388
389    #[test]
390    fn suggests_rustup_for_native() {
391        let host = detect_host().unwrap();
392        let target = TargetTriple::parse(&host.host_triple.triple).unwrap();
393        let info = assess_target(&target, &host);
394        assert!(info.is_native);
395        assert!(!info.requires_cross);
396        assert_eq!(info.toolchain_hint, ToolchainHint::Rustup);
397    }
398
399    #[test]
400    fn suggests_linker_for_windows_msvc() {
401        let host = detect_host().unwrap();
402        let target = TargetTriple::parse("x86_64-pc-windows-msvc").unwrap();
403        let info = assess_target(&target, &host);
404        assert_eq!(info.linker_hint, LinkerHint::MSVC);
405    }
406
407    #[test]
408    fn suggests_lld_for_cross_linux() {
409        let host = detect_host().unwrap();
410        let target = TargetTriple::parse("x86_64-unknown-linux-gnu").unwrap();
411        let info = assess_target(&target, &host);
412        assert_eq!(info.linker_hint, LinkerHint::Lld);
413    }
414
415    #[test]
416    fn capability_matrix_works() {
417        let mut matrix = CapabilityMatrix::new().unwrap();
418        let target = TargetTriple::parse("x86_64-unknown-linux-gnu").unwrap();
419        let info = matrix.assess(&target);
420        assert_eq!(info.supported, TargetSupport::Tier1);
421    }
422}