Skip to content

Commit cbbab78

Browse files
committed
xangcastle/uv-1-wheel-build-memory
1 parent 4f164ee commit cbbab78

18 files changed

Lines changed: 557 additions & 26 deletions

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module(
66
)
77

88
bazel_dep(name = "bazel_features", version = "1.38.0")
9-
bazel_dep(name = "bazel_lib", version = "3.0.0")
9+
bazel_dep(name = "bazel_lib", version = "3.2.0")
1010
bazel_dep(name = "bazel_skylib", version = "1.4.2")
1111
bazel_dep(name = "gawk", version = "5.3.2.bcr.7")
1212
bazel_dep(name = "platforms", version = "1.0.0")

docs/uv-patching.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,28 @@ Pre-build patches are applied to the extracted source tree after archive extract
8383
- Removing problematic native build dependencies
8484
- Patching source code that affects the build output
8585

86+
### Allocating wheel build memory
87+
88+
Set `build_memory_mb` to the expected peak memory of a local sdist build so
89+
Bazel can limit concurrent wheel builds:
90+
91+
```starlark
92+
uv.override_package(
93+
lock = "//:uv.lock",
94+
name = "native-package",
95+
build_memory_mb = 6144,
96+
)
97+
```
98+
99+
Bazel rounds the estimate up to one of its supported resource classes. On
100+
Linux, setting a nonzero estimate also enables aggregate resident-memory
101+
sampling for the build process and its descendants. Estimates above 32768 MiB
102+
are rejected because bazel-lib 3.2 would otherwise clamp them to 32768 MiB and
103+
silently under-reserve the action. A measurement appears whenever the sampled
104+
peak crosses another 256 MiB threshold and once when the build finishes. If
105+
procfs is unavailable or cannot be read, the final measurement is reported as
106+
unavailable.
107+
86108
### Adding extra dependencies or data
87109

88110
Some packages have implicit runtime dependencies that aren't declared in their metadata:

