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