Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions mesonpy/_editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def _work_to_do(self, env: dict[str, str]) -> bool:
dry_run_build_cmd = self._build_cmd + ['-n']
# Check adapted from
# https://github.com/mesonbuild/meson/blob/a35d4d368a21f4b70afa3195da4d6292a649cb4c/mesonbuild/mtest.py#L1635-L1636
p = subprocess.run(dry_run_build_cmd, cwd=self._build_path, env=env, capture_output=True)
p = subprocess.run(dry_run_build_cmd, cwd=self._build_path, env=env, check=False, capture_output=True)
return b'ninja: no work to do.' not in p.stdout and b'samu: nothing to do' not in p.stdout

@functools.lru_cache(maxsize=1)
Expand All @@ -332,15 +332,39 @@ def _rebuild(self) -> Node:
env[MARKER] = os.pathsep.join((env.get(MARKER, ''), self._build_path))

if self._verbose or bool(env.get(VERBOSE, '')):
log_path = None
# 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)
# Redirect build log to file.
log_path = os.path.join(self._build_path, 'meson-logs', 'meson-python-build-log.txt')
with open(log_path, 'w') as log:
subprocess.run(self._build_cmd, cwd=self._build_path, env=env, check=True,
stderr=subprocess.STDOUT, stdout=log)
except subprocess.CalledProcessError as exc:
raise ImportError(f're-building the {self._name} meson-python editable wheel package failed') from exc
msg = f're-building the {self._name} meson-python editable wheel package failed'
if log_path:
with open(log_path, 'r', encoding='utf8') as log:
# Skip to the error.
for line in log:
if line.startswith('FAILED: '):
break
else:
# When no `FAILED: ` line is found, rewind to the
# beginning of the log.
log.seek(0)
if line.strip().endswith(' build.ninja'):
# When the error occureed when rebuilding `ninja.build`,
# the meson output appears before the `FAILED: ` line.
# Rewind the build log to the beginning to report the
# error.
log.seek(0)
error = log.read()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first line that the break triggers on won't get printed here, right? Would be nice to capture that line as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving it out was a conscious decision. That line reads:

FAILED: [code={exitcode}] {target_name}

and I don't think it adds much information: the exit code is not relevant in the vast majority of the cases, and the target name is more often than not completely opaque to the user.

However, if you insist, I can include this line.

msg = f'{msg}:\n{error}'
Comment thread
dnicolodi marked this conversation as resolved.
raise ImportError(msg) 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:
Expand Down
36 changes: 35 additions & 1 deletion tests/test_editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,43 @@ def test_editable_rebuild_error(package_purelib_and_platlib, tmp_path, verbose):
# 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 '):
with pytest.raises(ImportError, match='re-building the purelib-and-platlib ') as exc:
import plat # noqa: F401
assert not verbose or stdout.getvalue().startswith('meson-python: building ')
assert verbose or 'ninja: build stopped: subcommand failed.' in exc.value.msg

finally:
del sys.meta_path[0]
sys.modules.pop('pure', None)
path.write_text(code)


def test_editable_reconfigure_error(package_purelib_and_platlib, tmp_path):
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=False,
)
path = package_purelib_and_platlib / 'meson.build'
code = path.read_text()

try:
# Install editable hooks
sys.meta_path.insert(0, finder)

# Emit an error (with unicode characters) during reconfigure
with open(path, 'a', encoding='utf8') as f:
f.write('\n\nerror(\'injected error \N{BOMB}\')\n')

# 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 ') as exc:
import plat # noqa: F401
assert 'ERROR: Problem encountered: injected error' in exc.value.msg
assert 'ninja: error: rebuilding \'build.ninja\': subcommand failed' in exc.value.msg

finally:
del sys.meta_path[0]
Expand Down
Loading