Skip to content

Commit 727b709

Browse files
committed
feat(settings): add version-specific prebuilt wheel configuration
Enable fine-grained control over prebuilt vs source builds within variants by allowing per-version overrides. This resolves conflicts where different collections need different build methods for the same package version. - Add VersionSpecificSettings model with pre_built and wheel_server_url - Extend VariantInfo with versions mapping for version-specific overrides - Update PackageBuildInfo with version-aware methods (is_pre_built, get_wheel_server_url) - Modify bootstrapper, wheels, and build commands to use version-specific logic - Add comprehensive tests validating precedence and backward compatibility - Create detailed how-to documentation with examples and migration guide Maintains full backward compatibility with existing variant-wide settings. Version-specific settings take precedence over variant defaults when present.
1 parent e88a7c2 commit 727b709

File tree

7 files changed

+510
-27
lines changed

7 files changed

+510
-27
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
Version-Specific Prebuilt Settings
2+
===================================
3+
4+
When working with multiple collections that share the same variant but need
5+
different versions of a package, you may want some versions to use prebuilt
6+
wheels while others are built from source.
7+
8+
Version-specific prebuilt settings allow you to configure ``pre_built`` and
9+
``wheel_server_url`` on a per-version basis within a variant, providing
10+
fine-grained control over which package versions use prebuilt wheels.
11+
12+
Configuration
13+
-------------
14+
15+
Add version-specific settings under the ``versions`` key within a variant:
16+
17+
.. code-block:: yaml
18+
19+
# overrides/settings/torchvision.yaml
20+
variants:
21+
tpu-ubi9:
22+
# Default behavior for unlisted versions
23+
pre_built: false
24+
25+
# Version-specific overrides
26+
versions:
27+
# Use prebuilt wheel for this version
28+
"0.24.0.dev20250730":
29+
pre_built: true
30+
wheel_server_url: https://gitlab.com/api/v4/projects/12345/packages/pypi/simple
31+
32+
# Build from source for this version
33+
"0.23.0":
34+
pre_built: false
35+
36+
Available Settings
37+
------------------
38+
39+
Within each version-specific block, you can configure:
40+
41+
``pre_built``
42+
Boolean indicating whether to use prebuilt wheels for this version.
43+
44+
``wheel_server_url``
45+
URL to download prebuilt wheels from for this version.
46+
47+
``env``
48+
Environment variables specific to this version.
49+
50+
``annotations``
51+
Version-specific annotations.
52+
53+
Precedence Rules
54+
----------------
55+
56+
Version-specific settings override variant-wide settings. If both are defined,
57+
environment variables are merged with version-specific values taking precedence
58+
for conflicting keys.
59+
60+
Example Use Case
61+
----------------
62+
63+
Consider two TPU collections using different ``torchvision`` versions:
64+
65+
**Global Collection** (``collections/accelerated/tpu-ubi9/requirements.txt``):
66+
67+
.. code-block:: text
68+
69+
torchvision==0.24.0.dev20250730
70+
71+
**Torch-2.8.0 Collection** (``collections/torch-2.8.0/tpu-ubi9/requirements.txt``):
72+
73+
.. code-block:: text
74+
75+
torchvision==0.23.0
76+
77+
With the configuration above:
78+
79+
- Global collection downloads prebuilt ``torchvision==0.24.0.dev20250730`` wheels
80+
- Torch-2.8.0 collection builds ``torchvision==0.23.0`` from source
81+
- Both use the same variant (``tpu-ubi9``) with different build methods

src/fromager/bootstrapper.py

