@@ -12,11 +12,42 @@ use crate::table::Table;
12
12
use crate :: types:: RegistryKey ;
13
13
use crate :: value:: { IntoLua , Value } ;
14
14
15
+ #[ cfg( unix) ]
16
+ use { libloading:: Library , rustc_hash:: FxHashMap } ;
17
+
15
18
// Since Luau has some missing standard function, we re-implement them here
16
19
20
+ #[ cfg( unix) ]
21
+ const TARGET_MLUA_LUAU_ABI_VERSION : u32 = 1 ;
22
+
23
+ #[ cfg( all( unix, feature = "module" ) ) ]
24
+ #[ no_mangle]
25
+ #[ used]
26
+ pub static MLUA_LUAU_ABI_VERSION : u32 = TARGET_MLUA_LUAU_ABI_VERSION ;
27
+
17
28
// We keep reference to the `package` table in registry under this key
18
29
struct PackageKey ( RegistryKey ) ;
19
30
31
+ // We keep reference to the loaded dylibs in application data
32
+ #[ cfg( unix) ]
33
+ struct LoadedDylibs ( FxHashMap < PathBuf , Library > ) ;
34
+
35
+ #[ cfg( unix) ]
36
+ impl std:: ops:: Deref for LoadedDylibs {
37
+ type Target = FxHashMap < PathBuf , Library > ;
38
+
39
+ fn deref ( & self ) -> & Self :: Target {
40
+ & self . 0
41
+ }
42
+ }
43
+
44
+ #[ cfg( unix) ]
45
+ impl std:: ops:: DerefMut for LoadedDylibs {
46
+ fn deref_mut ( & mut self ) -> & mut Self :: Target {
47
+ & mut self . 0
48
+ }
49
+ }
50
+
20
51
impl Lua {
21
52
pub ( crate ) unsafe fn prepare_luau_state ( & self ) -> Result < ( ) > {
22
53
let globals = self . globals ( ) ;
@@ -162,6 +193,22 @@ fn create_package_table(lua: &Lua) -> Result<Table> {
162
193
}
163
194
package. raw_set ( "path" , search_path) ?;
164
195
196
+ // Set `package.cpath`
197
+ #[ cfg( unix) ]
198
+ {
199
+ let mut search_cpath = env:: var ( "LUAU_CPATH" )
200
+ . or_else ( |_| env:: var ( "LUA_CPATH" ) )
201
+ . unwrap_or_default ( ) ;
202
+ if search_cpath. is_empty ( ) {
203
+ if cfg ! ( any( target_os = "macos" , target_os = "ios" ) ) {
204
+ search_cpath = "?.dylib" . to_string ( ) ;
205
+ } else {
206
+ search_cpath = "?.so" . to_string ( ) ;
207
+ }
208
+ }
209
+ package. raw_set ( "cpath" , search_cpath) ?;
210
+ }
211
+
165
212
// Set `package.loaded` (table with a list of loaded modules)
166
213
let loaded = lua. create_table ( ) ?;
167
214
package. raw_set ( "loaded" , loaded. clone ( ) ) ?;
@@ -170,6 +217,12 @@ fn create_package_table(lua: &Lua) -> Result<Table> {
170
217
// Set `package.loaders`
171
218
let loaders = lua. create_sequence_from ( [ lua. create_function ( lua_loader) ?] ) ?;
172
219
package. raw_set ( "loaders" , loaders. clone ( ) ) ?;
220
+ #[ cfg( unix) ]
221
+ {
222
+ loaders. push ( lua. create_function ( dylib_loader) ?) ?;
223
+ let loaded_dylibs = LoadedDylibs ( FxHashMap :: default ( ) ) ;
224
+ lua. set_app_data ( loaded_dylibs) ;
225
+ }
173
226
lua. set_named_registry_value ( "_LOADERS" , loaders) ?;
174
227
175
228
Ok ( package)
@@ -225,3 +278,54 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
225
278
226
279
Ok ( Value :: Nil )
227
280
}
281
+
282
+ /// Tries to load a dynamic library
283
+ #[ cfg( unix) ]
284
+ fn dylib_loader ( lua : & Lua , modname : StdString ) -> Result < Value > {
285
+ let package = {
286
+ let key = lua. app_data_ref :: < PackageKey > ( ) . unwrap ( ) ;
287
+ lua. registry_value :: < Table > ( & key. 0 )
288
+ } ?;
289
+ let search_cpath = package. get :: < _ , StdString > ( "cpath" ) . unwrap_or_default ( ) ;
290
+
291
+ let find_symbol = |lib : & Library | unsafe {
292
+ if let Ok ( entry) = lib. get :: < ffi:: lua_CFunction > ( format ! ( "luaopen_{modname}\0 " ) . as_bytes ( ) )
293
+ {
294
+ return lua. create_c_function ( * entry) . map ( Value :: Function ) ;
295
+ }
296
+ // Try all in one mode
297
+ if let Ok ( entry) = lib. get :: < ffi:: lua_CFunction > (
298
+ format ! ( "luaopen_{}\0 " , modname. replace( '.' , "_" ) ) . as_bytes ( ) ,
299
+ ) {
300
+ return lua. create_c_function ( * entry) . map ( Value :: Function ) ;
301
+ }
302
+ "cannot find module entrypoint" . into_lua ( lua)
303
+ } ;
304
+
305
+ if let Some ( file_path) = package_searchpath ( & modname, & search_cpath, true ) {
306
+ let file_path = file_path. canonicalize ( ) ?;
307
+ // Load the library and check for symbol
308
+ unsafe {
309
+ // Check if it's already loaded
310
+ if let Some ( lib) = lua. app_data_ref :: < LoadedDylibs > ( ) . unwrap ( ) . get ( & file_path) {
311
+ return find_symbol ( lib) ;
312
+ }
313
+ if let Ok ( lib) = Library :: new ( & file_path) {
314
+ // Check version
315
+ let mod_version = lib. get :: < * const u32 > ( b"MLUA_LUAU_ABI_VERSION" ) ;
316
+ let mod_version = mod_version. map ( |v| * * v) . unwrap_or_default ( ) ;
317
+ if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
318
+ let err = format ! ( "wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})" ) ;
319
+ return err. into_lua ( lua) ;
320
+ }
321
+ let symbol = find_symbol ( & lib) ;
322
+ lua. app_data_mut :: < LoadedDylibs > ( )
323
+ . unwrap ( )
324
+ . insert ( file_path, lib) ;
325
+ return symbol;
326
+ }
327
+ }
328
+ }
329
+
330
+ Ok ( Value :: Nil )
331
+ }
0 commit comments