diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 9fa30519c..c2b5dd6ca 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -621,6 +621,8 @@ def build_editable(self, directory: Path, verbose: bool = False) -> pathlib.Path hook_name={_as_python_declaration(hook_module_name)}, project_path={_as_python_declaration(self._source_dir)}, build_path={_as_python_declaration(self._build_dir)}, + install_dir={_as_python_declaration(install_path)}, + uninstall_old = True, import_paths={_as_python_declaration(import_paths)}, top_level_modules={_as_python_declaration(self.top_level_modules)}, rebuild_commands={_as_python_declaration(rebuild_commands)}, @@ -882,7 +884,6 @@ def build_commands(self, install_dir: Optional[pathlib.Path] = None) -> Sequence ( 'meson', 'install', - '--only-changed', '--destdir', os.fspath(install_dir or self._install_dir), *self._meson_args['install'], diff --git a/mesonpy/_editable.py b/mesonpy/_editable.py index f3690c3ec..ee29ae276 100644 --- a/mesonpy/_editable.py +++ b/mesonpy/_editable.py @@ -1,6 +1,7 @@ import functools import importlib.abc import os +import shutil import subprocess import sys import warnings @@ -63,6 +64,8 @@ def __init__( hook_name: str, project_path: str, build_path: str, + install_dir: str, + uninstall_old: bool, import_paths: List[str], top_level_modules: List[str], rebuild_commands: List[List[str]], @@ -72,6 +75,8 @@ def __init__( self._hook_name = hook_name self._project_path = project_path self._build_path = build_path + self.install_dir = install_dir + self.uninstall_old = uninstall_old self._import_paths = import_paths self._top_level_modules = top_level_modules self._rebuild_commands = rebuild_commands @@ -115,6 +120,9 @@ def _proc(self, command: List[str]) -> None: @functools.lru_cache(maxsize=1) def rebuild(self) -> None: self._debug(f'{{cyan}}{{bold}}+ rebuilding {self._project_path}{{reset}}') + if self.uninstall_old: + if os.path.exists(self.install_dir): + shutil.rmtree(self.install_dir) for command in self._rebuild_commands: self._proc(command) self._debug('{cyan}{bold}+ successfully rebuilt{reset}') @@ -148,6 +156,8 @@ def install( hook_name: str, project_path: str, build_path: str, + install_dir: str, + uninstall_old: bool, import_paths: List[str], top_level_modules: List[str], rebuild_commands: List[List[str]], @@ -163,6 +173,8 @@ def install( hook_name, project_path, build_path, + install_dir, + uninstall_old, import_paths, top_level_modules, rebuild_commands, diff --git a/tests/packages/scipy-like/mypkg/submod/deleted.py b/tests/packages/scipy-like/mypkg/submod/deleted.py new file mode 100644 index 000000000..d3d3470d3 --- /dev/null +++ b/tests/packages/scipy-like/mypkg/submod/deleted.py @@ -0,0 +1 @@ +print('Deleted file exists') diff --git a/tests/test_wheel.py b/tests/test_wheel.py index fb1c07948..2df8ad46b 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -69,6 +69,7 @@ def test_scipy_like(wheel_scipy_like): f'mypkg/cy_extmod{EXT_SUFFIX}', 'mypkg/submod/__init__.py', 'mypkg/submod/unknown_filetype.npq', + 'mypkg/submod/deleted.py' } if sys.platform in {'win32', 'cygwin'}: # Currently Meson is installing .dll.a (import libraries) next @@ -278,3 +279,35 @@ def test_editable_broken_non_existent_build_dir( venv.pip('install', os.path.join(tmp_path, mesonpy.build_editable(tmp_path))) assert venv.python('-c', 'import plat; print(plat.foo())').strip() == 'bar' + + +def test_editable_syncs_removed_file( + package_scipy_like, + editable_scipy_like, + venv, + tmp_path, +): + # Test that a deleted file is removed from the install dir + shutil.rmtree(tmp_path) # copytree requires dest not to exist + shutil.copytree(package_scipy_like, tmp_path) + mesonpy_dir = os.path.join(package_scipy_like, '.mesonpy') + if os.path.isdir(mesonpy_dir): + venv.pip('uninstall', '-y', 'mypkg') + shutil.rmtree(mesonpy_dir) + + os.chdir(tmp_path) + venv.pip('install', os.path.join(tmp_path, mesonpy.build_editable(tmp_path))) + + assert venv.python('-c', 'import mypkg.submod.deleted').strip() == 'Deleted file exists' + + # Delete a file and check if it is removed + # (in this case, cannot be imported) + os.remove(os.path.join(tmp_path, 'mypkg/submod/deleted.py')) + assert not os.path.exists(os.path.join(tmp_path, 'mypkg/submod/deleted.py')) + + try: + # Use assert to fail the test if the call succeeds + assert not venv.python('-c', 'import mypkg.submod.deleted') + except subprocess.CalledProcessError: + # We're expecting this to fail since the file got deleted + pass