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}