use std::path::{Path, PathBuf};
use crate::target::{Arch, Env, Os, TargetModel, TargetSource};
pub const BUNDLED: &[(&str, &str)] = &[
("alloca.h", include_str!("../include/alloca.h")),
("assert.h", include_str!("../include/assert.h")),
("avx2intrin.h", include_str!("../include/avx2intrin.h")),
(
"avx512bf16intrin.h",
include_str!("../include/avx512bf16intrin.h"),
),
(
"avx512bf16vlintrin.h",
include_str!("../include/avx512bf16vlintrin.h"),
),
(
"avx512bitalgintrin.h",
include_str!("../include/avx512bitalgintrin.h"),
),
(
"avx512bitalgvlintrin.h",
include_str!("../include/avx512bitalgvlintrin.h"),
),
(
"avx512bwintrin.h",
include_str!("../include/avx512bwintrin.h"),
),
(
"avx512cdintrin.h",
include_str!("../include/avx512cdintrin.h"),
),
(
"avx512dqintrin.h",
include_str!("../include/avx512dqintrin.h"),
),
(
"avx512fintrin.h",
include_str!("../include/avx512fintrin.h"),
),
(
"avx512fp16intrin.h",
include_str!("../include/avx512fp16intrin.h"),
),
(
"avx512fp16vlintrin.h",
include_str!("../include/avx512fp16vlintrin.h"),
),
(
"avx512ifmaintrin.h",
include_str!("../include/avx512ifmaintrin.h"),
),
(
"avx512ifmavlintrin.h",
include_str!("../include/avx512ifmavlintrin.h"),
),
(
"avx512vbmi2intrin.h",
include_str!("../include/avx512vbmi2intrin.h"),
),
(
"avx512vbmi2vlintrin.h",
include_str!("../include/avx512vbmi2vlintrin.h"),
),
(
"avx512vbmiintrin.h",
include_str!("../include/avx512vbmiintrin.h"),
),
(
"avx512vbmivlintrin.h",
include_str!("../include/avx512vbmivlintrin.h"),
),
(
"avx512vlbwintrin.h",
include_str!("../include/avx512vlbwintrin.h"),
),
(
"avx512vldqintrin.h",
include_str!("../include/avx512vldqintrin.h"),
),
(
"avx512vlintrin.h",
include_str!("../include/avx512vlintrin.h"),
),
(
"avx512vnniintrin.h",
include_str!("../include/avx512vnniintrin.h"),
),
(
"avx512vnnivlintrin.h",
include_str!("../include/avx512vnnivlintrin.h"),
),
(
"avx512vp2intersectintrin.h",
include_str!("../include/avx512vp2intersectintrin.h"),
),
(
"avx512vpopcntdqintrin.h",
include_str!("../include/avx512vpopcntdqintrin.h"),
),
(
"avx512vpopcntdqvlintrin.h",
include_str!("../include/avx512vpopcntdqvlintrin.h"),
),
(
"avxifmaintrin.h",
include_str!("../include/avxifmaintrin.h"),
),
("avxintrin.h", include_str!("../include/avxintrin.h")),
(
"avxvnniint16intrin.h",
include_str!("../include/avxvnniint16intrin.h"),
),
(
"avxvnniint8intrin.h",
include_str!("../include/avxvnniint8intrin.h"),
),
(
"avxvnniintrin.h",
include_str!("../include/avxvnniintrin.h"),
),
("complex.h", include_str!("../include/complex.h")),
("cpuid.h", include_str!("../include/cpuid.h")),
("ctype.h", include_str!("../include/ctype.h")),
("emmintrin.h", include_str!("../include/emmintrin.h")),
("errno.h", include_str!("../include/errno.h")),
("f16cintrin.h", include_str!("../include/f16cintrin.h")),
("float.h", include_str!("../include/float.h")),
("gfniintrin.h", include_str!("../include/gfniintrin.h")),
("immintrin.h", include_str!("../include/immintrin.h")),
("inttypes.h", include_str!("../include/inttypes.h")),
("iso646.h", include_str!("../include/iso646.h")),
("limits.h", include_str!("../include/limits.h")),
("math.h", include_str!("../include/math.h")),
("mm_malloc.h", include_str!("../include/mm_malloc.h")),
("mmintrin.h", include_str!("../include/mmintrin.h")),
("nmmintrin.h", include_str!("../include/nmmintrin.h")),
("pmmintrin.h", include_str!("../include/pmmintrin.h")),
("popcntintrin.h", include_str!("../include/popcntintrin.h")),
("setjmp.h", include_str!("../include/setjmp.h")),
("sha512intrin.h", include_str!("../include/sha512intrin.h")),
("signal.h", include_str!("../include/signal.h")),
("sm3intrin.h", include_str!("../include/sm3intrin.h")),
("sm4intrin.h", include_str!("../include/sm4intrin.h")),
("smmintrin.h", include_str!("../include/smmintrin.h")),
("stdalign.h", include_str!("../include/stdalign.h")),
("stdarg.h", include_str!("../include/stdarg.h")),
("stdatomic.h", include_str!("../include/stdatomic.h")),
("stdbool.h", include_str!("../include/stdbool.h")),
("stdckdint.h", include_str!("../include/stdckdint.h")),
("stddef.h", include_str!("../include/stddef.h")),
("stdint.h", include_str!("../include/stdint.h")),
("stdio.h", include_str!("../include/stdio.h")),
("stdlib.h", include_str!("../include/stdlib.h")),
("stdnoreturn.h", include_str!("../include/stdnoreturn.h")),
("string.h", include_str!("../include/string.h")),
("threads.h", include_str!("../include/threads.h")),
("time.h", include_str!("../include/time.h")),
("tmmintrin.h", include_str!("../include/tmmintrin.h")),
("uchar.h", include_str!("../include/uchar.h")),
("vaesintrin.h", include_str!("../include/vaesintrin.h")),
(
"vpclmulqdqintrin.h",
include_str!("../include/vpclmulqdqintrin.h"),
),
("wchar.h", include_str!("../include/wchar.h")),
("wctype.h", include_str!("../include/wctype.h")),
("wmmintrin.h", include_str!("../include/wmmintrin.h")),
("x86intrin.h", include_str!("../include/x86intrin.h")),
("xmmintrin.h", include_str!("../include/xmmintrin.h")),
];
pub const BUNDLED_DIR: &str = "<cinrs>";
pub fn bundled(name: &str) -> Option<&'static str> {
BUNDLED
.iter()
.find(|(n, _)| *n == name)
.map(|(_, text)| *text)
}
pub fn bundled_name(name: &str) -> String {
format!("{BUNDLED_DIR}/{name}")
}
pub const POSIX_HEADERS: &[&str] = &[
"unistd.h",
"fcntl.h",
"strings.h",
"sys/types.h",
"aio.h",
"arpa/inet.h",
"dirent.h",
"dlfcn.h",
"fnmatch.h",
"glob.h",
"grp.h",
"langinfo.h",
"libgen.h",
"monetary.h",
"net/if.h",
"netdb.h",
"netinet/in.h",
"netinet/tcp.h",
"nl_types.h",
"poll.h",
"pthread.h",
"pwd.h",
"regex.h",
"sched.h",
"semaphore.h",
"spawn.h",
"sys/ipc.h",
"sys/mman.h",
"sys/msg.h",
"sys/resource.h",
"sys/select.h",
"sys/sem.h",
"sys/shm.h",
"sys/socket.h",
"sys/stat.h",
"sys/statvfs.h",
"sys/time.h",
"sys/times.h",
"sys/uio.h",
"sys/un.h",
"sys/utsname.h",
"sys/wait.h",
"syslog.h",
"tar.h",
"termios.h",
"ulimit.h",
"utime.h",
"utmpx.h",
"wordexp.h",
];
pub fn is_posix_header(name: &str) -> bool {
POSIX_HEADERS.contains(&name)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Form {
Quoted,
Angled,
}
#[derive(Clone, Debug, Default)]
pub enum Origin {
Dir(PathBuf),
SystemDir(PathBuf),
Bundled,
#[default]
Unknown,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum System {
#[default]
Off,
Last,
First,
}
impl System {
pub fn from_env_value(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"" | "0" | "off" | "false" | "no" => Some(System::Off),
"1" | "on" | "true" | "yes" => Some(System::Last),
"first" => Some(System::First),
_ => None,
}
}
pub fn is_on(self) -> bool {
self != System::Off
}
}
pub const SYSTEM_ENV_VAR: &str = "CINRS_SYSTEM_INCLUDE";
pub const SYSTEM_PATH_ENV_VAR: &str = "CINRS_SYSTEM_INCLUDE_PATH";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Entry {
Dir(PathBuf),
WorkingDir,
Bundled,
System(PathBuf),
}
#[derive(Clone, Debug, Default)]
pub struct SearchPaths {
pragma: Vec<PathBuf>,
options: Vec<PathBuf>,
env: Vec<PathBuf>,
system: Vec<PathBuf>,
mode: System,
}
pub const ENV_VAR: &str = "CINRS_INCLUDE_PATH";
pub const MANIFEST_DIR_VAR: &str = "CARGO_MANIFEST_DIR";
impl SearchPaths {
pub fn new(options: &[PathBuf]) -> Self {
Self {
pragma: Vec::new(),
options: options.to_vec(),
env: std::env::var_os(ENV_VAR)
.map(|value| std::env::split_paths(&value).collect())
.unwrap_or_default(),
system: Vec::new(),
mode: System::Off,
}
}
pub fn add_pragma(&mut self, dir: &str) {
let path = Path::new(dir);
let resolved = if path.is_absolute() {
path.to_path_buf()
} else {
match std::env::var_os(MANIFEST_DIR_VAR) {
Some(root) => Path::new(&root).join(path),
None => path.to_path_buf(),
}
};
if !self.pragma.contains(&resolved) {
self.pragma.push(resolved);
}
}
fn dirs(&self) -> impl Iterator<Item = &PathBuf> {
self.pragma
.iter()
.chain(self.options.iter())
.chain(self.env.iter())
}
pub fn enable_system(&mut self, mode: System, dirs: Vec<PathBuf>) {
self.mode = mode;
self.system = dirs;
}
pub fn system_mode(&self) -> System {
self.mode
}
pub fn entries(&self, from_system: bool) -> Vec<Entry> {
let mut entries: Vec<Entry> = self.dirs().cloned().map(Entry::Dir).collect();
entries.push(Entry::WorkingDir);
let system = self.system.iter().cloned().map(Entry::System);
match self.mode {
System::Off => entries.push(Entry::Bundled),
System::Last if !from_system => {
entries.push(Entry::Bundled);
entries.extend(system);
}
System::Last | System::First => {
entries.extend(system);
entries.push(Entry::Bundled);
}
}
entries
}
}
pub fn system_directories(
target: &TargetModel,
source: &TargetSource,
) -> Result<Vec<PathBuf>, String> {
if let Some(value) = std::env::var_os(SYSTEM_PATH_ENV_VAR) {
let dirs: Vec<PathBuf> = std::env::split_paths(&value)
.filter(|dir| !dir.as_os_str().is_empty())
.collect();
if !dirs.is_empty() {
return Ok(dirs);
}
}
if *target != TargetModel::host() {
let named = match source.triple() {
Some(triple) => format!("'{triple}'"),
None => "another machine".to_owned(),
};
return Err(format!(
"the platform's include directories are the *host*'s, and this unit is being \
translated for {named}; a header laid out for another machine is worse than none, \
so point {SYSTEM_PATH_ENV_VAR} at the target's sysroot include directories — or \
leave the system headers switched off for this target"
));
}
match target.os {
Os::Linux => {
let mut dirs = vec![PathBuf::from("/usr/local/include")];
for tuple in multiarch_tuples(target) {
let dir = PathBuf::from(format!("/usr/include/{tuple}"));
if dir.is_dir() {
dirs.push(dir);
}
}
dirs.push(PathBuf::from("/usr/include"));
Ok(dirs)
}
Os::Darwin => match apple_sdk_include() {
Some(dir) => Ok(vec![dir]),
None => Err(format!(
"on Apple's platforms the C library's headers live inside the SDK, and \
'xcrun --show-sdk-path' — the one thing that knows where it is — is not \
installed or answered nothing, so cinrs has no directory to offer: set \
{SYSTEM_PATH_ENV_VAR} to \"$(xcrun --show-sdk-path)/usr/include\""
)),
},
os => Err(format!(
"cinrs has no default include directories for {}; set {SYSTEM_PATH_ENV_VAR} to the \
directories the platform's headers live in",
os.as_str()
)),
}
}
fn apple_sdk_include() -> Option<PathBuf> {
static SDK: std::sync::OnceLock<Option<PathBuf>> = std::sync::OnceLock::new();
SDK.get_or_init(|| {
let output = std::process::Command::new("xcrun")
.arg("--show-sdk-path")
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = String::from_utf8(output.stdout).ok()?;
let path = path.trim();
if path.is_empty() {
return None;
}
let include = Path::new(path).join("usr").join("include");
include.is_dir().then_some(include)
})
.clone()
}
fn multiarch_tuples(target: &TargetModel) -> Vec<String> {
let env = match target.env {
Env::Musl => "musl",
Env::Bionic => "android",
Env::Uclibc => "uclibc",
_ => "gnu",
};
let archs: &[&str] = match target.arch {
Arch::X86_64 => &["x86_64"],
Arch::X86 => &["i386"],
Arch::Aarch64 => &["aarch64"],
Arch::Arm => &["arm"],
Arch::Riscv32 => &["riscv32"],
Arch::Riscv64 => &["riscv64"],
Arch::PowerPc => &["powerpc"],
Arch::PowerPc64 => {
if target.big_endian {
&["powerpc64"]
} else {
&["powerpc64le"]
}
}
Arch::S390x => &["s390x"],
Arch::Mips => {
if target.big_endian {
&["mips"]
} else {
&["mipsel"]
}
}
Arch::Mips64 => {
if target.big_endian {
&["mips64"]
} else {
&["mips64el"]
}
}
Arch::Sparc => &["sparc"],
Arch::Sparc64 => &["sparc64"],
Arch::LoongArch64 => &["loongarch64"],
Arch::Wasm32 => &[],
};
let suffixes: &[&str] = if target.arch == Arch::Arm && env == "gnu" {
&["eabihf", "eabi"]
} else {
&[""]
};
let mut tuples = Vec::new();
for arch in archs {
for suffix in suffixes {
tuples.push(format!("{arch}-linux-{env}{suffix}"));
}
}
tuples
}
#[derive(Clone, Debug)]
pub struct Resolved {
pub name: String,
pub text: String,
pub origin: Origin,
pub key: String,
pub path: Option<PathBuf>,
pub found_in: Option<Entry>,
pub system: bool,
}
impl Resolved {
fn make_system(&mut self) {
self.system = true;
self.path = None;
if let Origin::Dir(dir) = std::mem::take(&mut self.origin) {
self.origin = Origin::SystemDir(dir);
}
}
}
#[derive(Clone, Debug)]
pub enum Error {
NotFound {
searched: Vec<String>,
},
Unreadable {
path: String,
error: String,
},
}
pub fn resolve(
name: &str,
form: Form,
origin: &Origin,
paths: &SearchPaths,
) -> Result<Resolved, Error> {
let mut searched: Vec<String> = Vec::new();
if Path::new(name).is_absolute() {
return absolute(name, origin);
}
if form == Form::Quoted {
match origin {
Origin::Dir(dir) | Origin::SystemDir(dir) => {
if let Some(mut found) = read_file(&dir.join(name))? {
if let Origin::SystemDir(_) = origin {
found.make_system();
}
return Ok(found);
}
searched.push(display_dir(dir));
}
Origin::Bundled => {
if let Some(found) = read_bundled(name) {
return Ok(found);
}
searched.push(BUNDLED_DIR.to_owned());
}
Origin::Unknown => {}
}
}
walk(name, form, paths, from_system(origin), 0, searched)
}
fn from_system(origin: &Origin) -> bool {
matches!(origin, Origin::SystemDir(_))
}
fn absolute(name: &str, origin: &Origin) -> Result<Resolved, Error> {
match read_file(Path::new(name))? {
Some(mut found) => {
if from_system(origin) {
found.make_system();
}
Ok(found)
}
None => Err(Error::NotFound {
searched: vec![display_path(Path::new(name))],
}),
}
}
pub fn resolve_next(
name: &str,
origin: &Origin,
current: Option<&Entry>,
paths: &SearchPaths,
) -> Result<Resolved, Error> {
if Path::new(name).is_absolute() {
return absolute(name, origin);
}
let system = from_system(origin);
let start = match current {
Some(entry) => paths
.entries(system)
.iter()
.position(|e| e == entry)
.map_or(0, |at| at + 1),
None => 0,
};
walk(name, Form::Angled, paths, system, start, Vec::new())
}
fn walk(
name: &str,
form: Form,
paths: &SearchPaths,
from_system: bool,
start: usize,
mut searched: Vec<String>,
) -> Result<Resolved, Error> {
for entry in paths.entries(from_system).into_iter().skip(start) {
let shown = match &entry {
Entry::Dir(dir) | Entry::System(dir) => {
if let Some(mut found) = read_file(&dir.join(name))? {
if matches!(entry, Entry::System(_)) {
found.make_system();
}
found.found_in = Some(entry);
return Ok(found);
}
display_dir(dir)
}
Entry::WorkingDir => {
if form != Form::Quoted || !is_path(name) {
continue;
}
if let Some(mut found) = read_file(Path::new(name))? {
found.found_in = Some(entry);
return Ok(found);
}
display_dir(Path::new(""))
}
Entry::Bundled => {
if let Some(mut found) = read_bundled(name) {
found.found_in = Some(entry);
return Ok(found);
}
BUNDLED_DIR.to_owned()
}
};
if !searched.contains(&shown) {
searched.push(shown);
}
}
Err(Error::NotFound { searched })
}
#[derive(Clone, Debug)]
pub struct Embedded {
pub name: String,
pub bytes: Vec<u8>,
pub path: PathBuf,
}
pub fn resolve_embed(
name: &str,
form: Form,
origin: &Origin,
paths: &SearchPaths,
) -> Result<Embedded, Error> {
let mut searched: Vec<String> = Vec::new();
if Path::new(name).is_absolute() {
return match read_bytes(Path::new(name))? {
Some(found) => Ok(found),
None => Err(Error::NotFound {
searched: vec![display_path(Path::new(name))],
}),
};
}
if form == Form::Quoted {
match origin {
Origin::Dir(dir) | Origin::SystemDir(dir) => {
if let Some(found) = read_bytes(&dir.join(name))? {
return Ok(found);
}
searched.push(display_dir(dir));
}
Origin::Bundled | Origin::Unknown => {}
}
}
for dir in paths.dirs() {
if let Some(found) = read_bytes(&dir.join(name))? {
return Ok(found);
}
let shown = display_dir(dir);
if !searched.contains(&shown) {
searched.push(shown);
}
}
if form == Form::Quoted && is_path(name) {
if let Some(found) = read_bytes(Path::new(name))? {
return Ok(found);
}
let shown = display_dir(Path::new(""));
if !searched.contains(&shown) {
searched.push(shown);
}
}
Err(Error::NotFound { searched })
}
fn read_bytes(path: &Path) -> Result<Option<Embedded>, Error> {
match std::fs::metadata(path) {
Ok(meta) if meta.is_file() => {}
_ => return Ok(None),
}
let bytes = std::fs::read(path).map_err(|error| Error::Unreadable {
path: display_path(path),
error: error.to_string(),
})?;
Ok(Some(Embedded {
name: display_path(path),
bytes,
path: std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf()),
}))
}
fn is_path(name: &str) -> bool {
name.contains('/') || name.contains('\\')
}
fn read_bundled(name: &str) -> Option<Resolved> {
let text = bundled(name)?;
let display = bundled_name(name);
Some(Resolved {
text: text.to_owned(),
origin: Origin::Bundled,
key: display.clone(),
name: display,
path: None,
found_in: Some(Entry::Bundled),
system: false,
})
}
pub fn read_source(path: &Path) -> Result<Resolved, Error> {
match read_file(path)? {
Some(found) => Ok(found),
None => Err(Error::NotFound {
searched: vec![display_path(path)],
}),
}
}
fn read_file(path: &Path) -> Result<Option<Resolved>, Error> {
match std::fs::metadata(path) {
Ok(meta) if meta.is_file() => {}
_ => return Ok(None),
}
let text = std::fs::read_to_string(path).map_err(|error| Error::Unreadable {
path: display_path(path),
error: error.to_string(),
})?;
let absolute = std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf());
let key = std::fs::canonicalize(path)
.unwrap_or_else(|_| absolute.clone())
.display()
.to_string();
Ok(Some(Resolved {
name: display_path(path),
text,
origin: Origin::Dir(path.parent().unwrap_or(Path::new("")).to_path_buf()),
key,
path: Some(absolute),
found_in: None,
system: false,
}))
}
pub fn display_path(path: &Path) -> String {
let relative = std::env::current_dir()
.ok()
.and_then(|cwd| path.strip_prefix(&cwd).ok().map(Path::to_path_buf));
relative
.unwrap_or_else(|| path.to_path_buf())
.display()
.to_string()
}
fn display_dir(dir: &Path) -> String {
let shown = display_path(dir);
if shown.is_empty() {
".".to_owned()
} else {
shown
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_bundled_header_is_listed_once_and_sorted() {
let mut names: Vec<&str> = BUNDLED.iter().map(|(n, _)| *n).collect();
let count = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), count, "a header is listed twice");
let listed: Vec<&str> = BUNDLED.iter().map(|(n, _)| *n).collect();
assert_eq!(listed, names, "keep the table sorted by name");
}
#[test]
fn a_bundled_header_is_found_without_touching_the_disk() {
let found = resolve(
"stddef.h",
Form::Angled,
&Origin::Unknown,
&SearchPaths::default(),
)
.expect("stddef.h is bundled");
assert_eq!(found.name, "<cinrs>/stddef.h");
assert!(found.path.is_none());
assert!(found.text.contains("size_t"));
}
#[test]
fn a_quoted_name_that_is_a_path_is_taken_from_the_working_directory() {
let found = resolve(
"include/stddef.h",
Form::Quoted,
&Origin::Dir(PathBuf::from("include")),
&SearchPaths::default(),
)
.expect("the file is there, relative to the package directory");
assert!(found.text.contains("size_t"));
assert!(
found.path.is_some(),
"it is a real file, not the bundled one"
);
}
#[test]
fn a_bare_name_in_the_working_directory_does_not_shadow_a_bundled_header() {
assert!(!is_path("stdio.h"));
assert!(is_path("sub/stdio.h"));
assert!(is_path("sub\\stdio.h"));
}
#[test]
fn a_missing_header_lists_where_it_looked() {
let error = resolve(
"nowhere.h",
Form::Quoted,
&Origin::Dir(PathBuf::from("src")),
&SearchPaths::default(),
)
.expect_err("nothing is called nowhere.h");
match error {
Error::NotFound { searched } => assert_eq!(searched, ["src", "<cinrs>"]),
other => panic!("expected a not-found error, got {other:?}"),
}
}
fn with_system(mode: System) -> SearchPaths {
let mut paths = SearchPaths::default();
paths.enable_system(mode, vec![PathBuf::from("/usr/include")]);
paths
}
#[test]
fn the_switch_reads_the_spellings_a_build_system_uses() {
assert_eq!(System::from_env_value("1"), Some(System::Last));
assert_eq!(System::from_env_value(" YES "), Some(System::Last));
assert_eq!(System::from_env_value("First"), Some(System::First));
assert_eq!(System::from_env_value("0"), Some(System::Off));
assert_eq!(System::from_env_value(""), Some(System::Off));
assert_eq!(System::from_env_value("maybe"), None);
assert!(!System::Off.is_on());
assert!(System::Last.is_on() && System::First.is_on());
}
#[test]
fn the_mode_decides_where_the_platform_goes() {
let system = Entry::System(PathBuf::from("/usr/include"));
assert_eq!(
SearchPaths::default().entries(false),
[Entry::WorkingDir, Entry::Bundled],
"off by default"
);
assert_eq!(
with_system(System::Last).entries(false),
[Entry::WorkingDir, Entry::Bundled, system.clone()]
);
assert_eq!(
with_system(System::First).entries(false),
[Entry::WorkingDir, system.clone(), Entry::Bundled]
);
assert_eq!(
with_system(System::Last).entries(true),
[Entry::WorkingDir, system.clone(), Entry::Bundled]
);
assert_eq!(
SearchPaths::default().entries(true),
[Entry::WorkingDir, Entry::Bundled],
"and nothing at all while the switch is off"
);
}
#[test]
fn the_platform_is_searched_only_when_the_switch_is_on() {
let name = "sys/stat.h";
let there = Path::new("/usr/include").join(name).is_file();
let off = resolve(
name,
Form::Angled,
&Origin::Unknown,
&SearchPaths::default(),
);
assert!(off.is_err(), "the platform is not searched by default");
if there {
let on = resolve(
name,
Form::Angled,
&Origin::Unknown,
&with_system(System::Last),
)
.expect("the platform has it");
assert!(on.system, "and it is marked as the platform's");
assert!(on.path.is_none(), "so it is not tracked for rebuilds");
assert!(matches!(on.origin, Origin::SystemDir(_)));
}
}
#[test]
fn the_bundled_copy_wins_unless_the_platform_goes_first() {
let name = "stdio.h";
if !Path::new("/usr/include").join(name).is_file() {
return;
}
let last = resolve(
name,
Form::Angled,
&Origin::Unknown,
&with_system(System::Last),
)
.expect("bundled");
assert_eq!(last.name, bundled_name(name));
let first = resolve(
name,
Form::Angled,
&Origin::Unknown,
&with_system(System::First),
)
.expect("the platform's");
assert!(first.system, "the platform's copy came first");
}
#[test]
fn a_cross_target_has_no_default_directories() {
let host = TargetModel::host();
let triple = if host.arch == Arch::S390x {
"sparc64-unknown-linux-gnu"
} else {
"s390x-unknown-linux-gnu"
};
let target = TargetModel::from_triple(triple).expect("a target this crate models");
let source = TargetSource::Env(triple.to_owned());
if std::env::var_os(SYSTEM_PATH_ENV_VAR).is_some() {
return;
}
let message = system_directories(&target, &source).expect_err("a cross build has none");
assert!(message.contains(SYSTEM_PATH_ENV_VAR), "{message}");
assert!(message.contains(triple), "{message}");
}
#[test]
fn the_multiarch_tuple_follows_the_model() {
let tuples =
|triple: &str| multiarch_tuples(&TargetModel::from_triple(triple).expect("modelled"));
assert_eq!(tuples("x86_64-unknown-linux-gnu"), ["x86_64-linux-gnu"]);
assert_eq!(tuples("i686-unknown-linux-gnu"), ["i386-linux-gnu"]);
assert_eq!(tuples("aarch64-unknown-linux-musl"), ["aarch64-linux-musl"]);
assert_eq!(
tuples("arm-unknown-linux-gnueabihf"),
["arm-linux-gnueabihf", "arm-linux-gnueabi"],
"the ABI is not in the model, so both spellings are offered"
);
assert_eq!(
tuples("powerpc64le-unknown-linux-gnu"),
["powerpc64le-linux-gnu"]
);
assert_eq!(tuples("s390x-unknown-linux-gnu"), ["s390x-linux-gnu"]);
}
}