diff --git a/mesonpy/_editable.py b/mesonpy/_editable.py index 7568d069..5e742d11 100644 --- a/mesonpy/_editable.py +++ b/mesonpy/_editable.py @@ -349,7 +349,7 @@ def _rebuild(self) -> Node: def _path_hook(self, path: str) -> MesonpyPathFinder: if os.altsep: - path.replace(os.altsep, os.sep) + path = path.replace(os.altsep, os.sep) path, _, key = path.rpartition(os.sep) if path == __file__: tree = self._rebuild() diff --git a/tests/test_editable.py b/tests/test_editable.py index b8a14695..9282c18f 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -349,3 +349,45 @@ def test_editable_rebuild_error(package_purelib_and_platlib, tmp_path, verbose): def test_install_data(venv, editable_install_data, tmp_path): venv.pip('install', os.fspath(editable_install_data)) venv.python('-c', 'import package') + + +@pytest.mark.skipif(not os.altsep, reason='os.altsep is only set on Windows') +@pytest.mark.skipif(FREE_THREADED_BUILD and CYTHON_VERSION < (3, 1, 0), + reason='Cython version too old, no free-threaded CPython support') +def test_path_hook_altsep_search_path(package_complex, tmp_path): + """The path hook must match submodule search paths that use os.altsep. + + After a real editable build, importlib/pkgutil look up subpackages via + path hooks. On Windows those paths may use '/' (PYTHONPATH, pathlib, + Meson) even though __file__ uses '\\'. The hook normalizes altsep + before rpartition(os.sep); discarding the replace means the hook + never matches __file__ and subpackage discovery fails. + """ + if os.altsep in _editable.__file__: + pytest.skip('__file__ already uses altsep; hook compares to it as-is') + + project = mesonpy.Project(package_complex, tmp_path) + finder = _editable.MesonpyMetaFinder( + 'complex', {'complex'}, os.fspath(tmp_path), project._build_command, True + ) + + try: + sys.meta_path.insert(0, finder) + sys.path_hooks.insert(0, finder._path_hook) + + import complex + assert complex.__name__ == 'complex' + + # Same location importlib would use, but spelled with os.altsep. + altsep_path = _editable.__file__.replace(os.sep, os.altsep) + os.altsep + 'complex' + importer = pkgutil.get_importer(altsep_path) + assert isinstance(importer, _editable.MesonpyPathFinder) + + names = {module.name for module in pkgutil.iter_modules([altsep_path])} + assert 'more' in names + finally: + del sys.meta_path[0] + del sys.path_hooks[0] + for name in list(sys.modules): + if name == 'complex' or name.startswith('complex.'): + del sys.modules[name]