e2e/BUILD.bazel

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,14 @@ write_source_files(
110110
# shows up as the per-wheel dicts collapsing into one merged list.
111111
"snapshots/abi3_compat.whl_install.cffi.BUILD.bazel": "@whl_install__abi3_compat__cffi__2_0_0//:BUILD.bazel",
112112

113-
# @sdist_build for jpype1 with uv.override_package toolchains/env
113+
# @sdist_build for jpype1 with uv.override_package build resources,
114+
# toolchains, and env
114115
# ----------------------------------------------------------------
115116
# Covers the override_package -> repo-rule -> BUILD-template
116-
# plumbing for the augment-on-defaults `toolchains = [...]` /
117+
# plumbing for `build_memory_mb`, `toolchains = [...]`, and
117118
# `env = {...}`. The override layers a JDK runtime on top of the
118-
# default CC toolchain; the snapshot pins both toolchain
119-
# references and both env families (CC + JAVA_HOME/JAVA/JAR).
119+
# default CC toolchain; the snapshot pins the memory declaration,
120+
# toolchain references, and both env families (CC + JAVA_HOME/JAVA/JAR).
120121
# Pairs with the analysis test at
121122
# //uv/private/pep517_whl:toolchain_env_test which asserts the
122123
# rule expands env keys correctly given an explicit fixture.

e2e/cases/uv-sdist-jdk-build/setup.MODULE.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ uv.unstable_annotate_packages(
2323
lock = "//cases/uv-sdist-jdk-build:uv.lock",
2424
)
2525

26-
# Layer a JDK runtime toolchain into the build env. `toolchains` and
27-
# `env` here are additive — these entries are appended to whatever
28-
# sdist_build's BUILD template emits by default.
26+
# Reserve local build memory and layer a JDK runtime toolchain into the build
27+
# env. `toolchains` and `env` are additive to sdist_build's defaults.
2928
uv.override_package(
3029
name = "jpype1",
30+
build_memory_mb = 2048,
3131
env = {
3232
"JAR": "$(JAVABASE)/bin/jar",
3333
"JAVA": "$(JAVA)",

e2e/cases/uv-sdist-mpicc/BUILD.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ load("@aspect_rules_py//py:defs.bzl", "py_test")
99
py_test(
1010
name = "test",
1111
srcs = ["__test__.py"],
12-
data = ["@aspect_rules_py//uv/private/pep517_whl:build_helper.py"],
12+
data = [
13+
"@aspect_rules_py//uv/private/pep517_whl:build_helper.py",
14+
"@aspect_rules_py//uv/private/pep517_whl:memory_monitor",
15+
],
1316
dep_group = "uv_sdist_mpicc",
1417
main = "__test__.py",
1518
python_version = "3.11",

e2e/cases/uv-sdist-mpicc/__test__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,14 @@ def make_sdist(workdir):
7676

7777

7878
def run_helper(helper, sdist, outdir, path_entries, expect):
79+
rules_py_root = os.path.dirname(
80+
os.path.dirname(os.path.dirname(os.path.dirname(helper)))
81+
)
7982
env = {
8083
"MPICC_TEST_EXPECT": expect,
8184
"PATH": os.pathsep.join(path_entries),
8285
"HOME": os.environ.get("TEST_TMPDIR", "/tmp"),
86+
"PYTHONPATH": rules_py_root,
8387
}
8488
cc = shutil.which("cc") or "/usr/bin/cc"
8589
if os.path.exists(cc):

e2e/snapshots/sdist_build.uv_sdist_jdk_build.jpype1.BUILD.bazel

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

uv/private/extension/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bzl_library(
2222
"//uv/private:normalize_version",
2323
"//uv/private/constraints:repository",
2424
"//uv/private/git_archive:repository",
25+
"//uv/private/pep517_whl:build_memory",
2526
"//uv/private/pprint:defs",
2627
"//uv/private/sdist_build:repository",
2728
"//uv/private/sdist_configure:defs",

uv/private/extension/defs.bzl

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ load("//uv/private:normalize_version.bzl", "normalize_version")
6060
load("//uv/private:parse_whl_name.bzl", "parse_whl_name")
6161
load("//uv/private/constraints:repository.bzl", "configurations_hub")
6262
load("//uv/private/git_archive:repository.bzl", "git_archive")
63+
load("//uv/private/pep517_whl:build_memory.bzl", "validate_build_memory_mb")
6364
load("//uv/private/pprint:defs.bzl", "pprint")
6465
load("//uv/private/sdist_build:repository.bzl", "sdist_build")
6566
load("//uv/private/sdist_configure:defs.bzl", "DEFAULT_CONFIGURE_SCRIPT")
@@ -246,20 +247,25 @@ def _parse_projects(module_ctx, hub_specs):
246247
))
247248

248249
has_target = override.target != None
250+
validate_build_memory_mb(
251+
override.build_memory_mb,
252+
"uv.override_package() for '{}'".format(override.name),
253+
)
249254
has_modifications = (
250255
override.pre_build_patches or
251256
override.post_install_patches or
252257
override.extra_deps or
253258
override.extra_data or
254259
override.toolchains or
255-
override.env
260+
override.env or
261+
override.build_memory_mb
256262
)
257263

258264
if has_target and has_modifications:
259-
fail("uv.override_package() for '{}': `target` is mutually exclusive with patch/exclude attributes. Use `target` for full replacement OR patch/exclude attributes for modifications, not both.".format(override.name))
265+
fail("uv.override_package() for '{}': `target` is mutually exclusive with modification attributes. Use `target` for full replacement OR build, patch, and data attributes for modifications, not both.".format(override.name))
260266

261267
if not has_target and not has_modifications:
262-
fail("uv.override_package() for '{}': must specify either `target` for full replacement or at least one modification attribute (pre_build_patches, post_install_patches, extra_deps, extra_data).".format(override.name))
268+
fail("uv.override_package() for '{}': must specify either `target` for full replacement or at least one build, patch, or data modification attribute.".format(override.name))
263269

264270
package_overrides[override_key] = override
265271

@@ -410,9 +416,11 @@ def _parse_projects(module_ctx, hub_specs):
410416
# they don't replace them. Empty == no augmentation.
411417
extra_toolchains = []
412418
extra_env = {}
419+
build_memory_mb = 0
413420
if pkg_override:
414421
extra_toolchains = [str(t) for t in pkg_override.toolchains]
415422
extra_env = pkg_override.env
423+
build_memory_mb = pkg_override.build_memory_mb
416424

417425
sbuild_specs[sbuild_id] = struct(
418426
src = sdist,
@@ -425,6 +433,7 @@ def _parse_projects(module_ctx, hub_specs):
425433
configure_command = project.unstable_configure_command,
426434
extra_toolchains = extra_toolchains,
427435
extra_env = extra_env,
436+
build_memory_mb = build_memory_mb,
428437
)
429438

430439
has_sbuild = True
@@ -649,6 +658,8 @@ def _uv_impl(module_ctx):
649658
sbuild_kwargs["extra_toolchains"] = sbuild_cfg.extra_toolchains
650659
if sbuild_cfg.extra_env:
651660
sbuild_kwargs["extra_env"] = sbuild_cfg.extra_env
661+
if sbuild_cfg.build_memory_mb:
662+
sbuild_kwargs["build_memory_mb"] = sbuild_cfg.build_memory_mb
652663
sdist_build(**sbuild_kwargs)
653664

654665
for install_id, install_cfg in cfg.install_cfgs.items():
@@ -746,12 +757,16 @@ _override_package_tag = tag_class(
746757
# Mutually exclusive with patch/exclude attributes.
747758
"target": attr.label(mandatory = False),
748759

749-
# Per-package toolchain plumbing for native sdist builds. Both
750-
# attributes AUGMENT the defaults baked into sdist_build's
760+
# Per-package resource and toolchain settings for sdist builds.
761+
# toolchains and env AUGMENT the defaults baked into sdist_build's
751762
# generated `pep517_native_whl(...)` call (the CC toolchain +
752763
# CC/CXX/AR/LD/STRIP env) — they don't replace them. Use these
753764
# to layer extra toolchains (Java runtime, Rust, …) and extra
754765
# env vars on top of the defaults.
766+
"build_memory_mb": attr.int(
767+
default = 0,
768+
doc = "Estimated peak memory in MB for this package's local wheel build, from 0 to 32768. Bazel rounds up to a supported resource class; zero uses its default estimate.",
769+
),
755770
"toolchains": attr.label_list(
756771
default = [],
757772
doc = "Extra toolchain targets appended to the generated pep517_native_whl(...) call's `toolchains` list. Each target's TemplateVariableInfo make-variables become available for $(VAR) expansion in `env`.",
@@ -760,7 +775,6 @@ _override_package_tag = tag_class(
760775
default = {},
761776
doc = "Extra environment variables merged into the build action's `env` dict. Values may reference $(VAR) make-variables sourced from the default CC toolchain or any extra `toolchains` listed above.",
762777
),
763-
764778
# Pre-build patches: applied to extracted sdist source before wheel build.
765779
"pre_build_patches": attr.label_list(
766780
default = [],

uv/private/pep517_whl/BUILD.bazel

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
load("@bazel_lib//:bzl_library.bzl", "bzl_library")
22
load("@bazel_skylib//rules:write_file.bzl", "write_file")
33
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
4-
load(":rule.bzl", "pep517_native_whl")
5-
load(":test.bzl", "pep517_native_whl_toolchain_env_test")
4+
load("//py:defs.bzl", "py_library", "py_test")
5+
load(":rule.bzl", "pep517_native_whl", "pep517_whl")
6+
load(
7+
":test.bzl",
8+
"build_memory_failure_test",
9+
"build_memory_test",
10+
"pep517_native_whl_toolchain_env_test",
11+
)
612

713
package(default_visibility = [
814
"//uv/private:__subpackages__",
@@ -16,7 +22,24 @@ exports_files(
1622
bzl_library(
1723
name = "rule",
1824
srcs = ["rule.bzl"],
19-
deps = ["//py/private/toolchain:types"],
25+
deps = [
26+
":build_memory",
27+
"//py/private/toolchain:types",
28+
"@bazel_lib//lib:resource_sets",
29+
],
30+
)
31+
32+
py_library(
33+
name = "memory_monitor",
34+
srcs = ["memory_monitor.py"],
35+
visibility = ["//visibility:public"],
36+
)
37+
38+
py_test(
39+
name = "memory_monitor_test",
40+
srcs = ["memory_monitor_test.py"],
41+
main = "memory_monitor_test.py",
42+
deps = [":memory_monitor"],
2043
)
2144

2245
bzl_library(
@@ -59,6 +82,35 @@ sh_binary(
5982
tags = ["manual"],
6083
)
6184

85+
pep517_whl(
86+
name = "__excessive_memory_fixture",
87+
src = ":__stub_sdist",
88+
build_memory_mb = 32769,
89+
tags = ["manual"],
90+
tool = ":__stub_tool",
91+
version = "0.0.1",
92+
)
93+
94+
build_memory_failure_test(
95+
name = "build_memory_limit_test",
96+
expected_error = "build_memory_mb must be between 0 and 32768",
97+
target_under_test = ":__excessive_memory_fixture",
98+
)
99+
100+
pep517_whl(
101+
name = "__maximum_memory_fixture",
102+
src = ":__stub_sdist",
103+
build_memory_mb = 32768,
104+
tags = ["manual"],
105+
tool = ":__stub_tool",
106+
version = "0.0.1",
107+
)
108+
109+
build_memory_test(
110+
name = "build_memory_maximum_test",
111+
target_under_test = ":__maximum_memory_fixture",
112+
)
113+
62114
# Fixture replicates the toolchains + env defaults emitted by sdist_build's
63115
# BUILD template (see uv/private/sdist_build/repository.bzl). The analysis
64116
# test below asserts these expand to non-empty toolchain paths.
@@ -88,3 +140,8 @@ pep517_native_whl_toolchain_env_test(
88140
name = "toolchain_env_test",
89141
target_under_test = ":__toolchain_env_fixture",
90142
)
143+
144+
bzl_library(
145+
name = "build_memory",
146+
srcs = ["build_memory.bzl"],
147+
)

0 commit comments

Comments
 (0)