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//! # Why the root is a function of the tuple
22//!
23//! `spec/cross-compile/02-the-goal.md` claim 5 asks for byte identical output from different hosts.
24//! A sysroot that lands in a directory named after the host, or after the day it was built, or
25//! after a hash of an absolute path, breaks that before anything is compiled. So the root is the
26//! cache directory the caller chose plus the canonical spelling of the tuple, and nothing else.
27//!
28//! The canonical spelling is the key rather than a hash of it because it is already unique, it is
29//! already a legal directory name, and a cache a person can read is a cache a person can debug. It
30//! carries the whole ten field model, so `x86_64-linux-gnu` and `x86_64-linux-gnu.2.28` are
31//! different directories, which is the point of `env_version` being in the tuple at all.
32
33use std::path::{Path, PathBuf};
34
35use rucc_tuple::{Arch, DataModel, Env, Os, TargetTuple};
36
37/// One target's sysroot: where its headers are, where its link inputs are, and where the record
38/// of what they are is.
39///
40/// Constructed rather than discovered. Nothing here checks that any of these directories exists,
41/// because the caller that is about to produce a sysroot needs the same answer as the caller that
42/// is about to read one, and a constructor that failed for an absent directory would give the
43/// first one nothing to create.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Sysroot {
46 target: TargetTuple,
47 root: PathBuf,
48}
49
50impl Sysroot {
51 /// The sysroot for this target inside this cache directory.
52 ///
53 /// The path is `<cache>/sysroots/<canonical tuple>`. Two hosts running this with the same
54 /// cache directory and the same target get the same path, which is what makes the tuple a
55 /// cache key and is the reason `spec/cross-compile/03-target-model.md` section 3.2 admits a
56 /// field only when it changes how a call is made or a struct is laid out.
57 #[must_use]
58 pub fn in_cache(cache: &Path, target: TargetTuple) -> Self {
59 let root = cache.join("sysroots").join(target.to_canonical_string());
60 Sysroot { target, root }
61 }
62
63 /// A sysroot rooted at a directory the user named, with `--sysroot` or `-isysroot`.
64 ///
65 /// The layout below the root is the same, so a user who assembled a tree the way we lay one
66 /// out is served by every other method here. A user who did not is served by
67 /// [`Options::sysroot`](crate::Options::sysroot), which replaces step 3 of section 8.5
68 /// wholesale rather than assuming a shape.
69 #[must_use]
70 pub fn at(root: PathBuf, target: TargetTuple) -> Self {
71 Sysroot { target, root }
72 }
73
74 /// The target this sysroot is for.
75 #[must_use]
76 pub const fn target(&self) -> TargetTuple {
77 self.target
78 }
79
80 /// The directory everything else here is under.
81 #[must_use]
82 pub fn root(&self) -> &Path {
83 &self.root
84 }
85
86 /// The cache key, which is the canonical spelling of the tuple.
87 ///
88 /// The whole tuple and not a summary of it. A key that dropped `env_version` would serve a
89 /// sysroot built against glibc 2.28 to a target that pinned 2.34, and the failure would be a
90 /// missing symbol at link time on one machine and not on another.
91 #[must_use]
92 pub fn cache_key(&self) -> String {
93 self.target.to_canonical_string()
94 }
95
96 /// The headers that differ by architecture, which are searched before the generic ones.
97 ///
98 /// Section 8.3's second split. For musl this is `bits/`, which is a few dozen small files
99 /// against a few hundred shared ones, so the copy per architecture is cheap and the
100 /// alternative of one whole tree per architecture is not.
101 #[must_use]
102 pub fn arch_include(&self) -> PathBuf {
103 self.root.join("include").join(self.header_arch())
104 }
105
106 /// The headers every architecture shares, which is almost all of them.
107 #[must_use]
108 pub fn generic_include(&self) -> PathBuf {
109 self.root.join("include").join("generic")
110 }
111
112 /// Both include directories, in search order, most specific first.
113 #[must_use]
114 pub fn includes(&self) -> Vec<PathBuf> {
115 vec![self.arch_include(), self.generic_include()]
116 }
117
118 /// The link inputs: the start files, the libc archive or its generated stubs, and the
119 /// compiler's own runtime for this target.
120 #[must_use]
121 pub fn lib(&self) -> PathBuf {
122 self.root.join("lib")
123 }
124
125 /// The manifest naming every input with its source, its hash and its licence.
126 ///
127 /// A file rather than a directory, and at the top rather than beside the libraries, because
128 /// the thing a person does with it is read it first.
129 #[must_use]
130 pub fn manifest_path(&self) -> PathBuf {
131 self.root.join("manifest")
132 }
133
134 /// The name the target's libc gives to its per architecture header directory.
135 ///
136 /// Not the architecture component of the canonical tuple, which carries a baseline the headers
137 /// do not care about: `armv7a-linux-musleabihf` and `armv5te-linux-musleabi` read the same
138 /// `arm` directory, because a header does not know which instructions the chip has. 32-bit x86
139 /// is `i386` in every libc's source tree whatever the tuple spells it.
140 ///
141 /// The data model is here because an ILP32 ABI on a 64-bit architecture cannot read the LP64
142 /// tree. Every type in the headers that carries a pointer or a `long` is a different size, so
143 /// it gets its own directory, and `x86_64-linux-gnux32` is the row in the table that proves it.
144 /// Any future ILP32-on-64 row gets a suffixed name from the same rule rather than quietly
145 /// reading the LP64 headers, which is the failure this method is arranged to make impossible.
146 #[must_use]
147 pub fn header_arch(&self) -> &'static str {
148 let narrow = self.target.data_model() == DataModel::Ilp32On64;
149 match (self.target.arch(), narrow) {
150 // x32 is what everyone calls it, including musl and glibc, so it does not get the
151 // suffix the rule below would give it.
152 (Arch::X86_64, true) => "x32",
153 (Arch::X86_64, false) => "x86_64",
154 (Arch::X86, _) => "i386",
155 (Arch::Aarch64 | Arch::Arm64Ec, true) => "aarch64_ilp32",
156 (Arch::Aarch64 | Arch::Arm64Ec, false) => "aarch64",
157 (Arch::Arm, _) => "arm",
158 (Arch::Riscv64, true) => "riscv64_ilp32",
159 (Arch::Riscv64, false) => "riscv64",
160 (Arch::Riscv32, _) => "riscv32",
161 (Arch::S390x, true) => "s390x_ilp32",
162 (Arch::S390x, false) => "s390x",
163 (Arch::PowerPc64, true) => "powerpc64_ilp32",
164 (Arch::PowerPc64, false) => "powerpc64",
165 (Arch::LoongArch64, true) => "loongarch64_ilp32",
166 (Arch::LoongArch64, false) => "loongarch64",
167 (Arch::Wasm32, _) => "wasm32",
168 }
169 }
170}
171
172/// Whether we can produce a sysroot for this target without the user fetching anything.
173///
174/// Section 8.2's table has seven rows and two of them are legal walls rather than engineering.
175/// The macOS SDK is restricted by the Xcode licence to Apple-branded hardware and the Windows SDK
176/// is not redistributable, so for those two the answer is a path the user supplies under their own
177/// licence, and `spec/cross-compile/13-distribution.md` owns the mechanism.
178///
179/// This returns false for those two and true for everything else, including freestanding, which
180/// needs nine compiler headers and no link inputs at all.
181#[must_use]
182pub fn can_be_bundled(target: TargetTuple) -> bool {
183 !matches!(target.os(), Os::MacOs | Os::IOs) && target.env() != Env::Msvc
184}