From 3fc976653070ffdb9507fbd1aa6c7a23e3e658a0 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 09:15:52 -0700 Subject: [PATCH 01/11] tests(precompile): add tests for directory and pyc inputs in srcs Add analysis and execution tests verifying precompiling behavior when directory or pyc file inputs are passed in target srcs. --- .../precompile/precompile_tests.bzl | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/tests/base_rules/precompile/precompile_tests.bzl b/tests/base_rules/precompile/precompile_tests.bzl index d2c1da6b8f..63865a30e6 100644 --- a/tests/base_rules/precompile/precompile_tests.bzl +++ b/tests/base_rules/precompile/precompile_tests.bzl @@ -529,6 +529,148 @@ def _test_precompile_enabled_succeeds(name): _tests.append(_test_precompile_enabled_succeeds) +def _directory_impl(ctx): + out = ctx.actions.declare_directory(ctx.label.name) + ctx.actions.run_shell( + outputs = [out], + command = """\ +mkdir -p "$1" +echo "x = 1" > "$1/foo.py" +echo "y = 2" > "$1/bar.py" +""", + arguments = [out.path], + mnemonic = "TestDirectory", + ) + return [DefaultInfo(files = depset([out]))] + +_directory = rule(implementation = _directory_impl) + +def _test_directory_input(name): + rt_util.helper_target( + _directory, + name = name + "_dir.py", + ) + rt_util.helper_target( + py_library, + name = name + "_subject", + srcs = ["lib.py", name + "_dir.py"], + precompile = "enabled", + ) + analysis_test( + name = name, + impl = _test_directory_input_impl, + target = name + "_subject", + config_settings = _COMMON_CONFIG_SETTINGS, + ) + +def _test_directory_input_impl(env, target): + target = env.expect.that_target(target) + target.default_outputs().contains_at_least_predicates([ + matching.file_path_matches("__pycache__/lib.fakepy-45.pyc"), + matching.file_path_matches("/lib.py"), + matching.file_path_matches("/" + env.ctx.label.name + "_dir.py"), + ]) + py_info = target.provider(PyInfo, factory = py_info_subject) + py_info.direct_pyc_files().contains_exactly([ + "{package}/__pycache__/lib.fakepy-45.pyc", + ]) + py_info.transitive_pyc_files().contains_exactly([ + "{package}/__pycache__/lib.fakepy-45.pyc", + ]) + +_tests.append(_test_directory_input) + +# buildifier: disable=function-docstring-header +def _test_directory_input_succeeds(name): + """Verify that a `py_test` target with a directory input in srcs builds + and runs when precompiling is enabled. + """ + _directory( + name = name + "_dir.py", + ) + write_file( + name = name + "_main", + out = name + "_main.py", + content = [ + "print('Hello from directory input test')", + "", + ], + ) + py_test( + name = name, + srcs = [name + "_main.py", name + "_dir.py"], + main = name + "_main.py", + precompile = "enabled", + tags = ["no-pyrefly"], + ) + +_tests.append(_test_directory_input_succeeds) + +def _test_pyc_source_input(name): + rt_util.helper_target( + write_file, + name = name + "_pyc", + out = name + "_foo.pyc", + content = [""], + ) + rt_util.helper_target( + py_library, + name = name + "_subject", + srcs = ["lib.py", name + "_pyc"], + precompile = "enabled", + ) + analysis_test( + name = name, + impl = _test_pyc_source_input_impl, + target = name + "_subject", + config_settings = _COMMON_CONFIG_SETTINGS, + ) + +def _test_pyc_source_input_impl(env, target): + target = env.expect.that_target(target) + target.default_outputs().contains_at_least_predicates([ + matching.file_path_matches("__pycache__/lib.fakepy-45.pyc"), + matching.file_path_matches("/lib.py"), + matching.file_path_matches("/" + env.ctx.label.name + "_foo.pyc"), + ]) + py_info = target.provider(PyInfo, factory = py_info_subject) + py_info.direct_pyc_files().contains_exactly([ + "{package}/__pycache__/lib.fakepy-45.pyc", + ]) + py_info.transitive_pyc_files().contains_exactly([ + "{package}/__pycache__/lib.fakepy-45.pyc", + ]) + +_tests.append(_test_pyc_source_input) + +# buildifier: disable=function-docstring-header +def _test_pyc_source_input_succeeds(name): + """Verify that a `py_test` target with a pyc input in srcs builds + and runs when precompiling is enabled. + """ + write_file( + name = name + "_pyc", + out = name + "_foo.pyc", + content = [""], + ) + write_file( + name = name + "_main", + out = name + "_main.py", + content = [ + "print('Hello from pyc input test')", + "", + ], + ) + py_test( + name = name, + srcs = [name + "_main.py", name + "_pyc"], + main = name + "_main.py", + precompile = "enabled", + tags = ["no-pyrefly"], + ) + +_tests.append(_test_pyc_source_input_succeeds) + def runfiles_contains_at_least_predicates(runfiles, predicates): for predicate in predicates: runfiles.contains_predicate(predicate) From fa59b853018059681a228e804115b5fbe941cde5 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 09:16:21 -0700 Subject: [PATCH 02/11] fix(precompile): skip directory and pyc file inputs in precompilation Directories and precompiled pyc files cannot be compiled directly into pyc files. Skip precompiling when src.is_directory is true or the extension is pyc/pyo while retaining them in target outputs. --- python/private/attributes.bzl | 2 +- python/private/common.bzl | 2 +- python/private/precompile.bzl | 3 +++ python/private/py_library.bzl | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index e1e77cba03..8c7bb73264 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -331,7 +331,7 @@ as part of a runnable program (packaging rules may include them, however). allow_files = True, ), "srcs": lambda: attrb.LabelList( - allow_files = [".py", ".py3"], + allow_files = [".py", ".py3", ".pyc"], # Necessary for --compile_one_dependency to work. flags = ["DIRECT_COMPILE_TIME_INPUT"], doc = """ diff --git a/python/private/common.bzl b/python/private/common.bzl index 7e8c6decf3..59de9bf813 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -204,7 +204,7 @@ def filter_to_py_srcs(srcs): # TODO(b/203567235): Get the set of recognized extensions from # elsewhere, as there may be others. e.g. Bazel recognizes .py3 # as a valid extension. - return [f for f in srcs if f.extension == "py"] + return [f for f in srcs if f.extension in ("py", "py3", "pyc")] def collect_cc_info(ctx, extra_deps = []): """Collect C++ information from dependencies for Bazel. diff --git a/python/private/precompile.bzl b/python/private/precompile.bzl index 898dc3ee2a..5e239505c2 100644 --- a/python/private/precompile.bzl +++ b/python/private/precompile.bzl @@ -106,6 +106,9 @@ def _precompile(ctx, src, *, use_pycache): if ctx.label.package != src.owner.package: return None + if src.is_directory or src.extension in ("pyc", "pyo"): + return None + exec_tools_info = ctx.toolchains[EXEC_TOOLS_TOOLCHAIN_TYPE].exec_tools target_toolchain = ctx.toolchains[TARGET_TOOLCHAIN_TYPE].py3_runtime diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index 3118d3c9a1..af55028ea1 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -130,7 +130,7 @@ def _validate_srcs(ctx): found_match = False for file in files: - if file.is_directory or file.extension in ("py", "py3"): + if file.is_directory or file.extension in ("py", "py3", "pyc"): found_match = True break From 43720bed7e6e2e4736cec55ccc11aae3ab2d8af8 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 11:36:03 -0700 Subject: [PATCH 03/11] fix(precompile): use is_py_source helper and allow arbitrary files in srcs Allow arbitrary files in the srcs attribute while filtering to Python source files via an is_py_source helper. Skip precompilation for directories or any file that is not a Python source file, and remove legacy py3 extension references. --- python/private/attributes.bzl | 9 +++++---- python/private/common.bzl | 12 ++++++------ python/private/precompile.bzl | 7 +++++-- python/private/py_library.bzl | 7 ++++--- tests/base_rules/precompile/precompile_tests.bzl | 1 - 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index 8c7bb73264..30cbb4557b 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -331,14 +331,15 @@ as part of a runnable program (packaging rules may include them, however). allow_files = True, ), "srcs": lambda: attrb.LabelList( - allow_files = [".py", ".py3", ".pyc"], + allow_files = True, # Necessary for --compile_one_dependency to work. flags = ["DIRECT_COMPILE_TIME_INPUT"], doc = """ The list of Python source files that are processed to create the target. This -includes all your checked-in code and may include generated source files. The -`.py` files belong in `srcs` and library targets belong in `deps`. Other binary -files that may be needed at run time belong in `data`. +includes all your checked-in code and may include generated source files. The +`.py` files, `.pyc` files, and directory artifacts belong in `srcs`. Library +targets belong in `deps`. Other binary files that may be needed at run time +belong in `data`. """, ), "srcs_version": lambda: attrb.String( diff --git a/python/private/common.bzl b/python/private/common.bzl index 59de9bf813..475958e3d8 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -198,13 +198,13 @@ def csv(values): """Convert a list of strings to comma separated value string.""" return ", ".join(sorted(values)) +def is_py_source(f): + """Whether the given file is considered a Python source file.""" + return f.extension == "py" + def filter_to_py_srcs(srcs): """Filters .py files from the given list of files""" - - # TODO(b/203567235): Get the set of recognized extensions from - # elsewhere, as there may be others. e.g. Bazel recognizes .py3 - # as a valid extension. - return [f for f in srcs if f.extension in ("py", "py3", "pyc")] + return [f for f in srcs if is_py_source(f)] def collect_cc_info(ctx, extra_deps = []): """Collect C++ information from dependencies for Bazel. @@ -398,7 +398,7 @@ def create_py_info( # longer supported in `deps`. files = target[DefaultInfo].files.to_list() for f in files: - if f.extension == "py": + if is_py_source(f): py_info.transitive_sources.add(f) py_info.merge_uses_shared_libraries(cc_helper.is_valid_shared_library_artifact(f)) for target in ctx.attr.pyi_deps: diff --git a/python/private/precompile.bzl b/python/private/precompile.bzl index 5e239505c2..4f6cda0867 100644 --- a/python/private/precompile.bzl +++ b/python/private/precompile.bzl @@ -15,7 +15,7 @@ load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load(":attributes.bzl", "PrecompileAttr", "PrecompileInvalidationModeAttr", "PrecompileSourceRetentionAttr") -load(":common.bzl", "actions_run") +load(":common.bzl", "actions_run", "is_py_source") load(":flags.bzl", "PrecompileFlag") load(":py_interpreter_program.bzl", "PyInterpreterProgramInfo") load(":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE") @@ -106,7 +106,10 @@ def _precompile(ctx, src, *, use_pycache): if ctx.label.package != src.owner.package: return None - if src.is_directory or src.extension in ("pyc", "pyo"): + if src.is_directory: + return None + + if not is_py_source(src): return None exec_tools_info = ctx.toolchains[EXEC_TOOLS_TOOLCHAIN_TYPE].exec_tools diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index af55028ea1..efdd68c417 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -35,6 +35,7 @@ load( "create_output_group_info", "create_py_info", "filter_to_py_srcs", + "is_py_source", ) load(":common_labels.bzl", "labels") load(":flags.bzl", "AddSrcsToRunfilesFlag", "PrecompileFlag", "VenvsSitePackages") @@ -130,7 +131,7 @@ def _validate_srcs(ctx): found_match = False for file in files: - if file.is_directory or file.extension in ("py", "py3", "pyc"): + if file.is_directory or is_py_source(file) or file.extension == "pyc": found_match = True break @@ -139,8 +140,8 @@ def _validate_srcs(ctx): fail( ("{} does not produce any py_library srcs files " + - "(expected .py or .py3) and is not an empty target providing " + - "PyInfo").format( + "(expected .py, .pyc, or directory) and is not an empty target " + + "providing PyInfo").format( target.label, ), attr = "srcs", diff --git a/tests/base_rules/precompile/precompile_tests.bzl b/tests/base_rules/precompile/precompile_tests.bzl index 63865a30e6..948cff008a 100644 --- a/tests/base_rules/precompile/precompile_tests.bzl +++ b/tests/base_rules/precompile/precompile_tests.bzl @@ -631,7 +631,6 @@ def _test_pyc_source_input_impl(env, target): target.default_outputs().contains_at_least_predicates([ matching.file_path_matches("__pycache__/lib.fakepy-45.pyc"), matching.file_path_matches("/lib.py"), - matching.file_path_matches("/" + env.ctx.label.name + "_foo.pyc"), ]) py_info = target.provider(PyInfo, factory = py_info_subject) py_info.direct_pyc_files().contains_exactly([ From a121c7175da111eea8bc8a66dd242a367f1cceda Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 19:55:01 -0700 Subject: [PATCH 04/11] fix(precompile): include pyc input sources in default outputs and format srcs doc Propagate input .pyc files through precompilation into target default outputs, and format the srcs attribute docstring with a list of allowed file types. --- python/private/attributes.bzl | 13 +++++++++---- python/private/py_executable.bzl | 10 ++++++++-- python/private/py_library.bzl | 9 +++++++-- tests/base_rules/precompile/precompile_tests.bzl | 1 + 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index 30cbb4557b..22be59fabe 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -336,10 +336,15 @@ as part of a runnable program (packaging rules may include them, however). flags = ["DIRECT_COMPILE_TIME_INPUT"], doc = """ The list of Python source files that are processed to create the target. This -includes all your checked-in code and may include generated source files. The -`.py` files, `.pyc` files, and directory artifacts belong in `srcs`. Library -targets belong in `deps`. Other binary files that may be needed at run time -belong in `data`. +includes all your checked-in code and may include generated source files. + +Allowed file types: +* `.py` +* `.pyc` +* directories + +Library targets belong in `deps`. Other binary files that may be needed at run +time belong in `data`. """, ), "srcs_version": lambda: attrb.String( diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 0e9c315a73..e3cdee05c7 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -52,6 +52,7 @@ load( "csv", "filter_to_py_srcs", "is_bool", + "is_py_source", "is_windows_platform", "maybe_create_repo_mapping", "relative_path", @@ -1199,9 +1200,14 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = # the original source. main_py_source = main_py direct_sources = filter_to_py_srcs(ctx.files.srcs) - precompile_result = maybe_precompile(ctx, direct_sources) + direct_py_and_pyc_srcs = [ + f + for f in ctx.files.srcs + if is_py_source(f) or f.extension == "pyc" + ] + precompile_result = maybe_precompile(ctx, direct_py_and_pyc_srcs) - required_py_files = precompile_result.keep_srcs + required_py_files = filter_to_py_srcs(precompile_result.keep_srcs) required_pyc_files = [] implicit_pyc_files = [] implicit_pyc_source_files = direct_sources diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index efdd68c417..8b49088b50 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -158,10 +158,15 @@ def py_library_impl(ctx): """ _validate_srcs(ctx) direct_sources = filter_to_py_srcs(ctx.files.srcs) + direct_py_and_pyc_srcs = [ + f + for f in ctx.files.srcs + if is_py_source(f) or f.extension == "pyc" + ] - precompile_result = maybe_precompile(ctx, direct_sources) + precompile_result = maybe_precompile(ctx, direct_py_and_pyc_srcs) - required_py_files = precompile_result.keep_srcs + required_py_files = filter_to_py_srcs(precompile_result.keep_srcs) required_pyc_files = [] implicit_pyc_files = [] implicit_pyc_source_files = direct_sources diff --git a/tests/base_rules/precompile/precompile_tests.bzl b/tests/base_rules/precompile/precompile_tests.bzl index 948cff008a..63865a30e6 100644 --- a/tests/base_rules/precompile/precompile_tests.bzl +++ b/tests/base_rules/precompile/precompile_tests.bzl @@ -631,6 +631,7 @@ def _test_pyc_source_input_impl(env, target): target.default_outputs().contains_at_least_predicates([ matching.file_path_matches("__pycache__/lib.fakepy-45.pyc"), matching.file_path_matches("/lib.py"), + matching.file_path_matches("/" + env.ctx.label.name + "_foo.pyc"), ]) py_info = target.provider(PyInfo, factory = py_info_subject) py_info.direct_pyc_files().contains_exactly([ From 828c904a76f9862e0c021b93b0ab108d6679cfd2 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 20:06:35 -0700 Subject: [PATCH 05/11] fix(precompile): pass direct sources containing py, pyc, and directories to maybe_precompile Compute direct_sources as the py, pyc, and directory files from srcs, pass direct_sources to maybe_precompile, and update tree artifact analysis test outputs. --- python/private/py_executable.bzl | 9 ++++----- python/private/py_library.bzl | 9 ++++----- tests/base_rules/py_library/py_library_tests.bzl | 4 +++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index e3cdee05c7..745e15688b 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1199,18 +1199,17 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = # precompiled pyc below) so the test-main validation can statically analyze # the original source. main_py_source = main_py - direct_sources = filter_to_py_srcs(ctx.files.srcs) - direct_py_and_pyc_srcs = [ + direct_sources = [ f for f in ctx.files.srcs - if is_py_source(f) or f.extension == "pyc" + if f.is_directory or is_py_source(f) or f.extension == "pyc" ] - precompile_result = maybe_precompile(ctx, direct_py_and_pyc_srcs) + precompile_result = maybe_precompile(ctx, direct_sources) required_py_files = filter_to_py_srcs(precompile_result.keep_srcs) required_pyc_files = [] implicit_pyc_files = [] - implicit_pyc_source_files = direct_sources + implicit_pyc_source_files = filter_to_py_srcs(direct_sources) if ctx.attr.precompile == PrecompileAttr.ENABLED: required_pyc_files.extend(precompile_result.pyc_files) diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index 8b49088b50..d59083803c 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -157,19 +157,18 @@ def py_library_impl(ctx): A list of modern providers to propagate. """ _validate_srcs(ctx) - direct_sources = filter_to_py_srcs(ctx.files.srcs) - direct_py_and_pyc_srcs = [ + direct_sources = [ f for f in ctx.files.srcs - if is_py_source(f) or f.extension == "pyc" + if f.is_directory or is_py_source(f) or f.extension == "pyc" ] - precompile_result = maybe_precompile(ctx, direct_py_and_pyc_srcs) + precompile_result = maybe_precompile(ctx, direct_sources) required_py_files = filter_to_py_srcs(precompile_result.keep_srcs) required_pyc_files = [] implicit_pyc_files = [] - implicit_pyc_source_files = direct_sources + implicit_pyc_source_files = filter_to_py_srcs(direct_sources) precompile_attr = ctx.attr.precompile precompile_flag = ctx.attr._precompile_flag[BuildSettingInfo].value diff --git a/tests/base_rules/py_library/py_library_tests.bzl b/tests/base_rules/py_library/py_library_tests.bzl index a3be7c4bcf..80e3e1ba22 100644 --- a/tests/base_rules/py_library/py_library_tests.bzl +++ b/tests/base_rules/py_library/py_library_tests.bzl @@ -134,7 +134,9 @@ def _test_srcs_can_contain_tree_artifact(name, config): ) def _test_srcs_can_contain_tree_artifact_impl(env, target): - env.expect.that_target(target).default_outputs().contains_exactly([]) + env.expect.that_target(target).default_outputs().contains_exactly([ + "{package}/{test_name}_tree.dir", + ]) _tests.append(_test_srcs_can_contain_tree_artifact) From 01ec36638475dd9ba27eeae0d0e13ea81de83192 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 20:28:29 -0700 Subject: [PATCH 06/11] fix(precompile): create filter_to_direct_sources helper and update precompile doc Factor out filter_to_direct_sources helper in common.bzl to share direct sources filtering across py_library and py_executable, and update _precompile return type docstring. --- python/private/common.bzl | 8 ++++++++ python/private/precompile.bzl | 2 +- python/private/py_executable.bzl | 8 ++------ python/private/py_library.bzl | 16 +++------------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/python/private/common.bzl b/python/private/common.bzl index 475958e3d8..5cff7f8723 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -206,6 +206,14 @@ def filter_to_py_srcs(srcs): """Filters .py files from the given list of files""" return [f for f in srcs if is_py_source(f)] +def filter_to_direct_sources(srcs): + """Filters Python sources, pyc files, and directory artifacts from srcs.""" + return [ + f + for f in srcs + if f.is_directory or is_py_source(f) or f.extension == "pyc" + ] + def collect_cc_info(ctx, extra_deps = []): """Collect C++ information from dependencies for Bazel. diff --git a/python/private/precompile.bzl b/python/private/precompile.bzl index 4f6cda0867..ceaa76cf88 100644 --- a/python/private/precompile.bzl +++ b/python/private/precompile.bzl @@ -98,7 +98,7 @@ def _precompile(ctx, src, *, use_pycache): file. Returns: - File of the generated pyc file. + File of the generated pyc file, or None if the source file was skipped. """ # Generating a file in another package is an error, so we have to skip diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 745e15688b..368a785686 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -50,9 +50,9 @@ load( "create_py_info", "create_windows_exe_launcher", "csv", + "filter_to_direct_sources", "filter_to_py_srcs", "is_bool", - "is_py_source", "is_windows_platform", "maybe_create_repo_mapping", "relative_path", @@ -1199,11 +1199,7 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = # precompiled pyc below) so the test-main validation can statically analyze # the original source. main_py_source = main_py - direct_sources = [ - f - for f in ctx.files.srcs - if f.is_directory or is_py_source(f) or f.extension == "pyc" - ] + direct_sources = filter_to_direct_sources(ctx.files.srcs) precompile_result = maybe_precompile(ctx, direct_sources) required_py_files = filter_to_py_srcs(precompile_result.keep_srcs) diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index d59083803c..47c4397411 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -34,8 +34,8 @@ load( "create_instrumented_files_info", "create_output_group_info", "create_py_info", + "filter_to_direct_sources", "filter_to_py_srcs", - "is_py_source", ) load(":common_labels.bzl", "labels") load(":flags.bzl", "AddSrcsToRunfilesFlag", "PrecompileFlag", "VenvsSitePackages") @@ -129,13 +129,7 @@ def _validate_srcs(ctx): ): continue - found_match = False - for file in files: - if file.is_directory or is_py_source(file) or file.extension == "pyc": - found_match = True - break - - if found_match: + if filter_to_direct_sources(files): continue fail( @@ -157,11 +151,7 @@ def py_library_impl(ctx): A list of modern providers to propagate. """ _validate_srcs(ctx) - direct_sources = [ - f - for f in ctx.files.srcs - if f.is_directory or is_py_source(f) or f.extension == "pyc" - ] + direct_sources = filter_to_direct_sources(ctx.files.srcs) precompile_result = maybe_precompile(ctx, direct_sources) From 1f5876810ed0c5ce2269a7d1c25608d62d19182a Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 20:44:33 -0700 Subject: [PATCH 07/11] docs(precompile): add news fragment, versionchanged doc, and missing bzl_library dep Add news fragment for skip precompile src errors fix, document allowed srcs file types with versionchanged in attributes.bzl, and add :common dependency to precompile bzl_library. --- news/skip_precompile_src_errors.fixed.md | 3 +++ python/private/BUILD.bazel | 1 + python/private/attributes.bzl | 4 ++++ 3 files changed, 8 insertions(+) create mode 100644 news/skip_precompile_src_errors.fixed.md diff --git a/news/skip_precompile_src_errors.fixed.md b/news/skip_precompile_src_errors.fixed.md new file mode 100644 index 0000000000..0369f98bdf --- /dev/null +++ b/news/skip_precompile_src_errors.fixed.md @@ -0,0 +1,3 @@ +(precompile) Fixed handling of directory and `.pyc` file inputs in `{obj}`srcs`` +when `{obj}`precompile`` is enabled on `{obj}`py_library``, `{obj}`py_binary``, +and `{obj}`py_test`` targets. diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index 0b0dc97f66..72bf7138f8 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -469,6 +469,7 @@ bzl_library( srcs = ["precompile.bzl"], deps = [ ":attributes", + ":common", ":flags", ":py_interpreter_program", ":toolchain_types", diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index 22be59fabe..46031e35bc 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -345,6 +345,10 @@ Allowed file types: Library targets belong in `deps`. Other binary files that may be needed at run time belong in `data`. + +:::{versionchanged} VERSION_NEXT_FEATURE +Allowed `.pyc` and directory inputs in `srcs`. +::: """, ), "srcs_version": lambda: attrb.String( From db7861042586e04060547a7ea6b18c2d2d593f42 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 23:30:41 -0700 Subject: [PATCH 08/11] docs(news): rename news fragment to 4113.fixed.md Associate news entry with newly created PR #4113. --- news/{skip_precompile_src_errors.fixed.md => 4113.fixed.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename news/{skip_precompile_src_errors.fixed.md => 4113.fixed.md} (100%) diff --git a/news/skip_precompile_src_errors.fixed.md b/news/4113.fixed.md similarity index 100% rename from news/skip_precompile_src_errors.fixed.md rename to news/4113.fixed.md From 59c04e52656952ec0f2b29000956f1b948498fad Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 23:36:46 -0700 Subject: [PATCH 09/11] docs(attributes): use VERSION_NEXT_PATCH for srcs versionchanged directive Update version directive in attributes.bzl from VERSION_NEXT_FEATURE to VERSION_NEXT_PATCH. --- python/private/attributes.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index 46031e35bc..d87def4f46 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -346,7 +346,7 @@ Allowed file types: Library targets belong in `deps`. Other binary files that may be needed at run time belong in `data`. -:::{versionchanged} VERSION_NEXT_FEATURE +:::{versionchanged} VERSION_NEXT_PATCH Allowed `.pyc` and directory inputs in `srcs`. ::: """, From 37801c3761c5a15eff9c094e3543a39e3bcc865b Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 24 Aug 2026 23:40:13 -0700 Subject: [PATCH 10/11] docs(news): fix {obj} syntax, line wrap, and append PR link to news entry Update news/4113.fixed.md to use valid Sphinx MyST {obj} syntax, wrap lines within 80 columns, and link to PR #4113. --- news/4113.fixed.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/news/4113.fixed.md b/news/4113.fixed.md index 0369f98bdf..e852e1ac96 100644 --- a/news/4113.fixed.md +++ b/news/4113.fixed.md @@ -1,3 +1,4 @@ -(precompile) Fixed handling of directory and `.pyc` file inputs in `{obj}`srcs`` -when `{obj}`precompile`` is enabled on `{obj}`py_library``, `{obj}`py_binary``, -and `{obj}`py_test`` targets. +(precompile) Fixed handling of directory and `.pyc` file inputs in +{obj}`srcs` when {obj}`precompile` is enabled on {obj}`py_library`, +{obj}`py_binary`, and {obj}`py_test` targets +([#4113](https://github.com/bazel-contrib/rules_python/pull/4113)). From 63b8cb65c66750e50ac23afec20fa3efc60c4da6 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Wed, 26 Aug 2026 00:40:57 -0700 Subject: [PATCH 11/11] refactor(py_library): remove redundant srcs attr config and consolidate doc Remove redundant srcs_attr.set_allow_files(True) from py_library_impl_builder and consolidate the 2.3.2 versionchanged doc into COMMON_ATTRS srcs in attributes.bzl. --- python/private/attributes.bzl | 5 +++++ python/private/py_library.bzl | 12 +----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index d87def4f46..5b842f85bc 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -346,6 +346,11 @@ Allowed file types: Library targets belong in `deps`. Other binary files that may be needed at run time belong in `data`. +:::{versionchanged} 2.3.2 +As an exception, empty targets in `srcs` that provide {obj}`PyInfo` are +allowed. Ordinary library dependencies should remain in `deps`. +::: + :::{versionchanged} VERSION_NEXT_PATCH Allowed `.pyc` and directory inputs in `srcs`. ::: diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index 47c4397411..6282b71ee1 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -309,7 +309,7 @@ def create_py_library_rule_builder(): {obj}`ruleb.Rule` with the necessary settings for creating a `py_library` rule. """ - builder = ruleb.Rule( + return ruleb.Rule( implementation = py_library_impl, doc = _DEFAULT_PY_LIBRARY_DOC, exec_groups = dict(REQUIRED_EXEC_GROUP_BUILDERS), @@ -321,13 +321,3 @@ def create_py_library_rule_builder(): ruleb.ToolchainType(EXEC_TOOLS_TOOLCHAIN_TYPE, mandatory = False), ], ) - srcs_attr = builder.attrs.get("srcs") - srcs_attr.set_allow_files(True) - srcs_attr.set_doc(srcs_attr.doc() + """ - -:::{versionchanged} 2.3.2 -As an exception, empty targets in `srcs` that provide {obj}`PyInfo` are -allowed. Ordinary library dependencies should remain in `deps`. -::: -""") - return builder