1use std::collections::BTreeMap;
4
5use crate::model::{
6 Abi, HostDetectError, HostInfo, LinkerHint, OperatingSystem, SysrootHint,
7 TargetFamily, TargetInfo, TargetSupport, TargetTriple, ToolchainHint,
8};
9
10pub fn detect_host() -> Result<HostInfo, HostDetectError> {
12 HostInfo::detect()
13}
14
15pub 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
36fn determine_support_tier(target: &TargetTriple) -> TargetSupport {
38 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 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
80fn is_known_tier3(target: &TargetTriple) -> bool {
82 let triple = target.triple.as_str();
83
84 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
104fn suggest_toolchain_provider(
106 target: &TargetTriple,
107 host: &HostInfo,
108 support: &TargetSupport,
109) -> ToolchainHint {
110 if target.triple == host.host_triple.triple {
112 return ToolchainHint::Rustup;
113 }
114
115 match support {
116 TargetSupport::Tier1 | TargetSupport::Tier2 => {
117 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
133pub fn rustup_target_available(target: &TargetTriple) -> bool {
135 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
161fn 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 } 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
188fn suggest_linker(target: &TargetTriple, host: &HostInfo) -> LinkerHint {
190 match (target.family(), target.os.clone(), target.abi.clone()) {
191 (TargetFamily::Windows, _, Abi::Msvc) => LinkerHint::MSVC,
193 (TargetFamily::Windows, _, Abi::Gnu) => LinkerHint::Lld,
194
195 (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 (TargetFamily::MacOs, OperatingSystem::MacOs, _) => LinkerHint::Lld,
208 (TargetFamily::MacOs, OperatingSystem::Ios | OperatingSystem::TvOS | OperatingSystem::WatchOS, _) => {
209 LinkerHint::Lld
210 }
211
212 (TargetFamily::Wasm, _, _) => LinkerHint::Lld,
214
215 (TargetFamily::Other, OperatingSystem::FreeBSD, _) => LinkerHint::Lld,
217 (TargetFamily::Other, OperatingSystem::NetBSD, _) => LinkerHint::Lld,
218 (TargetFamily::Other, OperatingSystem::OpenBSD, _) => LinkerHint::Lld,
219
220 (TargetFamily::BareMetal, _, _) => LinkerHint::Lld,
222
223 _ => LinkerHint::Lld,
225 }
226}
227
228#[derive(Debug, Clone)]
230pub struct CapabilityMatrix {
231 pub host: HostInfo,
232 pub targets: BTreeMap<TargetTriple, TargetInfo>,
233}
234
235impl CapabilityMatrix {
236 pub fn new() -> Result<Self, HostDetectError> {
238 let host = detect_host()?;
239 Ok(Self {
240 host,
241 targets: BTreeMap::new(),
242 })
243 }
244
245 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 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 pub fn all_targets(&self) -> &BTreeMap<TargetTriple, TargetInfo> {
262 &self.targets
263 }
264
265 pub fn is_native(&self, target: &TargetTriple) -> bool {
267 target.triple == self.host.host_triple.triple
268 }
269
270 pub fn requires_cross(&self, target: &TargetTriple) -> bool {
272 !self.is_native(target)
273 }
274
275 pub fn toolchain_hint(&mut self, target: &TargetTriple) -> ToolchainHint {
277 self.get(target).toolchain_hint
278 }
279
280 pub fn sysroot_hint(&mut self, target: &TargetTriple) -> SysrootHint {
282 self.get(target).sysroot_hint
283 }
284
285 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
297pub struct KnownTargets;
299
300impl KnownTargets {
301 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 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 pub fn is_tier1(target: &str) -> bool {
338 Self::tier1().contains(&target)
339 }
340
341 pub fn is_tier2(target: &str) -> bool {
343 Self::tier2().contains(&target)
344 }
345
346 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}