diff --git a/mesonpy/_editable.py b/mesonpy/_editable.py index 1c34abe05..ff3971fac 100644 --- a/mesonpy/_editable.py +++ b/mesonpy/_editable.py @@ -327,20 +327,23 @@ def _work_to_do(self, env: dict[str, str]) -> bool: @functools.lru_cache(maxsize=1) def _rebuild(self) -> Node: - # skip editable wheel lookup during rebuild: during the build - # the module we are rebuilding might be imported causing a - # rebuild loop. - env = os.environ.copy() - env[MARKER] = os.pathsep.join((env.get(MARKER, ''), self._build_path)) - - if self._verbose or bool(env.get(VERBOSE, '')): - # We want to show some output only if there is some work to do - if self._work_to_do(env): - build_command = ' '.join(self._build_cmd) - print(f'meson-python: building {self._name}: {build_command}', flush=True) - subprocess.run(self._build_cmd, cwd=self._build_path, env=env) - else: - subprocess.run(self._build_cmd, cwd=self._build_path, env=env, stdout=subprocess.DEVNULL) + try: + # Skip editable wheel lookup during rebuild: during the build + # the module we are rebuilding might be imported causing a + # rebuild loop. + env = os.environ.copy() + env[MARKER] = os.pathsep.join((env.get(MARKER, ''), self._build_path)) + + if self._verbose or bool(env.get(VERBOSE, '')): + # We want to show some output only if there is some work to do. + if self._work_to_do(env): + build_command = ' '.join(self._build_cmd) + print(f'meson-python: building {self._name}: {build_command}', flush=True) + subprocess.run(self._build_cmd, cwd=self._build_path, env=env, check=True) + else: + subprocess.run(self._build_cmd, cwd=self._build_path, env=env, stdout=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError as exc: + raise ImportError(f're-building the {self._name} meson-python editable wheel package failed') from exc install_plan_path = os.path.join(self._build_path, 'meson-info', 'intro-install_plan.json') with open(install_plan_path, 'r', encoding='utf8') as f: diff --git a/tests/test_editable.py b/tests/test_editable.py index 7f51cddc2..acf9bda90 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -308,3 +308,35 @@ def test_editable_verbose(venv, package_complex, editable_complex, monkeypatch): # Another import without file changes should not show any output assert venv.python('-c', 'import complex') == '' + + +@pytest.mark.parametrize('verbose', [False, True], ids=('', 'verbose')) +def test_editable_rebuild_error(package_purelib_and_platlib, tmp_path, verbose): + with mesonpy._project({'builddir': os.fspath(tmp_path)}) as project: + + finder = _editable.MesonpyMetaFinder( + project._metadata.name, {'plat', 'pure'}, + os.fspath(tmp_path), project._build_command, + verbose=verbose, + ) + path = package_purelib_and_platlib / 'plat.c' + code = path.read_text() + + try: + # Install editable hooks + sys.meta_path.insert(0, finder) + + # Insert invalid code in the extension module source code + path.write_text('return') + + # Import module and trigger rebuild: the build fails and ImportErrror is raised + stdout = io.StringIO() + with redirect_stdout(stdout): + with pytest.raises(ImportError, match='re-building the purelib-and-platlib meson-python editable wheel package failed'): + import plat + assert not verbose or stdout.getvalue().startswith('meson-python: building ') + + finally: + del sys.meta_path[0] + sys.modules.pop('pure', None) + path.write_text(code)