1use std::str::FromStr;
4
5use crate::build::{BuildProfile, BuildProgress};
6use target_lexicon::{
7 Aarch64Architecture, Architecture, DefaultToHost, Environment, OperatingSystem,
8 Riscv32Architecture, Triple, Vendor,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum TargetPlatform {
21 MacOS,
24 IOS,
26 IOSSimulator,
28 TvOS,
30 TvOSSimulator,
32 WatchOS,
34 WatchOSSimulator,
36 VisionOS,
38 VisionOSSimulator,
40
41 Android,
44 Linux,
46 Windows,
48 Web,
50 Esp32S3,
52 Esp32C3,
54 Esp32P4,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub enum TargetBackend {
61 Apple,
63 Android,
65 Gtk4,
67 Hydrolysis,
69 WinUi,
71 Dew,
73}
74
75impl TargetBackend {
76 #[must_use]
81 pub const fn scaffold_packages(&self) -> &'static [&'static str] {
82 match self {
83 Self::Apple | Self::Android => &[],
84 Self::Gtk4 => &["waterui-gtk"],
85 Self::Hydrolysis => &["hydrolysis", "hydrolysis-m3"],
86 Self::WinUi => &["waterui-winui"],
87 Self::Dew => &["waterui-dew"],
88 }
89 }
90
91 #[must_use]
103 pub const fn default_development_profile(&self) -> BuildProfile {
104 match self {
105 Self::Hydrolysis => BuildProfile::Optimized,
106 _ => BuildProfile::Debug,
107 }
108 }
109}
110
111impl TargetPlatform {
112 #[must_use]
117 pub fn triple(&self) -> Triple {
118 match self {
119 Self::MacOS => Triple {
120 architecture: DefaultToHost::default().0.architecture,
121 vendor: Vendor::Apple,
122 operating_system: OperatingSystem::Darwin(None),
123 environment: Environment::Unknown,
124 binary_format: target_lexicon::BinaryFormat::Macho,
125 },
126 Self::IOS => Triple {
127 architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64),
128 vendor: Vendor::Apple,
129 operating_system: OperatingSystem::IOS(None),
130 environment: Environment::Unknown,
131 binary_format: target_lexicon::BinaryFormat::Macho,
132 },
133 Self::IOSSimulator => {
134 let arch = DefaultToHost::default().0.architecture;
135 let env = match arch {
136 Architecture::X86_64 => Environment::Unknown,
137 _ => Environment::Sim,
138 };
139 Triple {
140 architecture: arch,
141 vendor: Vendor::Apple,
142 operating_system: OperatingSystem::IOS(None),
143 environment: env,
144 binary_format: target_lexicon::BinaryFormat::Macho,
145 }
146 }
147 Self::TvOS => Triple {
148 architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64),
149 vendor: Vendor::Apple,
150 operating_system: OperatingSystem::TvOS(None),
151 environment: Environment::Unknown,
152 binary_format: target_lexicon::BinaryFormat::Macho,
153 },
154 Self::TvOSSimulator => Triple {
155 architecture: DefaultToHost::default().0.architecture,
156 vendor: Vendor::Apple,
157 operating_system: OperatingSystem::TvOS(None),
158 environment: Environment::Sim,
159 binary_format: target_lexicon::BinaryFormat::Macho,
160 },
161 Self::WatchOS => Triple {
162 architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64),
163 vendor: Vendor::Apple,
164 operating_system: OperatingSystem::WatchOS(None),
165 environment: Environment::Unknown,
166 binary_format: target_lexicon::BinaryFormat::Macho,
167 },
168 Self::WatchOSSimulator => Triple {
169 architecture: DefaultToHost::default().0.architecture,
170 vendor: Vendor::Apple,
171 operating_system: OperatingSystem::WatchOS(None),
172 environment: Environment::Sim,
173 binary_format: target_lexicon::BinaryFormat::Macho,
174 },
175 Self::VisionOS => Triple {
176 architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64),
177 vendor: Vendor::Apple,
178 operating_system: OperatingSystem::VisionOS(None),
179 environment: Environment::Unknown,
180 binary_format: target_lexicon::BinaryFormat::Macho,
181 },
182 Self::VisionOSSimulator => Triple {
183 architecture: DefaultToHost::default().0.architecture,
184 vendor: Vendor::Apple,
185 operating_system: OperatingSystem::VisionOS(None),
186 environment: Environment::Sim,
187 binary_format: target_lexicon::BinaryFormat::Macho,
188 },
189 Self::Android => Triple {
190 architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64),
191 vendor: Vendor::Unknown,
192 operating_system: OperatingSystem::Linux,
193 environment: Environment::Android,
194 binary_format: target_lexicon::BinaryFormat::Elf,
195 },
196 Self::Linux | Self::Windows => Triple::host(),
197 Self::Web => Triple::from_str("wasm32-unknown-unknown")
198 .expect("web target triple must remain valid"),
199 Self::Esp32S3 => Triple::from_str("xtensa-esp32s3-espidf")
200 .expect("esp32s3 target triple must remain valid"),
201 Self::Esp32C3 => Triple::from_str("riscv32imc-esp-espidf")
202 .expect("esp32c3 target triple must remain valid"),
203 Self::Esp32P4 => Triple::from_str("riscv32imafc-esp-espidf")
204 .expect("esp32p4 target triple must remain valid"),
205 }
206 }
207
208 #[must_use]
210 pub const fn available_backends(&self) -> &[TargetBackend] {
211 match self {
212 Self::MacOS => &[TargetBackend::Apple, TargetBackend::Hydrolysis],
213 Self::IOS
214 | Self::IOSSimulator
215 | Self::TvOS
216 | Self::TvOSSimulator
217 | Self::WatchOS
218 | Self::WatchOSSimulator
219 | Self::VisionOS
220 | Self::VisionOSSimulator => &[TargetBackend::Apple],
221 Self::Android => &[TargetBackend::Android],
222 Self::Linux => &[TargetBackend::Gtk4, TargetBackend::Hydrolysis],
223 Self::Windows => &[TargetBackend::Hydrolysis, TargetBackend::WinUi],
224 Self::Web => &[TargetBackend::Hydrolysis],
225 Self::Esp32S3 | Self::Esp32C3 | Self::Esp32P4 => &[TargetBackend::Dew],
226 }
227 }
228
229 #[must_use]
231 pub const fn default_backend(&self) -> TargetBackend {
232 match self {
233 Self::MacOS
234 | Self::IOS
235 | Self::IOSSimulator
236 | Self::TvOS
237 | Self::TvOSSimulator
238 | Self::WatchOS
239 | Self::WatchOSSimulator
240 | Self::VisionOS
241 | Self::VisionOSSimulator => TargetBackend::Apple,
242 Self::Android => TargetBackend::Android,
243 Self::Linux => TargetBackend::Gtk4,
244 Self::Windows | Self::Web => TargetBackend::Hydrolysis,
245 Self::Esp32S3 | Self::Esp32C3 | Self::Esp32P4 => TargetBackend::Dew,
246 }
247 }
248
249 #[must_use]
251 pub const fn is_simulator(&self) -> bool {
252 matches!(
253 self,
254 Self::IOSSimulator
255 | Self::TvOSSimulator
256 | Self::WatchOSSimulator
257 | Self::VisionOSSimulator
258 )
259 }
260
261 #[must_use]
263 pub const fn sdk_name(&self) -> Option<&'static str> {
264 match self {
265 Self::MacOS => Some("macosx"),
266 Self::IOS => Some("iphoneos"),
267 Self::IOSSimulator => Some("iphonesimulator"),
268 Self::TvOS => Some("appletvos"),
269 Self::TvOSSimulator => Some("appletvsimulator"),
270 Self::WatchOS => Some("watchos"),
271 Self::WatchOSSimulator => Some("watchsimulator"),
272 Self::VisionOS => Some("xros"),
273 Self::VisionOSSimulator => Some("xrsimulator"),
274 Self::Android
275 | Self::Linux
276 | Self::Windows
277 | Self::Web
278 | Self::Esp32S3
279 | Self::Esp32C3
280 | Self::Esp32P4 => None,
281 }
282 }
283
284 #[must_use]
286 pub fn arch(&self) -> Architecture {
287 match self {
288 Self::MacOS
289 | Self::IOSSimulator
290 | Self::TvOSSimulator
291 | Self::WatchOSSimulator
292 | Self::VisionOSSimulator
293 | Self::Linux
294 | Self::Windows => DefaultToHost::default().0.architecture,
295 Self::IOS | Self::TvOS | Self::WatchOS | Self::VisionOS | Self::Android => {
296 Architecture::Aarch64(Aarch64Architecture::Aarch64)
297 }
298 Self::Web => Architecture::Wasm32,
299 Self::Esp32S3 => Architecture::XTensa,
300 Self::Esp32C3 => Architecture::Riscv32(Riscv32Architecture::Riscv32imc),
301 Self::Esp32P4 => Architecture::Riscv32(Riscv32Architecture::Riscv32imafc),
302 }
303 }
304}
305
306#[derive(Debug, Clone)]
315pub struct PackageOptions {
316 distribution: bool,
329
330 debug: bool,
341
342 shared_rust_runtime: bool,
344
345 web_frontend: WebFrontendMode,
347
348 progress: Option<BuildProgress>,
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
361pub enum WebFrontendMode {
362 #[default]
364 Stage,
365 DevServer,
367}
368
369impl PackageOptions {
370 #[must_use]
372 pub const fn development() -> Self {
373 Self {
374 distribution: false,
375 debug: true,
376 shared_rust_runtime: true,
377 web_frontend: WebFrontendMode::Stage,
378 progress: None,
379 }
380 }
381
382 #[must_use]
384 pub const fn packaging(distribution: bool, debug: bool) -> Self {
385 Self {
386 distribution,
387 debug,
388 shared_rust_runtime: false,
389 web_frontend: WebFrontendMode::Stage,
390 progress: None,
391 }
392 }
393
394 #[must_use]
396 pub const fn with_debug(mut self, debug: bool) -> Self {
397 self.debug = debug;
398 self
399 }
400
401 #[must_use]
403 pub const fn with_dev_server(mut self, dev_server: bool) -> Self {
404 self.web_frontend = if dev_server {
405 WebFrontendMode::DevServer
406 } else {
407 WebFrontendMode::Stage
408 };
409 self
410 }
411
412 #[must_use]
414 pub const fn is_distribution(&self) -> bool {
415 self.distribution
416 }
417
418 #[must_use]
420 pub const fn is_debug(&self) -> bool {
421 self.debug
422 }
423
424 #[must_use]
426 pub const fn uses_shared_rust_runtime(&self) -> bool {
427 self.shared_rust_runtime
428 }
429
430 #[must_use]
432 pub const fn uses_dev_server(&self) -> bool {
433 matches!(self.web_frontend, WebFrontendMode::DevServer)
434 }
435
436 #[must_use]
439 pub fn with_progress(mut self, progress: BuildProgress) -> Self {
440 self.progress = Some(progress);
441 self
442 }
443
444 #[must_use]
446 pub const fn progress(&self) -> Option<&BuildProgress> {
447 self.progress.as_ref()
448 }
449}
450
451#[cfg(test)]
452mod package_options_tests {
453 use super::PackageOptions;
454
455 #[test]
456 fn development_embeds_shared_runtime_and_packaging_does_not() {
457 let development = PackageOptions::development();
458 assert!(development.is_debug());
459 assert!(!development.is_distribution());
460 assert!(development.uses_shared_rust_runtime());
461
462 for options in [
463 PackageOptions::packaging(false, true),
464 PackageOptions::packaging(true, false),
465 ] {
466 assert!(!options.uses_shared_rust_runtime());
467 }
468 }
469}