Skip to content

Commit 65da8da

Browse files
authored
fix(py_venv): Need venv in the exec config (#571)
The 1.5.1 release was bad for similar reasons to the 1.5.0 release -- the new `py_venv` machinery was taking a source dep on a tool which rather than pull a prebuilt artifact created an unmet dep on `rules_rust` to implement a source build. This second time it was because the `venv` builder tool rather than the interpreter `shim` wasn't depended on via a toolchain. This however is slightly harder to fix because while the `shim` tool is used in the target configuration strictly, the `venv` tool is used in _both_ the target and exec configurations. The original `py_binary` machinery uses it in target, and the new `py_venv` machinery uses it in exec to build the venv ahead of time. That both of these problems were present in 1.5.1 wasn't detected in integration testing because the final batch of integration test checks were missing the `/external/` spot check to make sure that Rust hadn't been fetched. This changeset does a couple things: - (refactor) Introduce a new Info provider so that our toolchain bins are returning an Info as they should not raw Files - (fix) Create a new rule label which refers to the resolved file of the `venv` toolchain, and depend on it in `exec` from `py_venv` so that we can get a prebuilt `venv` binary but will always choose one in the correct configuration for local usage without doing a source build - (fix) Register the new shim toolchains in our own MODULE.bazel - (fix) Don't revert the bzlmod dev-dep patches before running the tests for integration - (fix) Check that running the tests for integration didn't pull rust - (fix) Force tests across platform boundaries to exercise that we didn't break crossbuilding --- ### Changes are visible to end-users: yes - Searched for relevant documentation and updated as needed: yes - Breaking change (forces users to change their own code or config): no - Suggested release notes appear below: no ### Test plan - [x] New test cases added - [x] Existing cases extended to cover previously untested behavior
1 parent d13c4c2 commit 65da8da

19 files changed

+2716
-57
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,6 @@ jobs:
5858
defaults:
5959
run:
6060
working-directory: e2e/use_release
61-
# This test references pre-built artifacts from a prior release.
62-
# See py/private/toolchain/tools.bzl where this is referenced from rctx.os.environ.
63-
# Will need to bump this version in the future when there are breaking changes.
64-
# NB: update tools/integrity.bzl at the same time!
65-
env:
66-
RULES_PY_RELEASE_VERSION: 1.1.0
6761
steps:
6862
- uses: actions/checkout@v4
6963
- run: ./minimal_download_test.sh

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ register_toolchains(
3939
# For manual testing: comment these out to force use of pre-built binaries.
4040
"@aspect_rules_py//py/private/toolchain/venv/...",
4141
"@aspect_rules_py//py/private/toolchain/unpack/...",
42+
"@aspect_rules_py//py/private/toolchain/shim/...",
4243
)
4344

4445
# To allow Rust binaries in /py/tools to be built from source

e2e/use_release/minimal_download_test.sh

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
set -o errexit -o pipefail -o nounset
44

5+
set -x
6+
57
OS="$(uname | tr '[:upper:]' '[:lower:]')"
68
ARCH="$(arch)"
79
ALLOWED="rules_py_tools.${OS}_${ARCH}"
@@ -19,6 +21,9 @@ PIDFILE=$(realpath ./devserver.pid)
1921

2022
# First we produce a release artifact matrix
2123
mkdir artifacts
24+
# WARNING: For local testing you'll have to manually do this with
25+
# --platforms for linux otherwise you'll be missing artifacts. Which is
26+
# mighty annoying.
2227
DEST=$(realpath artifacts) bazel run //tools/release:copy_release_artifacts
2328

2429
# We kick off a dev http server on localhost
@@ -42,6 +47,12 @@ export RULES_PY_RELEASE_URL="http://localhost:$PORT/{filename}"
4247
cd ../..
4348
# Create the .orig file, whether there's a mismatch or not
4449
patch -p1 --backup < .bcr/patches/*.patch
50+
# Write a version to the `version.bzl` file.
51+
# This emulates the version stamping git will do when it makes an archive.
52+
cat <<"EOF" > tools/version.bzl
53+
VERSION = "999.99.9"
54+
IS_PRERELEASE = False
55+
EOF
4556
)
4657

4758
OUTPUT_BASE=$(mktemp -d)
@@ -87,24 +98,49 @@ fi
8798

8899
#############
89100
# Smoke test
90-
bazel test --test_output=streamed //...
101+
bazel "--output_base=$OUTPUT_BASE" test --test_output=streamed //...
91102

103+
#############
104+
# Demonstrate that as configured we're fully on prebuilt toolchains even for crossbuilds
105+
OUTPUT_BASE=$(mktemp -d)
92106
(
93-
cd ../..
94-
rm MODULE.bazel
95-
mv MODULE.bazel.orig MODULE.bazel
107+
cd ../..
108+
109+
# Check that the configured query doesn't use Rust for anything. If we're
110+
# using source toolchains, then we'll get a hit for Rust here.
111+
if bazel cquery 'kind("rust_binary", deps(//py/tests/py_venv_image_layer/...))' | grep "crate_index"; then
112+
>&2 echo "ERROR: we still have a rust dependency"
113+
exit 1
114+
fi
115+
116+
# Demonstrate that we can do crossbuilds with the tool
117+
bazel "--output_base=$OUTPUT_BASE" build //py/tests/py_venv_image_layer/...
118+
119+
# TODO: Note that we can't run and pass these tests because the old py_binary
120+
# implementation sees a different label for the venv tool (internal file vs
121+
# external repo file) and so its image tests fail if we run them here.
96122
)
97123

124+
# Note that we can't check to see if we've fetched rules_rust etc. because
125+
# despite being dev deps they're still visible from and fetched in the parent
126+
# module, even if unused.
127+
98128
#############
99129
# Smoke test py_venv examples
100130
(
101131
cd ../..
102-
bazel run //examples/py_venv:venv -- -c 'print("Hello, world")'
103-
bazel run //examples/py_venv:internal_venv
104-
bazel run --stamp //examples/py_venv:internal_venv
105-
bazel run //examples/py_venv:external_venv
106-
bazel run --stamp //examples/py_venv:external_venv
132+
# Exercise the static venv bits
133+
# Note that we only really expect
134+
bazel "--output_base=$OUTPUT_BASE" run //examples/py_venv:venv -- -c 'print("Hello, world")'
135+
bazel "--output_base=$OUTPUT_BASE" run //examples/py_venv:internal_venv
136+
bazel "--output_base=$OUTPUT_BASE" run --stamp //examples/py_venv:internal_venv
137+
bazel "--output_base=$OUTPUT_BASE" run //examples/py_venv:external_venv
138+
bazel "--output_base=$OUTPUT_BASE" run --stamp //examples/py_venv:external_venv
107139
)
108140

141+
# Note that we can't check to see if we've fetched rules_rust etc. because
142+
# despite being dev deps they're still visible from and fetched in the parent
143+
# module, even if unused.
144+
109145
# Shut down the devserver
110146
kill "$(cat $PIDFILE)"

py/extensions.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def _toolchains_extension_impl(module_ctx):
3131
root_name = toolchain.name
3232
else:
3333
registrations.append(toolchain.name)
34+
3435
for name in registrations:
3536
if name != root_name:
3637
rules_py_toolchains(name, register = False)

py/private/py_binary.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _py_binary_rule_impl(ctx):
7373
substitutions = {
7474
"{{BASH_RLOCATION_FN}}": BASH_RLOCATION_FUNCTION,
7575
"{{INTERPRETER_FLAGS}}": " ".join(py_toolchain.flags + ctx.attr.interpreter_options),
76-
"{{VENV_TOOL}}": to_rlocation_path(ctx, venv_toolchain.bin),
76+
"{{VENV_TOOL}}": to_rlocation_path(ctx, venv_toolchain.bin.bin),
7777
"{{ARG_COLLISION_STRATEGY}}": ctx.attr.package_collisions,
7878
"{{ARG_PYTHON}}": to_rlocation_path(ctx, py_toolchain.python) if py_toolchain.runfiles_interpreter else py_toolchain.python.path,
7979
"{{ARG_VENV_NAME}}": ".{}.venv".format(ctx.attr.name),

py/private/py_unpacked_wheel.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def _py_unpacked_wheel_impl(ctx):
2929
ctx.actions.run(
3030
outputs = [unpack_directory],
3131
inputs = depset([ctx.file.src], transitive = [py_toolchain.files]),
32-
executable = unpack_toolchain.bin,
32+
executable = unpack_toolchain.bin.bin,
3333
arguments = [arguments],
3434
mnemonic = "PyUnpackedWheel",
3535
progress_message = "Unpacking wheel {}".format(ctx.file.src.basename),

py/private/py_venv/py_venv.bzl

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ load("@aspect_bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocatio
55
load("//py/private:py_library.bzl", _py_library = "py_library_utils")
66
load("//py/private:py_semantics.bzl", _py_semantics = "semantics")
77
load("//py/private:transitions.bzl", "python_version_transition")
8-
load("//py/private/toolchain:types.bzl", "PY_TOOLCHAIN", "SHIM_TOOLCHAIN")
8+
load("//py/private/toolchain:types.bzl", "PY_TOOLCHAIN", "PyToolInfo", "SHIM_TOOLCHAIN", "VENV_TOOLCHAIN")
99

1010
VirtualenvInfo = provider(
1111
doc = """
@@ -74,7 +74,10 @@ def _py_venv_base_impl(ctx):
7474

7575
py_toolchain = _py_semantics.resolve_toolchain(ctx)
7676

77+
# Note that we HAVE to grab these files from toolchains so that we can swap
78+
# in prebuild versions in production.
7779
py_shim = ctx.toolchains[SHIM_TOOLCHAIN]
80+
venv_tool = ctx.attr._venv[PyToolInfo].bin
7881

7982
# Check for duplicate virtual dependency names. Those that map to the same resolution target would have been merged by the depset for us.
8083
virtual_resolution = _py_library.resolve_virtuals(ctx)
@@ -141,10 +144,10 @@ def _py_venv_base_impl(ctx):
141144
venv_dir = ctx.actions.declare_directory(venv_name)
142145

143146
ctx.actions.run(
144-
executable = ctx.file._venv_tool,
147+
executable = venv_tool,
145148
arguments = [
146149
"--location=" + venv_dir.path,
147-
"--python=" + py_shim.bin.path,
150+
"--python=" + py_shim.bin.bin.path,
148151
"--pth-file=" + site_packages_pth_file.path,
149152
"--env-file=" + env_file.path,
150153
"--bin-dir=" + ctx.bin_dir.path,
@@ -160,7 +163,7 @@ def _py_venv_base_impl(ctx):
160163
ctx.runfiles(files = [
161164
site_packages_pth_file,
162165
env_file,
163-
ctx.file._venv_tool,
166+
venv_tool,
164167
]),
165168
py_shim.default_info.default_runfiles,
166169
]).files,
@@ -313,12 +316,9 @@ A collision can occur when multiple packages providing the same file are install
313316
"_runfiles_lib": attr.label(
314317
default = "@bazel_tools//tools/bash/runfiles",
315318
),
316-
# Note that we're using the transitioned one for local execution, since
317-
# we're going to run the tool on the local platform and produce static files
318-
# not including this tool.
319-
"_venv_tool": attr.label(
320-
allow_single_file = True,
321-
default = "//py/tools/venv_bin:local_venv_bin",
319+
"_venv": attr.label(
320+
default = "//py/private/toolchain:resolved_venv_toolchain",
321+
cfg = "exec",
322322
),
323323
})
324324

@@ -365,6 +365,7 @@ py_venv_base = struct(
365365
toolchains = [
366366
PY_TOOLCHAIN,
367367
SHIM_TOOLCHAIN,
368+
VENV_TOOLCHAIN,
368369
],
369370
cfg = python_version_transition,
370371
)

py/private/toolchain/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
2+
load(":tools.bzl", "resolved_venv_toolchain")
23

34
exports_files(
45
["python.sh"],
@@ -20,6 +21,11 @@ toolchain_type(
2021
visibility = ["//visibility:public"],
2122
)
2223

24+
resolved_venv_toolchain(
25+
name = "resolved_venv_toolchain",
26+
visibility = ["//visibility:public"],
27+
)
28+
2329
bzl_library(
2430
name = "autodetecting",
2531
srcs = ["autodetecting.bzl"],

py/private/toolchain/repo.bzl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ def _toolchains_repo_impl(repository_ctx):
3434
build_content += """
3535
# Declare a toolchain Bazel will select for running {tool} on the {cfg} platform.
3636
toolchain(
37-
name = "{tool}_{platform}_toolchain",
37+
name = "{tool}_{platform}_{cfg}_toolchain",
3838
{cfg}_compatible_with = {compatible_with},
3939
# Bazel does not follow this attribute during analysis, so the referenced repo
4040
# will only be fetched if this toolchain is selected.
4141
toolchain = "@{user_repository_name}.{platform}//:{tool}_toolchain",
4242
toolchain_type = "{toolchain_type}",
4343
)
44+
4445
""".format(
4546
cfg = bin.cfg,
4647
tool = bin.name,

py/private/toolchain/tools.bzl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Declaration of concrete toolchains for our Rust tools"""
22

3+
load("@bazel_skylib//lib:structs.bzl", "structs")
4+
load(":types.bzl", "PyToolInfo", "VENV_TOOLCHAIN")
5+
36
def PrebuiltToolConfig(
47
target,
58
cfg = "target",
@@ -82,7 +85,7 @@ def _toolchain_impl(ctx):
8285
# Export all the providers inside our ToolchainInfo
8386
# so the resolved_toolchain rule can grab and re-export them.
8487
toolchain_info = platform_common.ToolchainInfo(
85-
bin = binary,
88+
bin = PyToolInfo(bin = binary),
8689
template_variables = template_variables,
8790
default_info = default_info,
8891
)
@@ -122,3 +125,15 @@ def source_toolchain(name, toolchain_type, bin):
122125
toolchain = toolchain_rule,
123126
toolchain_type = toolchain_type,
124127
)
128+
129+
# Forward all the providers
130+
def _resolved_toolchain_impl(ctx):
131+
toolchain_info = ctx.toolchains[VENV_TOOLCHAIN]
132+
return [toolchain_info] + structs.to_dict(toolchain_info).values()
133+
134+
# Copied from java_toolchain_alias
135+
# https://cs.opensource.google/bazel/bazel/+/master:tools/jdk/java_toolchain_alias.bzl
136+
resolved_venv_toolchain = rule(
137+
implementation = _resolved_toolchain_impl,
138+
toolchains = [VENV_TOOLCHAIN],
139+
)

0 commit comments

Comments
 (0)