Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 py/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ bzl_library(
":py_info",
":py_semantics",
":py_wheel",
":transitions",
"@bazel_skylib//lib:new_sets",
"@bazel_skylib//lib:types",
],
Expand Down
15 changes: 15 additions & 0 deletions py/private/interpreter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ string_flag(
build_setting_default = "",
)

# Internal transition scratch state. Python terminals capture the version
# settings they inherited before applying their own python_version attr, then
# data edges use them to return runtime files to the caller's configuration.
string_flag(
name = "baseline_python_version",
build_setting_default = "<unset>",
visibility = ["//py:__subpackages__"],
)

string_flag(
name = "baseline_rules_python_version",
build_setting_default = "<unset>",
visibility = ["//py:__subpackages__"],
)

# Interpreter feature exclusions. Pass one or more --exclude_feature flags to
# strip optional components from interpreter filegroups. Supported values:
# headers, docs, tkinter, idle, ensurepip, config, pydoc, lib2to3, turtle
Expand Down
4 changes: 4 additions & 0 deletions py/private/py_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("//py/private:providers.bzl", "PyVirtualInfo", "PyWheelsInfo")
load("//py/private:pth.bzl", "make_imports_depset")
load("//py/private:py_info.bzl", "PyInfo")
load("//py/private:transitions.bzl", "reset_python_flags_transition")

def _make_instrumented_files_info(ctx):
return coverage_common.instrumented_files_info(
Expand Down Expand Up @@ -190,8 +191,11 @@ _attrs = dict({
The transitive closure of the `data` dependencies will be available in the `.runfiles`
folder for this binary/test. The program may optionally use the Runfiles lookup library to
locate the data files, see https://pypi.org/project/bazel-runfiles/.
Data is analyzed in the inherited caller configuration. Put artifacts
that must match the terminal's Python environment in `deps`.
""",
allow_files = True,
cfg = reset_python_flags_transition,
Comment thread
tamird marked this conversation as resolved.
Comment thread
xangcastle marked this conversation as resolved.
),
"imports": attr.string_list(
doc = "List of import directories to be added to the PYTHONPATH.",
Expand Down
1 change: 1 addition & 0 deletions py/private/py_venv/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ bzl_library(
"//py/private:py_info",
"//py/private:py_library",
"//py/private:py_semantics",
"//py/private:transitions",
"@bazel_lib//lib:expand_make_vars",
"@bazel_lib//lib:paths",
"@hermetic_launcher//launcher:lib_bzl",
Expand Down
7 changes: 7 additions & 0 deletions py/private/py_venv/py_venv_exec.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ load("@bazel_lib//lib:expand_make_vars.bzl", "expand_locations", "expand_variabl
load("@hermetic_launcher//launcher:lib.bzl", "launcher")
load("//py/private:py_info.bzl", "PyInfo")
load("//py/private:py_semantics.bzl", _py_semantics = "semantics")
load("//py/private:transitions.bzl", "reset_python_flags_transition")
load(":types.bzl", "VirtualenvInfo", "venv_root")

# Identifiers the launcher always sets to the analysing rule's contextual
Expand Down Expand Up @@ -212,8 +213,11 @@ The transitive closure of the `data` dependencies will be available in
the `.runfiles` folder for this binary/test. The program may optionally
use the Runfiles lookup library to locate the data files, see
https://pypi.org/project/bazel-runfiles/.
Data is analyzed in the inherited caller configuration. Put artifacts
that must match the terminal's Python environment in `deps`.
""",
allow_files = True,
cfg = reset_python_flags_transition,
),
# Forwarded to the sibling py_venv (which is where srcs actually
# feed sys.path). Carried on the launcher only so Bazel's `args`
Expand All @@ -224,6 +228,9 @@ https://pypi.org/project/bazel-runfiles/.
doc = "Python source files. Forwarded to the sibling py_venv.",
allow_files = [".py"],
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
})

