From c9ffa5275715407dd308b5ab41428f9c47adada3 Mon Sep 17 00:00:00 2001 From: ethernet Date: Sat, 12 Sep 2026 15:36:37 -0400 Subject: [PATCH] fix(pm): stream verbose uv build-backend logs Enable verbose uv output whenever PM streams a command so long native dependency builds expose package activity and backend stdout/stderr before failure. Verify both sync and requirements installs with real offline uv builds that wait for their output to reach the parent. Both cases fail on the base and pass with this change. --- pm/environment.py | 2 + tests/pm/test_environment_build.py | 53 ++++++++++++++++++++ website/docs/reference/package-management.md | 1 + 3 files changed, 56 insertions(+) diff --git a/pm/environment.py b/pm/environment.py index 55c4eb2293..da58704050 100644 --- a/pm/environment.py +++ b/pm/environment.py @@ -152,6 +152,8 @@ class PythonEnvironment: if self.offline: command.append("--offline") if self.output is not None: + # uv hides build-backend output until failure without verbose mode. + command.append("--verbose") return _run_streaming(command, cwd=cwd, env=env, timeout=timeout, output=self.output) return subprocess.run(command, cwd=str(cwd), env=env, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout) diff --git a/tests/pm/test_environment_build.py b/tests/pm/test_environment_build.py index be1fb283e1..6a4f5c7e2e 100644 --- a/tests/pm/test_environment_build.py +++ b/tests/pm/test_environment_build.py @@ -215,6 +215,59 @@ def test_group_only_build_excludes_application_dependencies(locked_project, tmp_ cwd=tmp_path, env=env) == "1.0" +@pytest.mark.parametrize("operation", ["sync", "requirements"]) +def test_build_backend_output_is_streamed_before_build_finishes(installable_project, tmp_path, operation): + import io + from pm.environment import PythonEnvironment + + source, uv, env = installable_project + release = tmp_path / "release-build" + stdout_marker = "construction-root: backend stdout" + stderr_marker = "construction-root: backend stderr" + backend = source / "local_backend.py" + backend.write_text(backend.read_text() + f''' +import sys +import time + +original_build = build_wheel + +def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): + print({stdout_marker!r}, flush=True) + print({stderr_marker!r}, file=sys.stderr, flush=True) + release = Path({str(release)!r}) + deadline = time.monotonic() + 10 + while not release.exists() and time.monotonic() < deadline: + time.sleep(.01) + assert release.exists(), "backend output was hidden until build exit" + return original_build(wheel_directory, config_settings, metadata_directory) + +build_editable = build_wheel +''', encoding="utf-8") + + class AcknowledgingLog(io.StringIO): + def write(self, text): + written = super().write(text) + if stdout_marker in self.getvalue() and stderr_marker in self.getvalue(): + release.touch() + return written + + output = AcknowledgingLog() + environment = PythonEnvironment( + uv=uv, python=Path(sys.executable), destination=tmp_path / "built", + cache=tmp_path / "build-cache", offline=True, output=output, + env=dict(env, UV_NO_INDEX="1", UV_FIND_LINKS=str(tmp_path / "wheels")), + ) + environment.create() + if operation == "sync": + environment.sync(source, timeout=30) + else: + environment.install_requirements([source.as_uri()]) + environment.check() + assert release.is_file(), "both backend streams must arrive during the build" + assert _run([str(environment.executable), "-I", "-c", "import root_app; print(root_app.VALUE)"], + cwd=tmp_path, env=env) == "installed from the explicit source" + + def test_child_output_is_live_and_keeps_explicit_index_credentials(tmp_path): import io from pm.environment import PythonEnvironment diff --git a/website/docs/reference/package-management.md b/website/docs/reference/package-management.md index 173b3469a7..0117bac1cc 100644 --- a/website/docs/reference/package-management.md +++ b/website/docs/reference/package-management.md @@ -459,6 +459,7 @@ remain under uv and npm's own retry policies. ## Diagnostics +- **Slow Python dependency builds:** PM's streamed uv commands enable verbose output. Bundle and build logs show package activity and build-backend stdout/stderr while the build runs, not only after failure. - **Missing or outdated tool:** read `hermes pm doctor`, then use an explicit PM install on a writable installation. - **New environment requires restart:** restart the affected Hermes process. Do not add a second site-packages tree to its live imports. - **Dependency conflict:** read `hermes pm status`. Correct the plugin requirements before retrying admission.