Skip the maturin wheel build when its script is unreachable (#211) - #217
Skip the maturin wheel build when its script is unreachable (#211)#217leynos wants to merge 4 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughExtract wheel snapshot parsing into a dedicated helper, re-export it through the existing maturin helper, and add sysconfig-based detection with platform-specific tests, documentation, and a skip guard for interpreters where the maturin script is unavailable. ChangesMaturin build contracts
Sequence Diagram(s)sequenceDiagram
participant WheelBuildTest
participant maturin_script_locatable
participant sysconfig
participant ScriptsDirectory
WheelBuildTest->>maturin_script_locatable: check script availability
maturin_script_locatable->>sysconfig: read scripts directories
sysconfig-->>maturin_script_locatable: return scheme paths
maturin_script_locatable->>ScriptsDirectory: search for maturin stem
ScriptsDirectory-->>maturin_script_locatable: report presence or absence
maturin_script_locatable-->>WheelBuildTest: return boolean
WheelBuildTest->>WheelBuildTest: skip or build wheel snapshot
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 19 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (19 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
679ce9d to
3511d5d
Compare
Reviewer's GuideAdds an environment-aware detector for maturin’s compiled script so the maturin wheel snapshot test can skip in layered interpreters where the script is genuinely unreachable, and factors wheel snapshot parsing into a dedicated helper module while preserving existing imports. File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3511d5d78c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| def maturin_script_locatable() -> bool: | ||
| """Return whether maturin's own lookup can find its compiled script. |
There was a problem hiding this comment.
Document the new shared detector
This introduces maturin_script_locatable() as a reusable internal helper, but the commit does not record its intended scope and reuse policy in an architecture, design, or developers' guide document. Because this helper now determines when the native-wheel contract is skipped, document that boundary and the circumstances in which other tests should reuse it.
AGENTS.md reference: AGENTS.md:L118-L124
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@coderabbitai Has this now been resolved in the latest commit?
Use codegraph analysis to determine your answer.
If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '== Current revision and changed files =='
git rev-parse HEAD
git diff --name-status HEAD^ HEAD || true
printf '%s\n' '== Candidate guidance/documentation files =='
fd -HI -t f '^(AGENTS|CONTRIBUTING|ARCHITECTURE|DESIGN|DEVELOPMENT|README)(\..*)?$' . \
-E .git -E .venv -E node_modules | sort
printf '%s\n' '== Codegraph outline for maturin helper =='
ast-grep outline tests/helpers/maturin.py --items all --type function --match 'maturin|script|toolchain' --view expanded || true
printf '%s\n' '== Structural declarations and call sites =='
ast-grep run --lang python --pattern 'def maturin_script_locatable() -> bool: $$$' tests .
ast-grep run --lang python --pattern 'maturin_script_locatable()' .
printf '%s\n' '== Documentation and guidance references =='
rg -n -i -C 4 \
'maturin_script_locatable|maturin.{0,80}(script|sysconfig|overlay|skip)|script.{0,80}maturin' \
-g '*.md' -g '*.rst' -g '*.txt' -g '*.adoc' -g '*.py' . \
-g '!tests/helpers/maturin.py' -g '!**/.git/**' || true
printf '%s\n' '== Relevant latest diff =='
git diff HEAD^ HEAD -- AGENTS.md docs README.md CONTRIBUTING.md tests/helpers/maturin.py 2>/dev/null || trueLength of output: 27294
🏁 Script executed:
#!/bin/bash
set -euo pipefail
repo="leynos/cuprum"
pr_number="217"
needle="Document the new shared detector"
thread_id="$(
gh api graphql \
-F owner="${repo%%/*}" \
-F name="${repo#*/}" \
-F number="$pr_number" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 20) {
nodes { body author { login } }
}
}
}
}
}
}' \
--jq --arg needle "$needle" '
.data.repository.pullRequest.reviewThreads.nodes[]
| select(.comments.nodes[]? | (.author.login == "chatgpt-codex-connector[bot]" and (.body | contains($needle))))
| .id
' | head -n 1
)"
if [ -z "$thread_id" ]; then
echo "Matching review thread was not found." >&2
exit 1
fi
gh api graphql \
-F threadId="$thread_id" \
-f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread { id isResolved }
}
}'Length of output: 1569
🏁 Script executed:
#!/bin/bash
set -euo pipefail
repo="leynos/cuprum"
pr_number="217"
needle="Document the new shared detector"
threads_json="$(
gh api graphql \
-F owner="${repo%%/*}" \
-F name="${repo#*/}" \
-F number="$pr_number" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 20) {
nodes { body author { login } }
}
}
}
}
}
}'
)"
thread_id="$(
printf '%s' "$threads_json" |
jq -r --arg needle "$needle" '
.data.repository.pullRequest.reviewThreads.nodes[]
| select(
any(
.comments.nodes[];
.author.login == "chatgpt-codex-connector[bot]"
and (.body | contains($needle))
)
)
| .id
' |
head -n 1
)"
if [ -z "$thread_id" ] || [ "$thread_id" = "null" ]; then
echo "Matching review thread was not found." >&2
exit 1
fi
gh api graphql \
-F threadId="$thread_id" \
-f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread { id isResolved }
}
}' |
jq -e '
.data.resolveReviewThread.thread
| select(.isResolved == true)
'Length of output: 1819
🏁 Script executed:
#!/bin/bash
set -euo pipefail
gh api \
repos/leynos/cuprum/pulls/comments/3664558072 \
--jq '{id, node_id, user: .user.login, pull_request_review_id, html_url, body}'Length of output: 1229
@leynos, treat this finding as resolved.
docs/developers-guide.md Lines 1443-1482 now documents:
- the native-wheel skip boundary;
- the layered
uvinterpreter failure mode; - when tests must use both
toolchain_available()andmaturin_script_locatable(); - when they must not use the probe; and
- the policy to reuse and extend the shared helper rather than duplicate the
sysconfigscan.
I could not resolve the GitHub thread through the API: discussion r3664558072 belongs to chatgpt-codex-connector[bot], not coderabbitai, and I only resolve comments authored by this identity. No remaining-work prompt is needed.
🧬 Code Graph Analysis Results
Relevant snippets
tests/helpers/maturin.py
Lines 27-59 — MaturinBuildError (error type used when maturin build fails)
class MaturinBuildError(subprocess.CalledProcessError):
"""Maturin build failure with raw output and rendered diagnostics.
Attributes
----------
build_command : tuple[str, ...]
Command used to invoke the maturin wheel build.
returncode : int
Process exit status, inherited from ``CalledProcessError``.
stderr : str | bytes | None
Raw captured standard error, inherited from ``CalledProcessError``.
"""
def __init__(self, error: subprocess.CalledProcessError) -> None:
"""Store raw process diagnostics separately from ``str(error)``."""
super().__init__(
error.returncode,
error.cmd,
output=error.stdout,
stderr=error.stderr,
)
if isinstance(error.cmd, list | tuple):
self.build_command = tuple(str(part) for part in error.cmd)
else:
self.build_command = (str(error.cmd),)
def __str__(self) -> str:
"""Return an enriched diagnostic while preserving raw stderr."""
rendered_command = " ".join(self.build_command)
return (
f"maturin wheel build failed for command: {rendered_command}\n"
f"stderr:\n{self.stderr}"
)Lines 62-82 — read_expected_maturin_version(root) (reads maturin dev dependency pin from pyproject.toml)
def read_expected_maturin_version(root: Path) -> str:
"""Read the maturin version pinned in ``pyproject.toml``.
Raises
------
AssertionError
If the maturin dependency pin is missing.
FileNotFoundError
If ``pyproject.toml`` is absent.
OSError
If ``pyproject.toml`` cannot be read.
UnicodeDecodeError
If ``pyproject.toml`` is not valid UTF-8.
"""
pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
match = _MATURIN_PIN_RE.search(pyproject)
if match is None:
msg = "Could not locate maturin dev dependency pin in pyproject.toml"
raise AssertionError(msg)
return match.group(1)Lines 97-130 — read_maturin_pins(root) (reads synced maturin version pins across repo files)
def read_maturin_pins(root: Path) -> dict[str, str]:
"""Read maturin version pins from the synchronized locations.
Raises
------
AssertionError
If any maturin version pin is missing.
FileNotFoundError
If any pin source file is absent.
OSError
If any pin source file cannot be read.
UnicodeDecodeError
If any pin source file is not valid UTF-8.
"""
pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8")
action = (root / ".github/actions/build-wheels/action.yml").read_text(
encoding="utf-8"
)
return {
"pyproject.toml": _require_pin_match(
_MATURIN_PIN_RE.search(pyproject),
"pyproject.toml",
),
"build-wheels.yml": _require_pin_match(
_WORKFLOW_PIN_RE.search(workflow),
".github/workflows/build-wheels.yml",
),
"build-wheels/action.yml": _require_pin_match(
_ACTION_PIN_RE.search(action),
".github/actions/build-wheels/action.yml",
),
}Lines 133-162 — read_manylinux_aarch64_container_ref(root) (extracts MANYLINUX_AARCH64_CONTAINER pin from workflow)
def read_manylinux_aarch64_container_ref(root: Path) -> str:
"""Read the pinned manylinux aarch64 container reference.
Parameters
----------
root
Repository root containing ``.github/workflows/build-wheels.yml``.
Returns
-------
str
The pinned ``MANYLINUX_AARCH64_CONTAINER`` image reference.
Raises
------
AssertionError
If the ``MANYLINUX_AARCH64_CONTAINER`` pin is missing.
FileNotFoundError
If ``.github/workflows/build-wheels.yml`` is absent.
OSError
If ``.github/workflows/build-wheels.yml`` cannot be read.
UnicodeDecodeError
If ``.github/workflows/build-wheels.yml`` is not valid UTF-8.
"""
workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8")
return _require_pin_match(
_AARCH64_CONTAINER_PIN_RE.search(workflow),
".github/workflows/build-wheels.yml",
subject="MANYLINUX_AARCH64_CONTAINER pin",
)Lines 165-189 — workflow_uses_manylinux_aarch64_container_ref(root) (checks workflow uses env.MANYLINUX_AARCH64_CONTAINER)
def workflow_uses_manylinux_aarch64_container_ref(root: Path) -> bool:
"""Report whether the workflow references the pinned manylinux container.
Parameters
----------
root
Repository root containing ``.github/workflows/build-wheels.yml``.
Returns
-------
bool
``True`` when the Linux aarch64 build step uses
``env.MANYLINUX_AARCH64_CONTAINER``; otherwise ``False``.
Raises
------
FileNotFoundError
If ``.github/workflows/build-wheels.yml`` is absent.
OSError
If ``.github/workflows/build-wheels.yml`` cannot be read.
UnicodeDecodeError
If ``.github/workflows/build-wheels.yml`` is not valid UTF-8.
"""
workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8")
return _AARCH64_CONTAINER_USAGE_RE.search(workflow) is not NoneLines 200-206 — toolchain_available() (checks presence of cargo, rustc, and importable maturin)
def toolchain_available() -> bool:
"""Return whether the Rust toolchain and maturin are available."""
return (
shutil.which("cargo") is not None
and shutil.which("rustc") is not None
and _maturin_module_available()
)Lines 216-247 — maturin_script_locatable() (checks maturin’s bundled script lookup will find maturin)
def maturin_script_locatable() -> bool:
"""
Return whether maturin's own lookup can find its compiled script.
Parameters
----------
None
Returns
-------
bool
True if any sysconfig scheme's `scripts` directory contains a file whose stem is `maturin`.
Raises
------
None (exceptions are not raised; function relies on filesystem checks).
Implementation notes
----------------------
Uses `sysconfig.get_path("scripts", scheme)` across all schemes from the *running interpreter*
(`sys.prefix`/`sys.exec_prefix` conceptually), matching maturin’s `python -m maturin` behavior.
"""Lines 250-289 — build_native_wheel_artifact(root, out_dir) (invokes pinned maturin build and returns the single wheel artifact)
def build_native_wheel_artifact(root: Path, out_dir: Path) -> Path:
"""Build a native wheel with the pinned maturin version.
Raises
------
AssertionError
If the build does not produce exactly one wheel.
OSError
If the output directory cannot be created or inspected.
MaturinBuildError
If the maturin build command exits non-zero.
"""
out_dir.mkdir(parents=True, exist_ok=True)
command = [
sys.executable,
"-m",
"maturin",
"build",
"--release",
"--locked",
"--out",
str(out_dir),
"--manifest-path",
str(root / "rust/cuprum-rust/Cargo.toml"),
]
try:
subprocess.run( # noqa: S603 - trusted paths and pinned maturin
command,
check=True,
cwd=root,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as exc:
raise MaturinBuildError(exc) from exc
wheels = sorted(out_dir.glob("*.whl"))
if len(wheels) != 1:
msg = f"Expected exactly one wheel in {out_dir}, found {wheels!r}"
raise AssertionError(msg)
return wheels[0]Lines 292-294 — re-export
wheel_build_snapshot = _maturin_wheel.wheel_build_snapshottests/helpers/maturin_wheel.py
Lines 136-163 — wheel_build_snapshot(whl_path) (normalizes wheel metadata/layout for snapshot testing)
def wheel_build_snapshot(whl_path: Path) -> dict[str, typ.Any]:
"""Return a normalised snapshot of wheel metadata and layout.
Raises
------
AssertionError
If the wheel metadata is missing expected maturin fields.
OSError
If the wheel file cannot be opened or read.
zipfile.BadZipFile
If the wheel file is not a valid zip archive.
"""
with zipfile.ZipFile(whl_path) as archive:
entry_names = archive.namelist()
wheel_name = _locate_dist_info_wheel(entry_names)
metadata_name = wheel_name.replace("/WHEEL", "/METADATA")
wheel_payload = archive.read(wheel_name).decode("utf-8")
metadata_payload = archive.read(metadata_name).decode("utf-8")
generator, root_is_purelib = _parse_wheel_header(wheel_payload, whl_path)
return {
"generator": generator,
"metadata": _parse_metadata(metadata_payload),
"wheel": {
"root_is_purelib": root_is_purelib,
"tag": "<platform-tag>",
},
"entries": sorted(_normalise_wheel_entry(name) for name in entry_names),
}There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/helpers/maturin_wheel.py`:
- Line 40: Replace the dict[str, typ.Any] return type of _parse_metadata with a
TypedDict describing the fixed snapshot fields generator, metadata, wheel, and
entries, using their concrete value types. Update the parsed snapshot
construction and the test_maturin_wheel_build_snapshot call site as needed so
indexing snapshot_payload["generator"] and the other fields is statically typed
without Any.
In `@tests/helpers/maturin.py`:
- Around line 210-248: Update _script_named_maturin_exists to check only for the
exact maturin launcher file directly in the given scripts directory, removing
the recursive rglob/stem-based match that accepts decoy files such as
maturin.exe or nested entries. Add a regression test using a decoy file to
verify maturin_script_locatable does not report success when the exact launcher
is absent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5cf77047-b92a-481e-855f-4a5f30b06794
📒 Files selected for processing (3)
cuprum/unittests/test_maturin_build.pytests/helpers/maturin.pytests/helpers/maturin_wheel.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/shared-actions(auto-detected)leynos/pylint-pypy-shim(auto-detected)leynos/whitaker(auto-detected)
| def _script_named_maturin_exists(directory: Path) -> bool: | ||
| """Return whether ``directory`` contains a file stem named ``maturin``.""" | ||
| return any( | ||
| entry.is_file() and entry.stem == "maturin" for entry in directory.rglob("*") | ||
| ) | ||
|
|
||
|
|
||
| def maturin_script_locatable() -> bool: | ||
| """Return whether maturin's own lookup can find its compiled script. | ||
|
|
||
| Mirrors ``maturin.__main__.get_maturin_path``: the ``maturin`` PyPI | ||
| package resolves its bundled binary by walking each ``sysconfig`` | ||
| scheme's ``scripts`` directory for a file named ``maturin``, keyed off | ||
| ``sys.prefix``/``sys.exec_prefix`` of the *running* interpreter — not | ||
| ``sys.path`` or ``PATH``. This diverges from :func:`toolchain_available`, | ||
| whose ``importlib.util.find_spec`` check only confirms the ``maturin`` | ||
| module is importable. | ||
|
|
||
| The two checks disagree in layered/ephemeral interpreters such as a | ||
| ``uv run --with ...`` overlay: the overlay's ``sys.path`` includes the | ||
| project's own virtual environment (so the module imports fine and | ||
| :func:`toolchain_available` reports ``True``), but ``sys.prefix`` points | ||
| at a temporary environment that never received maturin's script, so | ||
| ``python -m maturin`` fails with ``Unable to find `maturin` script`` | ||
| even though a real build would succeed in the project's own virtualenv. | ||
| Checking this separately lets callers skip precisely where the build is | ||
| genuinely unreachable, without masking a regression in normal CI or | ||
| local-development environments, where ``sys.prefix`` matches the | ||
| virtualenv that installed maturin's script. | ||
| """ | ||
| script_dirs = ( | ||
| Path(sysconfig.get_path("scripts", scheme)) | ||
| for scheme in sysconfig.get_scheme_names() | ||
| ) | ||
| return any( | ||
| _script_named_maturin_exists(directory) | ||
| for directory in script_dirs | ||
| if directory.exists() | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files tests/helpers/maturin.py tests/unittests/test_maturin_build.py docs README.md 2>/dev/null || true
printf '\n--- maturin helper ---\n'
sed -n '1,280p' tests/helpers/maturin.py
printf '\n--- maturin build tests ---\n'
sed -n '1,260p' tests/unittests/test_maturin_build.pyRepository: leynos/cuprum
Length of output: 11298
🔍 Additional research query:
Deepwiki maturin get_maturin_path scripts lookup exact filename extension Windows
💡 Result:
I couldn’t verify a get_maturin_path symbol in the public sources I could access.
What I did find:
- Windows-specific interpreter lookup in maturin uses
python.exe; elsewhere it usespythonorpython3. (github.com) scriptsin maturin are console-entry metadata from[project.scripts](name -> module:callable), and they’re stored in wheel metadata alongsidegui_scripts/entry_points. (github.com)
If you want, I can keep digging for the exact source file/function once you share a branch/tag or the specific repo path.
🌐 Web query:
maturin __main__ get_maturin_path scripts directory filename extension Windows site:github.com/pypa/maturin
💡 Result:
Based on the available documentation, there is no public API or function named get_maturin_path in the maturin library [1][2][3][4]. Regarding the scripts directory and file extensions on Windows: 1. Scripts Directory: When Python packages (including those built with maturin) define console scripts in the project configuration (e.g., under [project.scripts] in pyproject.toml), they are typically installed into the Scripts directory of the active Python virtual environment or the Python installation directory on Windows [1][3]. 2. Windows Executable Extensions: On Windows, console scripts generated by Python packaging tools are typically created as executable files in the Scripts directory. These files commonly have a.exe extension (e.g., script_name.exe) to facilitate execution from the command line, often accompanied by wrapper scripts (such as.py or.cmd files) that invoke the underlying Python logic [1][3]. Maturin itself does not define the path for these scripts; it relies on standard Python packaging behavior and the environment's sysconfig settings to determine the appropriate installation locations [2].
Citations:
- 1: https://pypi.org/project/maturin/1.10.0/
- 2: https://pypi.org/project/maturin/0.7.4/
- 3: https://pypi.org/project/maturin/0.15.0/
- 4: https://pypi.org/project/maturin/0.12.5/
🔍 Additional research query:
Context7 maturin pypa/maturin get_maturin_path __main__ scripts
💡 Result:
maturin documents console scripts under [project.scripts]: the key is the script name, and the value is a module.path:class.function target; the function gets no arguments. (github.com)
For python -m package, Python runs that package’s __main__.py. The Python packaging docs also say __main__.py is the package’s main entry point, while console_scripts exposes an installed command. (docs.python.org)
I couldn’t verify get_maturin_path from the accessible repo search results. (github.com)
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- test files mentioning maturin ---'
rg -n "maturin" tests -g '!**/__pycache__/**' || true
printf '\n--- helper file with line numbers ---\n'
cat -n tests/helpers/maturin.py | sed -n '200,260p'Repository: leynos/cuprum
Length of output: 7868
🌐 Web query:
site:github.com/pyo3/maturin "get_maturin_path" "maturin" "__main__.py"
💡 Result:
The get_maturin_path function is an internal helper defined within the maturin/main.py file of the maturin repository [1]. It is not intended for public use as a standalone API [1]. Its purpose is to locate the maturin executable script on the user's system by searching through Python's sysconfig installation schemes (e.g., bin/scripts directories) [1]. When the maturin package is executed directly via python -m maturin (which triggers main.py), get_maturin_path is called to find the actual maturin binary [1]. Once found, it invokes that binary: - On Windows, it uses subprocess.call to execute the script [1]. - On non-Windows platforms, it uses os.execv to replace the current process with the maturin executable [1]. This mechanism ensures that when users run python -m maturin, the command behaves as if they had invoked the maturin executable directly, respecting the current Python environment's path configuration [1]. If the script cannot be found, it will print an error message and exit with status code 1 [1].
Citations:
🌐 Web query:
maturin "__main__.py" "Unable to find maturin script" github
💡 Result:
The error "Unable to find maturin script" originates from the maturin/main.py file [1], which is designed to execute the maturin binary located in your Python environment's scripts directory [1]. When you run python -m maturin, this file attempts to locate the maturin executable [1]. If it cannot find the file in the directories specified by sysconfig, it outputs this error message and exits [1]. This issue generally indicates that the maturin package is either not installed in your current Python environment or the path to its executable is not correctly configured in your PATH or Python's sysconfig paths [1]. To resolve this issue, consider the following steps: 1. Reinstall or update maturin: Ensure maturin is properly installed in your active virtual environment. You can do this by running: pip install --upgrade pip pip install maturin 2. Verify installation: Ensure that the maturin executable is accessible. You can check if the installation was successful by running maturin --version directly in your terminal. 3. Environment issues: If you are using a virtual environment (venv/poetry/pdm), ensure it is activated before running the command [2][3]. If you continue to face issues, it may be due to how your specific Python environment manages scripts [4]. If the installation fails, ensure you have the Rust compiler toolchain installed, as building maturin from source (or building projects with it) requires it [5]. The recommended way to install Rust is via rustup (https://rustup.rs) [6][5].
Citations:
- 1: https://github.com/PyO3/maturin/blob/main/maturin/__main__.py
- 2: https://www.maturin.rs/
- 3: https://stackoverflow.com/questions/78204333/how-to-run-rust-library-unit-tests-with-maturin
- 4: PDM will unconditionally install packages with MSYS2 Python rather than compile C/Rust extensions. pdm-project/pdm#3158
- 5: DietPi-Software | Home Assistant: Install/update fails on maturin compilation MichaIng/DietPi#5630
- 6: libsecp256k1-dev ton-blockchain/mytonctrl#273
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "maturin_script_locatable\(|toolchain_available\(" tests -g '!**/__pycache__/**' || true
printf '\n--- surrounding context in the relevant test file ---\n'
for f in $(rg -l "maturin_script_locatable\(" tests -g '!**/__pycache__/**' || true); do
echo "### $f"
nl -ba "$f" | sed -n '1,220p'
doneRepository: leynos/cuprum
Length of output: 408
🏁 Script executed (no clone):
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import urllib.request
urls = [
"https://raw.githubusercontent.com/PyO3/maturin/main/maturin/__main__.py",
"https://raw.githubusercontent.com/PyO3/maturin/main/maturin/main.py",
]
for url in urls:
print(f"--- {url} ---")
try:
with urllib.request.urlopen(url, timeout=20) as resp:
text = resp.read().decode("utf-8", "replace")
for needle in ["get_maturin_path", "Unable to find `maturin` script", "sysconfig", "rglob"]:
if needle in text:
idx = text.index(needle)
start = max(0, idx - 800)
end = min(len(text), idx + 1800)
print(text[start:end])
break
else:
print("needle not found")
except Exception as e:
print(f"ERROR: {e}")
PY
Length of output: 565
🏁 Script executed (no clone):
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import urllib.request
from textwrap import indent
candidates = [
"https://raw.githubusercontent.com/PyO3/maturin/main/maturin/__main__.py",
"https://raw.githubusercontent.com/PyO3/maturin/main/maturin/main.py",
]
for url in candidates:
print(f"\n=== {url} ===")
try:
with urllib.request.urlopen(url, timeout=20) as resp:
text = resp.read().decode("utf-8", "replace")
except Exception as e:
print(f"ERROR: {e}")
continue
for needle in ["def get_maturin_path", "Unable to find `maturin` script", "sysconfig.get_path", "scripts"]:
if needle in text:
idx = text.index(needle)
start = max(0, text.rfind("\n", 0, idx - 600))
end = min(len(text), text.find("\n\n", idx + 1))
if end == -1:
end = min(len(text), idx + 2200)
print(text[start:end])
break
else:
print("No target strings found.")
PY
Length of output: 567
Replace the recursive stem scan with an exact launcher check. rglob("*") plus stem == "maturin" accepts any maturin.* file under the scripts directory, so this probe can report success even when maturin’s launcher is missing. Add a regression test with a decoy file.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/helpers/maturin.py` around lines 210 - 248, Update
_script_named_maturin_exists to check only for the exact maturin launcher file
directly in the given scripts directory, removing the recursive rglob/stem-based
match that accepts decoy files such as maturin.exe or nested entries. Add a
regression test using a decoy file to verify maturin_script_locatable does not
report success when the exact launcher is absent.
There was a problem hiding this comment.
@coderabbitai Has this now been resolved in the latest commit?
Use codegraph analysis to determine your answer.
If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- current revision ---'
git rev-parse HEAD
git status --short
git diff -- tests/helpers/maturin.py tests/unittests/test_maturin_build.py || true
printf '%s\n' '--- helper codegraph outline ---'
ast-grep outline tests/helpers/maturin.py --items all --type function --match 'maturin|script' --view expanded
printf '%s\n' '--- helper structural implementations and call sites ---'
ast-grep run \
--pattern $'def _script_named_maturin_exists($DIR):\n $$$' \
--lang python tests/helpers/maturin.py
ast-grep run \
--pattern $'def maturin_script_locatable():\n $$$' \
--lang python tests/helpers/maturin.py
rg -n -C 5 '\b(_script_named_maturin_exists|maturin_script_locatable)\s*\(' tests
printf '%s\n' '--- maturin build test codegraph outline ---'
ast-grep outline tests/unittests/test_maturin_build.py --items all --type function --match 'maturin|script' --view expanded
printf '%s\n' '--- focused test source ---'
rg -n -C 12 '\btest_maturin_script_locatable\b|maturin_script_locatable|maturin\.exe|decoy|nested' \
tests/unittests/test_maturin_build.py tests/helpers/maturin.py || trueLength of output: 9622
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- current helper implementation ---'
nl -ba tests/helpers/maturin.py | sed -n '195,270p'
printf '%s\n' '--- current focused test implementation ---'
nl -ba tests/unittests/test_maturin_build.py | sed -n '1,260p'
printf '%s\n' '--- codegraph: helper callers and filesystem predicates ---'
rg -n -C 4 '\b(_script_named_maturin_exists|maturin_script_locatable)\b|\.rglob\(|\.glob\(|\.stem\b|is_file\(\)' \
tests/helpers/maturin.py tests/unittests/test_maturin_build.py || true
printf '%s\n' '--- codegraph: direct call relationships ---'
ast-grep run \
--pattern '_script_named_maturin_exists($ARG)' \
--lang python tests \
|| true
ast-grep run \
--pattern 'maturin_script_locatable()' \
--lang python tests \
|| true
printf '%s\n' '--- latest commits affecting review files ---'
git log -n 5 --oneline -- tests/helpers/maturin.py tests/unittests/test_maturin_build.pyLength of output: 3735
@leynos, do not resolve the thread. Codegraph analysis at e71ab57e312bea314d8e3bdd6e57e382ea5f0953 shows that maturin_script_locatable() still calls _script_named_maturin_exists() (Line 245), and that helper still performs directory.rglob("*") with entry.stem == "maturin" (Lines 210–214). It therefore continues to accept maturin.exe and nested maturin decoys.
Verify the existing CodeRabbit finding against the latest branch and fix it if still valid.
In `tests/helpers/maturin.py`, replace `_script_named_maturin_exists(directory: Path)`'s recursive `directory.rglob("*")` / `entry.stem == "maturin"` scan with a direct check for the exact `maturin` launcher file in `directory`. Do not accept extension-suffixed files such as `maturin.exe`, and do not search nested directories.
Locate the existing `maturin_script_locatable` tests before editing them. Add a focused regression test that:
1. Configures the mocked `sysconfig` scripts directory.
2. Creates a decoy `maturin.exe` file, or a nested `maturin` file.
3. Asserts that `maturin_script_locatable()` returns `False` when the exact direct `maturin` launcher is absent.
Retain the positive test proving that a direct `scripts_dir / "maturin"` file returns `True`. Keep the change minimal and run the focused test suite plus the repository’s applicable formatting, lint, and type-check commands.
🧬 Code Graph Analysis Results
[snippets relevant to understanding tests/helpers/maturin.py with respect to the comment]
tests/helpers/maturin.py
-
Lines ~1-21: Regex/constants for version/container pin extraction
_MATURIN_PIN_RE = re.compile(r"maturin==(\d+\.\d+\.\d+)") _WORKFLOW_PIN_RE = re.compile(r'MATURIN_VERSION:\s*"(\d+\.\d+\.\d+)"') _ACTION_PIN_RE = re.compile(r'default:\s*"(\d+\.\d+\.\d+)"') _AARCH64_CONTAINER_PIN_RE = re.compile( r"^\s*MANYLINUX_AARCH64_CONTAINER:\s*([^\s#]+)\s+#\s*\S.*$", re.MULTILINE, ) _AARCH64_CONTAINER_USAGE_RE = re.compile( r"^\s*container:\s*\$\{\{\s*env\.MANYLINUX_AARCH64_CONTAINER\s*\}\}\s*$", re.MULTILINE, )
-
Lines ~24-58:
MaturinBuildErrorfor enriched build failure diagnosticsclass MaturinBuildError(subprocess.CalledProcessError): """Maturin build failure with raw output and rendered diagnostics.""" def __init__(self, error: subprocess.CalledProcessError) -> None: """Store raw process diagnostics separately from ``str(error)``.""" super().__init__( error.returncode, error.cmd, output=error.stdout, stderr=error.stderr, ) if isinstance(error.cmd, list | tuple): self.build_command = tuple(str(part) for part in error.cmd) else: self.build_command = (str(error.cmd),) def __str__(self) -> str: """Return an enriched diagnostic while preserving raw stderr.""" rendered_command = " ".join(self.build_command) return ( f"maturin wheel build failed for command: {rendered_command}\n" f"stderr:\n{self.stderr}" )
-
Lines ~60-95:
read_expected_maturin_version(root)readsmaturin==X.Y.Zfrompyproject.tomldef read_expected_maturin_version(root: Path) -> str: pyproject = (root / "pyproject.toml").read_text(encoding="utf-8") match = _MATURIN_PIN_RE.search(pyproject) if match is None: msg = "Could not locate maturin dev dependency pin in pyproject.toml" raise AssertionError(msg) return match.group(1)
-
Lines ~97-122:
_require_pin_match(match, location, subject=...)raisesAssertionErrorwith locationdef _require_pin_match( match: re.Match[str] | None, location: str, *, subject: str = "maturin version pin", ) -> str: if match is None: msg = f"Could not locate {subject} in {location}" raise AssertionError(msg) return match.group(1)
-
Lines ~124-158:
read_maturin_pins(root)extracts pins frompyproject.toml, workflow, and actiondef read_maturin_pins(root: Path) -> dict[str, str]: pyproject = (root / "pyproject.toml").read_text(encoding="utf-8") workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8") action = (root / ".github/actions/build-wheels/action.yml").read_text( encoding="utf-8" ) return { "pyproject.toml": _require_pin_match( _MATURIN_PIN_RE.search(pyproject), "pyproject.toml", ), "build-wheels.yml": _require_pin_match( _WORKFLOW_PIN_RE.search(workflow), ".github/workflows/build-wheels.yml", ), "build-wheels/action.yml": _require_pin_match( _ACTION_PIN_RE.search(action), ".github/actions/build-wheels/action.yml", ), }
-
Lines ~160-202:
read_manylinux_aarch64_container_ref(root)andworkflow_uses_manylinux_aarch64_container_ref(root)read/verify container pin usage in workflowdef read_manylinux_aarch64_container_ref(root: Path) -> str: workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8") return _require_pin_match( _AARCH64_CONTAINER_PIN_RE.search(workflow), ".github/workflows/build-wheels.yml", subject="MANYLINUX_AARCH64_CONTAINER pin", ) def workflow_uses_manylinux_aarch64_container_ref(root: Path) -> bool: workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8") return _AARCH64_CONTAINER_USAGE_RE.search(workflow) is not None
-
Lines ~204-238: Local availability checks for Rust/maturin
_maturin_module_available()usesimportlib.util.find_spec("maturin")toolchain_available()checkscargo,rustc, and maturin import availability
def toolchain_available() -> bool: return ( shutil.which("cargo") is not None and shutil.which("rustc") is not None and _maturin_module_available() )
-
Lines ~240-313:
maturin_script_locatable()checks for amaturinscript file in interpreter scheme script dirs (walkssysconfigscheme directories)def maturin_script_locatable() -> bool: script_dirs = ( Path(sysconfig.get_path("scripts", scheme)) for scheme in sysconfig.get_scheme_names() ) return any( _script_named_maturin_exists(directory) for directory in script_dirs if directory.exists() )
-
Lines ~315-372:
build_native_wheel_artifact(root, out_dir)builds wheel usingpython -m maturin build --lockedand expects exactly one.whloutputdef build_native_wheel_artifact(root: Path, out_dir: Path) -> Path: out_dir.mkdir(parents=True, exist_ok=True) command = [ sys.executable, "-m", "maturin", "build", "--release", "--locked", "--out", str(out_dir), "--manifest-path", str(root / "rust/cuprum-rust/Cargo.toml"), ] try: subprocess.run( command, check=True, cwd=root, capture_output=True, text=True, ) except subprocess.CalledProcessError as exc: raise MaturinBuildError(exc) from exc wheels = sorted(out_dir.glob("*.whl")) if len(wheels) != 1: msg = f"Expected exactly one wheel in {out_dir}, found {wheels!r}" raise AssertionError(msg) return wheels[0]
-
Lines ~375-377: Re-export of
wheel_build_snapshotfrom sibling modulewheel_build_snapshot = _maturin_wheel.wheel_build_snapshot
tests/helpers/maturin_wheel.py
- Lines ~162-189:
wheel_build_snapshot(whl_path)opens wheel zip, reads*.dist-info/WHEELand*.dist-info/METADATA, parses fields, and returns a normalized snapshotdef wheel_build_snapshot(whl_path: Path) -> WheelBuildSnapshot: """Return a normalised snapshot of wheel metadata and layout.""" with zipfile.ZipFile(whl_path) as archive: entry_names = archive.namelist() wheel_name = _locate_dist_info_wheel(entry_names) metadata_name = wheel_name.replace("/WHEEL", "/METADATA") wheel_payload = archive.read(wheel_name).decode("utf-8") metadata_payload = archive.read(metadata_name).decode("utf-8") generator, root_is_purelib = _parse_wheel_header(wheel_payload, whl_path) return { "generator": generator, "metadata": _parse_metadata(metadata_payload), "wheel": { "root_is_purelib": root_is_purelib, "tag": "<platform-tag>", }, "entries": sorted(_normalise_wheel_entry(name) for name in entry_names), }
There was a problem hiding this comment.
@coderabbitai Has this now been resolved in the latest commit?
Use codegraph analysis to determine your answer.
If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- checked-out revision ---'
git rev-parse HEAD
git status --short
git log -n 8 --oneline -- tests/helpers/maturin.py tests/unittests/test_maturin_build.py
printf '%s\n' '--- helper codegraph outline ---'
ast-grep outline tests/helpers/maturin.py \
--items all --type function --match 'maturin|script' --view expanded
printf '%s\n' '--- exact helper implementations ---'
ast-grep run \
--pattern $'def _script_named_maturin_exists($DIRECTORY: Path) -> bool:\n $$$' \
--lang python tests/helpers/maturin.py || true
ast-grep run \
--pattern $'def maturin_script_locatable() -> bool:\n $$$' \
--lang python tests/helpers/maturin.py || true
printf '%s\n' '--- helper call graph and filesystem matching ---'
rg -n -C 5 \
'\b(_script_named_maturin_exists|maturin_script_locatable)\s*\(|\.rglob\(|\.glob\(|\.stem\b|maturin\.exe|nested' \
tests/helpers/maturin.py tests/unittests/test_maturin_build.py || true
printf '%s\n' '--- focused test codegraph outline ---'
ast-grep outline tests/unittests/test_maturin_build.py \
--items all --type function --match 'maturin|script' --view expanded
printf '%s\n' '--- GitHub review thread metadata ---'
gh api graphql -f query='
query {
repository(owner: "leynos", name: "cuprum") {
pullRequest(number: 217) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 20) {
nodes {
databaseId
author { login }
body
url
}
}
}
}
}
}
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[]
| select(any(.comments.nodes[]; .url == "https://github.com/leynos/cuprum/pull/217#discussion_r3664665154"))
| {id, isResolved, comments}'Length of output: 39300
#!/bin/bash
set -euo pipefail
gh api graphql \
-F threadId='PRRT_kwDOQgt8686UXLHr' \
-f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread {
id
isResolved
}
}
}' \
--jq '.data.resolveReviewThread.thread'
🧬 Code Graph Analysis Results
Relevant code snippets
tests/helpers/maturin.py (lines 28-60)
class MaturinBuildError(subprocess.CalledProcessError):
"""Maturin build failure with raw output and rendered diagnostics.
Attributes
----------
build_command : tuple[str, ...]
Command used to invoke the maturin wheel build.
returncode : int
Process exit status, inherited from ``CalledProcessError``.
stderr : str | bytes | None
Raw captured standard error, inherited from ``CalledProcessError``.
"""
def __init__(self, error: subprocess.CalledProcessError) -> None:
"""Store raw process diagnostics separately from ``str(error)``."""
super().__init__(
error.returncode,
error.cmd,
output=error.stdout,
stderr=error.stderr,
)
if isinstance(error.cmd, list | tuple):
self.build_command = tuple(str(part) for part in error.cmd)
else:
self.build_command = (str(error.cmd),)
def __str__(self) -> str:
"""Return an enriched diagnostic while preserving raw stderr."""
rendered_command = " ".join(self.build_command)
return (
f"maturin wheel build failed for command: {rendered_command}\n"
f"stderr:\n{self.stderr}"
)tests/helpers/maturin.py (lines 63-83)
def read_expected_maturin_version(root: Path) -> str:
"""Read the maturin version pinned in ``pyproject.toml``.
Raises
------
AssertionError
If the maturin dependency pin is missing.
FileNotFoundError
If ``pyproject.toml`` is absent.
OSError
If ``pyproject.toml`` cannot be read.
UnicodeDecodeError
If ``pyproject.toml`` is not valid UTF-8.
"""
pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
match = _MATURIN_PIN_RE.search(pyproject)
if match is None:
msg = "Could not locate maturin dev dependency pin in pyproject.toml"
raise AssertionError(msg)
return match.group(1)tests/helpers/maturin.py (lines 98-131)
def read_maturin_pins(root: Path) -> dict[str, str]:
"""Read maturin version pins from the synchronized locations.
Raises
------
AssertionError
If any maturin version pin is missing.
FileNotFoundError
If any pin source file is absent.
OSError
If any pin source file cannot be read.
UnicodeDecodeError
If any pin source file is not valid UTF-8.
"""
pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
workflow = (root / ".github/workflows/build-wheels.yml").read_text(encoding="utf-8")
action = (root / ".github/actions/build-wheels/action.yml").read_text(
encoding="utf-8"
)
return {
"pyproject.toml": _require_pin_match(
_MATURIN_PIN_RE.search(pyproject),
"pyproject.toml",
),
"build-wheels.yml": _require_pin_match(
_WORKFLOW_PIN_RE.search(workflow),
".github/workflows/build-wheels.yml",
),
"build-wheels/action.yml": _require_pin_match(
_ACTION_PIN_RE.search(action),
".github/actions/build-wheels/action.yml",
),
}tests/helpers/maturin.py (lines 217-248) — summary
def maturin_script_locatable() -> bool:
"""Return whether maturin's own lookup can find its compiled script.
Parameters: none.
Returns: bool indicating whether a file stem named 'maturin' exists under any
sysconfig 'scripts' directory for schemes present on the running interpreter.
Exceptions: none explicitly raised by this function (other failures would come
from sysconfig/path filesystem calls).
Important details: walks sysconfig.get_scheme_names(), checks directories exist,
and searches recursively for files whose stem is exactly 'maturin'.
"""tests/helpers/maturin.py (lines 251-290)
def build_native_wheel_artifact(root: Path, out_dir: Path) -> Path:
"""Build a native wheel with the pinned maturin version.
Raises
------
AssertionError
If the build does not produce exactly one wheel.
OSError
If the output directory cannot be created or inspected.
MaturinBuildError
If the maturin build command exits non-zero.
"""
out_dir.mkdir(parents=True, exist_ok=True)
command = [
sys.executable,
"-m",
"maturin",
"build",
"--release",
"--locked",
"--out",
str(out_dir),
"--manifest-path",
str(root / "rust/cuprum-rust/Cargo.toml"),
]
try:
subprocess.run( # noqa: S603 - trusted paths and pinned maturin
command,
check=True,
cwd=root,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as exc:
raise MaturinBuildError(exc) from exc
wheels = sorted(out_dir.glob("*.whl"))
if len(wheels) != 1:
msg = f"Expected exactly one wheel in {out_dir}, found {wheels!r}"
raise AssertionError(msg)
return wheels[0]tests/helpers/maturin.py (lines 293-295)
wheel_build_snapshot = _maturin_wheel.wheel_build_snapshottests/helpers/maturin_wheel.py (lines 162-189)
def wheel_build_snapshot(whl_path: Path) -> WheelBuildSnapshot:
"""Return a normalised snapshot of wheel metadata and layout.
Raises
------
AssertionError
If the wheel metadata is missing expected maturin fields.
OSError
If the wheel file cannot be opened or read.
zipfile.BadZipFile
If the wheel file is not a valid zip archive.
"""
with zipfile.ZipFile(whl_path) as archive:
entry_names = archive.namelist()
wheel_name = _locate_dist_info_wheel(entry_names)
metadata_name = wheel_name.replace("/WHEEL", "/METADATA")
wheel_payload = archive.read(wheel_name).decode("utf-8")
metadata_payload = archive.read(metadata_name).decode("utf-8")
generator, root_is_purelib = _parse_wheel_header(wheel_payload, whl_path)
return {
"generator": generator,
"metadata": _parse_metadata(metadata_payload),
"wheel": {
"root_is_purelib": root_is_purelib,
"tag": "<platform-tag>",
},
"entries": sorted(_normalise_wheel_entry(name) for name in entry_names),
}|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cuprum/unittests/test_maturin_build.py`:
- Line 73: Update both assertions in the maturin launcher-discovery tests,
including the assertion in the test containing maturin_script_locatable(), to
include descriptive failure messages identifying the expected launcher-discovery
outcome; preserve the existing assertion conditions.
In `@tests/helpers/maturin_wheel.py`:
- Line 33: Update the docstrings in tests/helpers/maturin_wheel.py at the
affected locations, including the prose associated with the metadata snapshot,
to use “normalized” instead of “normalised”; preserve all identifiers and other
wording.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a9fbc6eb-4724-4b04-a253-349c20c1f08f
📒 Files selected for processing (4)
cuprum/unittests/test_maturin_build.pydocs/developers-guide.mdtests/helpers/maturin.pytests/helpers/maturin_wheel.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/shared-actions(auto-detected)leynos/pylint-pypy-shim(auto-detected)leynos/whitaker(auto-detected)
|
|
||
|
|
||
| class WheelMetadata(typ.TypedDict): | ||
| """Normalised ``.dist-info/METADATA`` fields captured in the snapshot.""" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Standardize the new prose on Oxford -ize spelling.
Replace “normalised” with “normalized” in these docstrings. Based on learnings, prose outside identifiers must use en-GB-oxendict “-ize” spellings.
Also applies to: 67-67, 163-163
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/helpers/maturin_wheel.py` at line 33, Update the docstrings in
tests/helpers/maturin_wheel.py at the affected locations, including the prose
associated with the metadata snapshot, to use “normalized” instead of
“normalised”; preserve all identifiers and other wording.
Sources: Coding guidelines, Path instructions, Learnings
`test_maturin_wheel_build_snapshot` fails every scheduled mutmut run: `sys.executable -m maturin build` dies with "Unable to find `maturin` script" before any mutants are generated, aborting the whole baseline. The `maturin` PyPI package resolves its own compiled binary by walking each `sysconfig` scheme's `scripts` directory keyed off the running interpreter's `sys.prefix`, not `sys.path` or `PATH`. Under mutmut's `uv run --with mutmut==3.6.0` overlay, `sys.prefix` points at a temporary environment layered on top of the project's own virtualenv: the `maturin` module imports fine (via `sys.path`, so `cargo`/`rustc` and `toolchain_available()` all report success), but the overlay never received maturin's script, so the lookup comes up empty. Add `maturin_script_locatable()` to `tests/helpers/maturin.py`, mirroring maturin's own lookup, and skip the wheel-build test with a precise reason when it reports the script unreachable. In a normal virtualenv (CI, `build-wheels.yml`, local `uv run pytest`) `sys.prefix` matches the install location, the script is found, and the real build still runs — only the layered mutmut overlay skips. Add unit tests pinning the new detector's true/false behaviour by faking the `sysconfig` scheme lookup. Rebasing onto main brought in `MaturinBuildError` (#144), which — combined with the new `maturin_script_locatable()` detector — pushed `tests/helpers/maturin.py` past pylint's 400-line module limit. Extract the wheel-artifact snapshot parsers (`wheel_build_snapshot` and its private helpers) into a new sibling module `tests/helpers/maturin_wheel.py`, and re-export `wheel_build_snapshot` from `tests/helpers/maturin.py` so existing import sites are unchanged.
Record the scope and reuse policy for the shared `maturin_script_locatable()` detector in the developers' guide, as the abstraction/helper policy in AGENTS.md requires. The helper now decides when the native-wheel build contract is skipped, so document: - how it mirrors maturin's own `sysconfig`-scheme script lookup keyed off `sys.prefix`, and why that is deliberately narrower than `toolchain_available()`'s importability probe; - the layered/ephemeral interpreter case (the `uv run --with mutmut` overlay) where the two checks disagree and the skip is genuine, versus normal virtualenvs where the real build still runs; - the reuse policy: tests that shell out to `python -m maturin build` should gate on both `toolchain_available()` and `maturin_script_locatable()`; import-only or pin/metadata tests should not; extend the helper in place rather than re-deriving the scan. Also note that the wheel-artefact snapshot parsers now live in `tests/helpers/maturin_wheel.py` (re-exported from `tests/helpers/maturin.py`), matching the module split.
Replace the `dict[str, typ.Any]` returns in `tests/helpers/maturin_wheel.py` with `TypedDict`s describing the fixed snapshot shape: `WheelBuildSnapshot` (`generator`, `metadata`, `wheel`, `entries`), plus `WheelMetadata` and `WheelHeaders` for the nested payloads. Indexing `snapshot_payload["generator"]` at the `test_maturin_wheel_build_snapshot` call site is now statically typed without `Any`; `TypedDict` is a plain dict at runtime, so the syrupy snapshot comparison is unchanged. Also pin the deliberate stem-based, recursive matching in `_script_named_maturin_exists` with a regression test. maturin's own `get_maturin_path` walks the scripts directory with `os.walk` and compares `os.path.splitext(f)[0]` against `"maturin"`, so it accepts any extension at any depth; `maturin.exe` is the real launcher on the `windows-2022` wheel target. Narrowing the match to an exact top-level `maturin` filename would diverge from maturin and make the probe report unavailable on Windows, silently skipping the native-wheel contract there.
Give both remaining bare assertions in the maturin launcher-discovery tests descriptive failure messages naming the expected outcome, matching the style already used by the Windows-launcher test and the rest of the module. The assertion conditions are unchanged. Switch the `-ised` docstring prose in `tests/helpers/maturin_wheel.py` to the `-ized` Oxford endings the documentation style guide requires (`docs/documentation-style-guide.md`, en-GB-oxendict), matching the `_normalise_wheel_entry` docstring that already read "Normalize". Identifiers keep their existing spelling, so `_normalise_wheel_entry` and the `normalised` loop variable are untouched.
e71ab57 to
4f0cf31
Compare
Summary
cuprumhas failed at thebaseline since 2026-07-14:
test_maturin_wheel_build_snapshotdieswith
Unable to findmaturinscriptbefore mutmut generates anymutants, so no mutation testing has actually happened.
maturinPyPI package locates its own compiledbinary by walking each
sysconfigscheme'sscriptsdirectory keyedoff the running interpreter's
sys.prefix— notsys.path/PATH.Under mutmut's
uv run --with mutmut==3.6.0overlay,sys.prefixpoints at a temporary environment layered on top of the project's own
virtualenv: the
maturinmodule still imports fine (viasys.path,so
cargo/rustcand the existingtoolchain_available()check allreport success), but the overlay never received maturin's script, so
the lookup comes up empty and the build subprocess exits 1.
maturin_script_locatable()totests/helpers/maturin.py,mirroring maturin's own lookup exactly, and skip
test_maturin_wheel_build_snapshotwith a precise reason when itreports the script unreachable. In every normal environment (CI,
build-wheels.yml, localuv run pytest)sys.prefixmatches thevirtualenv that installed the script, so the real native build still
runs unchanged — only the layered mutmut overlay skips.
Closes #211
Why this design over the alternatives
The issue proposed two paths: exclude the test from mutmut's baseline
selection, or make the test locate maturin robustly and skip only when
genuinely unavailable. This PR takes the second path because it is the
more precise fix for the actual root cause:
overlay (confirmed empirically below), so a
pytest.skipwith anexact reason is not a workaround — it is accurate diagnosis.
[tool.mutmut]section inpyproject.toml(there currently is none;mutmut runs on defaults) and would silence the test everywhere
mutmut runs, even in environments where the build would actually
succeed.
maturin buildpath in every environment that can actually completeit, per the issue's stated preference.
Review walkthrough
tests/helpers/maturin.py:adds
_script_named_maturin_exists()andmaturin_script_locatable(),which reproduce
maturin.__main__.get_maturin_path()'s ownsysconfig-based scan for a file namedmaturinunder each scheme'sscriptsdirectory.cuprum/unittests/test_maturin_build.py:test_maturin_wheel_build_snapshotnow skips with a reason namingsys.prefixwhen the script can't be found, alongside the existingRust-toolchain skip. Two new unit tests
(
test_maturin_script_locatable_true_when_script_present/_false_when_script_absent) pin the detector's behaviour by fakingthe
sysconfigscheme lookup, independent of the real environment.tests/helpers/maturin_wheel.py:new module. Rebasing onto
mainpulled inMaturinBuildError(Fix Command-Query Separation violations (#118) #144),which together with the new detector pushed
tests/helpers/maturin.pypast pylint's 400-line module limit. The wheel-artifact snapshot parsers
(
wheel_build_snapshotand its private helpers) move here;tests/helpers/maturin.pyre-exportswheel_build_snapshot, so allimport sites are unchanged.
docs/developers-guide.md: documentsmaturin_script_locatable()'s scope, how it differs fromtoolchain_available(), the native-wheel skip boundary, and the reuse policy (which tests should gate on it), per the AGENTS.md abstraction/helper policy.Validation evidence
Reproduced the exact CI failure locally, then confirmed the fix:
Confirmed the fix does not mask a real build in normal environments:
Gates run against the two changed files (
make build,make check-fmt,make lint,make typecheck,make test):build,check-fmt,lint,typecheck: pass.test: all Python suites pass, including the fullcuprum/unittests/test_maturin_build.pyfile (14 passed). Onepre-existing Rust
trybuildUI fixture(
cuprum-rust::compile_tests::compile_time_ui,tests/ui/fail/const_availability_export.rs) fails on a rustcdiagnostic-wording drift unrelated to this change; confirmed it also
fails identically on an unmodified
origin/maincheckout.Test plan
uv run --with mutmut==3.6.0 python -m pytest -x cuprum/unittests/test_maturin_build.py— reproduced the failure,then confirmed the skip.
uv run pytest cuprum/unittests/test_maturin_build.py— fullfile green, real build still exercised in a normal virtualenv.
make check-fmt,make lint,make typecheck— pass.run.
References
baseline fails: maturin wheel build cannot find its own script.
Summary by Sourcery
Skip the maturin wheel-build snapshot test when maturin’s compiled script is not locatable by the running interpreter, while keeping normal builds exercised and reorganising wheel snapshot helpers into a dedicated module.
Bug Fixes:
Enhancements:
Tests: