1use std::path::Path;
13
14use super::preflight::{CapabilityManifest, LeanRuntimePreflight, manifest_error_to_lean_error, report_into_error};
15use super::{LeanLibrary, LeanLibraryBundle, LeanLibraryDependency, LeanModule};
16use crate::error::{LeanError, LeanResult};
17use crate::runtime::LeanRuntime;
18
19pub use lean_toolchain::{BuiltCapabilityArtifact, LeanBuiltCapability, LeanBuiltCapabilityError};
24
25fn built_capability_error_to_lean_error(err: &LeanBuiltCapabilityError) -> LeanError {
26 LeanError::module_init(err.to_string())
27}
28
29pub struct LeanCapability<'lean> {
32 bundle: LeanLibraryBundle<'lean>,
33 package: String,
34 module: String,
35}
36
37impl<'lean> LeanCapability<'lean> {
38 #[allow(clippy::needless_pass_by_value)]
47 pub fn from_build_manifest(runtime: &'lean LeanRuntime, spec: LeanBuiltCapability) -> LeanResult<Self> {
48 let report = LeanRuntimePreflight::new(spec.clone()).check();
49 if !report.is_ok() {
50 return Err(report_into_error(report));
51 }
52 let manifest_path = spec
53 .resolved_manifest_path()
54 .map_err(|err| built_capability_error_to_lean_error(&err))?;
55 let manifest = CapabilityManifest::read(&manifest_path).map_err(manifest_error_to_lean_error)?;
56 Self::open_with_dependencies(
57 runtime,
58 manifest.primary_dylib,
59 manifest.package,
60 manifest.module,
61 manifest.dependencies,
62 )
63 }
64
65 pub fn from_build_env(runtime: &'lean LeanRuntime, mut spec: LeanBuiltCapability) -> LeanResult<Self> {
77 let dylib_path = spec
78 .dylib_path()
79 .map_err(|err| built_capability_error_to_lean_error(&err))?;
80 let package = spec.take_package_name().ok_or_else(|| {
81 LeanError::linking("LeanBuiltCapability is missing the Lake package name; call `.package(...)`")
82 })?;
83 let module = spec.take_module_name().ok_or_else(|| {
84 LeanError::linking("LeanBuiltCapability is missing the root Lean module name; call `.module(...)`")
85 })?;
86 let dependencies = spec.take_dependencies();
87 Self::open_with_dependencies(runtime, dylib_path, package, module, dependencies)
88 }
89
90 pub fn open(
98 runtime: &'lean LeanRuntime,
99 dylib_path: impl AsRef<Path>,
100 package: impl Into<String>,
101 module: impl Into<String>,
102 ) -> LeanResult<Self> {
103 let package = package.into();
104 let module = module.into();
105 Self::open_with_dependencies(runtime, dylib_path, package, module, [])
106 }
107
108 pub fn open_with_dependencies(
120 runtime: &'lean LeanRuntime,
121 dylib_path: impl AsRef<Path>,
122 package: impl Into<String>,
123 module: impl Into<String>,
124 dependencies: impl IntoIterator<Item = LeanLibraryDependency>,
125 ) -> LeanResult<Self> {
126 let package = package.into();
127 let module = module.into();
128 let bundle = LeanLibraryBundle::open(runtime, dylib_path, dependencies)?;
129 let _module = bundle.initialize_module(&package, &module)?;
130 Ok(Self {
131 bundle,
132 package,
133 module,
134 })
135 }
136
137 pub fn module(&self) -> LeanResult<LeanModule<'lean, '_>> {
147 self.bundle.initialize_module(&self.package, &self.module)
148 }
149
150 #[must_use]
152 pub fn library(&self) -> &LeanLibrary<'lean> {
153 self.bundle.library()
154 }
155
156 #[must_use]
158 pub fn bundle(&self) -> &LeanLibraryBundle<'lean> {
159 &self.bundle
160 }
161
162 #[must_use]
164 pub fn package_name(&self) -> &str {
165 &self.package
166 }
167
168 #[must_use]
170 pub fn module_name(&self) -> &str {
171 &self.module
172 }
173}
174
175#[cfg(test)]
176#[allow(clippy::expect_used, clippy::panic)]
177mod tests {
178 use super::{
179 BuiltCapabilityArtifact, CapabilityManifest, LeanBuiltCapability, LeanBuiltCapabilityError,
180 LeanLibraryDependency,
181 };
182 use std::fs;
183 use std::path::PathBuf;
184
185 #[test]
186 fn built_capability_path_is_resolved_without_runtime_env() {
187 let spec = LeanBuiltCapability::path("/tmp/libcap.so")
188 .env_var("LEAN_RS_CAPABILITY_CAP_DYLIB")
189 .package("pkg")
190 .module("Cap");
191
192 let path = match spec.dylib_path() {
193 Ok(path) => path,
194 Err(err) => panic!("expected path, got {err}"),
195 };
196 assert_eq!(path, std::path::PathBuf::from("/tmp/libcap.so"));
197 assert_eq!(spec.package_name(), Some("pkg"));
198 assert_eq!(spec.module_name(), Some("Cap"));
199 }
200
201 #[test]
202 fn missing_runtime_env_is_typed() {
203 let spec = LeanBuiltCapability::env("LEAN_RS_TEST_MISSING_CAPABILITY_DYLIB")
204 .package("pkg")
205 .module("Cap");
206 let err = match spec.dylib_path() {
207 Ok(path) => panic!("expected missing env error, got {}", path.display()),
208 Err(err) => err,
209 };
210 assert!(matches!(
211 err,
212 LeanBuiltCapabilityError::EnvVarNotSet {
213 kind: BuiltCapabilityArtifact::Dylib,
214 ..
215 }
216 ));
217 }
218
219 #[test]
220 fn missing_runtime_manifest_env_is_typed() {
221 let spec = LeanBuiltCapability::manifest_env("LEAN_RS_TEST_MISSING_CAPABILITY_MANIFEST");
222 let err = match spec.resolved_manifest_path() {
223 Ok(path) => panic!("expected missing manifest env error, got {}", path.display()),
224 Err(err) => err,
225 };
226 assert!(matches!(
227 err,
228 LeanBuiltCapabilityError::EnvVarNotSet {
229 kind: BuiltCapabilityArtifact::Manifest,
230 ..
231 }
232 ));
233 }
234
235 #[test]
236 fn manifest_descriptor_parses_dependencies() {
237 let path = temp_manifest_path("manifest_descriptor_parses_dependencies");
238 write_manifest(
239 &path,
240 r#"{
241 "schema_version": 1,
242 "target_name": "Cap",
243 "package": "pkg",
244 "module": "Cap",
245 "primary_dylib": "/tmp/libcap.so",
246 "dependencies": [
247 {
248 "dylib_path": "/tmp/libdep.so",
249 "export_symbols_for_dependents": true,
250 "initializer": { "package": "dep_pkg", "module": "Dep" }
251 }
252 ]
253}"#,
254 );
255
256 let manifest = match CapabilityManifest::read(&path) {
257 Ok(manifest) => manifest,
258 Err(err) => panic!("expected manifest to parse, got {err}"),
259 };
260 assert_eq!(manifest.primary_dylib, PathBuf::from("/tmp/libcap.so"));
261 assert_eq!(manifest.package, "pkg");
262 assert_eq!(manifest.module, "Cap");
263 assert_eq!(manifest.dependencies.len(), 1);
264 let Some(dependency) = manifest.dependencies.first() else {
265 panic!("expected one dependency");
266 };
267 assert!(dependency.exports_symbols_for_dependents());
268 assert_eq!(dependency.path_ref(), std::path::Path::new("/tmp/libdep.so"));
269 let Some(initializer) = dependency.module_initializer() else {
270 panic!("expected dependency initializer");
271 };
272 assert_eq!(initializer.package_name(), "dep_pkg");
273 assert_eq!(initializer.module_name(), "Dep");
274 }
275
276 #[test]
277 fn unsupported_manifest_schema_is_typed() {
278 let path = temp_manifest_path("unsupported_manifest_schema_is_typed");
279 write_manifest(
280 &path,
281 r#"{
282 "schema_version": 999,
283 "package": "pkg",
284 "module": "Cap",
285 "primary_dylib": "/tmp/libcap.so"
286}"#,
287 );
288
289 let Err(err) = CapabilityManifest::read(&path) else {
290 panic!("expected unsupported schema error");
291 };
292 assert_eq!(err.code(), crate::LeanLoaderDiagnosticCode::UnsupportedManifestSchema);
293 assert!(err.message().contains("unsupported Lean capability manifest schema"));
294 }
295
296 #[test]
297 fn built_capability_records_dependency_descriptors() {
298 let spec = LeanBuiltCapability::path("/tmp/libcap.so").dependency(
299 LeanLibraryDependency::path("/tmp/libdep.so")
300 .export_symbols_for_dependents()
301 .initializer("dep_pkg", "Dep"),
302 );
303
304 let dependencies = spec.dependency_descriptors();
305 assert_eq!(dependencies.len(), 1);
306 let Some(dependency) = dependencies.first() else {
307 panic!("expected one dependency descriptor");
308 };
309 assert!(dependency.exports_symbols_for_dependents());
310 let Some(initializer) = dependency.module_initializer() else {
311 panic!("dependency initializer is recorded");
312 };
313 assert_eq!(initializer.package_name(), "dep_pkg");
314 assert_eq!(initializer.module_name(), "Dep");
315 }
316
317 fn temp_manifest_path(name: &str) -> PathBuf {
318 let dir = std::env::temp_dir().join(format!("lean-rs-manifest-{}-{name}", std::process::id()));
319 drop(fs::remove_dir_all(&dir));
320 fs::create_dir_all(&dir).expect("create manifest test dir");
321 dir.join("capability.json")
322 }
323
324 fn write_manifest(path: &std::path::Path, contents: &str) {
325 fs::write(path, contents).expect("write manifest fixture");
326 }
327}