From b4160810d8baa56641672693c0dd244a0ed49365 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 11 Apr 2026 19:07:52 -0700 Subject: [PATCH 1/5] base code --- python/private/py_executable.bzl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 6c65bf8f59..344a5b1fcd 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1252,6 +1252,17 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = app_runfiles = exec_result.app_runfiles + providers = [] + + _add_provider_default_info(providers, ...) + _add_provider_instrumented_files_info(providers, ctx) + _add_provider_run_environment_info(providers, ctx) + _add_provider_py_executable_info(providers, ...) + _add_provider_py_runtime_info(providers, ...) + _add_provider_py_cc_link_params_info(providers, ...) + _add_provider_py_info(providers, ...) + _add_provider_output_group_info(providers, ...) + return _create_providers( ctx = ctx, executable = executable, From 41011bedc4c098549b87ccfc7e89780cd270fdaa Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 11 Apr 2026 19:22:22 -0700 Subject: [PATCH 2/5] impl ok --- python/private/py_executable.bzl | 227 ++++++++++++++----------------- 1 file changed, 99 insertions(+), 128 deletions(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 344a5b1fcd..97ceb5e6f1 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1254,38 +1254,44 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = providers = [] - _add_provider_default_info(providers, ...) - _add_provider_instrumented_files_info(providers, ctx) - _add_provider_run_environment_info(providers, ctx) - _add_provider_py_executable_info(providers, ...) - _add_provider_py_runtime_info(providers, ...) - _add_provider_py_cc_link_params_info(providers, ...) - _add_provider_py_info(providers, ...) - _add_provider_output_group_info(providers, ...) - - return _create_providers( - ctx = ctx, + _add_provider_default_info( + providers, + ctx, executable = executable, + default_outputs = default_outputs.build(), runfiles_details = runfiles_details, - main_py = main_py, - imports = imports, + ) + _add_provider_instrumented_files_info(providers, ctx) + _add_provider_run_environment_info(providers, ctx, inherited_environment) + _add_provider_py_executable_info( + providers, + app_runfiles = app_runfiles, + build_data_file = runfiles_details.build_data_file, + interpreter_args = ctx.attr.interpreter_args, + interpreter_path = runtime_details.executable_interpreter_path, + main = main_py, + runfiles_without_exe = runfiles_details.runfiles_without_exe, + stage2_bootstrap = exec_result.stage2_bootstrap, + venv_interpreter_runfiles = exec_result.venv_interpreter_runfiles, + venv_interpreter_symlinks = exec_result.venv_interpreter_symlinks, + venv_python_exe = exec_result.venv_python_exe, + ) + _add_provider_py_runtime_info(providers, runtime_details) + _add_provider_py_cc_link_params_info(providers, cc_details.cc_info_for_propagating) + py_info, builtin_py_info = _add_provider_py_info( + providers, + ctx = ctx, original_sources = direct_sources, required_py_files = required_py_files, required_pyc_files = required_pyc_files, implicit_pyc_files = implicit_pyc_files, implicit_pyc_source_files = implicit_pyc_source_files, - default_outputs = default_outputs.build(), - runtime_details = runtime_details, - cc_info = cc_details.cc_info_for_propagating, - inherited_environment = inherited_environment, - output_groups = exec_result.output_groups, - stage2_bootstrap = exec_result.stage2_bootstrap, - app_runfiles = app_runfiles, - venv_python_exe = exec_result.venv_python_exe, - venv_interpreter_runfiles = exec_result.venv_interpreter_runfiles, - venv_interpreter_symlinks = exec_result.venv_interpreter_symlinks, - interpreter_args = ctx.attr.interpreter_args, + imports = imports, ) + _add_provider_output_group_info(providers, py_info, exec_result.output_groups) + + return providers + def _get_build_info(ctx, cc_toolchain): build_info_files = py_internal.cc_toolchain_build_info_files(cc_toolchain) @@ -1810,98 +1816,66 @@ def _is_tool_config(ctx): # a more public API. Until that's available, py_internal to the rescue. return py_internal.is_tool_configuration(ctx) -def _create_providers( +def _add_provider_default_info(providers, ctx, *, executable, default_outputs, runfiles_details): + providers.append(DefaultInfo( + executable = executable, + files = default_outputs, + default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( + ctx, + runfiles_details.default_runfiles, + ), + data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( + ctx, + runfiles_details.data_runfiles, + ), + )) + +def _add_provider_instrumented_files_info(providers, ctx): + providers.append(create_instrumented_files_info(ctx)) + +def _add_provider_run_environment_info(providers, ctx, inherited_environment): + expanded_env = {} + for key, value in ctx.attr.env.items(): + expanded_env[key] = _py_builtins.expand_location_and_make_variables( + ctx = ctx, + attribute_name = "env[{}]".format(key), + expression = value, + targets = ctx.attr.data, + ) + if "PYTHONBREAKPOINT" not in inherited_environment: + inherited_environment = inherited_environment + ["PYTHONBREAKPOINT"] + providers.append(RunEnvironmentInfo( + environment = expanded_env, + inherited_environment = inherited_environment, + )) + +def _add_provider_py_executable_info( + providers, *, - ctx, - executable, - main_py, - original_sources, - required_py_files, - required_pyc_files, - implicit_pyc_files, - implicit_pyc_source_files, - default_outputs, - runfiles_details, - imports, - cc_info, - inherited_environment, - runtime_details, - output_groups, - stage2_bootstrap, app_runfiles, - venv_python_exe, + build_data_file, + interpreter_args, + interpreter_path, + main, + runfiles_without_exe, + stage2_bootstrap, venv_interpreter_runfiles, venv_interpreter_symlinks, - interpreter_args): - """Creates the providers an executable should return. - - Args: - ctx: The rule ctx. - executable: File; the target's executable file. - main_py: File; the main .py entry point. - original_sources: `depset[File]` the direct `.py` sources for the - target that were the original input sources. - required_py_files: `depset[File]` the direct, `.py` sources for the - target that **must** be included by downstream targets. This should - only be Python source files. It should not include pyc files. - required_pyc_files: `depset[File]` the direct `.pyc` files this target - produces. - implicit_pyc_files: `depset[File]` pyc files that are only used if pyc - collection is enabled. - implicit_pyc_source_files: `depset[File]` source files for implicit pyc - files that are used when the implicit pyc files are not. - default_outputs: depset of Files; the files for DefaultInfo.files - runfiles_details: runfiles that will become the default and data runfiles. - imports: depset of strings; the import paths to propagate - cc_info: optional CcInfo; Linking information to propagate as - PyCcLinkParamsInfo. Note that only the linking information - is propagated, not the whole CcInfo. - inherited_environment: list of strings; Environment variable names - that should be inherited from the environment the executuble - is run within. - runtime_details: struct of runtime information; see _get_runtime_details() - output_groups: dict[str, depset[File]]; used to create OutputGroupInfo - stage2_bootstrap: File; the stage 2 bootstrap script. - app_runfiles: runfiles; the runfiles for the application (deps, etc). - venv_python_exe: File; the python executable in the venv. - venv_interpreter_runfiles: runfiles; runfiles specific to the interpreter - for the venv. - venv_interpreter_symlinks: depset[ExplicitSymlink]; interpreter-specific symlinks - to create for the venv. - interpreter_args: list of strings; arguments to pass to the interpreter. - - Returns: - A list of modern providers. - """ - providers = [ - DefaultInfo( - executable = executable, - files = default_outputs, - default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( - ctx, - runfiles_details.default_runfiles, - ), - data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( - ctx, - runfiles_details.data_runfiles, - ), - ), - create_instrumented_files_info(ctx), - _create_run_environment_info(ctx, inherited_environment), - PyExecutableInfo( - app_runfiles = app_runfiles, - build_data_file = runfiles_details.build_data_file, - interpreter_args = interpreter_args, - interpreter_path = runtime_details.executable_interpreter_path, - main = main_py, - runfiles_without_exe = runfiles_details.runfiles_without_exe, - stage2_bootstrap = stage2_bootstrap, - venv_interpreter_runfiles = venv_interpreter_runfiles, - venv_interpreter_symlinks = venv_interpreter_symlinks, - venv_python_exe = venv_python_exe, - ), - ] + venv_python_exe): + providers.append(PyExecutableInfo( + app_runfiles = app_runfiles, + build_data_file = build_data_file, + interpreter_args = interpreter_args, + interpreter_path = interpreter_path, + main = main, + runfiles_without_exe = runfiles_without_exe, + stage2_bootstrap = stage2_bootstrap, + venv_interpreter_runfiles = venv_interpreter_runfiles, + venv_interpreter_symlinks = venv_interpreter_symlinks, + venv_python_exe = venv_python_exe, + )) +def _add_provider_py_runtime_info(providers, runtime_details): # TODO - The effective runtime can be None for Windows + auto detecting toolchain. # This can be removed once that's fixed; see maybe_get_runtime_from_ctx(). if runtime_details.effective_runtime: @@ -1928,6 +1902,7 @@ def _create_providers( bootstrap_template = py_runtime_info.bootstrap_template, )) +def _add_provider_py_cc_link_params_info(providers, cc_info): # TODO(b/163083591): Remove the PyCcLinkParamsInfo once binaries-in-deps # are cleaned up. if cc_info: @@ -1935,6 +1910,16 @@ def _create_providers( PyCcLinkParamsInfo(cc_info = cc_info), ) +def _add_provider_py_info( + providers, + *, + ctx, + original_sources, + required_py_files, + required_pyc_files, + implicit_pyc_files, + implicit_pyc_source_files, + imports): py_info, builtin_py_info = create_py_info( ctx, original_sources = original_sources, @@ -1944,28 +1929,14 @@ def _create_providers( implicit_pyc_source_files = implicit_pyc_source_files, imports = imports, ) - providers.append(py_info) if builtin_py_info: providers.append(builtin_py_info) + return py_info, builtin_py_info + +def _add_provider_output_group_info(providers, py_info, output_groups): providers.append(create_output_group_info(py_info.transitive_sources, output_groups)) - return providers -def _create_run_environment_info(ctx, inherited_environment): - expanded_env = {} - for key, value in ctx.attr.env.items(): - expanded_env[key] = _py_builtins.expand_location_and_make_variables( - ctx = ctx, - attribute_name = "env[{}]".format(key), - expression = value, - targets = ctx.attr.data, - ) - if "PYTHONBREAKPOINT" not in inherited_environment: - inherited_environment = inherited_environment + ["PYTHONBREAKPOINT"] - return RunEnvironmentInfo( - environment = expanded_env, - inherited_environment = inherited_environment, - ) def _add_config_setting_defaults(kwargs): config_settings = kwargs.get("config_settings", None) From c65b3dd3e3f2b4fea0a395ea83a8da15f533c927 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 11 Apr 2026 19:27:59 -0700 Subject: [PATCH 3/5] add docs --- python/private/py_executable.bzl | 81 ++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 97ceb5e6f1..97da7fc52d 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1817,6 +1817,15 @@ def _is_tool_config(ctx): return py_internal.is_tool_configuration(ctx) def _add_provider_default_info(providers, ctx, *, executable, default_outputs, runfiles_details): + """Adds the DefaultInfo provider. + + Args: + providers: list of providers to append to. + ctx: The rule ctx. + executable: File; the target's executable file. + default_outputs: depset of Files; the files for DefaultInfo.files + runfiles_details: runfiles that will become the default and data runfiles. + """ providers.append(DefaultInfo( executable = executable, files = default_outputs, @@ -1831,9 +1840,24 @@ def _add_provider_default_info(providers, ctx, *, executable, default_outputs, r )) def _add_provider_instrumented_files_info(providers, ctx): + """Adds the InstrumentedFilesInfo provider. + + Args: + providers: list of providers to append to. + ctx: The rule ctx. + """ providers.append(create_instrumented_files_info(ctx)) def _add_provider_run_environment_info(providers, ctx, inherited_environment): + """Adds the RunEnvironmentInfo provider. + + Args: + providers: list of providers to append to. + ctx: The rule ctx. + inherited_environment: list of strings; Environment variable names + that should be inherited from the environment the executuble + is run within. + """ expanded_env = {} for key, value in ctx.attr.env.items(): expanded_env[key] = _py_builtins.expand_location_and_make_variables( @@ -1862,6 +1886,21 @@ def _add_provider_py_executable_info( venv_interpreter_runfiles, venv_interpreter_symlinks, venv_python_exe): + """Adds the PyExecutableInfo provider. + + Args: + providers: list of providers to append to. + app_runfiles: runfiles; the runfiles for the application (deps, etc). + build_data_file: File; a file with build stamp information. + interpreter_args: list of strings; arguments to pass to the interpreter. + interpreter_path: str; path to the Python interpreter. + main: File; the main .py entry point. + runfiles_without_exe: runfiles; the default runfiles, but without the executable. + stage2_bootstrap: File; the stage 2 bootstrap script. + venv_interpreter_runfiles: runfiles; runfiles specific to the interpreter for the venv. + venv_interpreter_symlinks: depset[ExplicitSymlink]; interpreter-specific symlinks to create for the venv. + venv_python_exe: File; the python executable in the venv. + """ providers.append(PyExecutableInfo( app_runfiles = app_runfiles, build_data_file = build_data_file, @@ -1876,6 +1915,12 @@ def _add_provider_py_executable_info( )) def _add_provider_py_runtime_info(providers, runtime_details): + """Adds the PyRuntimeInfo provider. + + Args: + providers: list of providers to append to. + runtime_details: struct of runtime information; see _get_runtime_details() + """ # TODO - The effective runtime can be None for Windows + auto detecting toolchain. # This can be removed once that's fixed; see maybe_get_runtime_from_ctx(). if runtime_details.effective_runtime: @@ -1903,6 +1948,14 @@ def _add_provider_py_runtime_info(providers, runtime_details): )) def _add_provider_py_cc_link_params_info(providers, cc_info): + """Adds the PyCcLinkParamsInfo provider. + + Args: + providers: list of providers to append to. + cc_info: optional CcInfo; Linking information to propagate as + PyCcLinkParamsInfo. Note that only the linking information + is propagated, not the whole CcInfo. + """ # TODO(b/163083591): Remove the PyCcLinkParamsInfo once binaries-in-deps # are cleaned up. if cc_info: @@ -1920,6 +1973,27 @@ def _add_provider_py_info( implicit_pyc_files, implicit_pyc_source_files, imports): + """Adds the PyInfo provider. + + Args: + providers: list of providers to append to. + ctx: The rule ctx. + original_sources: `depset[File]` the direct `.py` sources for the + target that were the original input sources. + required_py_files: `depset[File]` the direct, `.py` sources for the + target that **must** be included by downstream targets. This should + only be Python source files. It should not include pyc files. + required_pyc_files: `depset[File]` the direct `.pyc` files this target + produces. + implicit_pyc_files: `depset[File]` pyc files that are only used if pyc + collection is enabled. + implicit_pyc_source_files: `depset[File]` source files for implicit pyc + files that are used when the implicit pyc files are not. + imports: depset of strings; the import paths to propagate + + Returns: + tuple of (PyInfo, BuiltinPyInfo|None). + """ py_info, builtin_py_info = create_py_info( ctx, original_sources = original_sources, @@ -1935,6 +2009,13 @@ def _add_provider_py_info( return py_info, builtin_py_info def _add_provider_output_group_info(providers, py_info, output_groups): + """Adds the OutputGroupInfo provider. + + Args: + providers: list of providers to append to. + py_info: PyInfo; the PyInfo provider. + output_groups: dict[str, depset[File]]; used to create OutputGroupInfo + """ providers.append(create_output_group_info(py_info.transitive_sources, output_groups)) From 78652c47c34fec58a04a1977f9d6acd110753eea Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 11 Apr 2026 19:34:20 -0700 Subject: [PATCH 4/5] rename main_py arg --- python/private/py_executable.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 97da7fc52d..7c6b62dfde 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1269,7 +1269,7 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = build_data_file = runfiles_details.build_data_file, interpreter_args = ctx.attr.interpreter_args, interpreter_path = runtime_details.executable_interpreter_path, - main = main_py, + main_py = main_py, runfiles_without_exe = runfiles_details.runfiles_without_exe, stage2_bootstrap = exec_result.stage2_bootstrap, venv_interpreter_runfiles = exec_result.venv_interpreter_runfiles, @@ -1880,7 +1880,7 @@ def _add_provider_py_executable_info( build_data_file, interpreter_args, interpreter_path, - main, + main_py, runfiles_without_exe, stage2_bootstrap, venv_interpreter_runfiles, @@ -1894,7 +1894,7 @@ def _add_provider_py_executable_info( build_data_file: File; a file with build stamp information. interpreter_args: list of strings; arguments to pass to the interpreter. interpreter_path: str; path to the Python interpreter. - main: File; the main .py entry point. + main_py: File; the main .py entry point. runfiles_without_exe: runfiles; the default runfiles, but without the executable. stage2_bootstrap: File; the stage 2 bootstrap script. venv_interpreter_runfiles: runfiles; runfiles specific to the interpreter for the venv. @@ -1906,7 +1906,7 @@ def _add_provider_py_executable_info( build_data_file = build_data_file, interpreter_args = interpreter_args, interpreter_path = interpreter_path, - main = main, + main = main_py, runfiles_without_exe = runfiles_without_exe, stage2_bootstrap = stage2_bootstrap, venv_interpreter_runfiles = venv_interpreter_runfiles, From 01b08a600715a4a5a71b272dbd35d2e274fd35a9 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 11 Apr 2026 19:34:32 -0700 Subject: [PATCH 5/5] format --- python/private/py_executable.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 7c6b62dfde..6d6b23011a 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1292,7 +1292,6 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = return providers - def _get_build_info(ctx, cc_toolchain): build_info_files = py_internal.cc_toolchain_build_info_files(cc_toolchain) if cc_helper.is_stamping_enabled(ctx): @@ -1921,6 +1920,7 @@ def _add_provider_py_runtime_info(providers, runtime_details): providers: list of providers to append to. runtime_details: struct of runtime information; see _get_runtime_details() """ + # TODO - The effective runtime can be None for Windows + auto detecting toolchain. # This can be removed once that's fixed; see maybe_get_runtime_from_ctx(). if runtime_details.effective_runtime: @@ -1956,6 +1956,7 @@ def _add_provider_py_cc_link_params_info(providers, cc_info): PyCcLinkParamsInfo. Note that only the linking information is propagated, not the whole CcInfo. """ + # TODO(b/163083591): Remove the PyCcLinkParamsInfo once binaries-in-deps # are cleaned up. if cc_info: @@ -2018,7 +2019,6 @@ def _add_provider_output_group_info(providers, py_info, output_groups): """ providers.append(create_output_group_info(py_info.transitive_sources, output_groups)) - def _add_config_setting_defaults(kwargs): config_settings = kwargs.get("config_settings", None) if config_settings == None: