Skip to main content

rucc_tuple/
os.rs

1//! The system half of a tuple: operating system, environment, and the versions of each.
2
3use core::fmt;
4
5use crate::ObjectFormat;
6
7/// An operating system, in the sense of the thing that defines the syscall interface, the
8/// object format, the start files and the availability of a declaration.
9///
10/// Android is not here. It is a Linux kernel with a different libc, so it is `Os::Linux` with
11/// [`Env::Android`], which is also how GCC and LLVM spell it. The rule is that the OS field
12/// answers "whose kernel" and the environment field answers "whose libc", and Android is the
13/// case that shows they are separate questions.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum Os {
16    /// Linux. Every libc in the environment field is reachable from here.
17    Linux,
18    /// macOS.
19    MacOs,
20    /// iOS. A different OS from macOS and not a variant of it: a different platform value in
21    /// `LC_BUILD_VERSION`, a different set of availability macros, and a different SDK.
22    IOs,
23    /// Windows, either environment.
24    Windows,
25    /// FreeBSD 14 and later.
26    FreeBsd,
27    /// NetBSD 10.1 and later.
28    NetBsd,
29    /// OpenBSD. Dynamically linked libc only, because OpenBSD does not ship a static one and
30    /// breaks its libc ABI on purpose at every release.
31    OpenBsd,
32    /// illumos. In scope because it is open, which is the whole of the argument.
33    Illumos,
34    /// WASI. The preview number lives in the OS version, because `wasip1` and `wasip3` are
35    /// different ABIs rather than different releases of one.
36    Wasi,
37    /// No operating system. Freestanding, which is the kernel target and the embedded target
38    /// and the only row in the matrix that has to reach tier 1 without a libc.
39    None,
40}
41
42impl Os {
43    /// The canonical name, without the version.
44    pub const fn as_str(self) -> &'static str {
45        match self {
46            Os::Linux => "linux",
47            Os::MacOs => "macos",
48            Os::IOs => "ios",
49            Os::Windows => "windows",
50            Os::FreeBsd => "freebsd",
51            Os::NetBsd => "netbsd",
52            Os::OpenBsd => "openbsd",
53            Os::Illumos => "illumos",
54            Os::Wasi => "wasi",
55            Os::None => "none",
56        }
57    }
58
59    /// The object format this OS uses. There is exactly one per OS, which is why this is a
60    /// function of the OS and not a field anybody sets.
61    ///
62    /// The freestanding case is the exception and it is handled in [`crate::TargetTuple`],
63    /// because a freestanding target's format follows its architecture: a wasm freestanding
64    /// target emits wasm and an aarch64 one emits ELF.
65    pub const fn object_format(self) -> Option<ObjectFormat> {
66        match self {
67            Os::Linux | Os::FreeBsd | Os::NetBsd | Os::OpenBsd | Os::Illumos => {
68                Some(ObjectFormat::Elf)
69            }
70            Os::MacOs | Os::IOs => Some(ObjectFormat::MachO),
71            Os::Windows => Some(ObjectFormat::Coff),
72            Os::Wasi => Some(ObjectFormat::Wasm),
73            Os::None => None,
74        }
75    }
76
77    /// Whether this is one of the Darwin systems, which share a kernel, a linker, a set of ABI
78    /// divergences from AAPCS64 and a licence that stops us shipping their headers.
79    pub const fn is_darwin(self) -> bool {
80        matches!(self, Os::MacOs | Os::IOs)
81    }
82
83    /// Whether a program on this OS is linked against a libc that the tuple names.
84    ///
85    /// False for Darwin and Windows-MSVC in the sense that matters here: the system C library
86    /// is not a choice the user makes, so the environment field is carrying something else.
87    pub const fn has_selectable_libc(self) -> bool {
88        matches!(self, Os::Linux)
89    }
90
91    /// The environment used when the tuple does not name one.
92    ///
93    /// Windows defaults to `gnu` rather than `msvc`, and that is a decision rather than an
94    /// oversight. `spec/cross-compile/04-target-matrix.md` puts `x86_64-windows-gnu` at tier 1 and
95    /// `x86_64-windows-msvc` at tier 2 because mingw-w64 is the one we are allowed to ship, and
96    /// a default that requires the user to fetch a Microsoft SDK is a default that fails on a
97    /// fresh machine.
98    pub const fn default_env(self) -> Env {
99        match self {
100            Os::Linux => Env::Gnu,
101            Os::Windows => Env::Gnu,
102            _ => Env::None,
103        }
104    }
105
106    /// Whether an OS version in the tuple means something here.
107    ///
108    /// Darwin has a deployment target that decides which declarations exist, and WASI has a
109    /// preview number that decides the ABI. Everywhere else the version that matters belongs to
110    /// the libc and lives in the environment version instead.
111    pub const fn takes_version(self) -> bool {
112        matches!(self, Os::MacOs | Os::IOs | Os::Wasi)
113    }
114}
115
116impl fmt::Display for Os {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.write_str(self.as_str())
119    }
120}
121
122/// The environment component of a tuple.
123///
124/// For Linux this is the C library, and it is the field that decides the syscall wrappers, the
125/// start files, the symbol versions and whether a static link is supported. For Darwin it is
126/// the ABI variant, simulator or Mac Catalyst, which is not a libc at all. For Windows it is
127/// both at once: `gnu` means mingw-w64 and msvcrt, `msvc` means the Microsoft SDK and the
128/// universal CRT, and the two produce different object code for the same source.
129///
130/// One field carrying two meanings is not ideal, and it is what every existing toolchain does,
131/// so a tuple that spelled it differently would not round-trip through the tools everyone else
132/// uses. `spec/cross-compile/03-target-model.md` section 3.5 takes the compatibility.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
134pub enum Env {
135    /// No environment component. Darwin, the BSDs, illumos, WASI and freestanding.
136    #[default]
137    None,
138    /// glibc. Versioned, and the version is load bearing: `spec/cross-compile/09-libc-stubs.md` is mostly
139    /// about the fact that a symbol here has a version node attached to it.
140    Gnu,
141    /// musl. The default for static linking and the first cross target, because it is one
142    /// tarball with no version nodes and no `abilist`.
143    Musl,
144    /// The Microsoft toolchain: the Windows SDK headers and the universal CRT.
145    Msvc,
146    /// Bionic. A Linux kernel with an API level instead of a libc version.
147    Android,
148    /// A Darwin simulator ABI. Same architecture as the host, different platform value in
149    /// `LC_BUILD_VERSION`, different SDK.
150    Simulator,
151    /// Mac Catalyst. An iOS ABI hosted on macOS.
152    MacAbi,
153}
154
155impl Env {
156    /// The canonical name, without the version and without any ABI suffix.
157    ///
158    /// The suffixes that GCC fuses into this component, `eabihf` on ARM and `x32` on x86-64, are
159    /// not here. They come from the ABI and the data model, and [`crate::TargetTuple`] puts them
160    /// back when it formats the tuple. Storing them here as well would be the same fact in two
161    /// places, which is the shape of every bug where a target is configured half one way.
162    pub const fn as_str(self) -> &'static str {
163        match self {
164            Env::None => "",
165            Env::Gnu => "gnu",
166            Env::Musl => "musl",
167            Env::Msvc => "msvc",
168            Env::Android => "android",
169            Env::Simulator => "simulator",
170            Env::MacAbi => "macabi",
171        }
172    }
173
174    /// Whether this environment is a C library whose version the tuple may pin.
175    pub const fn is_libc(self) -> bool {
176        matches!(self, Env::Gnu | Env::Musl | Env::Msvc | Env::Android)
177    }
178
179    /// Whether a fully static link is supported.
180    ///
181    /// glibc technically permits one and it is a trap: `dlopen`, NSS and `iconv` all stop
182    /// working, quietly, at run time on the user's machine rather than at link time on ours.
183    /// `spec/cross-compile/10-runtime.md` section 10.6 says the driver warns instead of refusing, because a
184    /// program that uses none of those three is fine and it is not our place to say which.
185    pub const fn supports_static_link(self) -> bool {
186        matches!(self, Env::Musl | Env::None | Env::Msvc)
187    }
188
189    /// Whether a version attached to this environment is an API level rather than a release
190    /// number, which changes how it is compared and how the stub set is chosen.
191    pub const fn version_is_api_level(self) -> bool {
192        matches!(self, Env::Android)
193    }
194}
195
196impl fmt::Display for Env {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.write_str(self.as_str())
199    }
200}
201
202/// A version attached to an OS or to an environment.
203///
204/// Minor and patch are optional and absent is not zero. `macos.13` and `macos.13.0` are the
205/// same deployment target, and a version type that normalized one to the other would not round
206/// trip, so both spellings survive formatting and compare equal.
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
208pub struct Version {
209    major: u32,
210    minor: Option<u32>,
211    patch: Option<u32>,
212}
213
214impl Version {
215    /// A version with only a major component. `linux-gnu.2` is not useful and `wasip1` is
216    /// exactly this.
217    pub const fn major(major: u32) -> Self {
218        Version { major, minor: None, patch: None }
219    }
220
221    /// A two component version, which is what a glibc version is.
222    pub const fn new(major: u32, minor: u32) -> Self {
223        Version { major, minor: Some(minor), patch: None }
224    }
225
226    /// A three component version, which is what an SDK version is.
227    pub const fn full(major: u32, minor: u32, patch: u32) -> Self {
228        Version { major, minor: Some(minor), patch: Some(patch) }
229    }
230
231    /// The major component.
232    pub const fn major_part(self) -> u32 {
233        self.major
234    }
235
236    /// The minor component, if the tuple gave one.
237    pub const fn minor_part(self) -> Option<u32> {
238        self.minor
239    }
240
241    /// The patch component, if the tuple gave one.
242    pub const fn patch_part(self) -> Option<u32> {
243        self.patch
244    }
245
246    /// The version as a three component tuple with absent components read as zero, which is the
247    /// form to compare in. Do not use it to format: formatting from this loses the distinction
248    /// between `13` and `13.0`.
249    pub const fn to_triple(self) -> (u32, u32, u32) {
250        let minor = match self.minor {
251            Some(m) => m,
252            None => 0,
253        };
254        let patch = match self.patch {
255            Some(p) => p,
256            None => 0,
257        };
258        (self.major, minor, patch)
259    }
260
261    /// Whether this version is at least `other`, comparing on the zero filled form.
262    pub const fn at_least(self, other: Version) -> bool {
263        let (a0, a1, a2) = self.to_triple();
264        let (b0, b1, b2) = other.to_triple();
265        if a0 != b0 {
266            return a0 > b0;
267        }
268        if a1 != b1 {
269            return a1 > b1;
270        }
271        a2 >= b2
272    }
273
274    /// The LLVM spelling, which always has three components. `macosx13.0.0` rather than
275    /// `macos.13`.
276    pub fn to_llvm_string(self) -> String {
277        let (major, minor, patch) = self.to_triple();
278        format!("{major}.{minor}.{patch}")
279    }
280}
281
282impl PartialOrd for Version {
283    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
284        Some(self.cmp(other))
285    }
286}
287
288impl Ord for Version {
289    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
290        self.to_triple().cmp(&other.to_triple())
291    }
292}
293
294impl fmt::Display for Version {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        write!(f, "{}", self.major)?;
297        if let Some(minor) = self.minor {
298            write!(f, ".{minor}")?;
299            if let Some(patch) = self.patch {
300                write!(f, ".{patch}")?;
301            }
302        }
303        Ok(())
304    }
305}