You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Trying to make use of the one week 50% access to Fable! :) Here's a review of meson-python, following what I've done in other projects I maintain: https://iscinumpy.dev/post/claude-code-reviews/, such as scikit-build-core: scikit-build/scikit-build-core#1317 - I've been really happy with the results, the FP rate is nearly zero. Happy to generate fixes for things if other maintainers want me to!
(Also, congrats, no high-severity bugs. :) )
🤖 AI text below 🤖
Review complete — I read all six source modules in full and verified the suspicious findings against git history. Here's what I found, ordered by how much I'd prioritize each.
Bugs
_MESON_REQUIRED_VERSION is out of sync with pyproject.toml (mesonpy/__init__.py:95). The constant says 0.63.3 (unchanged since the runtime check was added in cc2e326), but pyproject.toml requires meson >= 0.64.0, and >= 1.2.3 on Python 3.12+. The comment on that line — and CLAUDE.md — say to keep them in sync. Beyond bumping it, the runtime check should probably be conditional on the Python version like the requirement is, otherwise meson 1.0 passes the check on Python 3.12 where it's known broken. Relatedly, on meson < 1.3.0 the python.allow_limited_api introspection at __init__.py:896-899 comes back None and silently disables the user's limited-api = true — worth confirming that's intended.
No-op str.replace in the editable path hook (mesonpy/_editable.py:352). path.replace(os.altsep, os.sep) discards its result; it needs path = path.replace(...). On POSIX altsep is None so nothing is affected, but on Windows the intended separator normalization never happens before path.rpartition(os.sep).
Missing check=True on RPath reads (mesonpy/_rpath.py:27 and :75). The macOS otool -l and ELF patchelf --print-rpath invocations don't check the return code. If the tool fails (missing patchelf, unreadable file), _get_rpath returns nothing, fix_rpath becomes a silent no-op, and the wheel ships with an RPATH still pointing at the build directory — a broken wheel with no error. The Solaris variant already passes check=True; the other two should match.
direct_url.json isn't valid JSON (mesonpy/__init__.py:548-550). The editable wheel writes source_dir.as_uri() — a bare unquoted file:///... string — where PEP 610 specifies a JSON object like {"url": "...", "dir_info": {"editable": true}}. It's latent because pip overwrites the file at install time (nothing in the test suite exercises it), but any other consumer parsing it will choke. Either write the proper object or drop the file.
Regex typo in the wheel filename pattern (mesonpy/_wheelfile.py:26). (:?-(?P<build>[^-]+))? was clearly meant to be (?:...) — as written it's a capturing group that permits a stray literal colon. Harmless today since meson-python never generates build tags, but it's wrong as a validator.
StopIteration can mask the split-package error (mesonpy/__init__.py:160). In the purelib/platlib conflict branch, next(entry for entry in wheel_files[other] ...) has no default. If the package was first registered by an install_subdirs entry whose directory walked to zero files, packages has the entry but wheel_files[other] has none, and the user gets a raw StopIteration/RuntimeError instead of the helpful BuildError. Use next(..., None) and degrade the message.
Symlink cycles hang sdist() (mesonpy/__init__.py:1063). The while member.issym(): resolution loop never terminates if the archive contains a symlink cycle (a -> b, b -> a). Malformed input, but a bounded loop or visited-set would turn a hang into a warning like the other symlink cases.
Outdated 32-bit-on-aarch64 tag (mesonpy/_tags.py:192). A 32-bit interpreter on an aarch64 kernel is tagged linux_armv7l; packaging ≥ 24.0 switched this case to linux_armv8l (Ability to install armv7l manylinux/musllinux wheels on armv8l pypa/packaging#690). Wheels tagged the old way are increasingly inconsistent with what pip/packaging compute on the install side.
Performance
WheelFileWriter.write (mesonpy/_wheelfile.py:96-98) slurps each file fully into memory to hash and write it. For scientific wheels with very large shared objects that's a real memory spike; streaming in chunks through zipfile.open(zinfo, 'w') while updating the sha256 incrementally fixes it without changing behavior.
_editable.walk() calls dirnames.sort() inside the per-entry loop (mesonpy/_editable.py:229), re-sorting once per subdirectory; the twin logic in __init__.py:177 correctly sorts once after the pruning loop. Trivial cost, but the two copies of this function have quietly diverged, which is the more interesting problem given _editable.py must stay standalone.
Simplifications
mesonpy/__init__.py:919: the one .format(style=style, cmd=...) call in a file that otherwise uses f-strings.
_env_ninja_command (__init__.py:1236) reuses the name version for both the required version parameter and the subprocess output — correct but needlessly confusing.
_string_or_strings (__init__.py:643): list([value,] if isinstance(value, str) else value) → [value] if isinstance(value, str) else list(value).
The sdist loop copies member twice when a symlink was resolved (__init__.py:1073 and :1085); harmless, but the second copy's comment ("before starting to modify it") no longer tells the whole story.
Modernizations (floor is Python 3.9)
@functools.lru_cache() / lru_cache(maxsize=None) → functools.cache at __init__.py:203, :959, :964.
_tags.py:_get_cpython_abi contains a version < (3, 8) pymalloc branch that can never run, and the whole function exists only as a fallback for CPython ≤ 3.8.7 on Windows (per its own comment) — on 3.9+ EXT_SUFFIX always parses, so _get_cpython_abi and its _get_config_var helper are effectively dead. Worth either deleting or keeping deliberately with a note.
Every file has from __future__ import annotations, yet the TYPE_CHECKING blocks still import List, Dict, Optional, Union, Tuple, Type, DefaultDict from typing — and _editable.py:310 already uses lowercase dict[str, str], so the style is inconsistent. Enabling ruff's UP (pyupgrade) rule set would mechanically convert to builtin generics and PEP 604 unions, and catch the functools.cache upgrades too.
Tooling nit: bare ruff check (the documented command) fails with E902 on the intentionally-dangling fixture symlinks tests/packages/symlinks/{baz,qux}.py; it only passes today because pre-commit filters the file list. Adding those two paths to tool.ruff.exclude makes the documented command work.
Typos
Comment-only, but there are a few: "determninistically" (__init__.py:176, _editable.py:228), "libray" (__init__.py:466), "correcly" (__init__.py:1213), "unexising" (_tags.py:118), "reelases" (_tags.py:148), "determiniation" (_tags.py:175), "edition.." (_util.py:36), and a stray space in def _log(string: str , ...) (__init__.py:224).
Overall the codebase is in good shape — tight scope, well-commented, and most of the historical platform workarounds are still justified. The items I'd actually act on first are the meson version drift (1), the RPath check=True (3), and the path.replace no-op (2); each is a small, test-friendly fix. Happy to implement any of these — for the bug fixes I'd add the regression tests first per your convention.
Trying to make use of the one week 50% access to Fable! :) Here's a review of meson-python, following what I've done in other projects I maintain: https://iscinumpy.dev/post/claude-code-reviews/, such as scikit-build-core: scikit-build/scikit-build-core#1317 - I've been really happy with the results, the FP rate is nearly zero. Happy to generate fixes for things if other maintainers want me to!
(Also, congrats, no high-severity bugs. :) )
🤖 AI text below 🤖
Review complete — I read all six source modules in full and verified the suspicious findings against git history. Here's what I found, ordered by how much I'd prioritize each.
Bugs
_MESON_REQUIRED_VERSIONis out of sync withpyproject.toml(mesonpy/__init__.py:95). The constant says0.63.3(unchanged since the runtime check was added in cc2e326), butpyproject.tomlrequiresmeson >= 0.64.0, and>= 1.2.3on Python 3.12+. The comment on that line — and CLAUDE.md — say to keep them in sync. Beyond bumping it, the runtime check should probably be conditional on the Python version like the requirement is, otherwise meson 1.0 passes the check on Python 3.12 where it's known broken. Relatedly, on meson < 1.3.0 thepython.allow_limited_apiintrospection at__init__.py:896-899comes backNoneand silently disables the user'slimited-api = true— worth confirming that's intended.No-op
str.replacein the editable path hook (mesonpy/_editable.py:352).path.replace(os.altsep, os.sep)discards its result; it needspath = path.replace(...). On POSIXaltsepisNoneso nothing is affected, but on Windows the intended separator normalization never happens beforepath.rpartition(os.sep).Missing
check=Trueon RPath reads (mesonpy/_rpath.py:27and:75). The macOSotool -land ELFpatchelf --print-rpathinvocations don't check the return code. If the tool fails (missing patchelf, unreadable file),_get_rpathreturns nothing,fix_rpathbecomes a silent no-op, and the wheel ships with an RPATH still pointing at the build directory — a broken wheel with no error. The Solaris variant already passescheck=True; the other two should match.direct_url.jsonisn't valid JSON (mesonpy/__init__.py:548-550). The editable wheel writessource_dir.as_uri()— a bare unquotedfile:///...string — where PEP 610 specifies a JSON object like{"url": "...", "dir_info": {"editable": true}}. It's latent because pip overwrites the file at install time (nothing in the test suite exercises it), but any other consumer parsing it will choke. Either write the proper object or drop the file.Regex typo in the wheel filename pattern (
mesonpy/_wheelfile.py:26).(:?-(?P<build>[^-]+))?was clearly meant to be(?:...)— as written it's a capturing group that permits a stray literal colon. Harmless today since meson-python never generates build tags, but it's wrong as a validator.StopIterationcan mask the split-package error (mesonpy/__init__.py:160). In the purelib/platlib conflict branch,next(entry for entry in wheel_files[other] ...)has no default. If the package was first registered by aninstall_subdirsentry whose directory walked to zero files,packageshas the entry butwheel_files[other]has none, and the user gets a rawStopIteration/RuntimeErrorinstead of the helpfulBuildError. Usenext(..., None)and degrade the message.Symlink cycles hang
sdist()(mesonpy/__init__.py:1063). Thewhile member.issym():resolution loop never terminates if the archive contains a symlink cycle (a -> b,b -> a). Malformed input, but a bounded loop or visited-set would turn a hang into a warning like the other symlink cases.Outdated 32-bit-on-aarch64 tag (
mesonpy/_tags.py:192). A 32-bit interpreter on an aarch64 kernel is taggedlinux_armv7l; packaging ≥ 24.0 switched this case tolinux_armv8l(Ability to installarmv7l manylinux/musllinuxwheels onarmv8lpypa/packaging#690). Wheels tagged the old way are increasingly inconsistent with what pip/packaging compute on the install side.Performance
WheelFileWriter.write(mesonpy/_wheelfile.py:96-98) slurps each file fully into memory to hash and write it. For scientific wheels with very large shared objects that's a real memory spike; streaming in chunks throughzipfile.open(zinfo, 'w')while updating the sha256 incrementally fixes it without changing behavior._editable.walk()callsdirnames.sort()inside the per-entry loop (mesonpy/_editable.py:229), re-sorting once per subdirectory; the twin logic in__init__.py:177correctly sorts once after the pruning loop. Trivial cost, but the two copies of this function have quietly diverged, which is the more interesting problem given_editable.pymust stay standalone.Simplifications
mesonpy/__init__.py:919: the one.format(style=style, cmd=...)call in a file that otherwise uses f-strings._env_ninja_command(__init__.py:1236) reuses the nameversionfor both the required version parameter and the subprocess output — correct but needlessly confusing._string_or_strings(__init__.py:643):list([value,] if isinstance(value, str) else value)→[value] if isinstance(value, str) else list(value).membertwice when a symlink was resolved (__init__.py:1073and:1085); harmless, but the second copy's comment ("before starting to modify it") no longer tells the whole story.Modernizations (floor is Python 3.9)
@functools.lru_cache()/lru_cache(maxsize=None)→functools.cacheat__init__.py:203,:959,:964._tags.py:_get_cpython_abicontains aversion < (3, 8)pymalloc branch that can never run, and the whole function exists only as a fallback for CPython ≤ 3.8.7 on Windows (per its own comment) — on 3.9+EXT_SUFFIXalways parses, so_get_cpython_abiand its_get_config_varhelper are effectively dead. Worth either deleting or keeping deliberately with a note.from __future__ import annotations, yet theTYPE_CHECKINGblocks still importList,Dict,Optional,Union,Tuple,Type,DefaultDictfrom typing — and_editable.py:310already uses lowercasedict[str, str], so the style is inconsistent. Enabling ruff'sUP(pyupgrade) rule set would mechanically convert to builtin generics and PEP 604 unions, and catch thefunctools.cacheupgrades too.ruff check(the documented command) fails with E902 on the intentionally-dangling fixture symlinkstests/packages/symlinks/{baz,qux}.py; it only passes today because pre-commit filters the file list. Adding those two paths totool.ruff.excludemakes the documented command work.Typos
Comment-only, but there are a few: "determninistically" (
__init__.py:176,_editable.py:228), "libray" (__init__.py:466), "correcly" (__init__.py:1213), "unexising" (_tags.py:118), "reelases" (_tags.py:148), "determiniation" (_tags.py:175), "edition.." (_util.py:36), and a stray space indef _log(string: str , ...)(__init__.py:224).Overall the codebase is in good shape — tight scope, well-commented, and most of the historical platform workarounds are still justified. The items I'd actually act on first are the meson version drift (1), the RPath
check=True(3), and thepath.replaceno-op (2); each is a small, test-friendly fix. Happy to implement any of these — for the bug fixes I'd add the regression tests first per your convention.