diff --git a/pyodide_build/pypabuild.py b/pyodide_build/pypabuild.py index 53c61947..f9fefede 100644 --- a/pyodide_build/pypabuild.py +++ b/pyodide_build/pypabuild.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Literal, cast -from build import BuildBackendException, ConfigSettingsType +from build import BuildBackendException, ConfigSettingsType, ProjectBuilder from build.env import DefaultIsolatedEnv from packaging.requirements import Requirement @@ -27,11 +27,10 @@ ) from pyodide_build.spec import _BuildSpecExports from pyodide_build.vendor._pypabuild import ( - _STYLES, _DefaultIsolatedEnv, _error, _handle_build_error, - _ProjectBuilder, + _styles, ) # corresponding env variables for symlinks @@ -170,7 +169,7 @@ def _build_in_isolated_env( installer = "uv" if uv_helper.should_use_uv() else "pip" with _DefaultIsolatedEnv(installer=installer) as env: env = cast(_DefaultIsolatedEnv, env) - builder = _ProjectBuilder.from_isolated_env( + builder = ProjectBuilder.from_isolated_env( env, srcdir, runner=_gen_runner(build_env, env), @@ -221,7 +220,7 @@ def _build_in_current_env( skip_dependency_check: bool = False, ) -> str: with common.replace_env(build_env): - builder = _ProjectBuilder(srcdir, runner=_gen_runner(build_env)) + builder = ProjectBuilder(srcdir, runner=_gen_runner(build_env)) if not skip_dependency_check: missing = builder.check_dependencies(distribution, config_settings or {}) @@ -388,10 +387,14 @@ def build( config_settings, skip_dependency_check, ) - print("{bold}{green}Successfully built {}{reset}".format(built, **_STYLES)) + print( + "{bold}{green}Successfully built {}{reset}".format( + built, **_styles.get() + ) + ) return built except Exception as e: # pragma: no cover tb = traceback.format_exc().strip("\n") - print("\n{dim}{}{reset}\n".format(tb, **_STYLES)) + print("\n{dim}{}{reset}\n".format(tb, **_styles.get())) _error(str(e)) sys.exit(1) diff --git a/pyodide_build/vendor/_pypabuild.py b/pyodide_build/vendor/_pypabuild.py index 1683e252..d8fd331c 100644 --- a/pyodide_build/vendor/_pypabuild.py +++ b/pyodide_build/vendor/_pypabuild.py @@ -21,19 +21,19 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import contextlib +import contextvars import os import subprocess import sys import traceback import warnings from collections.abc import Iterator -from typing import NoReturn +from typing import NoReturn, TextIO from build import ( BuildBackendException, BuildException, FailedProcessError, - ProjectBuilder, ) from build.env import DefaultIsolatedEnv @@ -46,27 +46,26 @@ "underline": "\33[4m", "reset": "\33[0m", } -_NO_COLORS = {color: "" for color in _COLORS} +_NO_COLORS = dict.fromkeys(_COLORS, "") +_styles = contextvars.ContextVar("_styles", default=_COLORS) -def _init_colors() -> dict[str, str]: + +def _init_colors() -> None: if "NO_COLOR" in os.environ: if "FORCE_COLOR" in os.environ: warnings.warn( "Both NO_COLOR and FORCE_COLOR environment variables are set, disabling color", stacklevel=2, ) - return _NO_COLORS + _styles.set(_NO_COLORS) elif "FORCE_COLOR" in os.environ or sys.stdout.isatty(): - return _COLORS - return _NO_COLORS - + return + _styles.set(_NO_COLORS) -_STYLES = _init_colors() - -def _cprint(fmt: str = "", msg: str = "") -> None: - print(fmt.format(msg, **_STYLES), flush=True) +def _cprint(fmt: str = "", msg: str = "", file: TextIO | None = None) -> None: + print(fmt.format(msg, **_styles.get()), file=file, flush=True) def _error(msg: str, code: int = 1) -> NoReturn: # pragma: no cover @@ -76,29 +75,14 @@ def _error(msg: str, code: int = 1) -> NoReturn: # pragma: no cover :param msg: Error message :param code: Error code """ - _cprint("{red}ERROR{reset} {}", msg) + _cprint("{red}ERROR{reset} {}", msg, file=sys.stderr) raise SystemExit(code) -class _ProjectBuilder(ProjectBuilder): - @staticmethod - def log(message: str) -> None: - _cprint("{bold}* {}{reset}", message) - - class _DefaultIsolatedEnv(DefaultIsolatedEnv): - @staticmethod - def log(message: str) -> None: - _cprint("{bold}* {}{reset}", message) - @property def scripts_dir(self) -> str: - if hasattr(self, "_env_backend"): # pypabuild >= 1.2.0 - return self._env_backend.scripts_dir - elif hasattr(self, "_scripts_dir"): - return self._scripts_dir - else: - raise AttributeError("No attribute '_env_backend' or '_scripts_dir' found") + return self._env_backend.scripts_dir @contextlib.contextmanager @@ -124,3 +108,7 @@ def _handle_build_error() -> Iterator[None]: tb = traceback.format_exc(-1) # type: ignore[unreachable] _cprint("\n{dim}{}{reset}\n", tb.strip("\n")) _error(str(e)) + except Exception as e: # pragma: no cover + tb = traceback.format_exc().strip("\n") + _cprint("\n{dim}{}{reset}\n", tb) + _error(str(e)) diff --git a/pyproject.toml b/pyproject.toml index 4bdf0229..cd30e0e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ classifiers = [ license = {text = "MPL-2.0"} requires-python = ">=3.12" dependencies = [ - "build~=1.2.0", + "build~=1.4.0", "pyodide-cli>=0.4.1", "pyodide-lock~=0.1.0", "auditwheel-emscripten~=0.2.0", @@ -65,7 +65,7 @@ test = [ "types-requests", ] uv = [ - "build[uv]~=1.2.0", + "build[uv]~=1.4.0", ] [tool.hatch.version]