Lines changed: 103 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,28 @@ def resolve_version(
8888
return self._resolved_requirements[req_str]
8989

9090
pbi = self.ctx.package_build_info(req)
91-
if pbi.pre_built:
92-
source_url, resolved_version = self._resolve_prebuilt_with_history(
93-
req=req,
94-
req_type=req_type,
95-
)
91+
92+
# Check if package has version-specific settings (any version-specific config)
93+
variant_info = pbi._ps.variants.get(pbi._variant)
94+
has_version_specific_prebuilt = (
95+
variant_info and variant_info.versions and len(variant_info.versions) > 0
96+
)
97+
98+
if pbi.pre_built or has_version_specific_prebuilt:
99+
try:
100+
source_url, resolved_version = self._resolve_prebuilt_with_history(
101+
req=req,
102+
req_type=req_type,
103+
)
104+
except Exception as e:
105+
# Version-specific prebuilt resolution failed, fall back to source
106+
logger.debug(
107+
f"{req.name}: prebuilt resolution failed, falling back to source: {e}"
108+
)
109+
source_url, resolved_version = self._resolve_source_with_history(
110+
req=req,
111+
req_type=req_type,
112+
)
96113
else:
97114
source_url, resolved_version = self._resolve_source_with_history(
98115
req=req,
@@ -185,7 +202,8 @@ def bootstrap(self, req: Requirement, req_type: RequirementType) -> Version:
185202

186203
source_url_type = sources.get_source_type(self.ctx, req)
187204

188-
if pbi.pre_built:
205+
# Use version-aware prebuilt check now that we have resolved_version
206+
if pbi.is_pre_built(resolved_version):
189207
wheel_filename, unpack_dir = self._download_prebuilt(
190208
req=req,
191209
req_type=req_type,
@@ -826,6 +844,19 @@ def _resolve_prebuilt_with_history(
826844
req: Requirement,
827845
req_type: RequirementType,
828846
) -> tuple[str, Version]:
847+
# Try version-specific resolution FIRST (highest priority)
848+
# This allows version-specific wheel_server_url settings to override
849+
# any cached resolutions from previous bootstraps or current graph
850+
try:
851+
wheel_url, resolved_version = (
852+
self._resolve_prebuilt_with_version_specific_urls(req, req_type)
853+
)
854+
return (wheel_url, resolved_version)
855+
except Exception:
856+
# No version-specific settings matched, fall back to cached resolution
857+
pass
858+
859+
# Fall back to cached resolution from graph
829860
cached_resolution = self._resolve_from_graph(
830861
req=req,
831862
req_type=req_type,
@@ -835,14 +866,70 @@ def _resolve_prebuilt_with_history(
835866
if cached_resolution and not req.url:
836867
wheel_url, resolved_version = cached_resolution
837868
logger.debug(f"resolved from previous bootstrap to {resolved_version}")
838-
else:
839-
servers = wheels.get_wheel_server_urls(
840-
self.ctx, req, cache_wheel_server_url=resolver.PYPI_SERVER_URL
841-
)
842-
wheel_url, resolved_version = wheels.resolve_prebuilt_wheel(
843-
ctx=self.ctx, req=req, wheel_server_urls=servers, req_type=req_type
844-
)
845-
return (wheel_url, resolved_version)
869+
return (wheel_url, resolved_version)
870+
871+
# No cached resolution found, raise to trigger fallback to source resolution
872+
raise ValueError("No prebuilt resolution found")
873+
874+
def _resolve_prebuilt_with_version_specific_urls(
875+
self,
876+
req: Requirement,
877+
req_type: RequirementType,
878+
) -> tuple[str, Version]:
879+
"""Resolve prebuilt wheel using version-specific wheel server URLs if configured."""
880+
pbi = self.ctx.package_build_info(req)
881+
882+
# Check if there are version-specific settings
883+
variant_info = pbi._ps.variants.get(pbi._variant)
884+
if not variant_info or not variant_info.versions:
885+
raise ValueError("No version-specific settings configured")
886+
887+
# Get the constraint for this package
888+
constraint = self.ctx.constraints.get_constraint(req.name)
889+
890+
# Try to resolve using version-specific wheel server URLs
891+
for version_str, version_settings in variant_info.versions.items():
892+
# Only process versions that have both wheel_server_url and pre_built=True
893+
if not (version_settings.wheel_server_url and version_settings.pre_built):
894+
continue
895+
896+
# Only try this version if it satisfies the requirement specifier AND constraint
897+
try:
898+
version_obj = Version(version_str)
899+
900+
# Check requirement specifier (if present)
901+
if req.specifier and version_obj not in req.specifier:
902+
continue
903+
904+
# Check constraint (if present) - this is the version from constraints.txt
905+
if constraint and version_obj not in constraint.specifier:
906+
continue
907+
908+
except Exception:
909+
continue # Skip invalid version strings
910+
911+
# Create a constraint for this specific version
912+
version_req = Requirement(f"{req.name}=={version_str}")
913+
914+
try:
915+
# Try to resolve this specific version from the version-specific server
916+
wheel_url, resolved_version = wheels.resolve_prebuilt_wheel(
917+
ctx=self.ctx,
918+
req=version_req,
919+
wheel_server_urls=[version_settings.wheel_server_url],
920+
req_type=req_type,
921+
)
922+
logger.info(
923+
f"{req.name}: using version-specific prebuilt wheel "
924+
f"{resolved_version} from {version_settings.wheel_server_url}"
925+
)
926+
return (wheel_url, resolved_version)
927+
except Exception:
928+
# Version not found on this server, try next
929+
continue
930+
931+
# No matching version-specific prebuilt settings found
932+
raise ValueError("No matching version-specific prebuilt settings found")
846933

847934
def _resolve_from_graph(
848935
self,
@@ -949,7 +1036,8 @@ def _add_to_graph(
9491036
req=req,
9501037
req_version=req_version,
9511038
download_url=download_url,
952-
pre_built=pbi.pre_built,
1039+
# Use version-aware prebuilt check for dependency graph
1040+
pre_built=pbi.is_pre_built(req_version),
9531041
)
9541042
self.ctx.write_to_graph_to_file()
9551043

src/fromager/commands/build.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,14 @@ def _build(
345345

346346
logger.info("starting processing")
347347
pbi = wkctx.package_build_info(req)
348-
prebuilt = pbi.pre_built
348+
# Use version-aware prebuilt check now that we have resolved_version
349+
prebuilt = pbi.is_pre_built(resolved_version)
349350

350351
wheel_server_urls = wheels.get_wheel_server_urls(
351-
wkctx, req, cache_wheel_server_url=cache_wheel_server_url
352+
wkctx,
353+
req,
354+
cache_wheel_server_url=cache_wheel_server_url,
355+
version=resolved_version,
352356
)
353357

354358
# See if we can reuse an existing wheel.

0 commit comments

Comments
 (0)