use std::{
collections::{HashMap, hash_map::Entry},
path::{Path, PathBuf},
};
use rllvm_core::{
catalog::{
ArchiveCache, ArchiveMember, CatalogOrigin, CatalogScope, DigestOrigin, ModuleCatalog,
ModuleRecord, ModuleStatus, hash_bytes, read_catalog,
},
error::Error,
};
use crate::facts::{ModuleAnalysis, ModuleReport, SourceStatus};
pub struct LoadedModule {
pub id: String,
pub bytes: Vec<u8>,
pub record: ModuleRecord,
}
pub struct Loaded {
pub pending: Vec<PendingModule>,
pub reports: Vec<ModuleReport>,
pub scope: CatalogScope,
pub origin: CatalogOrigin,
pub source_status: HashMap<(String, PathBuf), SourceState>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SourceState {
pub status: SourceStatus,
pub basis: Option<DigestOrigin>,
}
pub struct PendingModule {
pub id: String,
pub path: PathBuf,
pub member: Option<ArchiveMember>,
pub record: ModuleRecord,
}
fn resolve(catalog_dir: &Path, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
catalog_dir.join(path)
}
}
pub fn load_catalog(path: &Path) -> Result<Loaded, Error> {
let catalog: ModuleCatalog = read_catalog(path)?;
let catalog_dir = path.parent().unwrap_or(Path::new("."));
load_catalog_value(catalog, catalog_dir)
}
pub fn load_catalog_value(catalog: ModuleCatalog, catalog_dir: &Path) -> Result<Loaded, Error> {
let catalog_dir = catalog_dir.to_path_buf();
let mut pending = Vec::new();
let mut reports = Vec::new();
let mut source_status: HashMap<(String, PathBuf), SourceState> = HashMap::new();
let mut archives = ArchiveCache::default();
for record in &catalog.modules {
let report_base = |status, diagnostic| ModuleReport {
id: record.id.clone(),
status,
ir_stage: record.ir_stage.clone(),
debug_info: record.debug_info,
compiler: record.compiler.as_ref().map(|c| c.version.clone()),
configuration_id: record.configuration_id.clone(),
content_sha256: record.content_sha256.clone(),
target_triple: record.target_triple.clone(),
diagnostic,
};
let unavailable = match record.status {
ModuleStatus::Missing => Some(ModuleAnalysis::Missing),
ModuleStatus::Failed => Some(ModuleAnalysis::Failed),
ModuleStatus::Unsupported => Some(ModuleAnalysis::Unsupported),
ModuleStatus::Planned => Some(ModuleAnalysis::NotBuilt),
ModuleStatus::Available => None,
};
if let Some(status) = unavailable {
let recorded = (!record.diagnostics.is_empty()).then(|| record.diagnostics.join("; "));
reports.push(report_base(status, recorded));
continue;
}
let Some(path) = record.path.as_ref().map(|p| resolve(&catalog_dir, p)) else {
reports.push(report_base(ModuleAnalysis::Missing, None));
continue;
};
let bytes = match &record.archive_member {
Some(member) => match archives.module(&path, member).map(<[u8]>::to_vec) {
Ok(bytes) => bytes,
Err(Error::Io(io_error)) if io_error.kind() == std::io::ErrorKind::NotFound => {
reports.push(report_base(
ModuleAnalysis::Missing,
Some(io_error.to_string()),
));
continue;
}
Err(error) => {
reports.push(report_base(ModuleAnalysis::Failed, Some(error.to_string())));
continue;
}
},
None => match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
reports.push(report_base(
ModuleAnalysis::Missing,
Some(error.to_string()),
));
continue;
}
Err(error) => {
reports.push(report_base(ModuleAnalysis::Failed, Some(error.to_string())));
continue;
}
},
};
if let Some(recorded) = &record.content_sha256
&& hash_bytes(&bytes) != *recorded
{
reports.push(report_base(ModuleAnalysis::Changed, None));
continue;
}
for association in &record.sources {
let source = association.resolved_path();
let state = match (&association.digest, std::fs::read(&source)) {
(_, Err(_)) => SourceState {
status: SourceStatus::Missing,
basis: None,
},
(None, Ok(_)) => SourceState {
status: SourceStatus::Unknown,
basis: None,
},
(Some(digest), Ok(current)) => SourceState {
status: if digest.matches(¤t) {
SourceStatus::Current
} else {
SourceStatus::Modified
},
basis: Some(digest.origin),
},
};
match source_status.entry((record.id.clone(), source)) {
Entry::Occupied(mut slot) if slot.get().basis.is_none() => {
slot.insert(state);
}
Entry::Occupied(_) => {}
Entry::Vacant(slot) => {
slot.insert(state);
}
}
}
drop(bytes);
reports.push(report_base(ModuleAnalysis::Verified, None));
pending.push(PendingModule {
id: record.id.clone(),
path,
member: record.archive_member.clone(),
record: record.clone(),
});
}
Ok(Loaded {
pending,
reports,
scope: catalog.scope.clone(),
origin: catalog.origin.clone(),
source_status,
})
}
pub fn for_each_module(
loaded: &Loaded,
mut visit: impl FnMut(LoadedModule) -> Result<(), Error>,
) -> Result<Vec<(String, Error)>, Error> {
let mut archives = ArchiveCache::default();
let mut unreadable = Vec::new();
for pending in &loaded.pending {
let bytes = match &pending.member {
Some(member) => archives.module(&pending.path, member).map(<[u8]>::to_vec),
None => std::fs::read(&pending.path).map_err(Error::from),
};
let bytes = match bytes {
Ok(bytes) => bytes,
Err(error) => {
unreadable.push((pending.id.clone(), error));
continue;
}
};
visit(LoadedModule {
id: pending.id.clone(),
bytes,
record: pending.record.clone(),
})?;
}
Ok(unreadable)
}