Skip to main content

alien_core/
build_targets.rs

1//! Cross-compilation build target types
2//!
3//! These types identify target OS/architecture combinations used by the open-source
4//! build system (alien-build) for cross-compilation.
5
6use serde::{Deserialize, Serialize};
7#[cfg(feature = "openapi")]
8use utoipa::ToSchema;
9
10/// Build strategy for cross-compilation.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CargoBuildStrategy {
13    /// Plain `cargo build` — native toolchain, no cross-compilation.
14    Native,
15    /// `cargo zigbuild` — uses Zig as the C toolchain for Linux musl targets.
16    Zigbuild,
17    /// `cargo xwin build` — uses xwin to provide MSVC CRT/SDK for Windows targets from non-Windows hosts.
18    Xwin,
19}
20
21/// Operating system and architecture of the machine running a build.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum BuildHost {
24    /// Linux x86-64.
25    LinuxX64,
26    /// Linux ARM64.
27    LinuxArm64,
28    /// Windows x86-64.
29    WindowsX64,
30    /// Windows ARM64.
31    WindowsArm64,
32    /// macOS x86-64.
33    DarwinX64,
34    /// macOS ARM64.
35    DarwinArm64,
36    /// A host Alien does not recognize.
37    Unsupported,
38}
39
40impl BuildHost {
41    /// Detect the build host without coercing unknown machines into a supported target.
42    pub fn current() -> Self {
43        match (std::env::consts::OS, std::env::consts::ARCH) {
44            ("linux", "x86_64") => Self::LinuxX64,
45            ("linux", "aarch64") => Self::LinuxArm64,
46            ("windows", "x86_64") => Self::WindowsX64,
47            ("windows", "aarch64") => Self::WindowsArm64,
48            ("macos", "x86_64") => Self::DarwinX64,
49            ("macos", "aarch64") => Self::DarwinArm64,
50            _ => Self::Unsupported,
51        }
52    }
53
54    /// Human-readable host identifier for diagnostics and cache keys.
55    pub fn id(self) -> &'static str {
56        match self {
57            Self::LinuxX64 => "linux-x64",
58            Self::LinuxArm64 => "linux-arm64",
59            Self::WindowsX64 => "windows-x64",
60            Self::WindowsArm64 => "windows-arm64",
61            Self::DarwinX64 => "darwin-x64",
62            Self::DarwinArm64 => "darwin-arm64",
63            Self::Unsupported => "unsupported",
64        }
65    }
66}
67
68impl CargoBuildStrategy {
69    /// The cargo subcommand to use (e.g. "build", "zigbuild", "xwin").
70    pub fn cargo_subcommand(&self) -> &'static str {
71        match self {
72            Self::Native => "build",
73            Self::Zigbuild => "zigbuild",
74            Self::Xwin => "xwin",
75        }
76    }
77
78    /// Cargo args: for xwin, the subcommand is `cargo xwin build` (two args).
79    pub fn cargo_args(&self) -> Vec<&'static str> {
80        match self {
81            Self::Native => vec!["build"],
82            Self::Zigbuild => vec!["zigbuild"],
83            Self::Xwin => vec!["xwin", "build"],
84        }
85    }
86
87    /// Human-readable name for logging.
88    pub fn display_name(&self) -> &'static str {
89        match self {
90            Self::Native => "cargo build",
91            Self::Zigbuild => "cargo zigbuild",
92            Self::Xwin => "cargo xwin build",
93        }
94    }
95
96    /// The cargo tool binary to install (None for native builds).
97    pub fn install_package(&self) -> Option<&'static str> {
98        match self {
99            Self::Native => None,
100            Self::Zigbuild => Some("cargo-zigbuild"),
101            Self::Xwin => Some("cargo-xwin"),
102        }
103    }
104}
105
106/// Types of source binaries used for package building
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
108#[cfg_attr(feature = "openapi", derive(ToSchema))]
109#[serde(rename_all = "kebab-case")]
110pub enum SourceBinaryType {
111    /// alien-deploy binary
112    Cli,
113    /// alien-terraform binary
114    Terraform,
115    /// alien-operator binary
116    Operator,
117}
118
119impl SourceBinaryType {
120    /// Returns the binary filename (without extension)
121    pub fn binary_name(&self) -> &'static str {
122        match self {
123            SourceBinaryType::Cli => "alien-deploy",
124            SourceBinaryType::Terraform => "alien-terraform",
125            SourceBinaryType::Operator => "alien-operator",
126        }
127    }
128}
129
130impl std::fmt::Display for SourceBinaryType {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        match self {
133            SourceBinaryType::Cli => write!(f, "cli"),
134            SourceBinaryType::Terraform => write!(f, "terraform"),
135            SourceBinaryType::Operator => write!(f, "operator"),
136        }
137    }
138}
139
140/// Target OS and architecture for compiled binaries.
141///
142/// Used as keys in package output maps (CLI binaries, Terraform providers, etc.)
143/// and for cross-compilation target selection during builds.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
145#[cfg_attr(feature = "openapi", derive(ToSchema))]
146#[serde(rename_all = "kebab-case")]
147pub enum BinaryTarget {
148    /// Windows x64 (x86_64-pc-windows-msvc)
149    WindowsX64,
150    /// Linux x86_64 (musl)
151    LinuxX64,
152    /// Linux ARM64 (musl)
153    LinuxArm64,
154    /// macOS ARM64 (Apple Silicon)
155    DarwinArm64,
156}
157
158impl BinaryTarget {
159    /// All supported binary targets
160    pub const ALL: &'static [BinaryTarget] = &[
161        BinaryTarget::WindowsX64,
162        BinaryTarget::LinuxX64,
163        BinaryTarget::LinuxArm64,
164        BinaryTarget::DarwinArm64,
165    ];
166
167    /// Linux-only targets (for container/operator builds)
168    pub const LINUX: &'static [BinaryTarget] = &[BinaryTarget::LinuxX64, BinaryTarget::LinuxArm64];
169
170    /// Get the Rust target triple for this platform
171    pub fn rust_target_triple(&self) -> &'static str {
172        match self {
173            Self::WindowsX64 => "x86_64-pc-windows-msvc",
174            Self::LinuxX64 => "x86_64-unknown-linux-musl",
175            Self::LinuxArm64 => "aarch64-unknown-linux-musl",
176            Self::DarwinArm64 => "aarch64-apple-darwin",
177        }
178    }
179
180    /// Returns the cargo subcommand and tool name needed to build for this target.
181    ///
182    /// - Linux musl on a matching Linux host: `cargo build` (native musl toolchain)
183    /// - Linux musl from another host or architecture: `cargo zigbuild`
184    /// - Windows MSVC from non-Windows: `cargo xwin build` (xwin provides MSVC CRT/SDK)
185    /// - Windows MSVC on Windows: `cargo build` (native MSVC toolchain)
186    /// - macOS on macOS: `cargo build` (native Apple toolchain)
187    pub fn cargo_build_strategy(&self) -> CargoBuildStrategy {
188        self.cargo_build_strategy_for(BuildHost::current())
189    }
190
191    /// Select the cheapest correct Rust compiler for an explicit build host.
192    pub fn cargo_build_strategy_for(&self, host: BuildHost) -> CargoBuildStrategy {
193        match (self, host) {
194            (Self::LinuxX64, BuildHost::LinuxX64)
195            | (Self::LinuxArm64, BuildHost::LinuxArm64)
196            | (Self::WindowsX64, BuildHost::WindowsX64)
197            | (Self::DarwinArm64, BuildHost::DarwinArm64 | BuildHost::DarwinX64) => {
198                CargoBuildStrategy::Native
199            }
200            (Self::WindowsX64, _) => CargoBuildStrategy::Xwin,
201            (Self::LinuxX64 | Self::LinuxArm64, _) => CargoBuildStrategy::Zigbuild,
202            // The Rust target alone is insufficient off macOS: an Apple SDK is also required.
203            (Self::DarwinArm64, _) => CargoBuildStrategy::Native,
204        }
205    }
206
207    /// Get the binary extension for this platform
208    pub fn binary_extension(&self) -> &'static str {
209        match self {
210            Self::WindowsX64 => ".exe",
211            _ => "",
212        }
213    }
214
215    /// Get the platform identifier for runtime downloads (e.g., "linux-x64")
216    pub fn runtime_platform_id(&self) -> &'static str {
217        match self {
218            Self::WindowsX64 => "windows-x64",
219            Self::LinuxX64 => "linux-x64",
220            Self::LinuxArm64 => "linux-aarch64",
221            Self::DarwinArm64 => "darwin-aarch64",
222        }
223    }
224
225    /// Inverse of [`runtime_platform_id`] — the `.oci.tar` filename spelling
226    /// (`linux-aarch64`/`darwin-aarch64`), not the `linux-arm64`/`darwin-arm64` CLI names.
227    pub fn from_runtime_platform_id(id: &str) -> Option<Self> {
228        match id {
229            "windows-x64" => Some(Self::WindowsX64),
230            "linux-x64" => Some(Self::LinuxX64),
231            "linux-aarch64" => Some(Self::LinuxArm64),
232            "darwin-aarch64" => Some(Self::DarwinArm64),
233            _ => None,
234        }
235    }
236
237    /// Get the OCI os string for this target
238    pub fn oci_os(&self) -> &'static str {
239        match self {
240            Self::WindowsX64 => "windows",
241            Self::LinuxX64 | Self::LinuxArm64 => "linux",
242            Self::DarwinArm64 => "darwin",
243        }
244    }
245
246    /// Get the OCI architecture string for this target
247    pub fn oci_arch(&self) -> &'static str {
248        match self {
249            Self::WindowsX64 | Self::LinuxX64 => "amd64",
250            Self::LinuxArm64 | Self::DarwinArm64 => "arm64",
251        }
252    }
253
254    /// Get the Bun cross-compilation target for `bun build --compile --target`
255    pub fn bun_target(&self) -> &'static str {
256        match self {
257            Self::WindowsX64 => "bun-windows-x64",
258            Self::LinuxX64 => "bun-linux-x64",
259            Self::LinuxArm64 => "bun-linux-arm64",
260            Self::DarwinArm64 => "bun-darwin-arm64",
261        }
262    }
263
264    /// Terraform registry platform key (os_arch format)
265    pub fn terraform_key(&self) -> &'static str {
266        match self {
267            BinaryTarget::LinuxX64 => "linux_amd64",
268            BinaryTarget::LinuxArm64 => "linux_arm64",
269            BinaryTarget::DarwinArm64 => "darwin_arm64",
270            BinaryTarget::WindowsX64 => "windows_amd64",
271        }
272    }
273
274    /// Terraform OS string
275    pub fn terraform_os(&self) -> &'static str {
276        match self {
277            BinaryTarget::LinuxX64 | BinaryTarget::LinuxArm64 => "linux",
278            BinaryTarget::DarwinArm64 => "darwin",
279            BinaryTarget::WindowsX64 => "windows",
280        }
281    }
282
283    /// Terraform architecture string
284    pub fn terraform_arch(&self) -> &'static str {
285        match self {
286            BinaryTarget::LinuxX64 | BinaryTarget::WindowsX64 => "amd64",
287            BinaryTarget::LinuxArm64 | BinaryTarget::DarwinArm64 => "arm64",
288        }
289    }
290
291    /// Check if this target is a Darwin/macOS target
292    pub fn is_darwin(&self) -> bool {
293        matches!(self, Self::DarwinArm64)
294    }
295
296    /// Check if this is a Windows target
297    pub fn is_windows(&self) -> bool {
298        matches!(self, Self::WindowsX64)
299    }
300
301    /// Get the Linux container target matching the current host architecture.
302    /// Containers always run Linux (even on macOS via Docker's Linux VM),
303    /// so we map the host architecture to the corresponding Linux target.
304    pub fn linux_container_target() -> Self {
305        match Self::current_os() {
306            Self::DarwinArm64 | Self::LinuxArm64 => Self::LinuxArm64,
307            Self::LinuxX64 | Self::WindowsX64 => Self::LinuxX64,
308        }
309    }
310
311    /// Get all possible targets as a Vec
312    pub fn all() -> Vec<Self> {
313        Self::ALL.to_vec()
314    }
315
316    /// Get default targets for a platform
317    pub fn defaults_for_platform(platform: crate::Platform) -> Vec<Self> {
318        match platform {
319            crate::Platform::Aws => vec![Self::LinuxArm64],
320            crate::Platform::Gcp => vec![Self::LinuxX64],
321            crate::Platform::Azure => vec![Self::LinuxX64],
322            crate::Platform::Kubernetes => Self::LINUX.to_vec(),
323            crate::Platform::Machines => Self::LINUX.to_vec(),
324            crate::Platform::Local => vec![Self::current_os()],
325            crate::Platform::Test => vec![Self::LinuxX64],
326        }
327    }
328
329    /// Detect the current OS target
330    pub fn current_os() -> Self {
331        match BuildHost::current() {
332            BuildHost::WindowsX64 => Self::WindowsX64,
333            BuildHost::LinuxX64 => Self::LinuxX64,
334            BuildHost::LinuxArm64 => Self::LinuxArm64,
335            BuildHost::DarwinArm64 => Self::DarwinArm64,
336            host => panic!("unsupported Alien host: {}", host.id()),
337        }
338    }
339}
340
341impl std::fmt::Display for BinaryTarget {
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        match self {
344            BinaryTarget::WindowsX64 => write!(f, "windows-x64"),
345            BinaryTarget::LinuxX64 => write!(f, "linux-x64"),
346            BinaryTarget::LinuxArm64 => write!(f, "linux-arm64"),
347            BinaryTarget::DarwinArm64 => write!(f, "darwin-arm64"),
348        }
349    }
350}
351
352impl std::str::FromStr for BinaryTarget {
353    type Err = String;
354
355    fn from_str(s: &str) -> Result<Self, Self::Err> {
356        match s {
357            "windows-x64" => Ok(BinaryTarget::WindowsX64),
358            "linux-x64" => Ok(BinaryTarget::LinuxX64),
359            "linux-arm64" => Ok(BinaryTarget::LinuxArm64),
360            "darwin-arm64" => Ok(BinaryTarget::DarwinArm64),
361            _ => Err(format!("Unknown binary target: {}", s)),
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::{BinaryTarget, BuildHost, CargoBuildStrategy};
369    use crate::Platform;
370
371    #[test]
372    fn local_platform_defaults_to_current_host_target() {
373        assert_eq!(
374            BinaryTarget::defaults_for_platform(Platform::Local),
375            vec![BinaryTarget::current_os()]
376        );
377    }
378
379    #[test]
380    fn runtime_platform_id_round_trips() {
381        for target in BinaryTarget::ALL {
382            assert_eq!(
383                BinaryTarget::from_runtime_platform_id(target.runtime_platform_id()),
384                Some(*target),
385                "round-trip failed for {target}"
386            );
387        }
388        // The tarball spelling differs from the CLI/FromStr spelling.
389        assert_eq!(
390            BinaryTarget::from_runtime_platform_id("linux-aarch64"),
391            Some(BinaryTarget::LinuxArm64)
392        );
393        assert_eq!(BinaryTarget::from_runtime_platform_id("linux-arm64"), None);
394        assert_eq!(BinaryTarget::from_runtime_platform_id("nonsense"), None);
395    }
396
397    #[test]
398    fn cloud_platform_defaults_remain_stable() {
399        assert_eq!(
400            BinaryTarget::defaults_for_platform(Platform::Aws),
401            vec![BinaryTarget::LinuxArm64]
402        );
403        assert_eq!(
404            BinaryTarget::defaults_for_platform(Platform::Gcp),
405            vec![BinaryTarget::LinuxX64]
406        );
407        assert_eq!(
408            BinaryTarget::defaults_for_platform(Platform::Azure),
409            vec![BinaryTarget::LinuxX64]
410        );
411        assert_eq!(
412            BinaryTarget::defaults_for_platform(Platform::Kubernetes),
413            vec![BinaryTarget::LinuxX64, BinaryTarget::LinuxArm64]
414        );
415    }
416
417    #[test]
418    fn cargo_build_strategy_uses_native_tools_only_on_compatible_hosts() {
419        assert_eq!(
420            BinaryTarget::LinuxX64.cargo_build_strategy_for(BuildHost::LinuxX64),
421            CargoBuildStrategy::Native
422        );
423        assert_eq!(
424            BinaryTarget::LinuxArm64.cargo_build_strategy_for(BuildHost::LinuxArm64),
425            CargoBuildStrategy::Native
426        );
427        assert_eq!(
428            BinaryTarget::LinuxArm64.cargo_build_strategy_for(BuildHost::LinuxX64),
429            CargoBuildStrategy::Zigbuild
430        );
431        assert_eq!(
432            BinaryTarget::WindowsX64.cargo_build_strategy_for(BuildHost::WindowsX64),
433            CargoBuildStrategy::Native
434        );
435        assert_eq!(
436            BinaryTarget::WindowsX64.cargo_build_strategy_for(BuildHost::DarwinArm64),
437            CargoBuildStrategy::Xwin
438        );
439        assert_eq!(
440            BinaryTarget::DarwinArm64.cargo_build_strategy_for(BuildHost::DarwinX64),
441            CargoBuildStrategy::Native
442        );
443    }
444}