pyodide/pyodide-build/pyodide_build/pypabuild.py

106 lines
3.3 KiB
Python

import contextlib
import os
import sys
import traceback
from itertools import chain
from pathlib import Path
from typing import Mapping
from build import BuildBackendException, ProjectBuilder # type: ignore[import]
from build.__main__ import ( # type: ignore[import]
_STYLES,
_error,
_handle_build_error,
_IsolatedEnvBuilder,
_ProjectBuilder,
)
from build.env import IsolatedEnv # type: ignore[import]
from packaging.requirements import Requirement
from .common import get_hostsitepackages, get_pyversion
UNISOLATED_PACKAGES = ["numpy", "scipy", "cffi", "pycparser", "pythran", "cython"]
def symlink_unisolated_packages(env: IsolatedEnv):
pyversion = get_pyversion()
site_packages_path = f"lib/{pyversion}/site-packages"
env_site_packages = Path(env._path) / site_packages_path
host_site_packages = Path(get_hostsitepackages())
for name in UNISOLATED_PACKAGES:
for path in chain(
host_site_packages.glob(f"{name}*"), host_site_packages.glob(f"_{name}*")
):
(env_site_packages / path.name).unlink(missing_ok=True)
(env_site_packages / path.name).symlink_to(path)
def remove_unisolated_requirements(requires: set[str]) -> set[str]:
for reqstr in list(requires):
req = Requirement(reqstr)
for avoid_name in UNISOLATED_PACKAGES:
if avoid_name in req.name:
requires.remove(reqstr)
return requires
@contextlib.contextmanager
def replace_env(build_env: Mapping[str, str]):
old_environ = dict(os.environ)
os.environ.clear()
os.environ.update(build_env)
try:
yield
finally:
os.environ.clear()
os.environ.update(old_environ)
def install_reqs(env: IsolatedEnv, reqs: set[str]):
env.install(remove_unisolated_requirements(reqs))
def _build_in_isolated_env(
build_env: Mapping[str, str],
builder: ProjectBuilder,
outdir: str,
distribution: str,
) -> str:
with _IsolatedEnvBuilder() as env:
builder.python_executable = env.executable
builder.scripts_dir = env.scripts_dir
# first install the build dependencies
symlink_unisolated_packages(env)
install_reqs(env, builder.build_system_requires)
installed_requires_for_build = False
try:
build_reqs = builder.get_requires_for_build(distribution)
except BuildBackendException:
pass
else:
install_reqs(env, build_reqs)
installed_requires_for_build = True
with replace_env(build_env):
if not installed_requires_for_build:
install_reqs(env, builder.get_requires_for_build(distribution))
return builder.build(distribution, outdir, {})
def build(build_env: Mapping[str, str]):
srcdir = Path.cwd()
outdir = srcdir / "dist"
builder = _ProjectBuilder(srcdir)
distribution = "wheel"
try:
with _handle_build_error():
built = _build_in_isolated_env(
build_env, builder, str(outdir), distribution
)
print("{bold}{green}Successfully built {}{reset}".format(built, **_STYLES))
except Exception as e: # pragma: no cover
tb = traceback.format_exc().strip("\n")
print("\n{dim}{}{reset}\n".format(tb, **_STYLES))
_error(str(e))
sys.exit(1)