Skip to content

Commit dfa4fe9

Browse files
committed
fix: fixes to prepare for making bootstrap=script the default for Linux
1 parent abdf560 commit dfa4fe9

21 files changed

+352
-56
lines changed

.bazelignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ examples/pip_parse/bazel-pip_parse
2525
examples/pip_parse_vendored/bazel-pip_parse_vendored
2626
examples/pip_repository_annotations/bazel-pip_repository_annotations
2727
examples/py_proto_library/bazel-py_proto_library
28+
gazelle/bazel-gazelle
2829
tests/integration/compile_pip_requirements/bazel-compile_pip_requirements
2930
tests/integration/ignore_root_user_error/bazel-ignore_root_user_error
3031
tests/integration/local_toolchains/bazel-local_toolchains

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Unreleased changes template.
7272
* (toolchains) Previously [#2636](https://github.com/bazel-contrib/rules_python/pull/2636)
7373
changed the semantics of `ignore_root_user_error` from "ignore" to "warning". This is now
7474
flipped back to ignoring the issue, and will only emit a warning when the attribute is set
75-
`False`.
75+
`False`.
7676
* (pypi) The PyPI extension will no longer write the lock file entries as the
7777
extension has been marked reproducible.
7878
Fixes [#2434](https://github.com/bazel-contrib/rules_python/issues/2434).
@@ -81,6 +81,10 @@ Unreleased changes template.
8181
[PR #2746](https://github.com/bazel-contrib/rules_python/pull/2746).
8282
* (rules) {attr}`py_binary.srcs` and {attr}`py_test.srcs` is no longer mandatory when
8383
`main_module` is specified (for `--bootstrap_impl=script`)
84+
* (rules) On Windows, {obj}`--bootstrap_impl=system_python` is forced. This
85+
allows setting `--bootstrap_impl=script` in bazelrc for mixed-platform
86+
environments.
87+
8488

8589
[20250317]: https://github.com/astral-sh/python-build-standalone/releases/tag/20250317
8690

@@ -102,6 +106,11 @@ Unreleased changes template.
102106
* (packaging) An empty `requires_file` is treated as if it were omitted, resulting in a valid `METADATA` file.
103107
* (rules) py_wheel and sphinxdocs rules now propagate `target_compatible_with` to all targets they create.
104108
[PR #2788](https://github.com/bazel-contrib/rules_python/pull/2788).
109+
* Fixes when using {obj}`--bootstrap_impl=script`:
110+
* `compile_pip_requirements` is now works with it
111+
* The `sys._base_executable` value will reflect the underlying interpreter,
112+
not venv interpreter.
113+
* The {obj}`//python/runtime_env_toolchains:all` toolchain now works with it.
105114

106115
{#v0-0-0-added}
107116
### Added

examples/pip_repository_annotations/.bazelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ try-import %workspace%/user.bazelrc
55
# is in examples/bzlmod as the `whl_mods` feature.
66
common --noenable_bzlmod
77
common --enable_workspace
8+
common --legacy_external_runfiles=false
89
common --incompatible_python_disallow_native_rules

examples/pip_repository_annotations/pip_repository_annotations_test.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import unittest
2222
from pathlib import Path
2323

24-
from rules_python.python.runfiles import runfiles
24+
from python.runfiles import runfiles
2525

2626

2727
class PipRepositoryAnnotationsTest(unittest.TestCase):
@@ -34,11 +34,7 @@ def wheel_pkg_dir(self) -> str:
3434

3535
def test_build_content_and_data(self):
3636
r = runfiles.Create()
37-
rpath = r.Rlocation(
38-
"pip_repository_annotations_example/external/{}/generated_file.txt".format(
39-
self.wheel_pkg_dir()
40-
)
41-
)
37+
rpath = r.Rlocation("{}/generated_file.txt".format(self.wheel_pkg_dir()))
4238
generated_file = Path(rpath)
4339
self.assertTrue(generated_file.exists())
4440

@@ -47,11 +43,7 @@ def test_build_content_and_data(self):
4743

4844
def test_copy_files(self):
4945
r = runfiles.Create()
50-
rpath = r.Rlocation(
51-
"pip_repository_annotations_example/external/{}/copied_content/file.txt".format(
52-
self.wheel_pkg_dir()
53-
)
54-
)
46+
rpath = r.Rlocation("{}/copied_content/file.txt".format(self.wheel_pkg_dir()))
5547
copied_file = Path(rpath)
5648
self.assertTrue(copied_file.exists())
5749

@@ -61,7 +53,7 @@ def test_copy_files(self):
6153
def test_copy_executables(self):
6254
r = runfiles.Create()
6355
rpath = r.Rlocation(
64-
"pip_repository_annotations_example/external/{}/copied_content/executable{}".format(
56+
"{}/copied_content/executable{}".format(
6557
self.wheel_pkg_dir(),
6658
".exe" if platform.system() == "windows" else ".py",
6759
)
@@ -82,7 +74,7 @@ def test_data_exclude_glob(self):
8274
current_wheel_version = "0.38.4"
8375

8476
r = runfiles.Create()
85-
dist_info_dir = "pip_repository_annotations_example/external/{}/site-packages/wheel-{}.dist-info".format(
77+
dist_info_dir = "{}/site-packages/wheel-{}.dist-info".format(
8678
self.wheel_pkg_dir(),
8779
current_wheel_version,
8880
)
@@ -113,11 +105,8 @@ def test_extra(self):
113105
# This test verifies that annotations work correctly for pip packages with extras
114106
# specified, in this case requests[security].
115107
r = runfiles.Create()
116-
rpath = r.Rlocation(
117-
"pip_repository_annotations_example/external/{}/generated_file.txt".format(
118-
self.requests_pkg_dir()
119-
)
120-
)
108+
path = "{}/generated_file.txt".format(self.requests_pkg_dir())
109+
rpath = r.Rlocation(path)
121110
generated_file = Path(rpath)
122111
self.assertTrue(generated_file.exists())
123112

gazelle/WORKSPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ load("//:internal_dev_deps.bzl", "internal_dev_deps")
4242

4343
internal_dev_deps()
4444

45+
register_toolchains("@rules_python//python/runtime_env_toolchains:all")
46+
4547
load("//:deps.bzl", _py_gazelle_deps = "gazelle_deps")
4648

4749
# gazelle:repository_macro deps.bzl%go_deps

python/config_settings/BUILD.bazel

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ load(
1111
"PrecompileSourceRetentionFlag",
1212
"VenvsSitePackages",
1313
"VenvsUseDeclareSymlinkFlag",
14+
rp_string_flag = "string_flag",
1415
)
1516
load(
1617
"//python/private/pypi:flags.bzl",
@@ -87,14 +88,27 @@ string_flag(
8788
visibility = ["//visibility:public"],
8889
)
8990

90-
string_flag(
91+
rp_string_flag(
9192
name = "bootstrap_impl",
9293
build_setting_default = BootstrapImplFlag.SYSTEM_PYTHON,
94+
override = select({
95+
# Windows doesn't yet support bootstrap=script, so force disable it
96+
":_is_windows": BootstrapImplFlag.SYSTEM_PYTHON,
97+
"//conditions:default": "",
98+
}),
9399
values = sorted(BootstrapImplFlag.__members__.values()),
94100
# NOTE: Only public because it's an implicit dependency
95101
visibility = ["//visibility:public"],
96102
)
97103

104+
# For some reason, @platforms//os:windows can't be directly used
105+
# in the select() for the flag. But it can be used when put behind
106+
# a config_setting().
107+
config_setting(
108+
name = "_is_windows",
109+
constraint_values = ["@platforms//os:windows"],
110+
)
111+
98112
# This is used for pip and hermetic toolchain resolution.
99113
string_flag(
100114
name = "py_linux_libc",

python/private/flags.bzl

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,38 @@ AddSrcsToRunfilesFlag = FlagEnum(
3535
is_enabled = _AddSrcsToRunfilesFlag_is_enabled,
3636
)
3737

38+
def _string_flag_impl(ctx):
39+
if ctx.attr.override:
40+
value = ctx.attr.override
41+
else:
42+
value = ctx.build_setting_value
43+
44+
if value not in ctx.attr.values:
45+
fail((
46+
"Invalid value for {name}: got {value}, must " +
47+
"be one of {allowed}"
48+
).format(
49+
name = ctx.label,
50+
value = value,
51+
allowed = ctx.attr.values,
52+
))
53+
54+
return [
55+
BuildSettingInfo(value = value),
56+
config_common.FeatureFlagInfo(value = value),
57+
]
58+
59+
string_flag = rule(
60+
implementation = _string_flag_impl,
61+
build_setting = config.string(flag = True),
62+
attrs = {
63+
"override": attr.string(),
64+
"values": attr.string_list(),
65+
},
66+
)
67+
3868
def _bootstrap_impl_flag_get_value(ctx):
39-
return ctx.attr._bootstrap_impl_flag[BuildSettingInfo].value
69+
return ctx.attr._bootstrap_impl_flag[config_common.FeatureFlagInfo].value
4070

4171
# buildifier: disable=name-conventions
4272
BootstrapImplFlag = enum(

python/private/get_local_runtime_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"micro": sys.version_info.micro,
2323
"include": sysconfig.get_path("include"),
2424
"implementation_name": sys.implementation.name,
25+
"base_executable": sys._base_executable,
2526
}
2627

2728
config_vars = [

python/private/local_runtime_repo.bzl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ def _local_runtime_repo_impl(rctx):
8484
info = json.decode(exec_result.stdout)
8585
logger.info(lambda: _format_get_info_result(info))
8686

87+
# We use base_executable because we want the path within a Python
88+
# installation directory ("PYTHONHOME"). The problems with sys.executable
89+
# are:
90+
# * If we're in an activated venv, then we don't want the venv's
91+
# `bin/python3` path to be used -- it isn't an actual Python installation.
92+
# * If sys.executable is a wrapper (e.g. pyenv), then (1) it may not be
93+
# located within an actual Python installation directory, and (2) it
94+
# can interfer with Python recognizing when it's within a venv.
95+
#
96+
# In some cases, it may be a symlink (usually e.g. `python3->python3.12`),
97+
# but we don't realpath() it to respect what it has decided is the
98+
# appropriate path.
99+
interpreter_path = info["base_executable"]
100+
87101
# NOTE: Keep in sync with recursive glob in define_local_runtime_toolchain_impl
88102
repo_utils.watch_tree(rctx, rctx.path(info["include"]))
89103

python/private/py_executable.bzl

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ def _create_executable(
350350
main_py = main_py,
351351
imports = imports,
352352
runtime_details = runtime_details,
353+
venv = venv,
353354
)
354355
extra_runfiles = ctx.runfiles([stage2_bootstrap] + venv.files_without_interpreter)
355356
zip_main = _create_zip_main(
@@ -538,11 +539,14 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
538539
ctx.actions.write(pyvenv_cfg, "")
539540

540541
runtime = runtime_details.effective_runtime
542+
541543
venvs_use_declare_symlink_enabled = (
542544
VenvsUseDeclareSymlinkFlag.get_value(ctx) == VenvsUseDeclareSymlinkFlag.YES
543545
)
546+
recreate_venv_at_runtime = False
544547

545-
if not venvs_use_declare_symlink_enabled:
548+
if not venvs_use_declare_symlink_enabled or not runtime.supports_build_time_venv:
549+
recreate_venv_at_runtime = True
546550
if runtime.interpreter:
547551
interpreter_actual_path = runfiles_root_path(ctx, runtime.interpreter.short_path)
548552
else:
@@ -557,6 +561,8 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
557561
ctx.actions.write(interpreter, "actual:{}".format(interpreter_actual_path))
558562

559563
elif runtime.interpreter:
564+
# Some wrappers around the interpreter (e.g. pyenv) use the program
565+
# name to decide what to do, so preserve the name.
560566
py_exe_basename = paths.basename(runtime.interpreter.short_path)
561567

562568
# Even though ctx.actions.symlink() is used, using
@@ -594,7 +600,8 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
594600
if "t" in runtime.abi_flags:
595601
version += "t"
596602

597-
site_packages = "{}/lib/python{}/site-packages".format(venv, version)
603+
venv_site_packages = "lib/python{}/site-packages".format(version)
604+
site_packages = "{}/{}".format(venv, venv_site_packages)
598605
pth = ctx.actions.declare_file("{}/bazel.pth".format(site_packages))
599606
ctx.actions.write(pth, "import _bazel_site_init\n")
600607

@@ -616,10 +623,12 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
616623

617624
return struct(
618625
interpreter = interpreter,
619-
recreate_venv_at_runtime = not venvs_use_declare_symlink_enabled,
626+
recreate_venv_at_runtime = recreate_venv_at_runtime,
620627
# Runfiles root relative path or absolute path
621628
interpreter_actual_path = interpreter_actual_path,
622629
files_without_interpreter = [pyvenv_cfg, pth, site_init] + site_packages_symlinks,
630+
# string; venv-relative path to the site-packages directory.
631+
venv_site_packages = venv_site_packages,
623632
)
624633

625634
def _create_site_packages_symlinks(ctx, site_packages):
@@ -716,7 +725,8 @@ def _create_stage2_bootstrap(
716725
output_sibling,
717726
main_py,
718727
imports,
719-
runtime_details):
728+
runtime_details,
729+
venv = None):
720730
output = ctx.actions.declare_file(
721731
# Prepend with underscore to prevent pytest from trying to
722732
# process the bootstrap for files starting with `test_`
@@ -741,6 +751,7 @@ def _create_stage2_bootstrap(
741751
"%main%": main_py_path,
742752
"%main_module%": ctx.attr.main_module,
743753
"%target%": str(ctx.label),
754+
"%venv_rel_site_packages%": venv.venv_site_packages if venv else "",
744755
"%workspace_name%": ctx.workspace_name,
745756
},
746757
is_executable = True,
@@ -766,6 +777,12 @@ def _create_stage1_bootstrap(
766777

767778
python_binary_actual = venv.interpreter_actual_path if venv else ""
768779

780+
# Runtime may be None on Windows due to the --python_path flag.
781+
if runtime and runtime.supports_build_time_venv:
782+
resolve_python_binary_at_runtime = "0"
783+
else:
784+
resolve_python_binary_at_runtime = "1"
785+
769786
subs = {
770787
"%interpreter_args%": "\n".join([
771788
'"{}"'.format(v)
@@ -775,7 +792,9 @@ def _create_stage1_bootstrap(
775792
"%python_binary%": python_binary_path,
776793
"%python_binary_actual%": python_binary_actual,
777794
"%recreate_venv_at_runtime%": str(int(venv.recreate_venv_at_runtime)) if venv else "0",
795+
"%resolve_python_binary_at_runtime%": resolve_python_binary_at_runtime,
778796
"%target%": str(ctx.label),
797+
"%venv_rel_site_packages%": venv.venv_site_packages if venv else "",
779798
"%workspace_name%": ctx.workspace_name,
780799
}
781800

0 commit comments

Comments
 (0)