use std::path::{Path, PathBuf};
use color_eyre::eyre::{Result, WrapErr, eyre};
use fs_err as fs;
use crate::{
CopySpec, SysrootExtract, SysrootKind, SysrootOptions, extract_sysroot, sysroot_copy_options,
};
const BSD_TARGET_OSES: &[&str] = &["freebsd", "openbsd", "netbsd", "dragonfly"];
const FREEBSD_REQUIRED_FILES: &[&str] = &[
"usr/include/kenv.h",
"lib/crt1.o",
"lib/crti.o",
"lib/crtbegin.o",
"lib/libc.a",
"lib/crtend.o",
"lib/crtn.o",
];
const GENERIC_BSD_REQUIRED_DIRS: &[&str] = &["usr/include", "lib"];
const BSD_COPY_PATHS: &[CopySpec] = &[
CopySpec {
guest_path: "/usr/include",
local_parent: "usr",
},
CopySpec {
guest_path: "/usr/lib",
local_parent: "",
},
];
pub fn extract_bsd_sysroot(options: SysrootOptions) -> Result<PathBuf> {
let SysrootOptions {
root_image,
guest_target,
output_parent,
disk_format,
mount_mode,
force,
} = options;
let dir_name = bsd_sysroot_dir_name(&guest_target)?;
let copy_options = sysroot_copy_options(root_image, BSD_COPY_PATHS, &[])
.with_disk_format(disk_format)
.with_mount_mode(mount_mode);
extract_sysroot(
SysrootExtract {
kind: SysrootKind::Bsd,
output_parent,
dir_name,
copy_options,
force,
},
normalize_bsd_sysroot_layout,
|path| validate_bsd_sysroot(&guest_target, path),
)
}
pub fn bsd_sysroot_dir_name(guest_target: &str) -> Result<String> {
let (arch, os) = bsd_target_arch_os(guest_target)?;
Ok(format!("{os}-{arch}"))
}
pub fn validate_bsd_sysroot(guest_target: &str, sysroot: impl AsRef<Path>) -> Result<()> {
let (_, os) = bsd_target_arch_os(guest_target)?;
let sysroot = sysroot.as_ref();
if os == "freebsd" {
return validate_freebsd_sysroot(sysroot);
}
validate_generic_bsd_sysroot(sysroot)
}
fn bsd_target_arch_os(guest_target: &str) -> Result<(&str, &str)> {
let parts = Vec::from_iter(guest_target.split('-'));
let arch = parts
.first()
.copied()
.filter(|arch| !arch.is_empty())
.ok_or_else(|| eyre!("BSD sysroot target '{guest_target}' is missing an architecture"))?;
if parts.len() < 3 {
return Err(unsupported_bsd_target(guest_target));
}
let os = parts
.iter()
.copied()
.find(|part| BSD_TARGET_OSES.contains(part))
.ok_or_else(|| unsupported_bsd_target(guest_target))?;
Ok((arch, os))
}
fn unsupported_bsd_target(guest_target: &str) -> color_eyre::Report {
eyre!(
"unsupported BSD sysroot target '{guest_target}'; expected a Rust target triple containing one of: {}",
BSD_TARGET_OSES.join(", ")
)
}
fn validate_freebsd_sysroot(sysroot: &Path) -> Result<()> {
let missing = Vec::from_iter(
FREEBSD_REQUIRED_FILES
.iter()
.filter(|relative| !sysroot.join(relative).is_file())
.copied(),
);
if missing.is_empty() {
return Ok(());
}
Err(eyre!(
"FreeBSD sysroot '{}' is missing required files: {}",
sysroot.display(),
missing.join(", ")
))
}
fn validate_generic_bsd_sysroot(sysroot: &Path) -> Result<()> {
let missing = Vec::from_iter(
GENERIC_BSD_REQUIRED_DIRS
.iter()
.filter(|relative| !sysroot.join(relative).is_dir())
.copied(),
);
if missing.is_empty() {
return Ok(());
}
Err(eyre!(
"BSD sysroot '{}' is missing required directories: {}",
sysroot.display(),
missing.join(", ")
))
}
fn normalize_bsd_sysroot_layout(sysroot: &Path) -> Result<()> {
move_if_destination_missing(sysroot.join("include"), sysroot.join("usr/include"))?;
move_if_destination_missing(sysroot.join("usr/lib"), sysroot.join("lib"))?;
Ok(())
}
fn move_if_destination_missing(source: PathBuf, destination: PathBuf) -> Result<()> {
if !source.exists() || destination.exists() {
return Ok(());
}
let parent = destination
.parent()
.filter(|path| !path.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
fs::create_dir_all(parent)
.wrap_err_with(|| format!("create directory '{}'", parent.display()))?;
fs::rename(&source, &destination).wrap_err_with(|| {
format!(
"move extracted sysroot path '{}' to '{}'",
source.display(),
destination.display()
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use fs_err as fs;
#[test]
fn bsd_sysroot_dir_name_matches_rust_targets() {
assert_eq!(
bsd_sysroot_dir_name("aarch64-unknown-freebsd").unwrap(),
"freebsd-aarch64"
);
assert_eq!(
bsd_sysroot_dir_name("x86_64-unknown-freebsd").unwrap(),
"freebsd-x86_64"
);
assert_eq!(
bsd_sysroot_dir_name("riscv64gc-unknown-freebsd").unwrap(),
"freebsd-riscv64gc"
);
assert_eq!(
bsd_sysroot_dir_name("x86_64-unknown-openbsd").unwrap(),
"openbsd-x86_64"
);
assert!(bsd_sysroot_dir_name("aarch64-freebsd").is_err());
assert!(bsd_sysroot_dir_name("aarch64-unknown-linux-musl").is_err());
}
#[test]
fn normalizes_copy_out_basename_layout() -> Result<()> {
let dir = tempfile::tempdir()?;
fs::create_dir_all(dir.path().join("include"))?;
fs::write(dir.path().join("include/kenv.h"), b"header")?;
fs::create_dir_all(dir.path().join("lib"))?;
normalize_bsd_sysroot_layout(dir.path())?;
assert!(dir.path().join("usr/include/kenv.h").is_file());
assert!(dir.path().join("lib").is_dir());
assert!(!dir.path().join("include").exists());
Ok(())
}
#[test]
fn normalizes_full_path_layout() -> Result<()> {
let dir = tempfile::tempdir()?;
fs::create_dir_all(dir.path().join("usr/include"))?;
fs::write(dir.path().join("usr/include/kenv.h"), b"header")?;
fs::create_dir_all(dir.path().join("usr/lib"))?;
fs::write(dir.path().join("usr/lib/libc.a"), b"libc")?;
normalize_bsd_sysroot_layout(dir.path())?;
assert!(dir.path().join("usr/include/kenv.h").is_file());
assert!(dir.path().join("lib/libc.a").is_file());
assert!(!dir.path().join("usr/lib/libc.a").exists());
Ok(())
}
#[test]
fn validates_required_freebsd_files() -> Result<()> {
let dir = tempfile::tempdir()?;
for file in FREEBSD_REQUIRED_FILES {
let path = dir.path().join(file);
fs::create_dir_all(path.parent().unwrap())?;
fs::write(path, b"required")?;
}
validate_bsd_sysroot("x86_64-unknown-freebsd", dir.path())?;
fs::remove_file(dir.path().join("lib/libc.a"))?;
assert!(validate_bsd_sysroot("x86_64-unknown-freebsd", dir.path()).is_err());
Ok(())
}
#[test]
fn validates_generic_bsd_directories() -> Result<()> {
let dir = tempfile::tempdir()?;
fs::create_dir_all(dir.path().join("usr/include"))?;
fs::create_dir_all(dir.path().join("lib"))?;
validate_bsd_sysroot("x86_64-unknown-openbsd", dir.path())?;
fs::remove_dir_all(dir.path().join("lib"))?;
assert!(validate_bsd_sysroot("x86_64-unknown-openbsd", dir.path()).is_err());
Ok(())
}
}