_test_attrs = dict({
Expand Down
90 changes: 82 additions & 8 deletions py/private/transitions.bzl
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
"""Common transition implementation used by the various terminals."""

DEP_GROUP_FLAG = "@aspect_rules_py//uv/private/constraints/dep_group:dep_group"
_DEP_GROUP_BASELINE_FLAG = "@aspect_rules_py//uv/private/constraints/dep_group:baseline"

# Our own python_version flag, replacing the rules_python one.
PYTHON_VERSION_FLAG = "@aspect_rules_py//py/private/interpreter:python_version"
_PYTHON_VERSION_BASELINE_FLAG = "@aspect_rules_py//py/private/interpreter:baseline_python_version"

# rules_python's flag, kept for backward compatibility during migration.
_RPY_VERSION_FLAG = "@rules_python//python/config_settings:python_version"
_RPY_VERSION_BASELINE_FLAG = "@aspect_rules_py//py/private/interpreter:baseline_rules_python_version"

# Interpreter feature flags that must be propagated through transitions.
_FREETHREADED_FLAG = "@aspect_rules_py//py/private/interpreter:freethreaded"
_BASELINE_UNSET = "<unset>"

def _baseline(settings, flag, current):
baseline = settings[flag]
if baseline == _BASELINE_UNSET:
return current
return baseline

def _python_version(settings):
return settings[PYTHON_VERSION_FLAG] or settings[_RPY_VERSION_FLAG]

def _python_transition_impl(settings, attr):
acc = {}
acc[_PYTHON_VERSION_BASELINE_FLAG] = _baseline(
settings,
_PYTHON_VERSION_BASELINE_FLAG,
settings[PYTHON_VERSION_FLAG],
)
acc[_RPY_VERSION_BASELINE_FLAG] = _baseline(
settings,
_RPY_VERSION_BASELINE_FLAG,
settings[_RPY_VERSION_FLAG],
)
if attr.python_version:
version = str(attr.python_version)
else:
version = settings[PYTHON_VERSION_FLAG] or settings[_RPY_VERSION_FLAG]
version = _python_version(settings)

acc[PYTHON_VERSION_FLAG] = version
acc[_RPY_VERSION_FLAG] = version
Expand All @@ -28,11 +49,14 @@ def _python_transition_impl(settings, attr):
dep_group = getattr(attr, "dep_group", None)
if dep_group:
acc[DEP_GROUP_FLAG] = str(dep_group)
acc[_DEP_GROUP_BASELINE_FLAG] = _baseline(
settings,
_DEP_GROUP_BASELINE_FLAG,
settings[DEP_GROUP_FLAG],
)
else:
acc[DEP_GROUP_FLAG] = settings[DEP_GROUP_FLAG]

# Propagate interpreter feature flags
acc[_FREETHREADED_FLAG] = settings[_FREETHREADED_FLAG]
acc[_DEP_GROUP_BASELINE_FLAG] = settings[_DEP_GROUP_BASELINE_FLAG]

return acc

Expand All @@ -41,13 +65,63 @@ python_transition = transition(
inputs = [
PYTHON_VERSION_FLAG,
_RPY_VERSION_FLAG,
_PYTHON_VERSION_BASELINE_FLAG,
_RPY_VERSION_BASELINE_FLAG,
DEP_GROUP_FLAG,
_DEP_GROUP_BASELINE_FLAG,
],
outputs = [
PYTHON_VERSION_FLAG,
_RPY_VERSION_FLAG,
_PYTHON_VERSION_BASELINE_FLAG,
_RPY_VERSION_BASELINE_FLAG,
DEP_GROUP_FLAG,
_DEP_GROUP_BASELINE_FLAG,
],
)

# Runtime data is outside the Python environment selected by terminal attrs.
# Return every setting those attrs can override to its inherited value, then
# clear the scratch state so data targets share the caller's canonical
# configuration.
def _reset_python_flags_transition_impl(settings, _attr):
return {
PYTHON_VERSION_FLAG: _baseline(
settings,
_PYTHON_VERSION_BASELINE_FLAG,
settings[PYTHON_VERSION_FLAG],
),
_RPY_VERSION_FLAG: _baseline(
settings,
_RPY_VERSION_BASELINE_FLAG,
settings[_RPY_VERSION_FLAG],
),
_PYTHON_VERSION_BASELINE_FLAG: _BASELINE_UNSET,
_RPY_VERSION_BASELINE_FLAG: _BASELINE_UNSET,
DEP_GROUP_FLAG: _baseline(
settings,
_DEP_GROUP_BASELINE_FLAG,
settings[DEP_GROUP_FLAG],
),
_DEP_GROUP_BASELINE_FLAG: _BASELINE_UNSET,
}

reset_python_flags_transition = transition(
implementation = _reset_python_flags_transition_impl,
inputs = [
PYTHON_VERSION_FLAG,
_RPY_VERSION_FLAG,
_PYTHON_VERSION_BASELINE_FLAG,
_RPY_VERSION_BASELINE_FLAG,
DEP_GROUP_FLAG,
_FREETHREADED_FLAG,
_DEP_GROUP_BASELINE_FLAG,
],
outputs = [
PYTHON_VERSION_FLAG,
_RPY_VERSION_FLAG,
_PYTHON_VERSION_BASELINE_FLAG,
_RPY_VERSION_BASELINE_FLAG,
DEP_GROUP_FLAG,
_FREETHREADED_FLAG,
_DEP_GROUP_BASELINE_FLAG,
],
)
6 changes: 4 additions & 2 deletions py/tests/cc-deps/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,22 @@ cc_binary(
deps = ["@rules_python//python/cc:current_py_cc_headers_abi3"],
)

# Python extensions retain the terminal's interpreter configuration through
# deps so their headers match the runtime that imports them.
py_test(
name = "test_smoke",
srcs = ["test_smoke.py"],
data = [":example_library.so"],
imports = ["."],
python_version = "3.13",
target_compatible_with = _WINDOWS_INCOMPATIBLE,
deps = [":example_library.so"],
)

py_test(
name = "test_limited_api",
srcs = ["test_limited_api.py"],
data = [":limited_api_library.so"],
imports = ["."],
python_version = "3.13",
target_compatible_with = _WINDOWS_INCOMPATIBLE,
deps = [":limited_api_library.so"],
)
78 changes: 78 additions & 0 deletions py/tests/reset-data-edges/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
load("//py:defs.bzl", "py_binary", "py_library", "py_venv")
load(":tests.bzl", "probe", "reset_data_edges_test_suite", "root", "terminal")

probe(name = "probe")

# The launcher's data edge is reached through each parent venv's deps edge.
py_binary(
name = "launcher",
srcs = ["main.py"],
data = [":probe"],
main = "main.py",
tags = ["manual"],
)

py_library(
name = "library",
srcs = ["main.py"],
data = [":probe"],
tags = ["manual"],
)

py_venv(
name = "first",
srcs = ["main.py"],
data = [":probe"],
dep_group = "first",
python_version = "3.12",
tags = ["manual"],
deps = [
":launcher",
":library",
],
)

# Nested terminal transitions must keep the baseline inherited above their
# parent, not recapture the parent's selected environment.
terminal(
name = "parent_terminal",
data = [":probe"],
dep_group = "parent",
python_version = "3.12",
tags = ["manual"],
deps = [":nested_terminal"],
)

terminal(
name = "nested_terminal",
data = [":probe"],
dep_group = "nested",
python_version = "3.9",
tags = ["manual"],
)

py_venv(
name = "second",
srcs = ["main.py"],
data = [":probe"],
dep_group = "second",
python_version = "3.12",
tags = ["manual"],
deps = [
":launcher",
":library",
],
)

root(
name = "root",
tags = ["manual"],
deps = [
":first",
":parent_terminal",
":probe",
":second",
],
)

reset_data_edges_test_suite()
1 change: 1 addition & 0 deletions py/tests/reset-data-edges/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pass
Loading
Loading