Skip to content
Open
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
3 changes: 2 additions & 1 deletion docs/api/py.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ from the already extracted whl file.
<pre>
load("@aspect_rules_py//py:defs.bzl", "PyInfo")

PyInfo(<a href="#PyInfo-transitive_sources">transitive_sources</a>, <a href="#PyInfo-imports">imports</a>, <a href="#PyInfo-virtual_dependencies">virtual_dependencies</a>, <a href="#PyInfo-virtual_resolutions">virtual_resolutions</a>)
PyInfo(<a href="#PyInfo-transitive_sources">transitive_sources</a>, <a href="#PyInfo-transitive_pyi_files">transitive_pyi_files</a>, <a href="#PyInfo-imports">imports</a>, <a href="#PyInfo-virtual_dependencies">virtual_dependencies</a>, <a href="#PyInfo-virtual_resolutions">virtual_resolutions</a>)
</pre>

Python source, import-path, and virtual-dependency information for a target's dependency closure.
Expand All @@ -298,6 +298,7 @@ Python source, import-path, and virtual-dependency information for a target's de
| Name | Description |
| :------------- | :------------- |
| <a id="PyInfo-transitive_sources"></a>transitive_sources | depset[File] — postorder depset of first-party `.py` sources in the transitive closure. |
| <a id="PyInfo-transitive_pyi_files"></a>transitive_pyi_files | depset[File] — postorder depset of `.pyi` type stubs in the transitive closure. |
| <a id="PyInfo-imports"></a>imports | depset[str] — import roots to place on `sys.path` (rlocation-root-relative). |
| <a id="PyInfo-virtual_dependencies"></a>virtual_dependencies | depset[str] — names of required virtual dependencies, independent of their resolution status. |
| <a id="PyInfo-virtual_resolutions"></a>virtual_resolutions | depset[struct(virtual, target)] — virtual-dependency-name to concrete-target resolutions. |
Expand Down
1 change: 1 addition & 0 deletions py/private/py_info.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RulesPyInfo = provider(
doc = "Python source, import-path, and virtual-dependency information for a target's dependency closure.",
fields = {
"transitive_sources": "depset[File] — postorder depset of first-party `.py` sources in the transitive closure.",
"transitive_pyi_files": "depset[File] — postorder depset of `.pyi` type stubs in the transitive closure.",
"imports": "depset[str] — import roots to place on `sys.path` (rlocation-root-relative).",
"virtual_dependencies": "depset[str] — names of required virtual dependencies, independent of their resolution status.",
"virtual_resolutions": "depset[struct(virtual, target)] — virtual-dependency-name to concrete-target resolutions.",
Expand Down
12 changes: 10 additions & 2 deletions py/private/py_info_interop.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
The `deps` attribute on rules_py rules accepts targets built by either
ruleset. rules_py always emits its own `PyInfo`
(`//py/private:py_info.bzl`); native `@rules_python` targets (e.g.
a `py_proto_library`) carry `@rules_python`'s. Both expose `transitive_sources`
and `imports`, which is everything rules_py reads from a foreign dep.
a `py_proto_library`) carry `@rules_python`'s. Both expose the source, type
stub, and import information rules_py reads from a foreign dep.

This module is the single place that knows about both providers. Rule code
calls these accessors at the API edge instead of loading `@rules_python`'s
Expand Down Expand Up @@ -37,3 +37,11 @@ def get_py_info(target):
if RulesPythonPyInfo in target:
return target[RulesPythonPyInfo]
return None

def get_transitive_pyi_files(target):
"""Return type stubs from rules_py's or `@rules_python`'s `PyInfo`."""
if PyInfo in target:
return target[PyInfo].transitive_pyi_files
Comment thread
jbedard marked this conversation as resolved.
if RulesPythonPyInfo in target:
return target[RulesPythonPyInfo].transitive_pyi_files
return depset()
24 changes: 23 additions & 1 deletion py/private/py_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("//py/private:providers.bzl", "PyWheelsInfo")
load("//py/private:pth.bzl", "make_imports_depset")
load("//py/private:py_info.bzl", "PyInfo")
load("//py/private:py_info_interop.bzl", "RulesPythonPyInfo", "get_py_info", "has_py_info")
load("//py/private:py_info_interop.bzl", "RulesPythonPyInfo", "get_py_info", "get_transitive_pyi_files", "has_py_info")
load("//py/private:transitions.bzl", "reset_python_flags_transition")

def _make_instrumented_files_info(ctx):
Expand All @@ -34,6 +34,16 @@ def _make_srcs_depset(ctx, extra_depsets = []):
] + extra_depsets,
)

