From bf8020199a2e8058db690be1beb1879c40ac814d Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 4 Feb 2024 21:10:07 +0100 Subject: [PATCH] ENH: respect exclude_dirs and exclude_files in editable installs Add support for the exclude_dirs and exclude_files arguments of install_subdir() Meson function. Extend editable install tests to cover this functionality and a more complex package layout. --- mesonpy/_editable.py | 28 ++++++++++++++------- tests/packages/complex/meson.build | 39 ++++++++++++++++++++++++------ tests/test_editable.py | 10 +++++--- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/mesonpy/_editable.py b/mesonpy/_editable.py index 112c5bdb4..2d584e426 100644 --- a/mesonpy/_editable.py +++ b/mesonpy/_editable.py @@ -222,13 +222,21 @@ def get(self, key: Union[str, Tuple[str, ...]]) -> Optional[Union[Node, str]]: return dict.get(node, key) -def walk(root: str, path: str = '') -> Iterator[pathlib.Path]: - with os.scandir(os.path.join(root, path)) as entries: - for entry in entries: - if entry.is_dir(): - yield from walk(root, os.path.join(path, entry.name)) - else: - yield pathlib.Path(path, entry.name) +def walk(src: str, exclude_files: Set[str], exclude_dirs: Set[str]) -> Iterator[str]: + for root, dirnames, filenames in os.walk(src): + for name in dirnames.copy(): + dirsrc = os.path.join(root, name) + relpath = os.path.relpath(dirsrc, src) + if relpath in exclude_dirs: + dirnames.remove(name) + # sort to process directories determninistically + dirnames.sort() + for name in sorted(filenames): + filesrc = os.path.join(root, name) + relpath = os.path.relpath(filesrc, src) + if relpath in exclude_files: + continue + yield relpath def collect(install_plan: Dict[str, Dict[str, Any]]) -> Node: @@ -238,8 +246,10 @@ def collect(install_plan: Dict[str, Dict[str, Any]]) -> Node: path = pathlib.Path(target['destination']) if path.parts[0] in {'{py_platlib}', '{py_purelib}'}: if key == 'install_subdirs' and os.path.isdir(src): - for entry in walk(src): - tree[(*path.parts[1:], *entry.parts)] = os.path.join(src, *entry.parts) + exclude_files = {os.path.normpath(x) for x in target.get('exclude_files', [])} + exclude_dirs = {os.path.normpath(x) for x in target.get('exclude_dirs', [])} + for entry in walk(src, exclude_files, exclude_dirs): + tree[(*path.parts[1:], *entry.split(os.sep))] = os.path.join(src, entry) else: tree[path.parts[1:]] = src return tree diff --git a/tests/packages/complex/meson.build b/tests/packages/complex/meson.build index d19df4f65..e508e4954 100644 --- a/tests/packages/complex/meson.build +++ b/tests/packages/complex/meson.build @@ -15,13 +15,36 @@ endif py = import('python').find_installation() -py.install_sources('move.py', subdir: 'complex/more', pure: false) - -install_data('foo.py', rename: 'bar.py', install_dir: py.get_install_dir(pure: false) / 'complex') - -install_subdir('complex', install_dir: py.get_install_dir(pure: false)) - -py.extension_module('test', 'test.pyx', install: true, subdir: 'complex') -py.extension_module('baz', 'complex/more/baz.pyx', install: true, subdir: 'complex/more') +py.install_sources( + 'move.py', + subdir: 'complex/more', + pure: false, +) + +install_data( + 'foo.py', + rename: 'bar.py', + install_dir: py.get_install_dir(pure: false) / 'complex', +) + +install_subdir( + 'complex', + install_dir: py.get_install_dir(pure: false), + exclude_files: ['more/meson.build', 'more/baz.pyx'], +) + +py.extension_module( + 'test', + 'test.pyx', + install: true, + subdir: 'complex', +) + +py.extension_module( + 'baz', + 'complex/more/baz.pyx', + install: true, + subdir: 'complex/more', +) subdir('complex/more') diff --git a/tests/test_editable.py b/tests/test_editable.py index 7b89bcdb9..88bb94399 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -17,12 +17,14 @@ def test_walk(package_complex): - entries = set(_editable.walk(os.fspath(package_complex / 'complex'))) - assert entries == { + entries = _editable.walk( + os.fspath(package_complex / 'complex'), + [os.path.normpath('more/meson.build'), os.path.normpath('more/baz.pyx')], + ['namespace'], + ) + assert {pathlib.Path(x) for x in entries} == { pathlib.Path('__init__.py'), pathlib.Path('more/__init__.py'), - pathlib.Path('namespace/bar.py'), - pathlib.Path('namespace/foo.py') }