Skip to content

Commit 128b1b2

Browse files
smoparthclaude
andcommitted
feat(wheels): separate cache wheel resolution from pre-built path
Use PyPICacheProvider (introduced in #1143) for local and cache wheel server lookups in the build and download-sequence paths. This separates trusted internal sources (no cooldown, no upload-time checks) from upstream pre-built sources. - Add resolve_cached_wheel() using PyPICacheProvider - Add resolve_existing_wheel() to probe cache servers first, then pre-built servers, preserving the per-package server exclusivity - Restructure _is_wheel_built() to use resolve_existing_wheel() - Route download_sequence local server through resolve_cached_wheel - Remove supports_upload_time workaround from resolve_all_prebuilt_wheels - Add tests for cache/local cooldown bypass and upstream cooldown retention Follows up on #1143 Co-Authored-By: Claude <claude@anthropic.com> Signed-off-by: Shanmukh Pawan <smoparth@redhat.com>
1 parent fb25f39 commit 128b1b2

4 files changed

Lines changed: 218 additions & 47 deletions

File tree

src/fromager/commands/build.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -353,17 +353,13 @@ def _build(
353353
pbi = wkctx.package_build_info(req)
354354
prebuilt = pbi.pre_built
355355

356-
wheel_server_urls = wheels.get_wheel_server_urls(
357-
wkctx, req, cache_wheel_server_url=cache_wheel_server_url
358-
)
359-
360356
# See if we can reuse an existing wheel.
361357
if not force:
362358
wheel_filename = _is_wheel_built(
363359
wkctx,
364360
req.name,
365361
resolved_version,
366-
wheel_server_urls,
362+
cache_wheel_server_url=cache_wheel_server_url,
367363
)
368364
if wheel_filename:
369365
logger.info("using existing wheel from %s", wheel_filename)
@@ -476,23 +472,24 @@ def _is_wheel_built(
476472
wkctx: context.WorkContext,
477473
dist_name: str,
478474
resolved_version: Version,
479-
wheel_server_urls: list[str],
475+
*,
476+
cache_wheel_server_url: str | None = None,
480477
) -> pathlib.Path | None:
481478
req = Requirement(f"{dist_name}=={resolved_version}")
479+
pbi = wkctx.package_build_info(req)
482480

483481
try:
484-
logger.info(
485-
"checking if a suitable wheel for %s was already built on %s",
486-
req,
487-
wheel_server_urls,
488-
)
489-
url, _ = wheels.resolve_prebuilt_wheel(
482+
url = wheels.resolve_existing_wheel(
490483
ctx=wkctx,
491484
req=req,
492-
wheel_server_urls=wheel_server_urls,
485+
pbi=pbi,
486+
cache_wheel_server_url=cache_wheel_server_url,
493487
)
488+
if url is None:
489+
logger.info("could not locate existing wheel")
490+
return None
491+
494492
logger.info("found candidate wheel %s", url)
495-
pbi = wkctx.package_build_info(req)
496493
build_tag_from_settings = pbi.build_tag(resolved_version)
497494
build_tag = build_tag_from_settings if build_tag_from_settings else (0, "")
498495
wheel_basename = resolver.extract_filename_from_url(url)
@@ -512,28 +509,26 @@ def _is_wheel_built(
512509
return None
513510

514511
wheel_filename: pathlib.Path | None = None
515-
if url.startswith(wkctx.wheel_server_url):
512+
if wkctx.wheel_server_url and url.startswith(wkctx.wheel_server_url):
516513
logging.debug("found wheel on local server")
517514
wheel_filename = wkctx.wheels_downloads / wheel_basename
518515
if not wheel_filename.exists():
519516
logger.info("wheel not found in local cache, preparing to download")
520517
wheel_filename = None
521518

522519
if not wheel_filename:
523-
# if the found wheel was on an external server, then download it
524520
logger.info("downloading wheel from %s", url)
525521
wheel_filename = wheels.download_wheel(req, url, wkctx.wheels_downloads)
526522

527523
return wheel_filename
528524
except Exception:
529525
logger.debug(
530-
"could not locate prebuilt wheel %s-%s on %s",
526+
"could not locate existing wheel %s-%s",
531527
dist_name,
532528
resolved_version,
533-
wheel_server_urls,
534529
exc_info=True,
535530
)
536-
logger.info("could not locate prebuilt wheel")
531+
logger.info("could not locate existing wheel")
537532
return None
538533

539534

src/fromager/commands/download_sequence.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ def download_sequence(
5252
the build order file that have source_url_type as sdist.
5353
5454
"""
55-
if wkctx.wheel_server_url:
56-
wheel_servers = [wkctx.wheel_server_url]
57-
else:
58-
wheel_servers = [sdist_server_url]
55+
# The local wheel server is a trusted cache (wheels built in this or
56+
# prior runs). An external sdist_server_url used as a wheel fallback
57+
# is an upstream index that needs cooldown and override hooks.
58+
use_cache_resolution = bool(wkctx.wheel_server_url)
59+
wheel_server_url = (
60+
wkctx.wheel_server_url if use_cache_resolution else sdist_server_url
61+
)
5962

6063
logger.info("reading build order from %s", build_order_file)
6164
with read.open_file_or_url(build_order_file) as f:
@@ -83,7 +86,6 @@ def download_one(entry: dict[str, typing.Any]) -> None:
8386
except Exception as err:
8487
logger.error(f"failed to download sdist for {req}: {err}")
8588
if not ignore_missing_sdists:
86-
# Re-raise with package context since context var is lost across threads
8789
raise RuntimeError(f"Failed to download sdist for {req}") from err
8890
else:
8991
logger.info(
@@ -92,12 +94,21 @@ def download_one(entry: dict[str, typing.Any]) -> None:
9294

9395
if include_wheels:
9496
try:
95-
wheel_url, _ = wheels.resolve_prebuilt_wheel(
96-
ctx=wkctx, req=req, wheel_server_urls=wheel_servers
97-
)
97+
if use_cache_resolution:
98+
resolved_url, _ = wheels.resolve_cached_wheel(
99+
ctx=wkctx,
100+
req=req,
101+
cache_server_url=wheel_server_url,
102+
)
103+
else:
104+
resolved_url, _ = wheels.resolve_prebuilt_wheel(
105+
ctx=wkctx,
106+
req=req,
107+
wheel_server_urls=[wheel_server_url],
108+
)
98109
wheels.download_wheel(
99110
req=req,
100-
wheel_url=wheel_url,
111+
wheel_url=resolved_url,
101112
output_directory=wkctx.wheels_downloads,
102113
)
103114
except Exception as err:

src/fromager/wheels.py

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from . import (
2626
dependencies,
2727
external_commands,
28+
finders,
2829
metrics,
2930
overrides,
3031
packagesettings,
@@ -528,6 +529,82 @@ def get_prebuilt_wheel_provider(
528529
)
529530

530531

532+
def resolve_existing_wheel(
533+
*,
534+
ctx: context.WorkContext,
535+
req: Requirement,
536+
pbi: packagesettings.PackageBuildInfo,
537+
cache_wheel_server_url: str | None,
538+
) -> str | None:
539+
"""Probe wheel servers in priority order and return the first matching URL.
540+
541+
Tries cache servers first (local, remote), then pre-built servers.
542+
"""
543+
cache_wheel_servers: list[str] = []
544+
prebuilt_wheel_servers: list[str] = []
545+
546+
if pbi.wheel_server_url:
547+
prebuilt_wheel_servers.append(pbi.wheel_server_url)
548+
else:
549+
if ctx.wheel_server_url:
550+
cache_wheel_servers.append(ctx.wheel_server_url)
551+
if cache_wheel_server_url:
552+
cache_wheel_servers.append(cache_wheel_server_url)
553+
554+
all_servers = cache_wheel_servers + prebuilt_wheel_servers
555+
if not all_servers:
556+
return None
557+
558+
logger.info(
559+
"checking if a suitable wheel for %s was already built on %s",
560+
req,
561+
all_servers,
562+
)
563+
564+
for server_url in cache_wheel_servers:
565+
try:
566+
url, _ = resolve_cached_wheel(
567+
ctx=ctx,
568+
req=req,
569+
cache_server_url=server_url,
570+
)
571+
return url
572+
except Exception:
573+
logger.debug("wheel not found on %s", server_url, exc_info=True)
574+
575+
for server_url in prebuilt_wheel_servers:
576+
try:
577+
url, _ = resolve_prebuilt_wheel(
578+
ctx=ctx,
579+
req=req,
580+
wheel_server_urls=[server_url],
581+
)
582+
return str(url)
583+
except Exception:
584+
logger.debug("wheel not found on %s", server_url, exc_info=True)
585+
586+
return None
587+
588+
589+
def resolve_cached_wheel(
590+
*,
591+
ctx: context.WorkContext,
592+
req: Requirement,
593+
cache_server_url: str,
594+
) -> tuple[str, Version]:
595+
"""Resolve a wheel from a trusted cache server (local or remote).
596+
597+
Uses ``PyPICacheProvider`` -- no cooldown, no hooks, no upload-time checks.
598+
"""
599+
provider = finders.PyPICacheProvider(
600+
cache_server_url=cache_server_url,
601+
constraints=ctx.constraints,
602+
)
603+
results = resolver.find_all_matching_from_provider(provider, req)
604+
wheel_url, version = results[0]
605+
return str(wheel_url), version
606+
607+
531608
def resolve_all_prebuilt_wheels(
532609
*,
533610
ctx: context.WorkContext,
@@ -552,11 +629,6 @@ def resolve_all_prebuilt_wheels(
552629
provider.cooldown = resolver.resolve_package_cooldown(
553630
ctx, req, req_type=req_type
554631
)
555-
# The local fromager wheel server is PEP 503-only and serves
556-
# packages that were already resolved and vetted earlier in the
557-
# same run. Don't fail-closed on missing upload_time there.
558-
if ctx.wheel_server_url and url == ctx.wheel_server_url:
559-
provider.supports_upload_time = False
560632

561633
# Get all matching candidates from provider
562634
results = resolver.find_all_matching_from_provider(provider, req)
@@ -579,10 +651,10 @@ def resolve_prebuilt_wheel(
579651
wheel_server_urls: list[str],
580652
req_type: requirements_file.RequirementType | None = None,
581653
) -> tuple[str, Version]:
582-
"""Return (URL, version) for the best matching wheel version.
654+
"""Return (URL, version) for the best matching pre-built wheel.
583655
584-
Tries wheel servers in order and returns result from the first that succeeds.
585-
Returns the highest matching version.
656+
Tries wheel servers in order and returns the highest matching version
657+
from the first server that succeeds.
586658
"""
587659
results = resolve_all_prebuilt_wheels(
588660
ctx=ctx, req=req, wheel_server_urls=wheel_server_urls, req_type=req_type

0 commit comments

Comments
 (0)