use std::path::{Path, PathBuf};
use lean_rs::error::LeanResult;
pub(crate) const SHIM_PACKAGE_NAME: &str = "lean_rs_host_shims";
pub(crate) const SHIM_LIB_NAME: &str = "LeanRsHostShims";
pub(crate) struct LakeProject {
root: PathBuf,
}
impl LakeProject {
pub(crate) fn new(root: impl AsRef<Path>) -> LeanResult<Self> {
let root = root.as_ref();
if !root.is_dir() {
return Err(lean_rs::__host_internals::host_module_init(format!(
"Lake project root '{}' does not exist or is not a directory",
root.display()
)));
}
Ok(Self {
root: root.to_path_buf(),
})
}
pub(crate) fn capability_dylib(&self, package: &str, lib_name: &str) -> PathBuf {
let dylib_extension = if cfg!(target_os = "macos") { "dylib" } else { "so" };
let lib_dir = self.root.join(".lake").join("build").join("lib");
let escaped_package = package.replace('_', "__");
let new_style = lib_dir.join(format!("lib{escaped_package}_{lib_name}.{dylib_extension}"));
let old_style = lib_dir.join(format!("lib{lib_name}.{dylib_extension}"));
if new_style.is_file() {
new_style
} else if old_style.is_file() {
old_style
} else {
new_style
}
}
pub(crate) fn olean_search_path(&self) -> PathBuf {
self.root.join(".lake").join("build").join("lib").join("lean")
}
pub(crate) fn shim_olean_search_path(&self) -> LeanResult<PathBuf> {
let manifest_path = self.root.join("lake-manifest.json");
let manifest_bytes = std::fs::read(&manifest_path).map_err(|err| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest at '{}' could not be read ({err})",
manifest_path.display()
))
})?;
let manifest: serde_json::Value = serde_json::from_slice(&manifest_bytes).map_err(|err| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest at '{}' is not valid JSON: {err}",
manifest_path.display()
))
})?;
let package_dir = shim_package_dir_from_manifest(&self.root, &manifest, &manifest_path)?;
Ok(package_dir.join(".lake").join("build").join("lib").join("lean"))
}
pub(crate) fn shim_dylib(&self) -> LeanResult<PathBuf> {
let manifest_path = self.root.join("lake-manifest.json");
let manifest_bytes = std::fs::read(&manifest_path).map_err(|err| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest at '{}' could not be read ({err}); the consumer must run `lake update` after \
adding `require lean_rs_host_shims` to their lakefile",
manifest_path.display()
))
})?;
let manifest: serde_json::Value = serde_json::from_slice(&manifest_bytes).map_err(|err| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest at '{}' is not valid JSON: {err}",
manifest_path.display()
))
})?;
let package_dir = shim_package_dir_from_manifest(&self.root, &manifest, &manifest_path)?;
let dylib_extension = if cfg!(target_os = "macos") { "dylib" } else { "so" };
let lib_dir = package_dir.join(".lake").join("build").join("lib");
let escaped_shim_package = SHIM_PACKAGE_NAME.replace('_', "__");
let new_style = lib_dir.join(format!("lib{escaped_shim_package}_{SHIM_LIB_NAME}.{dylib_extension}"));
let old_style = lib_dir.join(format!("lib{SHIM_LIB_NAME}.{dylib_extension}"));
if new_style.is_file() {
Ok(new_style)
} else if old_style.is_file() {
Ok(old_style)
} else {
Ok(new_style)
}
}
}
fn shim_package_dir_from_manifest(
lake_root: &Path,
manifest: &serde_json::Value,
manifest_path: &Path,
) -> LeanResult<PathBuf> {
let packages = manifest.get("packages").and_then(|p| p.as_array()).ok_or_else(|| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest at '{}' has no `packages` array (unexpected manifest schema)",
manifest_path.display()
))
})?;
let entry = packages
.iter()
.find(|p| p.get("name").and_then(|n| n.as_str()) == Some(SHIM_PACKAGE_NAME))
.ok_or_else(|| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest at '{}' lists no `{SHIM_PACKAGE_NAME}` package; the consumer's lakefile \
must `require lean_rs_host_shims from \"…\"` (path or git) and then run `lake update`",
manifest_path.display()
))
})?;
let package_type = entry.get("type").and_then(|t| t.as_str()).unwrap_or("");
match package_type {
"path" => {
let dir = entry.get("dir").and_then(|d| d.as_str()).ok_or_else(|| {
lean_rs::__host_internals::host_module_init(format!(
"Lake manifest entry for `{SHIM_PACKAGE_NAME}` has type=\"path\" but no `dir` field"
))
})?;
Ok(lake_root.join(dir))
}
"git" => {
let packages_dir = manifest
.get("packagesDir")
.and_then(|p| p.as_str())
.unwrap_or(".lake/packages");
Ok(lake_root.join(packages_dir).join(SHIM_PACKAGE_NAME))
}
other => Err(lean_rs::__host_internals::host_module_init(format!(
"Lake manifest entry for `{SHIM_PACKAGE_NAME}` has unsupported require type '{other}' \
(only `path` and `git` are supported today)"
))),
}
}