Skip to main content

release_hub/
target.rs

1//! Platform and installer target modeling.
2
3use crate::{Error, Result};
4use std::path::Path;
5
6/// Supported operating systems for release targeting.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum OS {
9    /// Linux targets.
10    Linux,
11    /// macOS / Darwin targets.
12    Macos,
13    /// Windows targets.
14    Windows,
15}
16
17/// Supported CPU architectures for release targeting.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum Arch {
20    /// 64-bit x86.
21    X86_64,
22    /// 64-bit ARM.
23    Arm64,
24}
25
26/// Installer formats understood by the platform backends.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum InstallerKind {
29    /// Linux AppImage package.
30    AppImage,
31    /// Debian package.
32    Deb,
33    /// RPM package.
34    Rpm,
35    /// macOS `.app.tar.gz` archive.
36    AppTarGz,
37    /// macOS `.app.zip` archive.
38    AppZip,
39    /// Windows MSI installer.
40    Msi,
41    /// Windows EXE / NSIS-style installer.
42    Nsis,
43}
44
45/// Runtime platform information for target selection.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SystemInfo {
48    /// Operating system component.
49    pub os: OS,
50    /// Architecture component.
51    pub arch: Arch,
52}
53
54/// Fully-resolved target descriptor used for source selection.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct TargetInfo {
57    /// Canonical target string such as `linux-x86_64`.
58    pub target: String,
59    /// Structured system information used to derive the target.
60    pub system: SystemInfo,
61}
62
63impl TargetInfo {
64    /// Converts structured system information into the crate's canonical target string.
65    ///
66    /// For example, macOS on Apple Silicon becomes `darwin-aarch64`.
67    pub fn from_system(system: SystemInfo) -> Self {
68        let os = match system.os {
69            OS::Linux => "linux",
70            OS::Macos => "darwin",
71            OS::Windows => "windows",
72        };
73        let arch = match system.arch {
74            Arch::X86_64 => "x86_64",
75            Arch::Arm64 => "aarch64",
76        };
77        Self {
78            target: format!("{os}-{arch}"),
79            system,
80        }
81    }
82}
83
84impl SystemInfo {
85    /// Detects the current host operating system and architecture.
86    ///
87    /// Only the platform combinations supported by this crate are recognized.
88    pub fn current() -> Result<Self> {
89        let os = if cfg!(target_os = "linux") {
90            OS::Linux
91        } else if cfg!(target_os = "macos") {
92            OS::Macos
93        } else if cfg!(target_os = "windows") {
94            OS::Windows
95        } else {
96            return Err(Error::UnsupportedOs);
97        };
98        let arch = if cfg!(target_arch = "x86_64") {
99            Arch::X86_64
100        } else if cfg!(target_arch = "aarch64") {
101            Arch::Arm64
102        } else {
103            return Err(Error::UnsupportedArch);
104        };
105        Ok(Self { os, arch })
106    }
107}
108
109impl InstallerKind {
110    /// Infers the installer format from an artifact filename or path.
111    ///
112    /// Matching is based on the final filename suffix, such as `.AppImage`,
113    /// `.app.tar.gz`, or `.msi`.
114    pub fn from_path(path: &Path) -> Result<Self> {
115        let name = path
116            .file_name()
117            .and_then(|name| name.to_str())
118            .unwrap_or_default();
119        if name.ends_with(".AppImage") {
120            Ok(Self::AppImage)
121        } else if name.ends_with(".deb") {
122            Ok(Self::Deb)
123        } else if name.ends_with(".rpm") {
124            Ok(Self::Rpm)
125        } else if name.ends_with(".app.tar.gz") {
126            Ok(Self::AppTarGz)
127        } else if name.ends_with(".app.zip") {
128            Ok(Self::AppZip)
129        } else if name.ends_with(".msi") {
130            Ok(Self::Msi)
131        } else if name.ends_with(".exe") {
132            Ok(Self::Nsis)
133        } else {
134            Err(Error::InvalidUpdaterFormat)
135        }
136    }
137}