rucc_sysroot/layout.rs
1//! The directory layout of one target's sysroot, and the cache key that names it.
2//!
3//! Design: `spec/cross-compile/08-sysroots.md` sections 8.2 and 8.3.
4//!
5//! # Why the directories are split the way they are
6//!
7//! Section 8.3 is about a multiplication. Headers are naively `arch x os x libc x libc-version`
8//! trees, which for glibc alone is eight architectures times six versions and several hundred
9//! megabytes, and `spec/cross-compile/13-distribution.md` has a size budget that number destroys.
10//!
11//! The fix is two splits that turn the product into a sum. The per version differences go inside
12//! the files as `#if __GLIBC_MINOR__ >= n`, so one tree serves every version. The per architecture
13//! differences stay in directories, because they are whole files rather than lines, but only for
14//! the small part of a libc that has any: `bits/` and a handful of others. Everything else is one
15//! copy.
16//!
17//! That is why a sysroot here has two include directories rather than one. [`Sysroot::arch_include`]
18//! holds the files that differ by architecture and is searched first, and
19//! [`Sysroot::generic_include`] holds the copy that every architecture shares.
20//!
21//! A Windows target has one rather than two, because mingw-w64's tree is the same files whatever
22//! the machine is and there is nothing for the first directory to hold. That is
23//! [`Sysroot::splits_by_arch`], and the reason it is a question about the target rather than a
24//! directory that happens to be empty is that the compiler prints what it searches.
25//!
26//! A Linux target searches four directories and not two, because the kernel's headers are a second
27//! pair with the same split and a different owner. They are [`Kernel`], their root is the cache
28//! rather than a sysroot, and the order is the libc's two and then the kernel's two, which is the
29//! order `zig cc -E -v` prints for a glibc target. `linux/` and `asm/` are nine megabytes of files
30//! that are the same for every target, so one tree is shared and only `asm/` is copied per
31//! architecture.
32//!
33//! # Why the root is a function of the tuple
34//!
35//! `spec/cross-compile/02-the-goal.md` claim 5 asks for byte identical output from different hosts.
36//! A sysroot that lands in a directory named after the host, or after the day it was built, or
37//! after a hash of an absolute path, breaks that before anything is compiled. So the root is the
38//! cache directory the caller chose plus the canonical spelling of the tuple, and nothing else.
39//!
40//! The canonical spelling is the key rather than a hash of it because it is already unique, it is
41//! already a legal directory name, and a cache a person can read is a cache a person can debug. It
42//! carries the whole ten field model, so `x86_64-linux-gnu` and `x86_64-linux-gnu.2.28` are
43//! different directories, which is the point of `env_version` being in the tuple at all.
44//!
45//! It is not the hash of the contents either, which is what
46//! `spec/cross-compile/13-distribution.md` section 13.2 asked for until tamnd/rucc#1021 settled it.
47//! A name cannot carry one: this function is what the producer calls to find out where to write
48//! files it has not written yet, so there are no contents to hash when the question is asked. The
49//! hash lives in the record instead, which is [`crate::Manifest::digest`], and the one thing the
50//! name has to be is the same on two hosts.
51
52use std::fmt;
53use std::path::{Path, PathBuf};
54
55use rucc_tuple::{Arch, DataModel, Endian, Env, Os, TargetTuple, Version};
56
57/// One target's sysroot: where its headers are, where its link inputs are, and where the record
58/// of what they are is.
59///
60/// Constructed rather than discovered. Nothing here checks that any of these directories exists,
61/// because the caller that is about to produce a sysroot needs the same answer as the caller that
62/// is about to read one, and a constructor that failed for an absent directory would give the
63/// first one nothing to create.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct Sysroot {
66 target: TargetTuple,
67 root: PathBuf,
68 stubs: PathBuf,
69 lib: PathBuf,
70}
71
72impl Sysroot {
73 /// The sysroot for this target inside this cache directory.
74 ///
75 /// The path is `<cache>/sysroots/<canonical tuple>`. Two hosts running this with the same
76 /// cache directory and the same target get the same path, which is what makes the tuple a
77 /// cache key and is the reason `spec/cross-compile/03-target-model.md` section 3.2 admits a
78 /// field only when it changes how a call is made or a struct is laid out.
79 #[must_use]
80 pub fn in_cache(cache: &Path, target: TargetTuple) -> Self {
81 let key = target.to_canonical_string();
82 let root = cache.join("sysroots").join(&key);
83 let stubs = cache.join("stubs").join(&key);
84 let lib = root.join("lib");
85 Sysroot { target, root, stubs, lib }
86 }
87
88 /// A sysroot rooted at a directory the user named, with `--sysroot` or `-isysroot`.
89 ///
90 /// The layout below the root is the same, so a user who assembled a tree the way we lay one
91 /// out is served by every other method here. A user who did not is served by
92 /// [`Options::sysroot`](crate::Options::sysroot), which replaces step 3 of section 8.5
93 /// wholesale rather than assuming a shape.
94 #[must_use]
95 pub fn at(root: PathBuf, target: TargetTuple) -> Self {
96 let stubs = root.join("lib");
97 let lib = stubs.clone();
98 Sysroot { target, root, stubs, lib }
99 }
100
101 /// The same sysroot with its link inputs in `dir` rather than in `lib`.
102 ///
103 /// For a tree with a directory of libraries for each target below `lib`, which is how the
104 /// wasi-sysroot of wasi-sdk is laid out: `lib/wasm32-wasip1/crt1-command.o` and
105 /// `lib/wasm32-wasip1/libc.a`. tamnd/rucc#3259.
106 #[must_use]
107 pub fn with_lib(self, dir: PathBuf) -> Self {
108 Sysroot { lib: dir, ..self }
109 }
110
111 /// The target this sysroot is for.
112 #[must_use]
113 pub const fn target(&self) -> TargetTuple {
114 self.target
115 }
116
117 /// The directory everything else here is under.
118 #[must_use]
119 pub fn root(&self) -> &Path {
120 &self.root
121 }
122
123 /// The cache key, which is the canonical spelling of the tuple.
124 ///
125 /// The whole tuple and not a summary of it. A key that dropped `env_version` would serve a
126 /// sysroot built against glibc 2.28 to a target that pinned 2.34, and the failure would be a
127 /// missing symbol at link time on one machine and not on another.
128 #[must_use]
129 pub fn cache_key(&self) -> String {
130 self.target.to_canonical_string()
131 }
132
133 /// The headers that differ by architecture, which are searched before the generic ones.
134 ///
135 /// Section 8.3's second split. For musl this is `bits/`, which is a few dozen small files
136 /// against a few hundred shared ones, so the copy per architecture is cheap and the
137 /// alternative of one whole tree per architecture is not.
138 #[must_use]
139 pub fn arch_include(&self) -> PathBuf {
140 self.root.join("include").join(self.header_arch())
141 }
142
143 /// The headers every architecture shares, which is almost all of them.
144 #[must_use]
145 pub fn generic_include(&self) -> PathBuf {
146 self.root.join("include").join("generic")
147 }
148
149 /// The libc's include directories, in search order, most specific first.
150 ///
151 /// Two rather than four. A Linux target also needs the kernel's own headers, which are
152 /// [`Kernel`] and are not under this root, because they are the same files for every target
153 /// that shares an architecture and carrying a copy of them per tuple is nine megabytes times
154 /// the size of the table.
155 ///
156 /// One rather than two for a Windows or a WASI target, which is [`Sysroot::splits_by_arch`].
157 #[must_use]
158 pub fn includes(&self) -> Vec<PathBuf> {
159 if self.splits_by_arch() {
160 vec![self.arch_include(), self.generic_include()]
161 } else {
162 vec![self.generic_include()]
163 }
164 }
165
166 /// Whether this target's headers are split by architecture at all.
167 ///
168 /// Section 8.3's second split is a libc's, and mingw-w64 has not got one. Measured rather than
169 /// assumed: installing mingw-w64 14.0.0's headers for `x86_64-w64-mingw32`,
170 /// `i686-w64-mingw32` and `aarch64-w64-mingw32` gives three trees of 1702 files that `diff -rq`
171 /// finds no difference between, because what a Windows header would branch on is the API level
172 /// the program asked for with `_WIN32_WINNT` rather than the machine it is being compiled for.
173 ///
174 /// So a Windows sysroot has `include/generic` and nothing beside it, and this is what keeps the
175 /// search path from naming a directory that a producer was never going to write. A path that is
176 /// simply absent would cost nothing at lookup time, which is why this is about honesty rather
177 /// than about speed: `-print-search-dirs` and `-E -v` print what is searched, and a directory
178 /// nobody publishes is a line that sends a person looking for a tree that does not exist.
179 ///
180 /// A WASI target has one tree for the same reason in other words. wasi-sdk keeps one header
181 /// directory for each preview and there is one architecture, so `bin/wasi-sysroot` in
182 /// `tamnd/rucc-cross` has nothing to put on the architecture side of the split.
183 #[must_use]
184 pub fn splits_by_arch(&self) -> bool {
185 !matches!(self.target.os(), Os::Windows | Os::Wasi)
186 }
187
188 /// The link inputs: the start files, the libc archive or its generated stubs, and the
189 /// compiler's own runtime for this target.
190 #[must_use]
191 pub fn lib(&self) -> PathBuf {
192 self.lib.clone()
193 }
194
195 /// Where the stub libraries a glibc link reads are, which is `<cache>/stubs/<canonical tuple>`
196 /// for a sysroot in the cache and [`Sysroot::lib`] for one the user named.
197 ///
198 /// Beside the sysroot rather than inside it, because the sysroot is a fetched archive whose
199 /// every file is in its manifest with a hash, and the stubs are written by the compiler out of
200 /// the description it carries, so they would be files the manifest does not know about. A tree
201 /// somebody assembled has its own `libc.so`, and that is where it is.
202 ///
203 /// The same key as the sysroot, release included, because a stub cut at glibc 2.28 and one cut
204 /// at 2.39 are different files and a program linked against the wrong one either runs on a
205 /// machine it should not or refuses one it could.
206 #[must_use]
207 pub fn stubs(&self) -> &Path {
208 &self.stubs
209 }
210
211 /// The manifest naming every input with its source, its hash and its licence.
212 ///
213 /// A file rather than a directory, and at the top rather than beside the libraries, because
214 /// the thing a person does with it is read it first.
215 #[must_use]
216 pub fn manifest_path(&self) -> PathBuf {
217 self.root.join("manifest")
218 }
219
220 /// The name the target's libc gives to its per architecture header directory.
221 ///
222 /// Not the architecture component of the canonical tuple, which carries a baseline the headers
223 /// do not care about: `armv7a-linux-musleabihf` and `armv5te-linux-musleabi` read the same
224 /// `arm` directory, because a header does not know which instructions the chip has. 32-bit x86
225 /// is `i386` in musl's source tree whatever the tuple spells it.
226 ///
227 /// # Why the libc is part of the answer
228 ///
229 /// The two libcs do not split their headers at the same place, and the name has to follow the
230 /// libc rather than a scheme of ours, because the producer installs what the libc's own build
231 /// system installs and the compiler has to look where that put it.
232 ///
233 /// musl splits per architecture and per ABI, which is what `arch/` in its source tree is, so
234 /// `x86_64`, `i386` and `x32` are three directories. glibc splits per architecture family and
235 /// handles the rest inside the files: one `x86` directory serves i386, x86-64 and x32, and 22
236 /// of the 31 files in its `bits/` branch on `__x86_64__`, `__ILP32__` or `__WORDSIZE` to do it,
237 /// starting with `bits/wordsize.h`. Checked against Zig 0.16, which ships twelve glibc
238 /// directories named after families and seventeen musl directories named after architectures.
239 ///
240 /// # The rule this is here to enforce
241 ///
242 /// An ILP32 ABI on a 64-bit architecture cannot read the LP64 headers. Every type that carries
243 /// a pointer or a `long` is a different size, and `x86_64-linux-gnux32` is the row that proves
244 /// it. For musl that is a separate directory, which is what the suffix below is. For glibc it
245 /// is a branch inside glibc's own files, so the directory is shared and the thing that checks
246 /// it is section 8.4's structural equivalence corpus rather than a path.
247 #[must_use]
248 pub fn header_arch(&self) -> &'static str {
249 if self.target.env() == Env::Gnu {
250 return self.header_family();
251 }
252 let narrow = self.target.data_model() == DataModel::Ilp32On64;
253 match (self.target.arch(), narrow) {
254 // x32 is what everyone calls it, including musl and glibc, so it does not get the
255 // suffix the rule below would give it.
256 (Arch::X86_64, true) => "x32",
257 (Arch::X86_64, false) => "x86_64",
258 (Arch::X86, _) => "i386",
259 (Arch::Aarch64 | Arch::Arm64Ec, true) => "aarch64_ilp32",
260 (Arch::Aarch64 | Arch::Arm64Ec, false) => "aarch64",
261 (Arch::Arm, _) => "arm",
262 (Arch::Riscv64, true) => "riscv64_ilp32",
263 (Arch::Riscv64, false) => "riscv64",
264 (Arch::Riscv32, _) => "riscv32",
265 (Arch::S390x, true) => "s390x_ilp32",
266 (Arch::S390x, false) => "s390x",
267 (Arch::PowerPc64, true) => "powerpc64_ilp32",
268 (Arch::PowerPc64, false) => "powerpc64",
269 (Arch::LoongArch64, true) => "loongarch64_ilp32",
270 (Arch::LoongArch64, false) => "loongarch64",
271 (Arch::Wasm32, _) => "wasm32",
272 }
273 }
274
275 /// The architecture family, which is how glibc names its per architecture header directory.
276 ///
277 /// The data model is not in it, on purpose, for the reason [`Sysroot::header_arch`] gives: the
278 /// family's files carry the branch themselves. `s390x` and `loongarch` are spelled the way
279 /// glibc's own `sysdeps` tree spells them, which is not the same shortening for both.
280 ///
281 /// # Why powerpc is the only family whose byte order is in the name
282 ///
283 /// The byte order is in the name exactly where the installed text depends on it, and that is one
284 /// family. Measured on glibc 2.44, by installing a family's headers twice with
285 /// `make install-headers` and diffing the two installs. `aarch64_be-linux-gnu` against
286 /// `aarch64-linux-gnu` is 474 files each and an empty diff, so one directory serves both orders.
287 /// `powerpc64-linux-gnu` against `powerpc64le-linux-gnu` is 474 files each and one file that
288 /// differs, `bits/long-double.h`, because little endian powerpc can redirect `long double` to
289 /// the float128 ABI and big endian powerpc cannot, so one install defines
290 /// `__LDOUBLE_REDIRECTS_TO_FLOAT128_ABI` as `(__LDBL_MANT_DIG__ == 113)` and the other defines
291 /// it as `0`. One directory for both orders would hand half of the powerpc rows a macro that is
292 /// wrong about their own ABI.
293 ///
294 /// The word size is not in the name, for powerpc either. The third run of the same experiment,
295 /// `powerpc-linux-gnu` against `powerpc64-linux-gnu` with the order held fixed, is 474 files
296 /// each and an empty diff, which is the x86 answer again: `bits/wordsize.h` is two files in
297 /// glibc's `sysdeps` tree for powerpc and they are byte identical, and both of them branch on
298 /// `__powerpc64__`. So the name is the family and the order and nothing else, which is why it is
299 /// `powerpc` and `powerpcle` rather than a spelling per width.
300 ///
301 /// `bits/endianness.h` is not the reason, which is worth saying because it reads like the
302 /// obvious one and tamnd/rucc#940 was written around it. glibc's copies of that file for arm,
303 /// aarch64 and powerpc branch on `__BIG_ENDIAN__` and `_BIG_ENDIAN` inside the file, the same
304 /// way `bits/wordsize.h` branches on `__x86_64__`, so both orders install the same text into it.
305 /// musl 1.2.5 does the same in `bits/alltypes.h` and `bits/signal.h` and ships no per order
306 /// directory under `arch/` at all, which is why [`Sysroot::header_arch`]'s musl names carry no
307 /// order either.
308 fn header_family(&self) -> &'static str {
309 match (self.target.arch(), self.target.endian()) {
310 (Arch::X86_64 | Arch::X86, _) => "x86",
311 // Arm64EC is a Windows ABI and never has glibc headers. It answers with the family it
312 // belongs to rather than with a word that is not a directory anywhere.
313 (Arch::Aarch64 | Arch::Arm64Ec, _) => "aarch64",
314 (Arch::Arm, _) => "arm",
315 (Arch::Riscv64 | Arch::Riscv32, _) => "riscv",
316 (Arch::S390x, _) => "s390x",
317 // `powerpc` is the big endian directory because that is the name glibc's own `sysdeps`
318 // tree uses and big endian is what the bare spelling means everywhere in this tuple
319 // model. `powerpcle` is the GNU spelling of the other one.
320 (Arch::PowerPc64, Endian::Big) => "powerpc",
321 (Arch::PowerPc64, Endian::Little) => "powerpcle",
322 (Arch::LoongArch64, _) => "loongarch",
323 // There is no glibc for wasm. The arm of the match exists because the type is closed
324 // and a wildcard here would quietly name a directory for a future architecture.
325 (Arch::Wasm32, _) => "wasm32",
326 }
327 }
328}
329
330/// The kernel's own headers, which are not the libc's and are shared by every target that can read
331/// them.
332///
333/// `linux/` and `asm/` are the system call interface rather than the C library, and a sysroot
334/// without them does not compile 31 of glibc's installed headers or 3 of musl's, `sys/quota.h` and
335/// `net/ethernet.h` among them. So they are part of what section 8.2 calls a sysroot even though no
336/// libc produced them.
337///
338/// # Why they are not under [`Sysroot`]
339///
340/// One tree serves every libc and every architecture except `asm/`, which is per architecture and
341/// small. Copying the shared part into each tuple's sysroot would be nine megabytes times the
342/// number of Linux rows in the table, for files that are identical in every copy. So the root is
343/// the cache directory rather than a sysroot, and a sysroot that was produced with it records the
344/// version in its manifest, which is [`crate::Manifest::kernel`] and the `kernel` line of the
345/// format. The tree carries its own record as well, headed `rucc kernel headers manifest 1`, because
346/// one tree serves every target and a sysroot manifest names one. Nothing checks the two against
347/// each other: a sysroot can be produced beside one tree and read beside another, and the manifest
348/// makes that visible rather than preventing it.
349///
350/// # Why the version is not in the path
351///
352/// The driver has to be able to compute this path before it reads anything, and a version in the
353/// path would mean asking the cache what it has before being able to ask where it is. It is the
354/// same decision [`Sysroot::in_cache`] makes about the libc version, where the tuple carries the
355/// version only because `env_version` is part of the target's identity, and the same gap: a cache
356/// populated by one release and read by the next gets whatever is there.
357///
358/// That gap does not close by putting a hash in a path, which is what
359/// `spec/cross-compile/13-distribution.md` section 13.2 used to say and what tamnd/rucc#1021
360/// settled: a path has to be known before anything has been read. What closes it is comparing a
361/// record against one somebody published, and the record says which release it was, here as the
362/// tree's own `manifest` and in a sysroot as [`crate::Manifest::kernel`].
363#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct Kernel {
365 arch: &'static str,
366 root: PathBuf,
367}
368
369impl Kernel {
370 /// The kernel headers in this cache directory for this target, when the target has any.
371 ///
372 /// [`None`] for everything that is not Linux with a libc we produce a tree for. Windows, the
373 /// BSDs and Darwin have their own system headers and no `linux/` at all, freestanding has no
374 /// system call interface by definition, and Android is Linux but bionic carries its own
375 /// scrubbed copy of the uapi headers, which is a different tree from this one and not a subset
376 /// of it.
377 #[must_use]
378 pub fn for_target(cache: &Path, target: TargetTuple) -> Option<Kernel> {
379 if target.os() != Os::Linux || !matches!(target.env(), Env::Gnu | Env::Musl) {
380 return None;
381 }
382 let arch = kernel_arch(target.arch())?;
383 Some(Kernel { arch, root: Kernel::in_cache(cache) })
384 }
385
386 /// Where the tree is under a cache directory, whichever target is asking.
387 ///
388 /// Separate from [`Kernel::for_target`] because an install puts the whole tree there and has no
389 /// target to ask with, and one place that spells the name is one place it can be wrong.
390 #[must_use]
391 pub fn in_cache(cache: &Path) -> PathBuf {
392 cache.join("kernel-headers")
393 }
394
395 /// The directory both of these are under.
396 #[must_use]
397 pub fn root(&self) -> &Path {
398 &self.root
399 }
400
401 /// `asm/`, which is the part of the interface that is per architecture.
402 ///
403 /// Named after the kernel's own architecture directory and not after ours or the libc's, which
404 /// is a third spelling of the same machine and the reason this is a method rather than a format
405 /// string at the call site.
406 #[must_use]
407 pub fn arch_include(&self) -> PathBuf {
408 self.root.join(self.arch)
409 }
410
411 /// `linux/`, `asm-generic/` and the rest, which are the same files for every architecture.
412 #[must_use]
413 pub fn generic_include(&self) -> PathBuf {
414 self.root.join("generic")
415 }
416
417 /// Both directories, in search order, most specific first.
418 #[must_use]
419 pub fn includes(&self) -> Vec<PathBuf> {
420 vec![self.arch_include(), self.generic_include()]
421 }
422
423 /// The kernel's name for this architecture.
424 #[must_use]
425 pub const fn arch(&self) -> &'static str {
426 self.arch
427 }
428}
429
430/// What `make headers_install ARCH=` takes, which is a third naming of the machine.
431///
432/// `arm64` rather than `aarch64` and `s390` rather than `s390x`, because those are the directories
433/// under `arch/` in the kernel's source tree, and the 31-bit s390 port leaving did not rename the
434/// one that stayed. One directory serves both widths of x86, of riscv and of powerpc, the same way
435/// glibc's family does and for the same reason: the uapi headers branch on the compiler's macros.
436///
437/// [`None`] for an architecture the kernel does not have, which is wasm.
438const fn kernel_arch(arch: Arch) -> Option<&'static str> {
439 match arch {
440 Arch::X86_64 | Arch::X86 => Some("x86"),
441 Arch::Aarch64 | Arch::Arm64Ec => Some("arm64"),
442 Arch::Arm => Some("arm"),
443 Arch::Riscv64 | Arch::Riscv32 => Some("riscv"),
444 Arch::S390x => Some("s390"),
445 Arch::PowerPc64 => Some("powerpc"),
446 Arch::LoongArch64 => Some("loongarch"),
447 Arch::Wasm32 => None,
448 }
449}
450
451/// Whether we can produce a sysroot for this target without the user fetching anything.
452///
453/// Section 8.2's table has seven rows and two of them are legal walls rather than engineering.
454/// The macOS SDK is restricted by the Xcode licence to Apple-branded hardware and the Windows SDK
455/// is not redistributable, so for those two the answer is a path the user supplies under their own
456/// licence, and `spec/cross-compile/13-distribution.md` owns the mechanism.
457///
458/// This returns false for those two and true for everything else, including freestanding, which
459/// needs nine compiler headers and no link inputs at all.
460///
461/// Which of the two walls a target is behind is [`crate::Wall`], and this is that question asked
462/// without caring about the answer. One of them is a predicate a producer filters a table with and
463/// the other is what a message has to say, and they are the same rule either way round.
464#[must_use]
465pub fn can_be_bundled(target: TargetTuple) -> bool {
466 crate::Wall::of(target).is_none()
467}
468
469/// The glibc our bundled header tree is derived from.
470///
471/// A fact about the tree and not a choice. `sysroots/manifest` in `tamnd/rucc-cross` pins the glibc
472/// source by version and hash, the tree is produced from that source, and this is that version. It
473/// moves when the pin moves and the two are checked against each other by the producer.
474pub const BUNDLED_GLIBC: Version = Version::new(2, 44);
475
476/// The `__GLIBC_MINOR__` a target gets when it is compiled against the bundled glibc tree.
477///
478/// Design: `spec/cross-compile/08-sysroots.md` section 8.3.
479///
480/// One tree serves every glibc release, with the differences written inside the files as
481/// `#if __GLIBC_MINOR__ >= n`, so the release is the part of it the target supplies. That is Zig's
482/// patch to the same tree and the same macro, which is where the spelling comes from: `features.h`
483/// keeps `__GLIBC__` at 2 and leaves the minor to the compiler, and `__GLIBC_PREREQ` reads both.
484///
485/// [`None`] for anything that is not glibc, because there is no such macro on musl or mingw and
486/// defining one would have every probe for it answer yes on a libc that does not have it.
487///
488/// The version is the one the tuple asked for, which is the point of `env_version` being in the
489/// tuple, and [`BUNDLED_GLIBC`] when it asked for nothing. Asking for an older release is how a
490/// program is kept off symbols and declarations the target's libc does not have, and it is honest
491/// only as far as the text goes: the declarations are guarded by the macro and the structure
492/// layouts in the same files are one release's. Issue #926's last box is where that is finished.
493/// The gap is too permissive rather than wrong about what it does say.
494///
495/// # Errors
496///
497/// A release newer than the tree, which is the one direction that cannot be approximated. Every
498/// `__GLIBC_PREREQ` in the program would answer yes and the declarations behind them would not be
499/// there, so the failure would be a missing declaration at best and a missing symbol at link time
500/// at worst. Both versions are in the error, because the two things a person can do about it are
501/// pin a release the tree has and name a sysroot that has the one they asked for, and neither is a
502/// choice they can make without being told which release the tree is.
503pub fn bundled_glibc_minor(target: TargetTuple) -> Result<Option<u32>, GlibcSkew> {
504 if target.os() != Os::Linux || target.env() != Env::Gnu {
505 return Ok(None);
506 }
507 let Some(asked) = target.env_version() else {
508 return Ok(Some(BUNDLED_GLIBC.minor_part().unwrap_or(0)));
509 };
510 // A glibc version is two components and a tuple will hold one or three, so a request this
511 // cannot read as a glibc release is a request for the tree's own version rather than an error:
512 // `gnu.2` is somebody naming the libc and not pinning it.
513 let Some(minor) = asked.minor_part() else {
514 return Ok(Some(BUNDLED_GLIBC.minor_part().unwrap_or(0)));
515 };
516 if asked.major_part() != BUNDLED_GLIBC.major_part()
517 || minor > BUNDLED_GLIBC.minor_part().unwrap_or(0)
518 {
519 return Err(GlibcSkew { asked, tree: BUNDLED_GLIBC });
520 }
521 Ok(Some(minor))
522}
523
524/// A glibc release the bundled tree cannot serve, and the release the tree is.
525///
526/// A type rather than a pair, because the two versions read the same way round in the message as
527/// they do here and a caller that swapped them would produce a diagnostic exactly as wrong as it is
528/// convincing.
529#[derive(Debug, Clone, Copy, PartialEq, Eq)]
530pub struct GlibcSkew {
531 /// What the target asked for.
532 pub asked: Version,
533 /// What the bundled tree is, which is [`BUNDLED_GLIBC`].
534 pub tree: Version,
535}
536
537impl fmt::Display for GlibcSkew {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 write!(
540 f,
541 "the target asked for glibc {}, and the bundled headers are glibc {}",
542 self.asked, self.tree
543 )
544 }
545}
546
547impl std::error::Error for GlibcSkew {}