Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ examples/pip_parse/bazel-pip_parse
examples/pip_parse_vendored/bazel-pip_parse_vendored
examples/pip_repository_annotations/bazel-pip_repository_annotations
examples/py_proto_library/bazel-py_proto_library
gazelle/bazel-gazelle
tests/integration/compile_pip_requirements/bazel-compile_pip_requirements
tests/integration/ignore_root_user_error/bazel-ignore_root_user_error
tests/integration/local_toolchains/bazel-local_toolchains
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Unreleased changes template.
* (toolchains) Previously [#2636](https://github.com/bazel-contrib/rules_python/pull/2636)
changed the semantics of `ignore_root_user_error` from "ignore" to "warning". This is now
flipped back to ignoring the issue, and will only emit a warning when the attribute is set
`False`.
`False`.
* (pypi) The PyPI extension will no longer write the lock file entries as the
extension has been marked reproducible.
Fixes [#2434](https://github.com/bazel-contrib/rules_python/issues/2434).
Expand All @@ -81,6 +81,10 @@ Unreleased changes template.
[PR #2746](https://github.com/bazel-contrib/rules_python/pull/2746).
* (rules) {attr}`py_binary.srcs` and {attr}`py_test.srcs` is no longer mandatory when
`main_module` is specified (for `--bootstrap_impl=script`)
* (rules) On Windows, {obj}`--bootstrap_impl=system_python` is forced. This
allows setting `--bootstrap_impl=script` in bazelrc for mixed-platform
environments.


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

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

{#v0-0-0-added}
### Added
Expand Down
1 change: 1 addition & 0 deletions examples/pip_repository_annotations/.bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ try-import %workspace%/user.bazelrc
# is in examples/bzlmod as the `whl_mods` feature.
common --noenable_bzlmod
common --enable_workspace
common --legacy_external_runfiles=false
common --incompatible_python_disallow_native_rules
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import unittest
from pathlib import Path

from rules_python.python.runfiles import runfiles
from python.runfiles import runfiles


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

def test_build_content_and_data(self):
r = runfiles.Create()
rpath = r.Rlocation(
"pip_repository_annotations_example/external/{}/generated_file.txt".format(
self.wheel_pkg_dir()
)
)
rpath = r.Rlocation("{}/generated_file.txt".format(self.wheel_pkg_dir()))
generated_file = Path(rpath)
self.assertTrue(generated_file.exists())

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

def test_copy_files(self):
r = runfiles.Create()
rpath = r.Rlocation(
"pip_repository_annotations_example/external/{}/copied_content/file.txt".format(
self.wheel_pkg_dir()
)
)
rpath = r.Rlocation("{}/copied_content/file.txt".format(self.wheel_pkg_dir()))
copied_file = Path(rpath)
self.assertTrue(copied_file.exists())

Expand All @@ -61,7 +53,7 @@ def test_copy_files(self):
def test_copy_executables(self):
r = runfiles.Create()
rpath = r.Rlocation(
"pip_repository_annotations_example/external/{}/copied_content/executable{}".format(
"{}/copied_content/executable{}".format(
self.wheel_pkg_dir(),
".exe" if platform.system() == "windows" else ".py",
)
Expand All @@ -82,7 +74,7 @@ def test_data_exclude_glob(self):
current_wheel_version = "0.38.4"

r = runfiles.Create()
dist_info_dir = "pip_repository_annotations_example/external/{}/site-packages/wheel-{}.dist-info".format(
dist_info_dir = "{}/site-packages/wheel-{}.dist-info".format(
self.wheel_pkg_dir(),
current_wheel_version,
)
Expand Down Expand Up @@ -113,11 +105,8 @@ def test_extra(self):
# This test verifies that annotations work correctly for pip packages with extras
# specified, in this case requests[security].
r = runfiles.Create()
rpath = r.Rlocation(
"pip_repository_annotations_example/external/{}/generated_file.txt".format(
self.requests_pkg_dir()
)
)
path = "{}/generated_file.txt".format(self.requests_pkg_dir())
rpath = r.Rlocation(path)
generated_file = Path(rpath)
self.assertTrue(generated_file.exists())

Expand Down
2 changes: 2 additions & 0 deletions gazelle/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ load("//:internal_dev_deps.bzl", "internal_dev_deps")

internal_dev_deps()

register_toolchains("@rules_python//python/runtime_env_toolchains:all")

load("//:deps.bzl", _py_gazelle_deps = "gazelle_deps")

# gazelle:repository_macro deps.bzl%go_deps
Expand Down
16 changes: 15 additions & 1 deletion python/config_settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ load(
"PrecompileSourceRetentionFlag",
"VenvsSitePackages",
"VenvsUseDeclareSymlinkFlag",
rp_string_flag = "string_flag",
)
load(
"//python/private/pypi:flags.bzl",
Expand Down Expand Up @@ -87,14 +88,27 @@ string_flag(
visibility = ["//visibility:public"],
)

string_flag(
rp_string_flag(
name = "bootstrap_impl",
build_setting_default = BootstrapImplFlag.SYSTEM_PYTHON,
override = select({
# Windows doesn't yet support bootstrap=script, so force disable it
":_is_windows": BootstrapImplFlag.SYSTEM_PYTHON,
"//conditions:default": "",
}),
values = sorted(BootstrapImplFlag.__members__.values()),
# NOTE: Only public because it's an implicit dependency
visibility = ["//visibility:public"],
)

# For some reason, @platforms//os:windows can't be directly used
# in the select() for the flag. But it can be used when put behind
# a config_setting().
config_setting(
name = "_is_windows",
constraint_values = ["@platforms//os:windows"],
)

# This is used for pip and hermetic toolchain resolution.
string_flag(
name = "py_linux_libc",
Expand Down
32 changes: 31 additions & 1 deletion python/private/flags.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,38 @@ AddSrcsToRunfilesFlag = FlagEnum(
is_enabled = _AddSrcsToRunfilesFlag_is_enabled,
)

def _string_flag_impl(ctx):
if ctx.attr.override:
value = ctx.attr.override
else:
value = ctx.build_setting_value

if value not in ctx.attr.values:
fail((
"Invalid value for {name}: got {value}, must " +
"be one of {allowed}"
).format(
name = ctx.label,
value = value,
allowed = ctx.attr.values,
))

return [
BuildSettingInfo(value = value),
config_common.FeatureFlagInfo(value = value),
]

string_flag = rule(
implementation = _string_flag_impl,
build_setting = config.string(flag = True),
attrs = {
"override": attr.string(),
"values": attr.string_list(),
},
)

def _bootstrap_impl_flag_get_value(ctx):
return ctx.attr._bootstrap_impl_flag[BuildSettingInfo].value
return ctx.attr._bootstrap_impl_flag[config_common.FeatureFlagInfo].value

# buildifier: disable=name-conventions
BootstrapImplFlag = enum(
Expand Down
1 change: 1 addition & 0 deletions python/private/get_local_runtime_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"micro": sys.version_info.micro,
"include": sysconfig.get_path("include"),
"implementation_name": sys.implementation.name,
"base_executable": sys._base_executable,
}

config_vars = [
Expand Down
14 changes: 14 additions & 0 deletions python/private/local_runtime_repo.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ def _local_runtime_repo_impl(rctx):
info = json.decode(exec_result.stdout)
logger.info(lambda: _format_get_info_result(info))

# We use base_executable because we want the path within a Python
# installation directory ("PYTHONHOME"). The problems with sys.executable
# are:
# * If we're in an activated venv, then we don't want the venv's
# `bin/python3` path to be used -- it isn't an actual Python installation.
# * If sys.executable is a wrapper (e.g. pyenv), then (1) it may not be
# located within an actual Python installation directory, and (2) it
# can interfer with Python recognizing when it's within a venv.
#
# In some cases, it may be a symlink (usually e.g. `python3->python3.12`),
# but we don't realpath() it to respect what it has decided is the
# appropriate path.
interpreter_path = info["base_executable"]

# NOTE: Keep in sync with recursive glob in define_local_runtime_toolchain_impl
repo_utils.watch_tree(rctx, rctx.path(info["include"]))

Expand Down
27 changes: 23 additions & 4 deletions python/private/py_executable.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ def _create_executable(
main_py = main_py,
imports = imports,
runtime_details = runtime_details,
venv = venv,
)
extra_runfiles = ctx.runfiles([stage2_bootstrap] + venv.files_without_interpreter)
zip_main = _create_zip_main(
Expand Down Expand Up @@ -538,11 +539,14 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
ctx.actions.write(pyvenv_cfg, "")

runtime = runtime_details.effective_runtime

venvs_use_declare_symlink_enabled = (
VenvsUseDeclareSymlinkFlag.get_value(ctx) == VenvsUseDeclareSymlinkFlag.YES
)
recreate_venv_at_runtime = False

if not venvs_use_declare_symlink_enabled:
if not venvs_use_declare_symlink_enabled or not runtime.supports_build_time_venv:
recreate_venv_at_runtime = True
if runtime.interpreter:
interpreter_actual_path = runfiles_root_path(ctx, runtime.interpreter.short_path)
else:
Expand All @@ -557,6 +561,8 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
ctx.actions.write(interpreter, "actual:{}".format(interpreter_actual_path))

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

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

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

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

return struct(
interpreter = interpreter,
recreate_venv_at_runtime = not venvs_use_declare_symlink_enabled,
recreate_venv_at_runtime = recreate_venv_at_runtime,
# Runfiles root relative path or absolute path
interpreter_actual_path = interpreter_actual_path,
files_without_interpreter = [pyvenv_cfg, pth, site_init] + site_packages_symlinks,
# string; venv-relative path to the site-packages directory.
venv_site_packages = venv_site_packages,
)

def _create_site_packages_symlinks(ctx, site_packages):
Expand Down Expand Up @@ -716,7 +725,8 @@ def _create_stage2_bootstrap(
output_sibling,
main_py,
imports,
runtime_details):
runtime_details,
venv = None):
output = ctx.actions.declare_file(
# Prepend with underscore to prevent pytest from trying to
# process the bootstrap for files starting with `test_`
Expand All @@ -741,6 +751,7 @@ def _create_stage2_bootstrap(
"%main%": main_py_path,
"%main_module%": ctx.attr.main_module,
"%target%": str(ctx.label),
"%venv_rel_site_packages%": venv.venv_site_packages if venv else "",
"%workspace_name%": ctx.workspace_name,
},
is_executable = True,
Expand All @@ -766,6 +777,12 @@ def _create_stage1_bootstrap(

python_binary_actual = venv.interpreter_actual_path if venv else ""

# Runtime may be None on Windows due to the --python_path flag.
if runtime and runtime.supports_build_time_venv:
resolve_python_binary_at_runtime = "0"
else:
resolve_python_binary_at_runtime = "1"

subs = {
"%interpreter_args%": "\n".join([
'"{}"'.format(v)
Expand All @@ -775,7 +792,9 @@ def _create_stage1_bootstrap(
"%python_binary%": python_binary_path,
"%python_binary_actual%": python_binary_actual,
"%recreate_venv_at_runtime%": str(int(venv.recreate_venv_at_runtime)) if venv else "0",
"%resolve_python_binary_at_runtime%": resolve_python_binary_at_runtime,
"%target%": str(ctx.label),
"%venv_rel_site_packages%": venv.venv_site_packages if venv else "",
"%workspace_name%": ctx.workspace_name,
}

Expand Down
26 changes: 25 additions & 1 deletion python/private/py_runtime_info.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def _PyRuntimeInfo_init(
stage2_bootstrap_template = None,
zip_main_template = None,
abi_flags = "",
site_init_template = None):
site_init_template = None,
supports_build_time_venv = True):
if (interpreter_path and interpreter) or (not interpreter_path and not interpreter):
fail("exactly one of interpreter or interpreter_path must be specified")

Expand Down Expand Up @@ -119,6 +120,7 @@ def _PyRuntimeInfo_init(
"site_init_template": site_init_template,
"stage2_bootstrap_template": stage2_bootstrap_template,
"stub_shebang": stub_shebang,
"supports_build_time_venv": supports_build_time_venv,
"zip_main_template": zip_main_template,
}

Expand Down Expand Up @@ -312,6 +314,28 @@ The following substitutions are made during template expansion:
"Shebang" expression prepended to the bootstrapping Python stub
script used when executing {obj}`py_binary` targets. Does not
apply to Windows.
""",
"supports_build_time_venv": """
:type: bool
True if this toolchain supports the build-time created virtual environment.
False if not or unknown. If build-time venv creation isn't supported, then binaries may
fallback to non-venv solutions or creating a venv at runtime.
In order to use the build-time created virtual environment, a toolchain needs
to meet two criteria:
1. Specifying the underlying executable (e.g. `/usr/bin/python3`, as reported by
`sys._base_executable`) for the venv executable (`$venv/bin/python3`, as reported
by `sys.executable`). This typically requires relative symlinking the venv
path to the underlying path at build time, or using the `PYTHONEXECUTABLE`
environment variable (Python 3.11+) at runtime.
2. Having the build-time created site-packages directory
(`<venv>/lib/python{version}/site-packages`) recognized by the runtime
interpreter. This typically requires the Python version to be known at
build-time and match at runtime.
:::{versionadded} VERSION_NEXT_FEATURE
:::
""",
"zip_main_template": """
:type: File
Expand Down
12 changes: 12 additions & 0 deletions python/private/py_runtime_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def _py_runtime_impl(ctx):
zip_main_template = ctx.file.zip_main_template,
abi_flags = abi_flags,
site_init_template = ctx.file.site_init_template,
supports_build_time_venv = ctx.attr.supports_build_time_venv,
))

if not IS_BAZEL_7_OR_HIGHER:
Expand Down Expand Up @@ -353,6 +354,17 @@ motivation.
Does not apply to Windows.
""",
),
"supports_build_time_venv": attr.bool(
doc = """
Whether this runtime supports virtualenvs created at build time.
See {obj}`PyRuntimeInfo.supports_build_time_venv` for docs.
:::{versionadded} VERSION_NEXT_FEATURE
:::
""",
default = True,
),
"zip_main_template": attr.label(
default = "//python/private:zip_main_template",
allow_single_file = True,
Expand Down
Loading