use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use eko::path::{Path, PathBuf};
use alloc::sync::Arc;
use core::{iter};
use eko::{env, file as fs};
use crate::rustc_fs_util::try_canonicalize;
use crate::rustc_target::spec::Target;
use crate::rustc_session::search_paths::{PathKind, SearchPath};
pub struct FileSearch {
cli_search_paths: Vec<SearchPath>,
tlib_path: SearchPath,
use_implicit_sysroot_deps: bool,
files: Vec<FileSearchCandidate>,
}
impl FileSearch {
pub fn cli_search_paths<'b>(&'b self, kind: PathKind) -> impl Iterator<Item = &'b SearchPath> {
self.cli_search_paths.iter().filter(move |sp| sp.kind.matches(kind))
}
pub fn search_paths<'b>(&'b self, kind: PathKind) -> impl Iterator<Item = &'b SearchPath> {
let exclude_sysroot = kind.matches(PathKind::Crate) && !self.use_implicit_sysroot_deps;
let maybe_tlib = (!exclude_sysroot).then_some(&self.tlib_path);
self.cli_search_paths
.iter()
.filter(move |sp| sp.kind.matches(kind))
.chain(maybe_tlib.into_iter())
}
pub fn get_library_candidates<'b>(
&'b self,
prefix: &'b str,
suffix: &'b str,
kind: PathKind,
) -> impl Iterator<Item = (&'b str, PathBuf)> {
let exclude_sysroot = kind.matches(PathKind::Crate) && !self.use_implicit_sysroot_deps;
let start = self.files.partition_point(|v| *v.filename < *prefix).min(self.files.len());
let end = self.files[start..].partition_point(|v| v.filename.starts_with(prefix));
let prefixed_items = &self.files[start..][..end];
prefixed_items
.into_iter()
.filter(move |c| {
c.kind.matches(kind)
&& !(exclude_sysroot && c.from_sysroot)
&& c.filename.ends_with(suffix)
})
.map(|c| (&c.filename[prefix.len()..c.filename.len() - suffix.len()], c.path()))
}
pub fn new(
cli_search_paths: &[SearchPath],
tlib_path: &SearchPath,
target: &Target,
use_implicit_sysroot_deps: bool,
) -> Self {
let prefixes = ["lib", &target.staticlib_prefix, &target.dll_prefix];
let mut files: Vec<FileSearchCandidate> = Vec::with_capacity(cli_search_paths.len());
for (search_path, is_sysroot) in
cli_search_paths.iter().map(|path| (path, false)).chain(iter::once((tlib_path, true)))
{
let Ok(dir) = eko::file::read_dir(&search_path.dir) else {
continue;
};
files.extend(dir.into_iter().filter_map(|entry| {
let filename = entry.file_name()?.to_str()?;
if !prefixes.iter().any(|prefix| filename.starts_with(prefix)) {
return None;
}
Some(FileSearchCandidate {
dir: Arc::clone(&search_path.dir),
filename: filename.into(),
kind: search_path.kind,
from_sysroot: is_sysroot,
})
}));
}
files.sort_unstable_by(|lhs, rhs| lhs.filename.cmp(&rhs.filename));
FileSearch {
cli_search_paths: cli_search_paths.to_owned(),
tlib_path: tlib_path.clone(),
use_implicit_sysroot_deps,
files,
}
}
}
#[derive(Debug)]
struct FileSearchCandidate {
dir: Arc<Path>,
filename: Box<str>,
kind: PathKind,
from_sysroot: bool,
}
impl FileSearchCandidate {
fn path(&self) -> PathBuf {
self.dir.join(&*self.filename)
}
}
pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf {
let rustlib_path = crate::rustc_target::relative_target_rustlib_path(sysroot, target_triple);
sysroot.join(rustlib_path).join("lib")
}
pub fn make_target_bin_path(sysroot: &Path, target_triple: &str) -> PathBuf {
let rustlib_path = crate::rustc_target::relative_target_rustlib_path(sysroot, target_triple);
sysroot.join(rustlib_path).join("bin")
}
#[cfg(unix)]
pub unsafe fn dll_path(function: *mut core::ffi::c_void) -> Result<PathBuf, String> {
use core::ffi::CStr;
#[cfg(not(target_os = "aix"))]
unsafe {
let mut info = core::mem::zeroed();
if libc::dladdr(function, &mut info) == 0 {
return Err("dladdr failed".into());
}
#[cfg(target_os = "cygwin")]
let fname_ptr = info.dli_fname.as_ptr();
#[cfg(not(target_os = "cygwin"))]
let fname_ptr = {
assert!(!info.dli_fname.is_null(), "dli_fname cannot be null");
info.dli_fname
};
let bytes = CStr::from_ptr(fname_ptr).to_bytes();
try_canonicalize(Path::new(bytes)).map_err(|e| e.to_string())
}
#[cfg(target_os = "aix")]
unsafe {
let addr = function as u64;
let mut buffer = vec![core::mem::zeroed::<libc::ld_info>(); 64];
loop {
if libc::loadquery(
libc::L_GETINFO,
buffer.as_mut_ptr() as *mut libc::c_void,
(size_of::<libc::ld_info>() * buffer.len()) as u32,
) >= 0
{
break;
} else {
if eko::file::Error::last_os_error().raw_os_error().unwrap() != libc::ENOMEM {
return Err("loadquery failed".into());
}
buffer.resize(buffer.len() * 2, core::mem::zeroed::<libc::ld_info>());
}
}
let mut current = buffer.as_mut_ptr() as *mut libc::ld_info;
loop {
let data_base = (*current).ldinfo_dataorg as u64;
let data_end = data_base + (*current).ldinfo_datasize;
if (data_base..data_end).contains(&addr) {
let bytes = CStr::from_ptr(&(*current).ldinfo_filename[0]).to_bytes();
return try_canonicalize(Path::new(bytes)).map_err(|e| e.to_string());
}
if (*current).ldinfo_next == 0 {
break;
}
current =
(current as *mut i8).offset((*current).ldinfo_next as isize) as *mut libc::ld_info;
}
return Err(format!("current dll's address {} is not in the load map", addr));
}
}
#[cfg(target_os = "wasi")]
pub unsafe fn dll_path(function: *mut core::ffi::c_void) -> Result<PathBuf, String> {
Err("dll_path is not supported on WASI".to_string())
}
fn current_dll_path() -> Result<PathBuf, String> {
use eko::thread::OnceLock;
static CURRENT_DLL_PATH: OnceLock<Result<PathBuf, String>> = OnceLock::new();
CURRENT_DLL_PATH
.get_or_init(|| unsafe { dll_path(current_dll_path as fn() -> _ as *mut _) })
.clone()
}
pub(crate) fn default_sysroot() -> PathBuf {
fn default_from_rustc_driver_dll() -> Result<PathBuf, String> {
let dll = current_dll_path()?;
let dir = dll.parent().and_then(|p| p.parent()).ok_or_else(|| {
format!("Could not move 2 levels upper using `parent()` on {}", dll.display())
})?;
let mut sysroot_dir = if dir.ends_with(crate::rustc_session::config::host_tuple()) {
dir.parent() .and_then(|p| p.parent()) .and_then(|p| p.parent()) .map(|s| s.to_owned())
.ok_or_else(|| {
format!("Could not move 3 levels upper using `parent()` on {}", dir.display())
})?
} else {
dir.to_owned()
};
if sysroot_dir.ends_with("lib") {
sysroot_dir =
sysroot_dir.parent().map(|real_sysroot| real_sysroot.to_owned()).ok_or_else(
|| format!("Could not move to parent path of {}", sysroot_dir.display()),
)?
}
Ok(sysroot_dir)
}
fn from_env_args_next() -> Option<PathBuf> {
let mut p = PathBuf::from_bytes(eko::env::args().into_iter().next()?);
if eko::file::read_link(&p).is_err() {
return None;
}
p.pop();
p.pop();
let mut rustlib_path = crate::rustc_target::relative_target_rustlib_path(&p, "dummy");
rustlib_path.pop(); rustlib_path.exists().then_some(p)
}
from_env_args_next()
.unwrap_or_else(|| default_from_rustc_driver_dll().expect("Failed finding sysroot"))
}