use crate::package::*;
pub(crate) fn normalized_relative_path(path: &Path) -> String {
harn_modules::package_execution::normalized_package_relative_path(path)
}
pub(crate) fn compute_content_hash(dir: &Path) -> Result<String, PackageError> {
harn_modules::package_execution::compute_package_content_hash(dir)
.map_err(|error| PackageError::Registry(error.to_string()))
}
#[cfg(test)]
pub(crate) fn compute_archive_content_hash(dir: &Path) -> Result<String, PackageError> {
harn_modules::package_execution::compute_archive_content_hash(dir)
.map_err(|error| PackageError::Registry(error.to_string()))
}
pub(crate) fn is_canonical_content_hash(hash: &str) -> bool {
harn_modules::package_execution::is_canonical_package_content_hash(hash)
}
pub(crate) fn verify_content_hash_or_compute(
dir: &Path,
expected: &str,
) -> Result<(), PackageError> {
let actual = harn_modules::package_execution::verify_package_content_hash(dir, expected)
.map_err(|error| PackageError::Registry(error.to_string()))?;
if actual != expected {
return Err(format!(
"content hash mismatch for {}: expected {}, got {}",
dir.display(),
expected,
actual
)
.into());
}
if read_cached_content_hash(dir)?.as_deref() != Some(expected) {
write_cached_content_hash(dir, expected)?;
}
Ok(())
}
pub(crate) fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), PackageError> {
fs::create_dir_all(dst)
.map_err(|error| format!("failed to create {}: {error}", dst.display()))?;
for entry in
fs::read_dir(src).map_err(|error| format!("failed to read {}: {error}", src.display()))?
{
let entry =
entry.map_err(|error| format!("failed to read {} entry: {error}", src.display()))?;
let ty = entry
.file_type()
.map_err(|error| format!("failed to stat {}: {error}", entry.path().display()))?;
let name = entry.file_name();
if name == OsStr::new(".git")
|| name == OsStr::new(CONTENT_HASH_FILE)
|| name == OsStr::new(CACHE_METADATA_FILE)
{
continue;
}
let dest_path = dst.join(entry.file_name());
if ty.is_dir() {
copy_dir_recursive(&entry.path(), &dest_path)?;
} else if ty.is_file() {
if let Some(parent) = dest_path.parent() {
fs::create_dir_all(parent)
.map_err(|error| format!("failed to create {}: {error}", parent.display()))?;
}
fs::copy(entry.path(), &dest_path).map_err(|error| {
format!(
"failed to copy {} to {}: {error}",
entry.path().display(),
dest_path.display()
)
})?;
}
}
Ok(())
}
#[cfg(unix)]
pub(crate) fn symlink_path_dependency(source: &Path, dest: &Path) -> Result<(), PackageError> {
std::os::unix::fs::symlink(source, dest).map_err(|error| {
PackageError::Registry(format!(
"failed to symlink {} to {}: {error}",
source.display(),
dest.display()
))
})
}
#[cfg(windows)]
pub(crate) fn symlink_path_dependency(source: &Path, dest: &Path) -> Result<(), PackageError> {
if source.is_dir() {
std::os::windows::fs::symlink_dir(source, dest)
} else {
std::os::windows::fs::symlink_file(source, dest)
}
.map_err(|error| {
PackageError::Registry(format!(
"failed to symlink {} to {}: {error}",
source.display(),
dest.display()
))
})
}
#[cfg(not(any(unix, windows)))]
pub(crate) fn symlink_path_dependency(_source: &Path, _dest: &Path) -> Result<(), PackageError> {
Err("symlinks are not supported on this platform"
.to_string()
.into())
}
pub(crate) fn materialize_path_dependency(
source: &Path,
dest_root: &Path,
alias: &str,
) -> Result<(), PackageError> {
if source.is_dir() {
let dest = dest_root.join(alias);
match symlink_path_dependency(source, &dest) {
Ok(()) => Ok(()),
Err(_) => copy_dir_recursive(source, &dest),
}
} else {
let dest = dest_root.join(format!("{alias}.harn"));
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)
.map_err(|error| format!("failed to create {}: {error}", parent.display()))?;
}
match symlink_path_dependency(source, &dest) {
Ok(()) => Ok(()),
Err(_) => {
fs::copy(source, &dest).map_err(|error| {
format!(
"failed to copy {} to {}: {error}",
source.display(),
dest.display()
)
})?;
Ok(())
}
}
}
}
pub(crate) fn materialized_hash_matches(dir: &Path, expected: &str) -> bool {
verify_content_hash_or_compute(dir, expected).is_ok()
}
pub(crate) fn resolve_path_dependency_source(
manifest_dir: &Path,
raw: &str,
) -> Result<PathBuf, PackageError> {
let source = {
let candidate = PathBuf::from(raw);
if candidate.is_absolute() {
candidate
} else {
manifest_dir.join(candidate)
}
};
if source.exists() {
return source.canonicalize().map_err(|error| {
PackageError::Registry(format!(
"failed to canonicalize {}: {error}",
source.display()
))
});
}
if source.extension().is_none() {
let with_ext = source.with_extension("harn");
if with_ext.exists() {
return with_ext.canonicalize().map_err(|error| {
PackageError::Registry(format!(
"failed to canonicalize {}: {error}",
with_ext.display()
))
});
}
}
Err(format!("package source not found: {}", source.display()).into())
}