Skip to content
Merged
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
16 changes: 11 additions & 5 deletions scripts/jvm_boot_glue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2240,12 +2240,18 @@ def resolve(
"""
# Step 1: Ensure portablemc environment is initialized
if not _ensure_portablemc_environment(inmemory=inmemory):
pmc_root = _SCRIPTS_DIR / "portablemc"
raise RuntimeError(
"portablemc 5.x environment initialization failed.\n"
"Ensure the vendored portablemc tree exists at:\n"
f" {_SCRIPTS_DIR / 'portablemc' / 'portablemc-py' / 'python'}\n"
"Or ensure the wheel is available at:\n"
f" {_SCRIPTS_DIR / 'portablemc' / 'target' / 'wheels'}"
"portablemc 5.x environment initialization failed.\n\n"
"The native extension (_portablemc.pyd) is required but not found.\n\n"

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.

issue: Error message only mentions .pyd even though .so is now supported elsewhere.

Since _pyd_present now also checks for .so artifacts, this message should either reference both _portablemc.pyd and _portablemc.so, or be rephrased in a platform-neutral way (e.g., “native extension (_portablemc.*) is required”) to avoid confusing non-Windows users.

"To fix this, run the build script:\n"
f" python {pmc_root / 'build_wheel.py'}\n\n"
"Requirements:\n"
" - Rust toolchain (https://rustup.rs/)\n"
" - maturin (pip install maturin)\n\n"
"Expected locations after build:\n"
f" - Wheel: {pmc_root / 'target' / 'wheels' / '*.whl'}\n"
f" - Package: {pmc_root / 'portablemc-py' / 'python' / 'portablemc'}"
)

# Step 2: Try the Python API
Expand Down
51 changes: 44 additions & 7 deletions scripts/local_portablemc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import importlib
import importlib.abc
import importlib.machinery
import importlib.util

Check failure on line 26 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / push-fix

ruff (F401)

scripts/local_portablemc.py:26:8: F401 `importlib.util` imported but unused help: Remove unused import: `importlib.util`

Check failure on line 26 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / push-fix

ruff (F401)

scripts/local_portablemc.py:26:8: F401 `importlib.util` imported but unused help: Remove unused import: `importlib.util`

Check failure on line 26 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / pr-comment

ruff (F401)

scripts/local_portablemc.py:26:8: F401 `importlib.util` imported but unused help: Remove unused import: `importlib.util`
import logging
import sys
import types
Expand All @@ -32,7 +32,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Sequence

Check failure on line 35 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / push-fix

ruff (F401)

scripts/local_portablemc.py:35:24: F401 `typing.Sequence` imported but unused help: Remove unused import: `typing.Sequence`

Check failure on line 35 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / push-fix

ruff (F401)

scripts/local_portablemc.py:35:24: F401 `typing.Sequence` imported but unused help: Remove unused import: `typing.Sequence`

Check failure on line 35 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / pr-comment

ruff (F401)

scripts/local_portablemc.py:35:24: F401 `typing.Sequence` imported but unused help: Remove unused import: `typing.Sequence`

log = logging.getLogger(__name__)

Expand All @@ -54,44 +54,81 @@
"""Return the first portablemc wheel found in the target/wheels dir."""
if not _WHEEL_SEARCH.is_dir():
return None
for whl in sorted(_WHEEL_SEARCH.glob("portablemc-*.whl")):
return whl
# Try different wheel naming patterns (maturin uses portablemc_py-*)
patterns = ["portablemc_py-*.whl", "portablemc-*.whl"]
for pattern in patterns:
for whl in sorted(_WHEEL_SEARCH.glob(pattern), reverse=True):
Comment on lines +58 to +60

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.

suggestion (bug_risk): Wheel selection uses lexicographic order and prefers portablemc_py-* even if a newer portablemc-* exists.

This contradicts the “Return most recent” intent: selection is based on filename ordering within each pattern, plus unconditional preference for portablemc_py-*. If actual recency matters, consider using st_mtime or parsed versions to compare wheels, and make the precedence between portablemc_py-* and portablemc-* explicit when their versions differ.

return whl # Return most recent
return None


def _pyd_present() -> bool:
"""True when the native extension already lives in the vendored tree."""
if not _PMC_PKG_DIR.is_dir():
return False
return any(_PMC_PKG_DIR.glob("_portablemc*.pyd"))
# Check for both naming conventions (.pyd for Windows, .so for Linux/Mac)
patterns = ["_portablemc*.pyd", "_portablemc*.so", "portablemc_py*.pyd", "portablemc_py*.so"]
for pattern in patterns:
if any(_PMC_PKG_DIR.glob(pattern)):
return True
return False


def _extract_pyd_from_wheel(wheel: Path) -> bool:
"""
Extract every file from the wheel's ``portablemc/`` directory into the
Extract every file from the wheel's package directory into the
vendored Python tree. Skips dist-info and __pycache__ entries.

The wheel may contain either:
- portablemc/ (if built with package name portablemc)
- portablemc_py/ (if built with maturin default naming)

Files are renamed appropriately to create a portablemc/ package.

Returns True on success.
"""
try:
_PMC_PKG_DIR.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(wheel, "r") as zf:
extracted = 0
# Detect the package prefix in the wheel
prefixes = ["portablemc/", "portablemc_py/"]
found_prefix = None
for name in zf.namelist():
for prefix in prefixes:
if name.startswith(prefix):
found_prefix = prefix
break
if found_prefix:
break

if not found_prefix:
log.warning("No portablemc package found in wheel %s", wheel.name)
return False

for name in zf.namelist():
if not name.startswith("portablemc/"):
if not name.startswith(found_prefix):
continue
if "__pycache__" in name or name.endswith("/"):
continue
# Strip the leading "portablemc/" prefix; write relative to _PMC_PKG_DIR
rel = name[len("portablemc/"):]
if ".dist-info" in name:
continue

# Strip the leading prefix; write relative to _PMC_PKG_DIR
rel = name[len(found_prefix):]
if not rel:
continue

# Rename portablemc_py to _portablemc in filenames
rel = rel.replace("portablemc_py", "_portablemc")

dest = _PMC_PKG_DIR / rel
dest.parent.mkdir(parents=True, exist_ok=True)
data = zf.read(name)
dest.write_bytes(data)
extracted += 1
log.debug("Extracted %s → %s", name, dest)

log.info("Extracted %d files from %s into %s", extracted, wheel.name, _PMC_PKG_DIR)
return extracted > 0
except Exception as exc:
Expand Down Expand Up @@ -269,7 +306,7 @@

# Get the PyInit function address
# The function name is PyInit_<module_name> for Python 3 extensions
init_func_name = b"PyInit__portablemc"

Check failure on line 309 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / push-fix

ruff (F841)

scripts/local_portablemc.py:309:9: F841 Local variable `init_func_name` is assigned to but never used help: Remove assignment to unused variable `init_func_name`

Check failure on line 309 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / push-fix

ruff (F841)

scripts/local_portablemc.py:309:9: F841 Local variable `init_func_name` is assigned to but never used help: Remove assignment to unused variable `init_func_name`

Check failure on line 309 in scripts/local_portablemc.py

View workflow job for this annotation

GitHub Actions / pr-comment

ruff (F841)

scripts/local_portablemc.py:309:9: F841 Local variable `init_func_name` is assigned to but never used help: Remove assignment to unused variable `init_func_name`
init_func_addr = mem_mod.get_proc_addr("PyInit__portablemc")

if not init_func_addr:
Expand Down
Loading