use std::collections::BTreeMap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use super::search::{FileSystem, RealFileSystem};
use super::table::FileTable;
pub trait InputProvider: Send + Sync + fmt::Debug {
fn read(&self, path: &Path) -> Option<Arc<[u8]>>;
fn contains(&self, path: &Path) -> bool {
self.read(path).is_some()
}
}
#[derive(Clone, Debug, Default)]
pub struct MemoryFiles {
files: BTreeMap<PathBuf, Arc<[u8]>>,
}
impl MemoryFiles {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, path: impl Into<PathBuf>, data: impl Into<Arc<[u8]>>) {
self.files.insert(path.into(), data.into());
}
#[must_use]
pub fn with(mut self, path: impl Into<PathBuf>, data: impl Into<Arc<[u8]>>) -> Self {
self.insert(path, data);
self
}
pub fn remove(&mut self, path: &Path) -> Option<Arc<[u8]>> {
self.files.remove(path)
}
#[must_use]
pub fn get(&self, path: &Path) -> Option<&Arc<[u8]>> {
self.files.get(path)
}
#[must_use]
pub fn len(&self) -> usize {
self.files.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
pub fn paths(&self) -> impl Iterator<Item = &Path> {
self.files.keys().map(PathBuf::as_path)
}
}
impl InputProvider for MemoryFiles {
fn read(&self, path: &Path) -> Option<Arc<[u8]>> {
self.files.get(path).map(Arc::clone)
}
fn contains(&self, path: &Path) -> bool {
self.files.contains_key(path)
}
}
impl<P: Into<PathBuf>, D: Into<Arc<[u8]>>> FromIterator<(P, D)> for MemoryFiles {
fn from_iter<I: IntoIterator<Item = (P, D)>>(iter: I) -> Self {
let mut files = Self::new();
for (path, data) in iter {
files.insert(path, data);
}
files
}
}
impl FileSystem for FileTable {
fn is_file(&self, path: &Path) -> bool {
self.provider()
.is_some_and(|provider| provider.contains(path))
|| RealFileSystem.is_file(path)
}
}
impl<T: FileSystem + ?Sized> FileSystem for &T {
fn is_file(&self, path: &Path) -> bool {
(**self).is_file(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::args::{CancelToken, LinkOptions};
use crate::input::table::Source;
#[test]
fn memory_files_answer_by_exact_path() {
let files: MemoryFiles = [("a.o", b"one".to_vec()), ("/lib/libx.a", b"two".to_vec())]
.into_iter()
.collect();
assert_eq!(files.len(), 2);
assert_eq!(files.read(Path::new("a.o")).as_deref(), Some(&b"one"[..]));
assert!(files.contains(Path::new("/lib/libx.a")));
assert!(!files.contains(Path::new("./a.o")));
assert_eq!(
files.paths().collect::<Vec<_>>(),
[Path::new("/lib/libx.a"), Path::new("a.o")]
);
}
#[test]
fn the_file_table_loads_provided_paths_without_copying() {
let data: Arc<[u8]> = Arc::from(&b"INPUT(b.o)\n"[..]);
let mut options = LinkOptions::new();
options.input_provider = Some(Arc::new(
MemoryFiles::new().with("/virtual/a.ld", Arc::clone(&data)),
));
let table = FileTable::for_link(&options);
assert!(table.is_file(Path::new("/virtual/a.ld")));
assert!(!table.is_file(Path::new("/virtual/b.o")));
let id = table.load_path(Path::new("/virtual/a.ld")).unwrap();
let file = table.get(id).unwrap();
assert!(std::ptr::eq(file.data(), &*data));
assert_eq!(file.path(), Path::new("/virtual/a.ld"));
let loaded = table.load_all(&[
Source::Path("/virtual/a.ld".into()),
Source::Path("/virtual/missing.o".into()),
]);
assert!(loaded[0].is_ok());
assert!(loaded[1].is_err());
}
#[test]
fn a_cancelled_table_loads_nothing() {
let token = CancelToken::new();
let mut options = LinkOptions::new();
options.cancel = Some(token.clone());
options.input_provider = Some(Arc::new(MemoryFiles::new().with("a", vec![1u8])));
let table = FileTable::for_link(&options);
assert!(table.load_path(Path::new("a")).is_ok());
token.cancel();
let error = table.load_path(Path::new("a")).unwrap_err();
assert!(CancelToken::is_cancellation(&error));
assert_eq!(error.to_string(), "link cancelled");
let all = table.load_all(&[Source::Path("a".into())]);
assert!(
all.iter()
.all(|r| r.as_ref().is_err_and(CancelToken::is_cancellation))
);
}
}