def _make_pyi_depset(ctx, extra_depsets = []):
return depset(
order = "postorder",
transitive = [
get_transitive_pyi_files(target)
for target in ctx.attr.deps
if has_py_info(target)
] + extra_depsets,
)

def _make_virtual_depset(ctx):
return depset(
order = "postorder",
Expand All @@ -56,6 +66,11 @@ def _make_resolved_virtual_depset(target):
transitive = transitive,
)

def _make_resolved_virtual_pyi_depset(target):
if has_py_info(target):
return get_transitive_pyi_files(target)
return depset()

def _make_virtual_resolutions_depset(ctx):
return depset(
order = "postorder",
Expand All @@ -77,6 +92,7 @@ def _resolve_virtuals(ctx):
# Check for duplicate virtual dependency names. Those that map to the same resolution target would have been merged by the depset for us.
seen = {}
v_srcs = []
v_pyi_files = []
v_runfiles = []
v_imports = []

Expand All @@ -88,6 +104,7 @@ def _resolve_virtuals(ctx):
seen.update([[resolution.virtual, i]])

v_srcs.append(_make_resolved_virtual_depset(resolution.target))
v_pyi_files.append(_make_resolved_virtual_pyi_depset(resolution.target))
v_runfiles.append(resolution.target[DefaultInfo].default_runfiles.files)

info = get_py_info(resolution.target)
Expand All @@ -100,6 +117,7 @@ def _resolve_virtuals(ctx):

return struct(
srcs = v_srcs,
pyi_files = v_pyi_files,
runfiles = v_runfiles,
imports = v_imports,
)
Expand Down Expand Up @@ -153,6 +171,7 @@ def _make_merged_runfiles(ctx, extra_depsets = [], extra_runfiles = [], extra_ru

def _py_library_impl(ctx):
transitive_srcs = _make_srcs_depset(ctx)
transitive_pyi_files = _make_pyi_depset(ctx)
imports = _make_imports_depset(ctx)
virtuals = _make_virtual_depset(ctx)
resolutions = _make_virtual_resolutions_depset(ctx)
Expand All @@ -168,6 +187,7 @@ def _py_library_impl(ctx):
PyInfo(
imports = imports,
transitive_sources = transitive_srcs,
transitive_pyi_files = transitive_pyi_files,
virtual_dependencies = virtuals,
virtual_resolutions = resolutions,
),
Expand All @@ -185,6 +205,7 @@ def _py_library_impl(ctx):
providers.append(RulesPythonPyInfo(
imports = imports,
transitive_sources = transitive_srcs,
transitive_pyi_files = transitive_pyi_files,
))

return providers
Expand Down Expand Up @@ -239,6 +260,7 @@ py_library_utils = struct(
implementation = _py_library_impl,
make_imports_depset = _make_imports_depset,
make_merged_runfiles = _make_merged_runfiles,
make_pyi_depset = _make_pyi_depset,
make_srcs_depset = _make_srcs_depset,
make_wheels_depset = _make_wheels_depset,
py_library_providers = _providers,
Expand Down
1 change: 1 addition & 0 deletions py/private/py_unpacked_wheel.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def _py_unpacked_wheel_impl(ctx):
),
PyInfo(
imports = imports,
transitive_pyi_files = depset(),
transitive_sources = depset([unpack_directory]),
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand Down
5 changes: 5 additions & 0 deletions py/private/py_venv/py_venv.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ def _assemble_venv_target(ctx):
ctx,
extra_depsets = virtual_resolution.srcs,
)
pyi_depset = _py_library.make_pyi_depset(
ctx,
extra_depsets = virtual_resolution.pyi_files,
)
runtime_files = depset(
direct = assembled.declared_outputs,
transitive = [
Expand All @@ -104,6 +108,7 @@ def _assemble_venv_target(ctx):
bin_python = assembled.bin_python,
imports = imports_depset,
runtime_runfiles = runfiles,
transitive_pyi_files = pyi_depset,
transitive_sources = srcs_depset,
runtime_files = runtime_files,
)
Expand Down
2 changes: 2 additions & 0 deletions py/private/py_venv/py_venv_exec.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def _py_venv_exec_impl(ctx):
# launcher will run with. `srcs` / `deps` live on the
# sibling venv, not on this rule.
imports = vinfo.imports,
transitive_pyi_files = vinfo.transitive_pyi_files,
transitive_sources = vinfo.transitive_sources,
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand All @@ -169,6 +170,7 @@ def _py_venv_exec_impl(ctx):
if ctx.attr._emit_rules_python_providers[BuildSettingInfo].value:
providers.append(RulesPythonPyInfo(
imports = vinfo.imports,
transitive_pyi_files = vinfo.transitive_pyi_files,
transitive_sources = vinfo.transitive_sources,
))

Expand Down
1 change: 1 addition & 0 deletions py/private/py_venv/types.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ binary's launcher exec's the venv's `bin_python`.
"bin_python": "File — the venv's bin/python symlink. Callers needing a launcher target point here.",
"imports": "depset[str] — rlocation-root-relative import paths covered by this venv. Mirrors `PyInfo.imports` of the venv's dep closure.",
"runtime_runfiles": "Runfiles — venv, wheels, and data without Python import sources.",
"transitive_pyi_files": "depset[File] — type stubs carried separately from runtime sources.",
"transitive_sources": "depset[File] — source artifacts carried by this venv: its own `srcs`, sources from `deps` that emit `PyInfo`, and files contributed by virtual-resolution targets. Surfaced by py_binary as `PyInfo.transitive_sources` so downstream consumers see the same source closure they'd see if srcs/deps lived on the binary directly.",
"runtime_files": "depset[File] — generated venv support files and the runfiles library; excludes dependency and interpreter runfiles.",
},
Expand Down
24 changes: 24 additions & 0 deletions py/tests/py-info-interop/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
load("//py:defs.bzl", "py_library")
load(":pyi_propagation_test.bzl", "pyi_propagation_test", "rules_python_pyi_fixture")

package(default_testonly = True)

rules_python_pyi_fixture(
name = "rules_python_library",
srcs = ["library.pyi"],
)

py_library(
name = "rules_py_library",
deps = [":rules_python_library"],
)

py_library(
name = "rules_py_consumer",
deps = [":rules_py_library"],
)

pyi_propagation_test(
name = "pyi_propagation_test",
target_under_test = ":rules_py_consumer",
)
1 change: 1 addition & 0 deletions py/tests/py-info-interop/library.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def greeting(name: str) -> str: ...
27 changes: 27 additions & 0 deletions py/tests/py-info-interop/pyi_propagation_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Analysis coverage for rules_python type-stub interoperability."""

load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("@rules_python//python:defs.bzl", RulesPythonPyInfo = "PyInfo")
load("//py:defs.bzl", "PyInfo")

def _rules_python_pyi_fixture_impl(ctx):
return [RulesPythonPyInfo(
transitive_sources = depset(),
transitive_pyi_files = depset(ctx.files.srcs),
)]

rules_python_pyi_fixture = rule(
implementation = _rules_python_pyi_fixture_impl,
attrs = {
"srcs": attr.label_list(allow_files = [".pyi"]),
},
)

def _pyi_propagation_test_impl(ctx):
env = analysistest.begin(ctx)
target = analysistest.target_under_test(env)
asserts.equals(env, ["library.pyi"], [file.basename for file in target[PyInfo].transitive_pyi_files.to_list()])
asserts.equals(env, [], target[PyInfo].transitive_sources.to_list())
return analysistest.end(env)

pyi_propagation_test = analysistest.make(_pyi_propagation_test_impl)
2 changes: 2 additions & 0 deletions py/tests/py_venv_conflict/collision_order_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ printf 'VALUE = "namespace"\n' > "$site/mixed_top/from_namespace.py"
),
PyInfo(
imports = depset([site_packages]),
transitive_pyi_files = depset(),
transitive_sources = depset([install_tree]),
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand Down Expand Up @@ -192,6 +193,7 @@ printf 'native' > "$site/collision_order/native_extension.so"
),
PyInfo(
imports = depset([site_packages]),
transitive_pyi_files = depset(),
transitive_sources = depset([install_tree]),
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand Down
2 changes: 2 additions & 0 deletions py/tests/py_venv_conflict/site_merge_order_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ printf 'VALUE = %s\n' "$4" > "$site/other/from_final.py"
),
PyInfo(
imports = depset(site_packages_paths),
transitive_pyi_files = depset(),
transitive_sources = depset(install_trees),
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand Down Expand Up @@ -230,6 +231,7 @@ printf 'VALUE = %s\n' "$4" > "$site/mixed/sibling.py"
),
PyInfo(
imports = depset(site_packages_paths),
transitive_pyi_files = depset(),
transitive_sources = depset(install_trees),
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand Down
1 change: 1 addition & 0 deletions uv/private/whl_install/rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ def _whl_install(ctx):
transitive_sources = depset([
install_dir,
]),
transitive_pyi_files = depset(),
imports = depset([site_packages_rfpath]),
virtual_dependencies = depset(),
virtual_resolutions = depset(),
Expand Down
Loading