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