diff --git a/mesonpy/_editable.py b/mesonpy/_editable.py index d6bf5a96e..0dafa26da 100644 --- a/mesonpy/_editable.py +++ b/mesonpy/_editable.py @@ -183,7 +183,13 @@ def build_module_spec(cls: type, name: str, path: str, tree: Optional[Node]) -> spec = importlib.machinery.ModuleSpec(name, loader, origin=path) spec.has_location = True if loader.is_package(name): - spec.submodule_search_locations = [] + spec.submodule_search_locations = [os.path.dirname(path)] + if tree is not None: + for submodule_node in tree.values(): + if isinstance(submodule_node, str): + parent_folder = os.path.dirname(submodule_node) + if parent_folder not in spec.submodule_search_locations: + spec.submodule_search_locations.append(parent_folder) return spec diff --git a/tests/test_editable.py b/tests/test_editable.py index ac9319d23..17db96a65 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -4,6 +4,7 @@ import os import pathlib +import pkgutil import sys import pytest @@ -86,10 +87,23 @@ def test_mesonpy_meta_finder(package_complex, tmp_path): # verify that we can import the modules import complex assert complex.__spec__.origin == os.fspath(package_complex / 'complex/__init__.py') - assert complex.__file__ == os.fspath(package_complex / 'complex/__init__.py') + assert complex.__spec__.submodule_search_locations == [ + os.fspath(package_complex / 'complex'), + str(tmp_path), + ] + assert complex.__file__ == complex.__spec__.origin + assert complex.__path__ == complex.__spec__.submodule_search_locations import complex.test assert complex.test.__spec__.origin == os.fspath(tmp_path / f'test{EXT_SUFFIX}') + assert complex.test.__file__ == complex.test.__spec__.origin + assert complex.test.__spec__.submodule_search_locations is None + assert not hasattr(complex.test, '__path__') assert complex.test.answer() == 42 + import complex.more + assert complex.more.__spec__.origin == os.fspath(package_complex / 'complex/more/__init__.py') + assert complex.more.__spec__.submodule_search_locations == [os.fspath(package_complex / 'complex/more')] + assert complex.more.__file__ == complex.more.__spec__.origin + assert complex.more.__path__ == complex.more.__spec__.submodule_search_locations import complex.namespace.foo assert complex.namespace.foo.__spec__.origin == os.fspath(package_complex / 'complex/namespace/foo.py') assert complex.namespace.foo.foo() == 'foo' @@ -98,6 +112,34 @@ def test_mesonpy_meta_finder(package_complex, tmp_path): del sys.meta_path[0] +def test_walk_packages(package_complex, tmp_path): + # build a package in a temporary directory + mesonpy.Project(package_complex, tmp_path) + + # point the meta finder to the build directory + finder = _editable.MesonpyMetaFinder({'complex'}, os.fspath(tmp_path), ['ninja']) + + try: + # install the finder in the meta path + sys.meta_path.insert(0, finder) + + import complex + + module_names = sorted( + module_info.name + for module_info in pkgutil.walk_packages(complex.__path__, prefix='complex.') + ) + assert module_names == [ + 'complex.more', + 'complex.test', + # XXX: should namespace packages be discovered by walk_packages? + ] + + finally: + # remove finder from the meta path + del sys.meta_path[0] + + def test_mesonpy_traversable(): tree = _editable.Node() tree[('package', '__init__.py')] = '/tmp/src/package/__init__.py'