cinrs_core/include.rs
1//! `#include` resolution: where a header is looked for, and what is bundled.
2//!
3//! # Search order
4//!
5//! `#include "name"` looks in
6//!
7//! 1. the directory of the file the directive is written in — for the macro's
8//! own text that is the directory of the invoking `.rs` file, and for a
9//! header it is the directory that header was found in;
10//! 2. the configured include directories, in the order [`SearchPaths`]
11//! describes;
12//! 3. the working directory, but only when the name *is* a path — when it
13//! holds a directory separator. That is what makes `#include __FILE__`
14//! work: [`Resolved::name`] is written relative to the working directory
15//! wherever it can be (a diagnostic naming an absolute path is a
16//! diagnostic that differs between two machines), so a header that
17//! includes itself by `__FILE__` is asking for
18//! `some/dir/thing.h` from a directive written *in* `some/dir`, which
19//! neither of the first two steps will find. A bare name is deliberately
20//! left out of this step, so that a `stdio.h` sitting in the working
21//! directory never shadows the bundled one;
22//! 4. the [bundled headers](bundled);
23//! 5. the platform's own directories — `/usr/include` and friends — but only
24//! when [the switch](System) is on.
25//!
26//! `#include <name>` skips steps 1 and 3. A name that is absolute is used as
27//! it stands.
28//!
29//! # The platform's own directories
30//!
31//! Step 5 is **off by default**, and everything above it is enough for a
32//! self-contained, target-model-portable unit. A real `<stdio.h>` is not
33//! plain C: glibc's is a thicket of `__attribute__`, `__extension__`,
34//! `__asm__` renaming and compiler builtins, and its layouts are the host's
35//! rather than the [target model](crate::target)'s. So `cinrs` ships its own
36//! small, plain-C99 declarations of the standard library: they declare exactly
37//! what the platform's real library exports, the linker binds the calls to the
38//! real implementation, and the C that uses them is ordinary C.
39//!
40//! **The bundled set is ISO C, plus what only the compiler can provide.**
41//! Everything in [`BUNDLED`] is a header the C standard describes, with two
42//! groups of exceptions: `<alloca.h>`, because `alloca` is implemented by this
43//! crate rather than by any library, and the Intel intrinsics headers —
44//! `<immintrin.h>`, `<xmmintrin.h>` and the rest — because `__m128i` and
45//! `_mm_add_epi32` are the compiler's too. Neither has a library behind it, so
46//! the platform's copy would have nothing to add and everything to break: a
47//! real `<immintrin.h>` is a thicket of `__attribute__((vector_size))` and
48//! `__builtin_ia32_*`, and this one is prototypes that
49//! [`crate::x86`] maps onto `core::arch`.
50//! POSIX is the platform's: `<unistd.h>`, `<fcntl.h>`, `<sys/types.h>`,
51//! `<pthread.h>`, `<sys/stat.h>` and the rest come from the platform's own
52//! directories, complete and consistent with each other, once the switch below
53//! is on. (Up to 0.1.0 four small POSIX headers were bundled too; they were
54//! incomplete — no `access`, no `fsync`, no `struct flock` — and a program that
55//! turned the platform on still got the bundled ones, which is the opposite of
56//! what it asked for.)
57//!
58//! What the bundled set cannot give is POSIX, and the things whose *layout*
59//! only the platform knows — `struct stat`, `DIR`, `pthread_mutex_t`, the real
60//! `FILE` — so a program that needs those turns the switch on with
61//! `#pragma cinrs system_include` (or `CINRS_SYSTEM_INCLUDE=1` in the
62//! environment, which is the crate-wide default the pragma overrides). With
63//! [`System::Last`] the bundled headers still win, and only a name they do not
64//! carry reaches the platform; with [`System::First`] the platform's copy of
65//! every header wins, which is what makes `FILE` the real `struct _IO_FILE`.
66//! The bundled ISO headers guard the types and macros a platform header would
67//! also define with that platform's own guard macros, so that the two sets can
68//! be mixed in [`System::Last`] mode: see the comments in `include/time.h`.
69//!
70//! The directories searched are [`SYSTEM_PATH_ENV_VAR`] when it is set, and
71//! otherwise [`system_directories`]'s per-target default. **The compiler's own
72//! private directories are never among them**: GCC's and Clang's
73//! `.../include/{limits,stdint,stddef,stdarg}.h` chain to the next header of
74//! the same name with `#include_next` and expect their own compiler's
75//! builtins, and every one of those headers is bundled here anyway.
76//!
77//! Nothing found under step 5 is tracked for rebuilds: a system header is part
78//! of the machine rather than of the crate, and `include_str!`-ing
79//! `/usr/include/stdio.h` into the build would make every unit rebuild when the
80//! libc package is upgraded, which is not what the file identifies.
81
82use std::path::{Path, PathBuf};
83
84use crate::target::{Arch, Env, Os, TargetModel, TargetSource};
85
86/// The bundled standard headers, as `(name, text)` pairs.
87///
88/// They are compiled into the crate rather than installed anywhere, so no part
89/// of a build depends on where `cinrs` itself lives on disk.
90///
91/// Every one of them is a header ISO C describes, with four exceptions:
92/// `<alloca.h>` is here because `alloca` is generated by this crate rather than
93/// called in any library, `<mm_malloc.h>` because it is the compiler's own
94/// (GCC's and Clang's `<xmmintrin.h>` include it, and programs count on the
95/// `<stdlib.h>` it brings), in portable C over `malloc` because GCC's calls
96/// `posix_memalign`, which the Microsoft runtime lacks; the Intel intrinsics
97/// headers are here because
98/// `_mm_add_epi32` is generated too — as a call to `core::arch` — and
99/// `<cpuid.h>` is here because GCC's copy names rbx as an `asm` operand, which
100/// rustc refuses, and this one saves and restores it in the template instead.
101/// In each case the compiler's copy is not one `cinrs` could use.
102/// A POSIX header is *not* here — see the [module docs](self).
103pub const BUNDLED: &[(&str, &str)] = &[
104 ("alloca.h", include_str!("../include/alloca.h")),
105 ("assert.h", include_str!("../include/assert.h")),
106 ("avx2intrin.h", include_str!("../include/avx2intrin.h")),
107 (
108 "avx512bf16intrin.h",
109 include_str!("../include/avx512bf16intrin.h"),
110 ),
111 (
112 "avx512bf16vlintrin.h",
113 include_str!("../include/avx512bf16vlintrin.h"),
114 ),
115 (
116 "avx512bitalgintrin.h",
117 include_str!("../include/avx512bitalgintrin.h"),
118 ),
119 (
120 "avx512bitalgvlintrin.h",
121 include_str!("../include/avx512bitalgvlintrin.h"),
122 ),
123 (
124 "avx512bwintrin.h",
125 include_str!("../include/avx512bwintrin.h"),
126 ),
127 (
128 "avx512cdintrin.h",
129 include_str!("../include/avx512cdintrin.h"),
130 ),
131 (
132 "avx512dqintrin.h",
133 include_str!("../include/avx512dqintrin.h"),
134 ),
135 (
136 "avx512fintrin.h",
137 include_str!("../include/avx512fintrin.h"),
138 ),
139 (
140 "avx512fp16intrin.h",
141 include_str!("../include/avx512fp16intrin.h"),
142 ),
143 (
144 "avx512fp16vlintrin.h",
145 include_str!("../include/avx512fp16vlintrin.h"),
146 ),
147 (
148 "avx512ifmaintrin.h",
149 include_str!("../include/avx512ifmaintrin.h"),
150 ),
151 (
152 "avx512ifmavlintrin.h",
153 include_str!("../include/avx512ifmavlintrin.h"),
154 ),
155 (
156 "avx512vbmi2intrin.h",
157 include_str!("../include/avx512vbmi2intrin.h"),
158 ),
159 (
160 "avx512vbmi2vlintrin.h",
161 include_str!("../include/avx512vbmi2vlintrin.h"),
162 ),
163 (
164 "avx512vbmiintrin.h",
165 include_str!("../include/avx512vbmiintrin.h"),
166 ),
167 (
168 "avx512vbmivlintrin.h",
169 include_str!("../include/avx512vbmivlintrin.h"),
170 ),
171 (
172 "avx512vlbwintrin.h",
173 include_str!("../include/avx512vlbwintrin.h"),
174 ),
175 (
176 "avx512vldqintrin.h",
177 include_str!("../include/avx512vldqintrin.h"),
178 ),
179 (
180 "avx512vlintrin.h",
181 include_str!("../include/avx512vlintrin.h"),
182 ),
183 (
184 "avx512vnniintrin.h",
185 include_str!("../include/avx512vnniintrin.h"),
186 ),
187 (
188 "avx512vnnivlintrin.h",
189 include_str!("../include/avx512vnnivlintrin.h"),
190 ),
191 (
192 "avx512vp2intersectintrin.h",
193 include_str!("../include/avx512vp2intersectintrin.h"),
194 ),
195 (
196 "avx512vpopcntdqintrin.h",
197 include_str!("../include/avx512vpopcntdqintrin.h"),
198 ),
199 (
200 "avx512vpopcntdqvlintrin.h",
201 include_str!("../include/avx512vpopcntdqvlintrin.h"),
202 ),
203 (
204 "avxifmaintrin.h",
205 include_str!("../include/avxifmaintrin.h"),
206 ),
207 ("avxintrin.h", include_str!("../include/avxintrin.h")),
208 (
209 "avxvnniint16intrin.h",
210 include_str!("../include/avxvnniint16intrin.h"),
211 ),
212 (
213 "avxvnniint8intrin.h",
214 include_str!("../include/avxvnniint8intrin.h"),
215 ),
216 (
217 "avxvnniintrin.h",
218 include_str!("../include/avxvnniintrin.h"),
219 ),
220 ("complex.h", include_str!("../include/complex.h")),
221 ("cpuid.h", include_str!("../include/cpuid.h")),
222 ("ctype.h", include_str!("../include/ctype.h")),
223 ("emmintrin.h", include_str!("../include/emmintrin.h")),
224 ("errno.h", include_str!("../include/errno.h")),
225 ("f16cintrin.h", include_str!("../include/f16cintrin.h")),
226 ("float.h", include_str!("../include/float.h")),
227 ("gfniintrin.h", include_str!("../include/gfniintrin.h")),
228 ("immintrin.h", include_str!("../include/immintrin.h")),
229 ("inttypes.h", include_str!("../include/inttypes.h")),
230 ("iso646.h", include_str!("../include/iso646.h")),
231 ("limits.h", include_str!("../include/limits.h")),
232 ("math.h", include_str!("../include/math.h")),
233 ("mm_malloc.h", include_str!("../include/mm_malloc.h")),
234 ("mmintrin.h", include_str!("../include/mmintrin.h")),
235 ("nmmintrin.h", include_str!("../include/nmmintrin.h")),
236 ("pmmintrin.h", include_str!("../include/pmmintrin.h")),
237 ("popcntintrin.h", include_str!("../include/popcntintrin.h")),
238 ("setjmp.h", include_str!("../include/setjmp.h")),
239 ("sha512intrin.h", include_str!("../include/sha512intrin.h")),
240 ("signal.h", include_str!("../include/signal.h")),
241 ("sm3intrin.h", include_str!("../include/sm3intrin.h")),
242 ("sm4intrin.h", include_str!("../include/sm4intrin.h")),
243 ("smmintrin.h", include_str!("../include/smmintrin.h")),
244 ("stdalign.h", include_str!("../include/stdalign.h")),
245 ("stdarg.h", include_str!("../include/stdarg.h")),
246 ("stdatomic.h", include_str!("../include/stdatomic.h")),
247 ("stdbool.h", include_str!("../include/stdbool.h")),
248 ("stdckdint.h", include_str!("../include/stdckdint.h")),
249 ("stddef.h", include_str!("../include/stddef.h")),
250 ("stdint.h", include_str!("../include/stdint.h")),
251 ("stdio.h", include_str!("../include/stdio.h")),
252 ("stdlib.h", include_str!("../include/stdlib.h")),
253 ("stdnoreturn.h", include_str!("../include/stdnoreturn.h")),
254 ("string.h", include_str!("../include/string.h")),
255 ("threads.h", include_str!("../include/threads.h")),
256 ("time.h", include_str!("../include/time.h")),
257 ("tmmintrin.h", include_str!("../include/tmmintrin.h")),
258 ("uchar.h", include_str!("../include/uchar.h")),
259 ("vaesintrin.h", include_str!("../include/vaesintrin.h")),
260 (
261 "vpclmulqdqintrin.h",
262 include_str!("../include/vpclmulqdqintrin.h"),
263 ),
264 ("wchar.h", include_str!("../include/wchar.h")),
265 ("wctype.h", include_str!("../include/wctype.h")),
266 ("wmmintrin.h", include_str!("../include/wmmintrin.h")),
267 ("x86intrin.h", include_str!("../include/x86intrin.h")),
268 ("xmmintrin.h", include_str!("../include/xmmintrin.h")),
269];
270
271/// The directory the bundled headers appear to live in.
272///
273/// It is not a directory at all — the headers are strings inside this crate —
274/// but a diagnostic has to name the file it is talking about, and
275/// `<cinrs>/stdio.h` says both which header it is and that it is ours. The
276/// angle brackets keep it from being mistaken for a path that exists.
277pub const BUNDLED_DIR: &str = "<cinrs>";
278
279/// The text of a bundled header, by name.
280pub fn bundled(name: &str) -> Option<&'static str> {
281 BUNDLED
282 .iter()
283 .find(|(n, _)| *n == name)
284 .map(|(_, text)| *text)
285}
286
287/// The display name a bundled header is known by: `<cinrs>/stdio.h`.
288pub fn bundled_name(name: &str) -> String {
289 format!("{BUNDLED_DIR}/{name}")
290}
291
292/// The POSIX headers a program is likely to reach for, none of which is
293/// bundled.
294///
295/// The list exists for one diagnostic: a `#include <unistd.h>` with the
296/// platform's directories switched off is not a missing file but a missing
297/// *switch*, and saying so is the difference between a puzzle and an
298/// instruction. The first four were bundled up to 0.1.0, which is why they come
299/// first; the rest are the ones a search of any real code base turns up. It is
300/// deliberately not the whole of POSIX — a name that is not here just gets the
301/// ordinary "file not found", which is not wrong, only terser.
302pub const POSIX_HEADERS: &[&str] = &[
303 "unistd.h",
304 "fcntl.h",
305 "strings.h",
306 "sys/types.h",
307 "aio.h",
308 "arpa/inet.h",
309 "dirent.h",
310 "dlfcn.h",
311 "fnmatch.h",
312 "glob.h",
313 "grp.h",
314 "langinfo.h",
315 "libgen.h",
316 "monetary.h",
317 "net/if.h",
318 "netdb.h",
319 "netinet/in.h",
320 "netinet/tcp.h",
321 "nl_types.h",
322 "poll.h",
323 "pthread.h",
324 "pwd.h",
325 "regex.h",
326 "sched.h",
327 "semaphore.h",
328 "spawn.h",
329 "sys/ipc.h",
330 "sys/mman.h",
331 "sys/msg.h",
332 "sys/resource.h",
333 "sys/select.h",
334 "sys/sem.h",
335 "sys/shm.h",
336 "sys/socket.h",
337 "sys/stat.h",
338 "sys/statvfs.h",
339 "sys/time.h",
340 "sys/times.h",
341 "sys/uio.h",
342 "sys/un.h",
343 "sys/utsname.h",
344 "sys/wait.h",
345 "syslog.h",
346 "tar.h",
347 "termios.h",
348 "ulimit.h",
349 "utime.h",
350 "utmpx.h",
351 "wordexp.h",
352];
353
354/// Whether `name` is one of [`POSIX_HEADERS`].
355pub fn is_posix_header(name: &str) -> bool {
356 POSIX_HEADERS.contains(&name)
357}
358
359/// How the header name was spelled.
360#[derive(Clone, Copy, PartialEq, Eq, Debug)]
361pub enum Form {
362 /// `#include "name"`, which searches the including file's directory first.
363 Quoted,
364 /// `#include <name>`, which does not.
365 Angled,
366}
367
368/// The directory a file's own `#include "…"` searches first.
369#[derive(Clone, Debug, Default)]
370pub enum Origin {
371 /// A directory on disk. Empty means the current directory, which is what
372 /// the parent of a bare `lib.rs` comes out as.
373 Dir(PathBuf),
374 /// A directory on disk reached through one of the platform's own
375 /// directories — the including file is a *system* header.
376 ///
377 /// It searches that directory first, as any other file does, and its
378 /// `<…>` includes then go to the platform's directories **before** the
379 /// bundled ones whatever the mode is. That is what keeps the platform's
380 /// header set self-consistent: glibc's `<pthread.h>` gets glibc's
381 /// `<time.h>`, and so one `struct timespec` rather than two.
382 SystemDir(PathBuf),
383 /// The bundled set: one bundled header including another finds it there.
384 Bundled,
385 /// Nowhere — the compiler would not say where the including file is.
386 #[default]
387 Unknown,
388}
389
390/// Whether the platform's own include directories are searched, and where in
391/// the order they go.
392///
393/// Set by `#pragma cinrs system_include` in a unit and by
394/// [`SYSTEM_ENV_VAR`] across a crate; see the [module docs](self).
395#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
396pub enum System {
397 /// Not searched at all, which is where the switch starts.
398 #[default]
399 Off,
400 /// Searched after the bundled headers: the bundled `<stdio.h>` still wins,
401 /// and only a header cinrs does not carry — `<sys/stat.h>`, `<pthread.h>`
402 /// — comes from the platform. `#pragma cinrs system_include`.
403 Last,
404 /// Searched before them, so that the platform's copy of every header wins.
405 /// `#pragma cinrs system_include first`.
406 First,
407}
408
409impl System {
410 /// The value [`SYSTEM_ENV_VAR`] holds, read the way a build system spells
411 /// a boolean: `1`, `on`, `true` and `yes` for [`System::Last`], `first`
412 /// for [`System::First`], and `0`, `off`, `false`, `no` or nothing at all
413 /// for [`System::Off`]. Anything else is `None`, which the caller reports.
414 pub fn from_env_value(value: &str) -> Option<Self> {
415 match value.trim().to_ascii_lowercase().as_str() {
416 "" | "0" | "off" | "false" | "no" => Some(System::Off),
417 "1" | "on" | "true" | "yes" => Some(System::Last),
418 "first" => Some(System::First),
419 _ => None,
420 }
421 }
422
423 /// Whether the platform's directories are searched at all.
424 pub fn is_on(self) -> bool {
425 self != System::Off
426 }
427}
428
429/// The environment variable that turns the platform's directories on for a
430/// whole crate: `1` for [`System::Last`], `first` for [`System::First`].
431///
432/// A `#pragma cinrs system_include` in a unit overrides it.
433pub const SYSTEM_ENV_VAR: &str = "CINRS_SYSTEM_INCLUDE";
434
435/// The environment variable that *replaces* [`system_directories`]'s
436/// per-target default, split the way the platform splits `PATH`.
437///
438/// This is the only way to name the directories on a target whose default
439/// cinrs does not know — Apple's, whose SDK path is knowable only from
440/// `xcrun --show-sdk-path`, and Windows — and the only way to point a cross
441/// build at a sysroot.
442pub const SYSTEM_PATH_ENV_VAR: &str = "CINRS_SYSTEM_INCLUDE_PATH";
443
444/// One place a search looks, in the order it looks.
445///
446/// A [`Resolved`] records the entry it was found under so that an
447/// `#include_next` written inside it can go on from the entry *after* that
448/// one, which is the whole of GCC's semantics for the directive.
449#[derive(Clone, Debug, PartialEq, Eq)]
450pub enum Entry {
451 /// A configured directory: `#pragma cinrs include_path`,
452 /// [`crate::Options::include_paths`] or [`ENV_VAR`].
453 Dir(PathBuf),
454 /// The working directory, which only a quoted name that is itself a path
455 /// is looked for in; see the [module docs](self).
456 WorkingDir,
457 /// The [bundled headers](BUNDLED).
458 Bundled,
459 /// One of the platform's own directories.
460 System(PathBuf),
461}
462
463/// The include directories a unit searches, in the order it searches them.
464///
465/// The lists are kept apart so that the order is a decision rather than an
466/// accident: what the unit itself asks for wins over what the build asked for,
467/// which wins over what the environment asked for, which wins over what is
468/// bundled — and the platform's own directories are not there at all until
469/// [`SearchPaths::enable_system`] puts them there.
470#[derive(Clone, Debug, Default)]
471pub struct SearchPaths {
472 /// Directories from `#pragma cinrs include_path`, in the order written.
473 pragma: Vec<PathBuf>,
474 /// Directories from [`crate::Options::include_paths`].
475 options: Vec<PathBuf>,
476 /// Directories from the `CINRS_INCLUDE_PATH` environment variable.
477 env: Vec<PathBuf>,
478 /// The platform's own directories, empty while the switch is off.
479 system: Vec<PathBuf>,
480 /// Where those go, and whether they are searched at all.
481 mode: System,
482}
483
484/// The environment variable holding a global list of include directories.
485///
486/// Split the way the platform splits `PATH`: with `:` on Unix and `;` on
487/// Windows, through [`std::env::split_paths`].
488pub const ENV_VAR: &str = "CINRS_INCLUDE_PATH";
489
490/// The environment variable Cargo sets to the package's own directory, which
491/// is what a relative `#pragma cinrs include_path` is resolved against.
492pub const MANIFEST_DIR_VAR: &str = "CARGO_MANIFEST_DIR";
493
494impl SearchPaths {
495 /// The directories configured before preprocessing starts.
496 pub fn new(options: &[PathBuf]) -> Self {
497 Self {
498 pragma: Vec::new(),
499 options: options.to_vec(),
500 env: std::env::var_os(ENV_VAR)
501 .map(|value| std::env::split_paths(&value).collect())
502 .unwrap_or_default(),
503 system: Vec::new(),
504 mode: System::Off,
505 }
506 }
507
508 /// Adds a directory named by `#pragma cinrs include_path`.
509 ///
510 /// A relative path is resolved against `CARGO_MANIFEST_DIR` — the package
511 /// being compiled, which the procedural macro reads from the environment
512 /// of the `rustc` process Cargo started — so that a `c99!` block means the
513 /// same thing however the build was invoked. Without that variable (a unit
514 /// test, a hand-rolled `rustc`) the path is left as it stands, and is then
515 /// relative to the working directory.
516 pub fn add_pragma(&mut self, dir: &str) {
517 let path = Path::new(dir);
518 let resolved = if path.is_absolute() {
519 path.to_path_buf()
520 } else {
521 match std::env::var_os(MANIFEST_DIR_VAR) {
522 Some(root) => Path::new(&root).join(path),
523 None => path.to_path_buf(),
524 }
525 };
526 if !self.pragma.contains(&resolved) {
527 self.pragma.push(resolved);
528 }
529 }
530
531 /// Every configured directory, in search order — the ones a `#embed`
532 /// resource is looked for in, which is everything but the header steps.
533 fn dirs(&self) -> impl Iterator<Item = &PathBuf> {
534 self.pragma
535 .iter()
536 .chain(self.options.iter())
537 .chain(self.env.iter())
538 }
539
540 /// Puts the platform's own directories on the path.
541 ///
542 /// Idempotent in the mode that matters: turning the switch on twice with
543 /// the same directories changes nothing, and asking for `first` after
544 /// asking for the plain form moves them, which is what a unit that writes
545 /// both pragmas means.
546 pub fn enable_system(&mut self, mode: System, dirs: Vec<PathBuf>) {
547 self.mode = mode;
548 self.system = dirs;
549 }
550
551 /// Whether — and where — the platform's own directories are searched.
552 pub fn system_mode(&self) -> System {
553 self.mode
554 }
555
556 /// Every place a header is looked for, in the order it is looked for,
557 /// after the including file's own directory.
558 ///
559 /// This is the list `#include_next` walks: a header found under
560 /// entry *i* continues from entry *i + 1*.
561 ///
562 /// `from_system` says the directive was written *in* one of the platform's
563 /// own headers, which puts the platform's directories ahead of the bundled
564 /// ones however the switch was set; see [`Origin::SystemDir`].
565 pub fn entries(&self, from_system: bool) -> Vec<Entry> {
566 let mut entries: Vec<Entry> = self.dirs().cloned().map(Entry::Dir).collect();
567 entries.push(Entry::WorkingDir);
568 let system = self.system.iter().cloned().map(Entry::System);
569 match self.mode {
570 System::Off => entries.push(Entry::Bundled),
571 System::Last if !from_system => {
572 entries.push(Entry::Bundled);
573 entries.extend(system);
574 }
575 System::Last | System::First => {
576 entries.extend(system);
577 entries.push(Entry::Bundled);
578 }
579 }
580 entries
581 }
582}
583
584/// The platform's own include directories for `target`, when nothing named
585/// them.
586///
587/// [`SYSTEM_PATH_ENV_VAR`] takes priority over this and is checked first;
588/// what is left is the default, and there are only two rules to it.
589///
590/// **A cross build has no default.** The directories below belong to the
591/// machine the compiler is *running* on, and a `<sys/stat.h>` laid out for
592/// another architecture is worse than no `<sys/stat.h>` at all — its
593/// `struct stat` would have the wrong offsets and the program would read the
594/// wrong bytes. So a target that is not the host is an error naming
595/// [`SYSTEM_PATH_ENV_VAR`], which is how a sysroot is pointed at.
596///
597/// **Only the C library's directories, never the compiler's.** On Linux that
598/// is `/usr/local/include`, the multiarch directory
599/// (`/usr/include/x86_64-linux-gnu`, and its like, added only when it exists)
600/// and `/usr/include`. GCC's `/usr/lib/gcc/*/include` and Clang's
601/// `/usr/lib/clang/*/include` are deliberately absent: their `limits.h`,
602/// `stdint.h`, `stddef.h` and `stdarg.h` are that compiler's own, chain onward
603/// with `#include_next`, and are bundled here anyway.
604///
605/// **Apple's default is asked of `xcrun`.** The SDK moves with Xcode, so there
606/// is no path to hard-code; `xcrun --show-sdk-path` is the one thing that knows
607/// it, and `apple_sdk_include` below runs it once per process. A macOS user who
608/// writes `#pragma cinrs system_include` therefore gets `<unistd.h>` the way a
609/// Linux user does. When `xcrun` is missing or says nothing — no Xcode, no
610/// command-line tools — the answer is the same error as before, naming
611/// [`SYSTEM_PATH_ENV_VAR`].
612///
613/// Windows and everything else has no default, because there is no such
614/// convention to follow.
615pub fn system_directories(
616 target: &TargetModel,
617 source: &TargetSource,
618) -> Result<Vec<PathBuf>, String> {
619 if let Some(value) = std::env::var_os(SYSTEM_PATH_ENV_VAR) {
620 let dirs: Vec<PathBuf> = std::env::split_paths(&value)
621 .filter(|dir| !dir.as_os_str().is_empty())
622 .collect();
623 if !dirs.is_empty() {
624 return Ok(dirs);
625 }
626 }
627 if *target != TargetModel::host() {
628 let named = match source.triple() {
629 Some(triple) => format!("'{triple}'"),
630 None => "another machine".to_owned(),
631 };
632 return Err(format!(
633 "the platform's include directories are the *host*'s, and this unit is being \
634 translated for {named}; a header laid out for another machine is worse than none, \
635 so point {SYSTEM_PATH_ENV_VAR} at the target's sysroot include directories — or \
636 leave the system headers switched off for this target"
637 ));
638 }
639 match target.os {
640 Os::Linux => {
641 let mut dirs = vec![PathBuf::from("/usr/local/include")];
642 for tuple in multiarch_tuples(target) {
643 let dir = PathBuf::from(format!("/usr/include/{tuple}"));
644 if dir.is_dir() {
645 dirs.push(dir);
646 }
647 }
648 dirs.push(PathBuf::from("/usr/include"));
649 Ok(dirs)
650 }
651 Os::Darwin => match apple_sdk_include() {
652 Some(dir) => Ok(vec![dir]),
653 None => Err(format!(
654 "on Apple's platforms the C library's headers live inside the SDK, and \
655 'xcrun --show-sdk-path' — the one thing that knows where it is — is not \
656 installed or answered nothing, so cinrs has no directory to offer: set \
657 {SYSTEM_PATH_ENV_VAR} to \"$(xcrun --show-sdk-path)/usr/include\""
658 )),
659 },
660 os => Err(format!(
661 "cinrs has no default include directories for {}; set {SYSTEM_PATH_ENV_VAR} to the \
662 directories the platform's headers live in",
663 os.as_str()
664 )),
665 }
666}
667
668/// `$(xcrun --show-sdk-path)/usr/include`, asked once per process.
669///
670/// Apple's C library headers live inside whichever SDK Xcode is pointing at,
671/// and `xcrun` is the only thing that knows which. A procedural macro is an
672/// ordinary program and may run one, so it does — once, because the answer
673/// cannot change while the process lives and because a unit may ask several
674/// times. `None` when there is no `xcrun`, when it fails, when its output is
675/// not a directory, or when the `usr/include` inside it is not there: every one
676/// of those means "the caller should say to set the environment variable"
677/// rather than "use a path that is not there".
678///
679/// The whole of the Darwin behaviour is in this one function so that the part
680/// a Linux machine cannot test is as small as it can be; [`system_directories`]
681/// is the part unit tests reach.
682fn apple_sdk_include() -> Option<PathBuf> {
683 static SDK: std::sync::OnceLock<Option<PathBuf>> = std::sync::OnceLock::new();
684 SDK.get_or_init(|| {
685 let output = std::process::Command::new("xcrun")
686 .arg("--show-sdk-path")
687 .output()
688 .ok()?;
689 if !output.status.success() {
690 return None;
691 }
692 let path = String::from_utf8(output.stdout).ok()?;
693 let path = path.trim();
694 if path.is_empty() {
695 return None;
696 }
697 let include = Path::new(path).join("usr").join("include");
698 include.is_dir().then_some(include)
699 })
700 .clone()
701}
702
703/// The multiarch directory names a Linux target's headers may live under,
704/// most specific first.
705///
706/// Debian's layout, which Ubuntu and a good many others follow: the tuple is
707/// `<arch>-linux-<env>`, with the architecture spelled the way `dpkg` spells
708/// it rather than the way the triple does — `i386` for 32-bit x86,
709/// `powerpc64le` for little-endian 64-bit PowerPC. Only a directory that
710/// really exists is used, which is what lets the Arm entries name both the
711/// hard-float and the soft-float spelling without guessing.
712fn multiarch_tuples(target: &TargetModel) -> Vec<String> {
713 let env = match target.env {
714 Env::Musl => "musl",
715 Env::Bionic => "android",
716 Env::Uclibc => "uclibc",
717 // A Linux triple that names no environment is glibc; see `Env`.
718 _ => "gnu",
719 };
720 let archs: &[&str] = match target.arch {
721 Arch::X86_64 => &["x86_64"],
722 Arch::X86 => &["i386"],
723 Arch::Aarch64 => &["aarch64"],
724 Arch::Arm => &["arm"],
725 Arch::Riscv32 => &["riscv32"],
726 Arch::Riscv64 => &["riscv64"],
727 Arch::PowerPc => &["powerpc"],
728 Arch::PowerPc64 => {
729 if target.big_endian {
730 &["powerpc64"]
731 } else {
732 &["powerpc64le"]
733 }
734 }
735 Arch::S390x => &["s390x"],
736 Arch::Mips => {
737 if target.big_endian {
738 &["mips"]
739 } else {
740 &["mipsel"]
741 }
742 }
743 Arch::Mips64 => {
744 if target.big_endian {
745 &["mips64"]
746 } else {
747 &["mips64el"]
748 }
749 }
750 Arch::Sparc => &["sparc"],
751 Arch::Sparc64 => &["sparc64"],
752 Arch::LoongArch64 => &["loongarch64"],
753 // Nothing installs headers under a wasm tuple.
754 Arch::Wasm32 => &[],
755 };
756 // Arm's ABI is part of the tuple and the model does not carry it, so both
757 // spellings are offered and the one that exists is taken.
758 let suffixes: &[&str] = if target.arch == Arch::Arm && env == "gnu" {
759 &["eabihf", "eabi"]
760 } else {
761 &[""]
762 };
763 let mut tuples = Vec::new();
764 for arch in archs {
765 for suffix in suffixes {
766 tuples.push(format!("{arch}-linux-{env}{suffix}"));
767 }
768 }
769 tuples
770}
771
772/// A header that was found.
773#[derive(Clone, Debug)]
774pub struct Resolved {
775 /// The name diagnostics call it: a path for a file on disk, and
776 /// `<cinrs>/stdio.h` for a bundled header.
777 pub name: String,
778 /// Its contents.
779 pub text: String,
780 /// Where its own `#include "…"` looks first.
781 pub origin: Origin,
782 /// What identifies it for `#pragma once` and the include-guard
783 /// optimisation: the canonical path of the file, or the bundled name.
784 pub key: String,
785 /// The absolute path of the file, for rebuild tracking. `None` for a
786 /// bundled header, which cannot change without the crate changing, and for
787 /// a header taken from one of the platform's own directories, which is
788 /// part of the machine rather than of the crate.
789 pub path: Option<PathBuf>,
790 /// The [entry](Entry) it was found under, which is where an
791 /// `#include_next` written inside it goes on *after*. `None` when it was
792 /// not found by a search at all: an absolute name, or the including file's
793 /// own directory.
794 pub found_in: Option<Entry>,
795 /// Whether it came from one of the platform's own directories.
796 pub system: bool,
797}
798
799impl Resolved {
800 /// Marks a header as one of the platform's own.
801 ///
802 /// Two things follow, and they are the whole of what "system header" means
803 /// here: its own `<…>` includes prefer the platform's directories, so that
804 /// the platform's header set stays self-consistent; and it is not tracked
805 /// for rebuilds, because it belongs to the machine rather than to the
806 /// crate.
807 fn make_system(&mut self) {
808 self.system = true;
809 self.path = None;
810 if let Origin::Dir(dir) = std::mem::take(&mut self.origin) {
811 self.origin = Origin::SystemDir(dir);
812 }
813 }
814}
815
816/// Why a header could not be included.
817#[derive(Clone, Debug)]
818pub enum Error {
819 /// Nothing of that name is anywhere the unit searches.
820 NotFound {
821 /// The directories that were looked in, in order, as a reader can
822 /// check them.
823 searched: Vec<String>,
824 },
825 /// A file of that name is there, but could not be read: no permission, not
826 /// UTF-8, gone between the test and the read.
827 Unreadable {
828 /// The file that could not be read.
829 path: String,
830 /// What the operating system said about it.
831 error: String,
832 },
833}
834
835/// Looks a header name up.
836pub fn resolve(
837 name: &str,
838 form: Form,
839 origin: &Origin,
840 paths: &SearchPaths,
841) -> Result<Resolved, Error> {
842 let mut searched: Vec<String> = Vec::new();
843
844 // An absolute name is not searched for: it either is the file or it is
845 // nothing.
846 if Path::new(name).is_absolute() {
847 return absolute(name, origin);
848 }
849
850 if form == Form::Quoted {
851 match origin {
852 Origin::Dir(dir) | Origin::SystemDir(dir) => {
853 if let Some(mut found) = read_file(&dir.join(name))? {
854 // A header beside a system header is a system header too:
855 // `bits/types.h` is reached that way, and it must not
856 // suddenly start preferring the bundled set.
857 if let Origin::SystemDir(_) = origin {
858 found.make_system();
859 }
860 return Ok(found);
861 }
862 searched.push(display_dir(dir));
863 }
864 Origin::Bundled => {
865 if let Some(found) = read_bundled(name) {
866 return Ok(found);
867 }
868 searched.push(BUNDLED_DIR.to_owned());
869 }
870 Origin::Unknown => {}
871 }
872 }
873
874 walk(name, form, paths, from_system(origin), 0, searched)
875}
876
877/// Whether the file writing the directive is one of the platform's own.
878fn from_system(origin: &Origin) -> bool {
879 matches!(origin, Origin::SystemDir(_))
880}
881
882/// A header named by an absolute path, which is not searched for: it either is
883/// the file or it is nothing.
884///
885/// One header of the platform's naming another by its full path keeps the set
886/// together, exactly as a relative name found beside it does.
887fn absolute(name: &str, origin: &Origin) -> Result<Resolved, Error> {
888 match read_file(Path::new(name))? {
889 Some(mut found) => {
890 if from_system(origin) {
891 found.make_system();
892 }
893 Ok(found)
894 }
895 None => Err(Error::NotFound {
896 searched: vec![display_path(Path::new(name))],
897 }),
898 }
899}
900
901/// `#include_next <name>`: the same search, taken up again at the entry
902/// *after* the one the file writing the directive was found under.
903///
904/// GCC's semantics exactly, and the reason the directive exists: a platform's
905/// `<limits.h>` finishes with `#include_next <limits.h>` to reach the *next*
906/// `limits.h` on the path rather than itself. `current` is
907/// [`Resolved::found_in`] of the file the directive is written in; a file that
908/// was not found by a search at all — the unit's own text, or a header taken
909/// from the including file's directory — has no entry to go on from, and GCC
910/// starts such a search at the beginning, which is what `None` does here.
911///
912/// The quoted and the angled forms mean the same thing, as they do in GCC: the
913/// including file's own directory is never step one of an `#include_next`.
914pub fn resolve_next(
915 name: &str,
916 origin: &Origin,
917 current: Option<&Entry>,
918 paths: &SearchPaths,
919) -> Result<Resolved, Error> {
920 if Path::new(name).is_absolute() {
921 return absolute(name, origin);
922 }
923 let system = from_system(origin);
924 let start = match current {
925 Some(entry) => paths
926 .entries(system)
927 .iter()
928 .position(|e| e == entry)
929 .map_or(0, |at| at + 1),
930 None => 0,
931 };
932 walk(name, Form::Angled, paths, system, start, Vec::new())
933}
934
935/// The part of the search that walks [`SearchPaths::entries`], from `start`.
936fn walk(
937 name: &str,
938 form: Form,
939 paths: &SearchPaths,
940 from_system: bool,
941 start: usize,
942 mut searched: Vec<String>,
943) -> Result<Resolved, Error> {
944 for entry in paths.entries(from_system).into_iter().skip(start) {
945 let shown = match &entry {
946 Entry::Dir(dir) | Entry::System(dir) => {
947 if let Some(mut found) = read_file(&dir.join(name))? {
948 if matches!(entry, Entry::System(_)) {
949 found.make_system();
950 }
951 found.found_in = Some(entry);
952 return Ok(found);
953 }
954 display_dir(dir)
955 }
956 // A name that is itself a path, taken from the working directory.
957 // This is what `#include __FILE__` needs: the name a header is
958 // known by is relative to the working directory, so a header that
959 // includes itself asks for `some/dir/thing.h` from inside
960 // `some/dir`, which neither the origin nor a `-I` will resolve. A
961 // bare header name is not looked for here, so nothing in the
962 // working directory can shadow a bundled header.
963 Entry::WorkingDir => {
964 if form != Form::Quoted || !is_path(name) {
965 continue;
966 }
967 if let Some(mut found) = read_file(Path::new(name))? {
968 found.found_in = Some(entry);
969 return Ok(found);
970 }
971 display_dir(Path::new(""))
972 }
973 Entry::Bundled => {
974 if let Some(mut found) = read_bundled(name) {
975 found.found_in = Some(entry);
976 return Ok(found);
977 }
978 BUNDLED_DIR.to_owned()
979 }
980 };
981 if !searched.contains(&shown) {
982 searched.push(shown);
983 }
984 }
985 Err(Error::NotFound { searched })
986}
987
988/// A resource `#embed` found.
989#[derive(Clone, Debug)]
990pub struct Embedded {
991 /// The name diagnostics call it.
992 pub name: String,
993 /// Its contents, byte for byte.
994 pub bytes: Vec<u8>,
995 /// Its absolute path, for rebuild tracking.
996 pub path: PathBuf,
997}
998
999/// Looks an `#embed` resource up (C23 6.10.3).
1000///
1001/// The search is `#include`'s with the two steps that are about *headers* left
1002/// out: there are no bundled resources — a picture is not a declaration — and
1003/// nothing is read from the working directory by a bare name. What is left is
1004/// the directory of the file the directive is written in, for the quoted form,
1005/// and then the include path.
1006pub fn resolve_embed(
1007 name: &str,
1008 form: Form,
1009 origin: &Origin,
1010 paths: &SearchPaths,
1011) -> Result<Embedded, Error> {
1012 let mut searched: Vec<String> = Vec::new();
1013
1014 if Path::new(name).is_absolute() {
1015 return match read_bytes(Path::new(name))? {
1016 Some(found) => Ok(found),
1017 None => Err(Error::NotFound {
1018 searched: vec![display_path(Path::new(name))],
1019 }),
1020 };
1021 }
1022
1023 if form == Form::Quoted {
1024 match origin {
1025 Origin::Dir(dir) | Origin::SystemDir(dir) => {
1026 if let Some(found) = read_bytes(&dir.join(name))? {
1027 return Ok(found);
1028 }
1029 searched.push(display_dir(dir));
1030 }
1031 // A bundled header's `#embed` has nowhere of its own to look, and
1032 // the platform's directories hold no resources either — `#embed`
1033 // is for a picture, not for a declaration.
1034 Origin::Bundled | Origin::Unknown => {}
1035 }
1036 }
1037
1038 for dir in paths.dirs() {
1039 if let Some(found) = read_bytes(&dir.join(name))? {
1040 return Ok(found);
1041 }
1042 let shown = display_dir(dir);
1043 if !searched.contains(&shown) {
1044 searched.push(shown);
1045 }
1046 }
1047
1048 // As for a header, a name that is itself a path is looked for from the
1049 // working directory, so that a resource named relative to it can be found
1050 // from a directive written elsewhere.
1051 if form == Form::Quoted && is_path(name) {
1052 if let Some(found) = read_bytes(Path::new(name))? {
1053 return Ok(found);
1054 }
1055 let shown = display_dir(Path::new(""));
1056 if !searched.contains(&shown) {
1057 searched.push(shown);
1058 }
1059 }
1060
1061 Err(Error::NotFound { searched })
1062}
1063
1064/// Reads a candidate resource, with [`read_file`]'s convention: `Ok(None)`
1065/// means there is no such file and the search goes on.
1066fn read_bytes(path: &Path) -> Result<Option<Embedded>, Error> {
1067 match std::fs::metadata(path) {
1068 Ok(meta) if meta.is_file() => {}
1069 _ => return Ok(None),
1070 }
1071 let bytes = std::fs::read(path).map_err(|error| Error::Unreadable {
1072 path: display_path(path),
1073 error: error.to_string(),
1074 })?;
1075 Ok(Some(Embedded {
1076 name: display_path(path),
1077 bytes,
1078 path: std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf()),
1079 }))
1080}
1081
1082/// Whether a header name names a directory as well as a file.
1083///
1084/// Both separators count on every platform: a `#include "sub/thing.h"` is
1085/// written with a forward slash in portable C whatever the host does with
1086/// them.
1087fn is_path(name: &str) -> bool {
1088 name.contains('/') || name.contains('\\')
1089}
1090
1091fn read_bundled(name: &str) -> Option<Resolved> {
1092 let text = bundled(name)?;
1093 let display = bundled_name(name);
1094 Some(Resolved {
1095 text: text.to_owned(),
1096 origin: Origin::Bundled,
1097 key: display.clone(),
1098 name: display,
1099 path: None,
1100 found_in: Some(Entry::Bundled),
1101 system: false,
1102 })
1103}
1104
1105/// Reads the file an `include_c99!("…")` names.
1106///
1107/// Not a search: the path was resolved against the directory of the invoking
1108/// `.rs` file before it got here, so it either is the translation unit or it is
1109/// nothing. What comes back is the same [`Resolved`] a header does — the
1110/// display name for diagnostics and for `__FILE__`, the text, the directory its
1111/// own `#include "…"` searches first, and the absolute path the expansion
1112/// tracks for rebuilds.
1113pub fn read_source(path: &Path) -> Result<Resolved, Error> {
1114 match read_file(path)? {
1115 Some(found) => Ok(found),
1116 None => Err(Error::NotFound {
1117 searched: vec![display_path(path)],
1118 }),
1119 }
1120}
1121
1122/// Reads a candidate path: `Ok(None)` when there is no such file, which means
1123/// the search goes on, and an error when there is one and it cannot be used,
1124/// which means it does not.
1125fn read_file(path: &Path) -> Result<Option<Resolved>, Error> {
1126 match std::fs::metadata(path) {
1127 Ok(meta) if meta.is_file() => {}
1128 // A directory of that name, or nothing at all: keep looking.
1129 _ => return Ok(None),
1130 }
1131 let text = std::fs::read_to_string(path).map_err(|error| Error::Unreadable {
1132 path: display_path(path),
1133 error: error.to_string(),
1134 })?;
1135 let absolute = std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf());
1136 let key = std::fs::canonicalize(path)
1137 .unwrap_or_else(|_| absolute.clone())
1138 .display()
1139 .to_string();
1140 Ok(Some(Resolved {
1141 name: display_path(path),
1142 text,
1143 origin: Origin::Dir(path.parent().unwrap_or(Path::new("")).to_path_buf()),
1144 key,
1145 path: Some(absolute),
1146 // Filled in by the search that found it; a file read by name — an
1147 // absolute `#include`, or `include_c99!` — was found by no search.
1148 found_in: None,
1149 system: false,
1150 }))
1151}
1152
1153/// How a path is written in a diagnostic.
1154///
1155/// Relative wherever it can be — a message naming
1156/// `/home/someone/crate/include/foo.h` is a message that differs between two
1157/// machines, which makes it useless in a test and noisy everywhere else — so a
1158/// path inside the working directory is shown relative to it.
1159pub fn display_path(path: &Path) -> String {
1160 let relative = std::env::current_dir()
1161 .ok()
1162 .and_then(|cwd| path.strip_prefix(&cwd).ok().map(Path::to_path_buf));
1163 relative
1164 .unwrap_or_else(|| path.to_path_buf())
1165 .display()
1166 .to_string()
1167}
1168
1169/// How a directory is written in the list a "not found" message shows.
1170fn display_dir(dir: &Path) -> String {
1171 let shown = display_path(dir);
1172 if shown.is_empty() {
1173 // `Path::parent` of a bare file name; the working directory.
1174 ".".to_owned()
1175 } else {
1176 shown
1177 }
1178}
1179
1180#[cfg(test)]
1181mod tests {
1182 use super::*;
1183
1184 #[test]
1185 fn every_bundled_header_is_listed_once_and_sorted() {
1186 let mut names: Vec<&str> = BUNDLED.iter().map(|(n, _)| *n).collect();
1187 let count = names.len();
1188 names.sort_unstable();
1189 names.dedup();
1190 assert_eq!(names.len(), count, "a header is listed twice");
1191 let listed: Vec<&str> = BUNDLED.iter().map(|(n, _)| *n).collect();
1192 assert_eq!(listed, names, "keep the table sorted by name");
1193 }
1194
1195 #[test]
1196 fn a_bundled_header_is_found_without_touching_the_disk() {
1197 let found = resolve(
1198 "stddef.h",
1199 Form::Angled,
1200 &Origin::Unknown,
1201 &SearchPaths::default(),
1202 )
1203 .expect("stddef.h is bundled");
1204 assert_eq!(found.name, "<cinrs>/stddef.h");
1205 assert!(found.path.is_none());
1206 assert!(found.text.contains("size_t"));
1207 }
1208
1209 #[test]
1210 fn a_quoted_name_that_is_a_path_is_taken_from_the_working_directory() {
1211 // What `#include __FILE__` in a header comes to: the name is a path
1212 // relative to the working directory, and the directive is written in
1213 // the directory that path leads to, so neither the origin nor a `-I`
1214 // resolves it.
1215 // `cargo test` runs with the package directory as the working
1216 // directory, and `include/` is where the bundled headers live as
1217 // files.
1218 let found = resolve(
1219 "include/stddef.h",
1220 Form::Quoted,
1221 &Origin::Dir(PathBuf::from("include")),
1222 &SearchPaths::default(),
1223 )
1224 .expect("the file is there, relative to the package directory");
1225 assert!(found.text.contains("size_t"));
1226 assert!(
1227 found.path.is_some(),
1228 "it is a real file, not the bundled one"
1229 );
1230 }
1231
1232 #[test]
1233 fn a_bare_name_in_the_working_directory_does_not_shadow_a_bundled_header() {
1234 // `Cargo.toml` is in the working directory and is not a header, but
1235 // the point is the rule: a name with no separator is never looked for
1236 // there, so the step cannot take a `stdio.h` somebody left lying
1237 // about in preference to ours.
1238 assert!(!is_path("stdio.h"));
1239 assert!(is_path("sub/stdio.h"));
1240 assert!(is_path("sub\\stdio.h"));
1241 }
1242
1243 #[test]
1244 fn a_missing_header_lists_where_it_looked() {
1245 let error = resolve(
1246 "nowhere.h",
1247 Form::Quoted,
1248 &Origin::Dir(PathBuf::from("src")),
1249 &SearchPaths::default(),
1250 )
1251 .expect_err("nothing is called nowhere.h");
1252 match error {
1253 Error::NotFound { searched } => assert_eq!(searched, ["src", "<cinrs>"]),
1254 other => panic!("expected a not-found error, got {other:?}"),
1255 }
1256 }
1257
1258 // -- the platform's own directories -------------------------------------
1259
1260 fn with_system(mode: System) -> SearchPaths {
1261 let mut paths = SearchPaths::default();
1262 paths.enable_system(mode, vec![PathBuf::from("/usr/include")]);
1263 paths
1264 }
1265
1266 #[test]
1267 fn the_switch_reads_the_spellings_a_build_system_uses() {
1268 assert_eq!(System::from_env_value("1"), Some(System::Last));
1269 assert_eq!(System::from_env_value(" YES "), Some(System::Last));
1270 assert_eq!(System::from_env_value("First"), Some(System::First));
1271 assert_eq!(System::from_env_value("0"), Some(System::Off));
1272 assert_eq!(System::from_env_value(""), Some(System::Off));
1273 assert_eq!(System::from_env_value("maybe"), None);
1274 assert!(!System::Off.is_on());
1275 assert!(System::Last.is_on() && System::First.is_on());
1276 }
1277
1278 #[test]
1279 fn the_mode_decides_where_the_platform_goes() {
1280 let system = Entry::System(PathBuf::from("/usr/include"));
1281 assert_eq!(
1282 SearchPaths::default().entries(false),
1283 [Entry::WorkingDir, Entry::Bundled],
1284 "off by default"
1285 );
1286 assert_eq!(
1287 with_system(System::Last).entries(false),
1288 [Entry::WorkingDir, Entry::Bundled, system.clone()]
1289 );
1290 assert_eq!(
1291 with_system(System::First).entries(false),
1292 [Entry::WorkingDir, system.clone(), Entry::Bundled]
1293 );
1294 // A directive written inside a system header prefers the platform
1295 // whatever the mode, so that glibc's `<pthread.h>` gets glibc's
1296 // `<time.h>` and there is one `struct timespec` rather than two.
1297 assert_eq!(
1298 with_system(System::Last).entries(true),
1299 [Entry::WorkingDir, system.clone(), Entry::Bundled]
1300 );
1301 assert_eq!(
1302 SearchPaths::default().entries(true),
1303 [Entry::WorkingDir, Entry::Bundled],
1304 "and nothing at all while the switch is off"
1305 );
1306 }
1307
1308 #[test]
1309 fn the_platform_is_searched_only_when_the_switch_is_on() {
1310 // Something every Unix has and this crate does not bundle. The test is
1311 // about the *order*, so a platform without it simply checks less.
1312 let name = "sys/stat.h";
1313 let there = Path::new("/usr/include").join(name).is_file();
1314 let off = resolve(
1315 name,
1316 Form::Angled,
1317 &Origin::Unknown,
1318 &SearchPaths::default(),
1319 );
1320 assert!(off.is_err(), "the platform is not searched by default");
1321 if there {
1322 let on = resolve(
1323 name,
1324 Form::Angled,
1325 &Origin::Unknown,
1326 &with_system(System::Last),
1327 )
1328 .expect("the platform has it");
1329 assert!(on.system, "and it is marked as the platform's");
1330 assert!(on.path.is_none(), "so it is not tracked for rebuilds");
1331 assert!(matches!(on.origin, Origin::SystemDir(_)));
1332 }
1333 }
1334
1335 #[test]
1336 fn the_bundled_copy_wins_unless_the_platform_goes_first() {
1337 let name = "stdio.h";
1338 if !Path::new("/usr/include").join(name).is_file() {
1339 return;
1340 }
1341 let last = resolve(
1342 name,
1343 Form::Angled,
1344 &Origin::Unknown,
1345 &with_system(System::Last),
1346 )
1347 .expect("bundled");
1348 assert_eq!(last.name, bundled_name(name));
1349 let first = resolve(
1350 name,
1351 Form::Angled,
1352 &Origin::Unknown,
1353 &with_system(System::First),
1354 )
1355 .expect("the platform's");
1356 assert!(first.system, "the platform's copy came first");
1357 }
1358
1359 #[test]
1360 fn a_cross_target_has_no_default_directories() {
1361 // The one target that is certainly not the host, whichever machine
1362 // this is: the model differs from the host's in at least its
1363 // architecture.
1364 let host = TargetModel::host();
1365 let triple = if host.arch == Arch::S390x {
1366 "sparc64-unknown-linux-gnu"
1367 } else {
1368 "s390x-unknown-linux-gnu"
1369 };
1370 let target = TargetModel::from_triple(triple).expect("a target this crate models");
1371 let source = TargetSource::Env(triple.to_owned());
1372 // Only meaningful when the environment has not named directories
1373 // itself, which is exactly the case the rule is about.
1374 if std::env::var_os(SYSTEM_PATH_ENV_VAR).is_some() {
1375 return;
1376 }
1377 let message = system_directories(&target, &source).expect_err("a cross build has none");
1378 assert!(message.contains(SYSTEM_PATH_ENV_VAR), "{message}");
1379 assert!(message.contains(triple), "{message}");
1380 }
1381
1382 #[test]
1383 fn the_multiarch_tuple_follows_the_model() {
1384 let tuples =
1385 |triple: &str| multiarch_tuples(&TargetModel::from_triple(triple).expect("modelled"));
1386 assert_eq!(tuples("x86_64-unknown-linux-gnu"), ["x86_64-linux-gnu"]);
1387 assert_eq!(tuples("i686-unknown-linux-gnu"), ["i386-linux-gnu"]);
1388 assert_eq!(tuples("aarch64-unknown-linux-musl"), ["aarch64-linux-musl"]);
1389 assert_eq!(
1390 tuples("arm-unknown-linux-gnueabihf"),
1391 ["arm-linux-gnueabihf", "arm-linux-gnueabi"],
1392 "the ABI is not in the model, so both spellings are offered"
1393 );
1394 assert_eq!(
1395 tuples("powerpc64le-unknown-linux-gnu"),
1396 ["powerpc64le-linux-gnu"]
1397 );
1398 assert_eq!(tuples("s390x-unknown-linux-gnu"), ["s390x-linux-gnu"]);
1399 }
1400}