use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use sha2::{Digest, Sha256};
use crate::context_manifest::ContextManifest;
use crate::module_artifact::ModuleCompilationContext;
use crate::VmError;
pub fn prepare_entry_store(source_path: &Path, source: &str) -> super::LookupOutcome {
let source_hash = super::sha256(source.as_bytes());
let (context_hash, manifest) = super::GraphWalk::new(source_path, source).finish();
super::LookupOutcome {
key: super::CacheKey {
source_hash,
context_hash,
harn_version: std::borrow::Cow::Borrowed(super::HARN_VERSION),
compiler_tag: super::compiler_options_tag(super::CompilerOptions::from_env()),
provenance: super::ModuleProvenance::User,
},
chunk: None,
manifest,
link_table: None,
}
}
pub(crate) fn derive_interface(
source_path: &Path,
source: &str,
) -> Result<(ModuleCompilationContext, Option<ContextManifest>), VmError> {
let slot = super::InterfaceSlot::open(source_path, source);
if let Some((context, manifest)) = slot.as_ref().and_then(super::InterfaceSlot::recall) {
return Ok((context, Some(manifest)));
}
let result = super::walk_import_graph_fingerprinted(
source_path,
source,
super::CODEGEN_FINGERPRINT,
true,
);
let context = match result.entry_compilation_context {
Some(context) => {
#[cfg(test)]
crate::module_artifact::INTERFACE_RESOLUTIONS.with(|count| count.set(count.get() + 1));
if let (Some(slot), Some(manifest)) = (&slot, &result.manifest) {
slot.store(&context, manifest);
}
context
}
None => crate::module_artifact::module_compilation_context_for_source(source_path, source)?,
};
Ok((context, result.manifest))
}
fn relative_path_label(base: &Path, target: &Path) -> Option<String> {
let base_components = base.components().collect::<Vec<_>>();
let target_components = target.components().collect::<Vec<_>>();
let common = base_components
.iter()
.zip(&target_components)
.take_while(|(left, right)| left == right)
.count();
if common == 0 && (base.is_absolute() || target.is_absolute()) {
return None;
}
let mut parts = Vec::new();
for component in &base_components[common..] {
if matches!(component, std::path::Component::Normal(_)) {
parts.push("..".to_string());
}
}
for component in &target_components[common..] {
match component {
std::path::Component::Normal(part) => parts.push(part.to_string_lossy().into_owned()),
std::path::Component::ParentDir => parts.push("..".to_string()),
std::path::Component::CurDir => {}
std::path::Component::RootDir | std::path::Component::Prefix(_) => return None,
}
}
Some(if parts.is_empty() {
".".to_string()
} else {
parts.join("/")
})
}
pub(super) fn relocatable_graph_hash(
source_path: &Path,
visited: &BTreeMap<PathBuf, ImportNode>,
codegen_fingerprint: &str,
) -> [u8; 32] {
let entry_identity = crate::module_source::canonical_identity(source_path);
let entry_dir = entry_identity.parent().unwrap_or(Path::new(""));
let label_of = |path: &Path| {
relocatable_label(entry_dir, path)
.unwrap_or_else(|| path.to_string_lossy().replace('\\', "/"))
};
let mut nodes = visited
.iter()
.map(|(path, node)| {
let label = match node {
ImportNode::Unresolved { anchor, import } => {
format!("{}\0unresolved\0{import}", label_of(anchor))
}
_ => label_of(path),
};
let mut bytes = Sha256::new();
hash_import_node(&mut bytes, node);
(label, <[u8; 32]>::from(bytes.finalize()))
})
.collect::<Vec<_>>();
nodes.sort_unstable();
let mut hasher = Sha256::new();
hasher.update(b"relocatable-entry-graph-v3\0");
seed_entry_context_hasher(&mut hasher, codegen_fingerprint);
for (label, node_digest) in nodes {
hasher.update(label.as_bytes());
hasher.update(b"\0");
hasher.update(node_digest);
hasher.update(b"\0");
}
hasher.finalize().into()
}
fn relocatable_label(entry_dir: &Path, path: &Path) -> Option<String> {
if let Some(within) = harn_modules::package_snapshot::path_within_package_generation(path) {
return Some(format!(
"@packages/{}",
within.to_string_lossy().replace('\\', "/")
));
}
relative_path_label(entry_dir, path)
}
pub(super) fn is_bare_import(import: &str) -> bool {
!(import.starts_with("./")
|| import.starts_with("../")
|| import.starts_with("std/")
|| import == "observability"
|| Path::new(import).is_absolute())
}
pub(super) fn seed_entry_context_hasher(hasher: &mut Sha256, codegen_fingerprint: &str) {
hasher.update(b"stdlib-digest\0");
hasher.update(crate::runtime_content::embedded_stdlib_digest_bytes());
hasher.update(b"\0");
hasher.update(b"codegen-fingerprint\0");
hasher.update(codegen_fingerprint.as_bytes());
hasher.update(b"\0");
}
pub(super) fn hash_import_node(hasher: &mut Sha256, node: &ImportNode) {
match node {
ImportNode::Resolved { content } => {
hasher.update(b"resolved\0");
hasher.update(content.as_bytes());
}
ImportNode::Unresolved { import, .. } => {
hasher.update(b"unresolved\0");
hasher.update(import.as_bytes());
}
ImportNode::IoError { kind } => {
hasher.update(b"ioerror\0");
hasher.update(kind.as_bytes());
}
}
}
pub(super) enum ImportNode {
Resolved {
content: Arc<str>,
},
Unresolved {
anchor: PathBuf,
import: Arc<str>,
},
IoError {
kind: String,
},
}
#[cfg(test)]
#[path = "package_key_tests.rs"]
mod package_key_tests;
#[cfg(test)]
#[path = "determinism_tests.rs"]
mod determinism_tests;