Skip to main content

lean_rs/module/
bundle.rs

1//! Capability-level loader that anchors dependent Lean libraries.
2//!
3//! [`LeanLibrary`] is intentionally low-level: it owns one dynamic-loader
4//! handle. [`LeanLibraryBundle`] is the shipped-capability boundary. It opens
5//! dependency dylibs first, gives them exported symbol visibility when a later
6//! Lean dylib needs it, initializes dependency modules when requested, and keeps
7//! every handle alive until the primary capability is dropped.
8
9use std::path::{Path, PathBuf};
10
11use super::{LeanLibrary, LeanModule};
12use crate::error::LeanResult;
13use crate::runtime::LeanRuntime;
14
15/// Initializer for a Lean module hosted by a loaded dylib.
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct LeanModuleInitializer {
18    package: String,
19    module: String,
20}
21
22impl LeanModuleInitializer {
23    /// Create an initializer descriptor from Lake package and root module names.
24    #[must_use]
25    pub fn new(package: impl Into<String>, module: impl Into<String>) -> Self {
26        Self {
27            package: package.into(),
28            module: module.into(),
29        }
30    }
31
32    /// Lake package name used by the initializer.
33    #[must_use]
34    pub fn package_name(&self) -> &str {
35        &self.package
36    }
37
38    /// Root Lean module name used by the initializer.
39    #[must_use]
40    pub fn module_name(&self) -> &str {
41        &self.module
42    }
43}
44
45/// Dependency dylib that must stay alive while a capability is loaded.
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct LeanLibraryDependency {
48    path: PathBuf,
49    exports_symbols_for_dependents: bool,
50    initializer: Option<LeanModuleInitializer>,
51}
52
53impl LeanLibraryDependency {
54    /// Add a dependency dylib to the bundle.
55    #[must_use]
56    pub fn path(path: impl Into<PathBuf>) -> Self {
57        Self {
58            path: path.into(),
59            exports_symbols_for_dependents: false,
60            initializer: None,
61        }
62    }
63
64    /// Make this dependency's Lean symbols available to later dylibs in the
65    /// same bundle.
66    ///
67    /// This is a capability-level requirement, not a platform-loader flag in
68    /// the public contract. On ELF platforms it maps to global symbol
69    /// visibility; other platforms use the equivalent behavior provided by the
70    /// native loader.
71    #[must_use]
72    pub fn export_symbols_for_dependents(mut self) -> Self {
73        self.exports_symbols_for_dependents = true;
74        self
75    }
76
77    /// Initialize a module from this dependency after it is opened.
78    #[must_use]
79    pub fn initializer(mut self, package: impl Into<String>, module: impl Into<String>) -> Self {
80        self.initializer = Some(LeanModuleInitializer::new(package, module));
81        self
82    }
83
84    /// On-disk path to the dependency dylib.
85    #[must_use]
86    pub fn path_ref(&self) -> &Path {
87        &self.path
88    }
89
90    /// Whether symbols from this dependency are exported to later bundle
91    /// members.
92    #[must_use]
93    pub fn exports_symbols_for_dependents(&self) -> bool {
94        self.exports_symbols_for_dependents
95    }
96
97    /// Optional module initializer for this dependency.
98    #[must_use]
99    pub fn module_initializer(&self) -> Option<&LeanModuleInitializer> {
100        self.initializer.as_ref()
101    }
102}
103
104/// A primary Lean capability plus its dependent Lean dylibs.
105///
106/// The primary library is dropped before dependency libraries so imported
107/// symbols remain available for as long as capability code can run.
108pub struct LeanLibraryBundle<'lean> {
109    primary: LeanLibrary<'lean>,
110    dependencies: Vec<LeanLibrary<'lean>>,
111}
112
113impl<'lean> LeanLibraryBundle<'lean> {
114    /// Open a primary capability dylib and every dependency it needs.
115    ///
116    /// Dependencies are opened and optionally initialized in iterator order.
117    /// The primary dylib is opened last. Every dependency handle is stored
118    /// inside the bundle, so helper functions can return a bundle without
119    /// leaking handles or relying on platform loader state that outlives Rust's
120    /// RAII values.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`crate::LeanError`] if any dependency or primary dylib cannot
125    /// be opened, or if any requested dependency initializer fails.
126    pub fn open(
127        runtime: &'lean LeanRuntime,
128        primary_path: impl AsRef<Path>,
129        dependencies: impl IntoIterator<Item = LeanLibraryDependency>,
130    ) -> LeanResult<Self> {
131        let mut opened_dependencies = Vec::new();
132        for dependency in dependencies {
133            let library = if dependency.exports_symbols_for_dependents {
134                LeanLibrary::open_globally(runtime, &dependency.path)?
135            } else {
136                LeanLibrary::open(runtime, &dependency.path)?
137            };
138            if let Some(initializer) = dependency.initializer {
139                let _module = library.initialize_module(&initializer.package, &initializer.module)?;
140            }
141            opened_dependencies.push(library);
142        }
143
144        let primary = LeanLibrary::open(runtime, primary_path)?;
145        Ok(Self {
146            primary,
147            dependencies: opened_dependencies,
148        })
149    }
150
151    /// Initialize a module from the primary capability library.
152    ///
153    /// This delegates to [`LeanLibrary::initialize_module`] while preserving the
154    /// bundle lifetime that keeps dependent dylibs anchored.
155    ///
156    /// # Errors
157    ///
158    /// Returns [`crate::LeanError`] if the requested module name cannot be
159    /// mapped to an initializer symbol, if that symbol is missing from the
160    /// primary library, or if the Lean initializer returns `IO.error`.
161    pub fn initialize_module<'bundle>(
162        &'bundle self,
163        package: &str,
164        module: &str,
165    ) -> LeanResult<LeanModule<'lean, 'bundle>> {
166        self.primary.initialize_module(package, module)
167    }
168
169    /// Borrow the primary capability library.
170    #[must_use]
171    pub fn library(&self) -> &LeanLibrary<'lean> {
172        &self.primary
173    }
174
175    /// Number of dependency dylibs anchored by this bundle.
176    #[must_use]
177    pub fn dependency_count(&self) -> usize {
178        self.dependencies.len()
179    }
180}
181
182impl std::fmt::Debug for LeanLibraryBundle<'_> {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        f.debug_struct("LeanLibraryBundle")
185            .field("primary", &self.primary)
186            .field("dependency_count", &self.dependencies.len())
187            .finish()
188    }
189}