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-agent binary
69    Agent,
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::Agent => "alien-agent",
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::Agent => write!(f, "agent"),
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::Local => vec![Self::current_os()],
270            crate::Platform::Test => vec![Self::LinuxX64],
271        }
272    }
273
274    /// Detect the current OS target
275    pub fn current_os() -> Self {
276        #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
277        return Self::WindowsX64;
278
279        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
280        return Self::LinuxX64;
281
282        #[cfg(all(target_os = "linux", target_arch = "aarch64"))]
283        return Self::LinuxArm64;
284
285        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
286        return Self::DarwinArm64;
287
288        #[cfg(not(any(
289            all(target_os = "windows", target_arch = "x86_64"),
290            all(target_os = "linux", target_arch = "x86_64"),
291            all(target_os = "linux", target_arch = "aarch64"),
292            all(target_os = "macos", target_arch = "aarch64")
293        )))]
294        {
295            Self::LinuxX64
296        }
297    }
298}
299
300impl std::fmt::Display for BinaryTarget {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        match self {
303            BinaryTarget::WindowsX64 => write!(f, "windows-x64"),
304            BinaryTarget::LinuxX64 => write!(f, "linux-x64"),
305            BinaryTarget::LinuxArm64 => write!(f, "linux-arm64"),
306            BinaryTarget::DarwinArm64 => write!(f, "darwin-arm64"),
307        }
308    }
309}
310
311impl std::str::FromStr for BinaryTarget {
312    type Err = String;
313
314    fn from_str(s: &str) -> Result<Self, Self::Err> {
315        match s {
316            "windows-x64" => Ok(BinaryTarget::WindowsX64),
317            "linux-x64" => Ok(BinaryTarget::LinuxX64),
318            "linux-arm64" => Ok(BinaryTarget::LinuxArm64),
319            "darwin-arm64" => Ok(BinaryTarget::DarwinArm64),
320            _ => Err(format!("Unknown binary target: {}", s)),
321        }
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::BinaryTarget;
328    use crate::Platform;
329
330    #[test]
331    fn local_platform_defaults_to_current_host_target() {
332        assert_eq!(
333            BinaryTarget::defaults_for_platform(Platform::Local),
334            vec![BinaryTarget::current_os()]
335        );
336    }
337
338    #[test]
339    fn runtime_platform_id_round_trips() {
340        for target in BinaryTarget::ALL {
341            assert_eq!(
342                BinaryTarget::from_runtime_platform_id(target.runtime_platform_id()),
343                Some(*target),
344                "round-trip failed for {target}"
345            );
346        }
347        // The tarball spelling differs from the CLI/FromStr spelling.
348        assert_eq!(
349            BinaryTarget::from_runtime_platform_id("linux-aarch64"),
350            Some(BinaryTarget::LinuxArm64)
351        );
352        assert_eq!(BinaryTarget::from_runtime_platform_id("linux-arm64"), None);
353        assert_eq!(BinaryTarget::from_runtime_platform_id("nonsense"), None);
354    }
355
356    #[test]
357    fn cloud_platform_defaults_remain_stable() {
358        assert_eq!(
359            BinaryTarget::defaults_for_platform(Platform::Aws),
360            vec![BinaryTarget::LinuxArm64]
361        );
362        assert_eq!(
363            BinaryTarget::defaults_for_platform(Platform::Gcp),
364            vec![BinaryTarget::LinuxX64]
365        );
366        assert_eq!(
367            BinaryTarget::defaults_for_platform(Platform::Azure),
368            vec![BinaryTarget::LinuxX64]
369        );
370        assert_eq!(
371            BinaryTarget::defaults_for_platform(Platform::Kubernetes),
372            vec![BinaryTarget::LinuxX64, BinaryTarget::LinuxArm64]
373        );
374    }
375}