diff --git a/CHANGELOG.md b/CHANGELOG.md index 013d33c686..2d438c7a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ Unreleased changes are tracked as individual files in the [news/](./news) directory, or view the [latest generated changelog](https://rules-python.readthedocs.io/en/latest/changelog.html). +### Fixed +* Fixed {attr}`py_library.pyi_deps` being erroneously included in {obj}`PyInfo.transitive_sources` and propagated into downstream binary and test runfiles. + {#v2-4-0} ## [2.4.0] - 2026-09-22 diff --git a/python/private/common.bzl b/python/private/common.bzl index 5cff7f8723..ec48b43717 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -412,7 +412,14 @@ def create_py_info( for target in ctx.attr.pyi_deps: # PyInfo may not be present e.g. cc_library rules. if PyInfo in target or (BuiltinPyInfo != None and BuiltinPyInfo in target): - py_info.merge(_get_py_info(target)) + info = _get_py_info(target) + py_info.imports.add(info.imports) + if hasattr(info, "transitive_pyi_files"): + py_info.transitive_pyi_files.add(info.transitive_pyi_files) + if hasattr(info, "transitive_original_sources"): + py_info.transitive_original_sources.add(info.transitive_original_sources) + elif hasattr(info, "transitive_sources"): + py_info.transitive_original_sources.add(info.transitive_sources) py_info.transitive_sources.add(required_py_files) diff --git a/tests/base_rules/base_tests.bzl b/tests/base_rules/base_tests.bzl index a9fadd7564..08d66ba6b0 100644 --- a/tests/base_rules/base_tests.bzl +++ b/tests/base_rules/base_tests.bzl @@ -100,6 +100,9 @@ def _test_py_info_populated_impl(env, target): "{package}/lib2.pyi", "{package}/subject.pyi", ]) + info.transitive_sources().contains_exactly([ + "{package}/test_py_info_populated_subject.py", + ]) _tests.append(_test_py_info_populated) diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl index f6b3c9bb60..48b0129838 100644 --- a/tests/base_rules/py_executable_base_tests.bzl +++ b/tests/base_rules/py_executable_base_tests.bzl @@ -519,6 +519,43 @@ def _test_py_runtime_info_provided_impl(env, target): _tests.append(_test_py_runtime_info_provided) +def _test_pyi_deps_not_in_runfiles(name, config): + rt_util.helper_target( + py_library, + name = name + "_types", + srcs = [rt_util.empty_file(name + "_type_stub.py")], + ) + rt_util.helper_target( + py_library, + name = name + "_lib", + srcs = [rt_util.empty_file(name + "_lib.py")], + pyi_deps = [name + "_types"], + ) + rt_util.helper_target( + config.rule, + name = name + "_subject", + srcs = [name + "_main.py"], + main = name + "_main.py", + deps = [name + "_lib"], + ) + analysis_test( + name = name, + impl = _test_pyi_deps_not_in_runfiles_impl, + target = name + "_subject", + ) + +def _test_pyi_deps_not_in_runfiles_impl(env, target): + target = env.expect.that_target(target) + target.runfiles().contains_at_least([ + "{workspace}/{package}/{test_name}_main.py", + "{workspace}/{package}/{test_name}_lib.py", + ]) + target.runfiles().not_contains( + "{workspace}/{package}/{test_name}_type_stub.py", + ) + +_tests.append(_test_pyi_deps_not_in_runfiles) + def _test_venv_output_prefix_with_path_separators(name, config): rt_util.helper_target( config.rule,