Skip to content

Commit 44294d1

Browse files
committed
fix: don't run uv cache clean in parallel with uv pip install
Refactor the code so `uv cache` is cleaned up after each batch of build wheels. Before there was a possibility that `uv cache clean` and `uv pip install` were running at the same time. Fixes: #756 Signed-off-by: Christian Heimes <[email protected]>
1 parent 48711f9 commit 44294d1

File tree

6 files changed

+39
-15
lines changed

6 files changed

+39
-15
lines changed

src/fromager/bootstrapper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ def _build_wheel(
422422
version=resolved_version,
423423
build_env=build_env,
424424
)
425+
# invalidate uv cache
426+
self.ctx.uv_clean_cache(req)
425427
server.update_wheel_mirror(self.ctx)
426428
# When we update the mirror, the built file moves to the
427429
# downloads directory.

src/fromager/build_environment.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -240,17 +240,6 @@ def get_distributions(self) -> typing.Mapping[str, Version]:
240240
mapping = json.loads(result.strip())
241241
return {name: Version(version) for name, version in sorted(mapping.items())}
242242

243-
def clean_cache(self, req: Requirement) -> None:
244-
"""Invalidate and clean uv cache for requirement
245-
246-
uv caches package metadata and unpacked wheels for faster dependency
247-
resolution and installation. ``uv pip install`` hardlinks files from
248-
cache location. This function removes a package from all caches, so
249-
subsequent installations use a new built.
250-
"""
251-
logger.debug("invalidate uv cache for %s", req.name)
252-
self.run(["uv", "cache", "clean", req.name])
253-
254243

255244
@metrics.timeit(description="prepare build environment")
256245
def prepare_build_environment(

src/fromager/commands/build.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ def build(
125125
force=True,
126126
cache_wheel_server_url=None,
127127
)
128+
# invalidate uv cache
129+
wkctx.uv_clean_cache(req)
128130
print(wheel_filename)
129131

130132

@@ -435,6 +437,8 @@ def _build(
435437
version=resolved_version,
436438
build_env=build_env,
437439
)
440+
# uv cache is cleaned in build / build_parallel commands
441+
# wkctx.uv_clean_cache(req)
438442

439443
hooks.run_post_build_hooks(
440444
ctx=wkctx,
@@ -687,11 +691,13 @@ def update_progressbar_cb(future: concurrent.futures.Future) -> None:
687691
max_workers=max_workers
688692
) as executor:
689693
futures: list[concurrent.futures.Future[tuple[pathlib.Path, bool]]] = []
694+
reqs: list[Requirement] = []
690695
logger.info(
691696
"starting to build: %s", sorted(n.key for n in buildable_nodes)
692697
)
693698
for node in buildable_nodes:
694699
req = Requirement(f"{node.canonicalized_name}=={node.version}")
700+
reqs.append(req)
695701
future = executor.submit(
696702
_build_parallel,
697703
wkctx=wkctx,
@@ -724,6 +730,9 @@ def update_progressbar_cb(future: concurrent.futures.Future) -> None:
724730
logger.error(f"Failed to build {node.key}: {e}")
725731
raise
726732

733+
# invalidate uv cache
734+
wkctx.uv_clean_cache(*reqs)
735+
727736
metrics.summarize(wkctx, "Building in parallel")
728737
_summary(wkctx, entries)
729738

src/fromager/commands/step.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,6 @@ def build_wheel(
224224
version=dist_version,
225225
build_env=build_env,
226226
)
227+
wkctx.uv_clean_cache(req)
227228
requirement_ctxvar.reset(token)
228229
print(wheel_filename)

src/fromager/context.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313
from packaging.utils import NormalizedName, canonicalize_name
1414
from packaging.version import Version
1515

16-
from . import constraints, dependency_graph, packagesettings, request_session
16+
from . import (
17+
constraints,
18+
dependency_graph,
19+
external_commands,
20+
packagesettings,
21+
request_session,
22+
)
1723

1824
if typing.TYPE_CHECKING:
1925
from . import build_environment
@@ -126,6 +132,26 @@ def pip_constraint_args(self) -> list[str]:
126132
path_to_constraints_file = path_to_constraints_file.absolute()
127133
return ["--constraint", os.fspath(path_to_constraints_file)]
128134

135+
def uv_clean_cache(self, *reqs: Requirement) -> None:
136+
"""Invalidate and clean uv cache for requirements
137+
138+
uv caches package metadata and unpacked wheels for faster dependency
139+
resolution and installation. ``uv pip install`` hardlinks files from
140+
cache location. This function removes a package from all caches, so
141+
subsequent installations use a new built.
142+
143+
WARNING: 'uv clean cache' is not concurrency safe with 'uv pip install'.
144+
"""
145+
if not reqs:
146+
raise ValueError("no requirements")
147+
148+
extra_environ: dict[str, str] = {"UV_CACHE_DIR": str(self.uv_cache)}
149+
cmd = ["uv", "clean", "cache"]
150+
req_list: list[str] = sorted(set(req.name for req in reqs))
151+
logger.debug("invalidate uv cache for %s", req_list)
152+
cmd.extend(req_list)
153+
external_commands.run(cmd, extra_environ=extra_environ)
154+
129155
def write_to_graph_to_file(self):
130156
with self.graph_file.open("w", encoding="utf-8") as f:
131157
self.dependency_graph.serialize(f)

src/fromager/wheels.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,6 @@ def build_wheel(
336336
build_dir=pbi.build_dir(sdist_root_dir),
337337
)
338338

339-
# invalidate uv's cache
340-
build_env.clean_cache(req)
341-
342339
wheels = list(ctx.wheels_build.glob("*.whl"))
343340
if len(wheels) != 1:
344341
raise FileNotFoundError(

0 commit comments

Comments
 (0)