npmgen_core/target/
mod.rs1mod defaults;
5mod resolver;
6
7pub use resolver::TargetResolver;
8
9use std::path::{Path, PathBuf};
10
11use crate::config::TargetSpec;
12
13pub(crate) const RELEASE_PROFILE_DIR: &str = "release";
16
17#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Target {
21 pub key: String,
22 pub os: String,
23 pub cpu: String,
24 pub triple: String,
25 pub windows: bool,
26}
27
28impl Target {
29 pub fn from_triple(triple: &str) -> Result<Self, TargetError> {
31 let segments: Vec<&str> = triple.split('-').collect();
32 let arch = segments.first().copied().unwrap_or_default();
33 let cpu = Self::cpu_for_arch(arch).ok_or_else(|| TargetError::UnknownTriple {
34 triple: triple.to_owned(),
35 })?;
36 let (system, os) = segments
39 .iter()
40 .rev()
41 .find_map(|segment| Self::os_for_system(segment).map(|os| (*segment, os)))
42 .ok_or_else(|| TargetError::UnknownTriple {
43 triple: triple.to_owned(),
44 })?;
45
46 Ok(Self {
47 key: format!("{os}-{cpu}"),
48 os: os.to_owned(),
49 cpu: cpu.to_owned(),
50 triple: triple.to_owned(),
51 windows: Self::is_windows_system(system),
52 })
53 }
54
55 pub fn from_spec(spec: &TargetSpec) -> Result<Self, TargetError> {
58 let triple = spec
59 .triple
60 .clone()
61 .or_else(|| Self::default_triple(&spec.key).map(str::to_owned))
62 .ok_or_else(|| TargetError::UnknownTargetKey {
63 key: spec.key.clone(),
64 })?;
65
66 let (key_os, key_cpu) =
67 spec.key
68 .split_once('-')
69 .ok_or_else(|| TargetError::InvalidKey {
70 key: spec.key.clone(),
71 })?;
72 let os = spec.os.clone().unwrap_or_else(|| key_os.to_owned());
73 let cpu = spec.cpu.clone().unwrap_or_else(|| key_cpu.to_owned());
74
75 Ok(Self {
76 windows: Self::is_windows_os(&os),
77 key: spec.key.clone(),
78 os,
79 cpu,
80 triple,
81 })
82 }
83
84 pub(super) fn defaults() -> Vec<Self> {
86 defaults::KEY_TRIPLES
87 .iter()
88 .map(|(_, triple)| Self::from_triple(triple).expect("default triples decode"))
89 .collect()
90 }
91
92 pub fn binary_filename(&self, stem: &str) -> String {
94 if self.windows {
95 format!("{stem}.exe")
96 } else {
97 stem.to_owned()
98 }
99 }
100
101 pub fn binary_path(&self, target_directory: &Path, bin: &str) -> PathBuf {
104 target_directory
105 .join(&self.triple)
106 .join(RELEASE_PROFILE_DIR)
107 .join(self.binary_filename(bin))
108 }
109
110 fn default_triple(key: &str) -> Option<&'static str> {
111 Self::lookup(defaults::KEY_TRIPLES, key)
112 }
113
114 fn cpu_for_arch(arch: &str) -> Option<&'static str> {
115 Self::lookup(defaults::ARCH_CPU, arch)
116 }
117
118 fn os_for_system(system: &str) -> Option<&'static str> {
119 Self::lookup(defaults::SYSTEM_OS, system)
120 }
121
122 fn is_windows_system(system: &str) -> bool {
123 system == defaults::WINDOWS_SYSTEM
124 }
125
126 fn is_windows_os(os: &str) -> bool {
127 os == defaults::WINDOWS_OS
128 }
129
130 fn lookup(table: &[(&str, &'static str)], needle: &str) -> Option<&'static str> {
131 table
132 .iter()
133 .find(|(key, _)| *key == needle)
134 .map(|(_, value)| *value)
135 }
136}
137
138#[derive(Debug, thiserror::Error)]
140pub enum TargetError {
141 #[error("target key {key:?} is not of the form <os>-<cpu>")]
142 InvalidKey { key: String },
143
144 #[error("no default triple for target key {key:?}; declare `triple` explicitly")]
145 UnknownTargetKey { key: String },
146
147 #[error("cannot derive os/cpu from target triple {triple:?}")]
148 UnknownTriple { triple: String },
149
150 #[error("--target {key:?} matches none of the resolved targets")]
151 UnknownFilterKey { key: String },
152
153 #[error("reading cargo config {}", path.display())]
154 CargoConfig {
155 path: PathBuf,
156 #[source]
157 source: std::io::Error,
158 },
159
160 #[error("parsing cargo config {}", path.display())]
161 CargoConfigParse {
162 path: PathBuf,
163 #[source]
164 source: toml::de::Error,
165 },
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::path::Path;
172
173 #[test]
174 fn decomposes_known_triples() {
175 let windows = Target::from_triple("x86_64-pc-windows-msvc").unwrap();
176 assert_eq!(windows.key, "win32-x64");
177 assert_eq!(windows.os, "win32");
178 assert_eq!(windows.cpu, "x64");
179 assert!(windows.windows);
180
181 let mac = Target::from_triple("aarch64-apple-darwin").unwrap();
182 assert_eq!(mac.key, "darwin-arm64");
183 assert!(!mac.windows);
184
185 let linux = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
186 assert_eq!(linux.key, "linux-x64");
187
188 let android = Target::from_triple("aarch64-linux-android").unwrap();
190 assert_eq!(android.key, "android-arm64");
191 assert_eq!(android.os, "android");
192 assert!(!android.windows);
193 }
194
195 #[test]
196 fn rejects_undecodable_triple() {
197 assert!(Target::from_triple("sparc-unknown-haiku").is_err());
198 }
199
200 #[test]
201 fn spec_defaults_triple_from_key() {
202 let spec = TargetSpec {
203 key: "win32-arm64".to_owned(),
204 triple: None,
205 os: None,
206 cpu: None,
207 };
208 let target = Target::from_spec(&spec).unwrap();
209 assert_eq!(target.triple, "aarch64-pc-windows-msvc");
210 assert_eq!(target.os, "win32");
211 assert_eq!(target.cpu, "arm64");
212 assert!(target.windows);
213 }
214
215 #[test]
216 fn spec_without_default_triple_needs_explicit_one() {
217 let spec = TargetSpec {
218 key: "haiku-x64".to_owned(),
219 triple: None,
220 os: None,
221 cpu: None,
222 };
223 assert!(Target::from_spec(&spec).is_err());
224 }
225
226 #[test]
227 fn binary_filename_appends_exe_on_windows_only() {
228 let windows = Target::from_triple("x86_64-pc-windows-msvc").unwrap();
229 let linux = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
230 assert_eq!(windows.binary_filename("tool"), "tool.exe");
231 assert_eq!(linux.binary_filename("tool"), "tool");
232 assert_eq!(
233 linux.binary_path(Path::new("/t"), "tool"),
234 Path::new("/t/x86_64-unknown-linux-gnu/release/tool")
235 );
236 }
237}