10
10
import importlib .abc
11
11
import importlib .machinery
12
12
import importlib .util
13
+ import inspect
13
14
import json
14
15
import os
15
16
import pathlib
@@ -182,7 +183,7 @@ def build_module_spec(cls: type, name: str, path: str, tree: Optional[Node]) ->
182
183
spec = importlib .machinery .ModuleSpec (name , loader , origin = path )
183
184
spec .has_location = True
184
185
if loader .is_package (name ):
185
- spec .submodule_search_locations = []
186
+ spec .submodule_search_locations = [os . path . join ( __file__ , name ) ]
186
187
return spec
187
188
188
189
@@ -253,6 +254,35 @@ def collect(install_plan: Dict[str, Dict[str, Any]]) -> Node:
253
254
tree [path .parts [1 :]] = src
254
255
return tree
255
256
257
+ def find_spec (fullname : str , tree : Node ) -> Optional [importlib .machinery .ModuleSpec ]:
258
+ namespace = False
259
+ parts = fullname .split ('.' )
260
+
261
+ # look for a package
262
+ package = tree .get (tuple (parts ))
263
+ if isinstance (package , Node ):
264
+ for loader , suffix in LOADERS :
265
+ src = package .get ('__init__' + suffix )
266
+ if isinstance (src , str ):
267
+ return build_module_spec (loader , fullname , src , package )
268
+ else :
269
+ namespace = True
270
+
271
+ # look for a module
272
+ for loader , suffix in LOADERS :
273
+ src = tree .get ((* parts [:- 1 ], parts [- 1 ] + suffix ))
274
+ if isinstance (src , str ):
275
+ return build_module_spec (loader , fullname , src , None )
276
+
277
+ # namespace
278
+ if namespace :
279
+ spec = importlib .machinery .ModuleSpec (fullname , None , is_package = True )
280
+ assert isinstance (spec .submodule_search_locations , list ) # make mypy happy
281
+ spec .submodule_search_locations .append (os .path .join (__file__ , fullname ))
282
+ return spec
283
+
284
+ return None
285
+
256
286
257
287
class MesonpyMetaFinder (importlib .abc .MetaPathFinder ):
258
288
def __init__ (self , names : Set [str ], path : str , cmd : List [str ], verbose : bool = False ):
@@ -271,40 +301,12 @@ def find_spec(
271
301
path : Optional [Sequence [Union [bytes , str ]]] = None ,
272
302
target : Optional [ModuleType ] = None
273
303
) -> Optional [importlib .machinery .ModuleSpec ]:
274
-
275
- if fullname .split ('.' , maxsplit = 1 )[0 ] not in self ._top_level_modules :
304
+ if fullname .split ('.' , 1 )[0 ] not in self ._top_level_modules :
276
305
return None
277
-
278
306
if self ._build_path in os .environ .get (MARKER , '' ).split (os .pathsep ):
279
307
return None
280
-
281
- namespace = False
282
308
tree = self ._rebuild ()
283
- parts = fullname .split ('.' )
284
-
285
- # look for a package
286
- package = tree .get (tuple (parts ))
287
- if isinstance (package , Node ):
288
- for loader , suffix in LOADERS :
289
- src = package .get ('__init__' + suffix )
290
- if isinstance (src , str ):
291
- return build_module_spec (loader , fullname , src , package )
292
- else :
293
- namespace = True
294
-
295
- # look for a module
296
- for loader , suffix in LOADERS :
297
- src = tree .get ((* parts [:- 1 ], parts [- 1 ] + suffix ))
298
- if isinstance (src , str ):
299
- return build_module_spec (loader , fullname , src , None )
300
-
301
- # namespace
302
- if namespace :
303
- spec = importlib .machinery .ModuleSpec (fullname , None )
304
- spec .submodule_search_locations = []
305
- return spec
306
-
307
- return None
309
+ return find_spec (fullname , tree )
308
310
309
311
@functools .lru_cache (maxsize = 1 )
310
312
def _rebuild (self ) -> Node :
@@ -327,6 +329,44 @@ def _rebuild(self) -> Node:
327
329
install_plan = json .load (f )
328
330
return collect (install_plan )
329
331
332
+ def _path_hook (self , path : str ) -> MesonpyPathFinder :
333
+ if os .altsep :
334
+ path .replace (os .altsep , os .sep )
335
+ path , _ , key = path .rpartition (os .sep )
336
+ if path == __file__ :
337
+ tree = self ._rebuild ()
338
+ node = tree .get (tuple (key .split ('.' )))
339
+ if isinstance (node , Node ):
340
+ return MesonpyPathFinder (node )
341
+ raise ImportError
342
+
343
+
344
+ class MesonpyPathFinder (importlib .abc .PathEntryFinder ):
345
+ def __init__ (self , tree : Node ):
346
+ self ._tree = tree
347
+
348
+ def find_spec (self , fullname : str , target : Optional [ModuleType ] = None ) -> Optional [importlib .machinery .ModuleSpec ]:
349
+ return find_spec (fullname , self ._tree )
350
+
351
+ def iter_modules (self , prefix : str ) -> Iterator [Tuple [str , bool ]]:
352
+ yielded = set ()
353
+ for name , node in self ._tree .items ():
354
+ modname = inspect .getmodulename (name )
355
+ if modname == '__init__' or modname in yielded :
356
+ continue
357
+ if isinstance (node , Node ):
358
+ modname = name
359
+ for _ , suffix in LOADERS :
360
+ src = node .get ('__init__' + suffix )
361
+ if isinstance (src , str ):
362
+ yielded .add (modname )
363
+ yield prefix + modname , True
364
+ elif modname and '.' not in modname :
365
+ yielded .add (modname )
366
+ yield prefix + modname , False
367
+
330
368
331
369
def install (names : Set [str ], path : str , cmd : List [str ], verbose : bool ) -> None :
332
- sys .meta_path .insert (0 , MesonpyMetaFinder (names , path , cmd , verbose ))
370
+ finder = MesonpyMetaFinder (names , path , cmd , verbose )
371
+ sys .meta_path .insert (0 , finder )
372
+ sys .path_hooks .insert (0 , finder ._path_hook )
0 commit comments