refactor: consolidate py_executable_bazel, common_bazel (#2523)

This furthers the work of removing the artificial split of code that
stemmed from
when the implementation was part of Bazel itself. Summary of changes:

* Move most of `py_executable_bazel.bzl` into `py_executable.bzl`
* Move most of `common_bazel.bzl` into `common.bzl`
* Create `precompile.bzl` for the precompile helpers. This is to avoid a
  circular dependency between common.bzl and attributes.bzl.

Work towards https://github.com/bazelbuild/rules_python/issues/2522
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
index 76e3a78..706506a 100644
--- a/python/private/BUILD.bazel
+++ b/python/private/BUILD.bazel
@@ -105,29 +105,17 @@
 )
 
 bzl_library(
-    name = "common_bazel_bzl",
-    srcs = ["common_bazel.bzl"],
-    deps = [
-        ":attributes_bzl",
-        ":common_bzl",
-        ":py_cc_link_params_info_bzl",
-        ":py_internal_bzl",
-        ":py_interpreter_program_bzl",
-        ":toolchain_types_bzl",
-        "@bazel_skylib//lib:paths",
-    ],
-)
-
-bzl_library(
     name = "common_bzl",
     srcs = ["common.bzl"],
     deps = [
         ":cc_helper_bzl",
+        ":py_cc_link_params_info_bzl",
         ":py_info_bzl",
         ":py_internal_bzl",
         ":reexports_bzl",
         ":rules_cc_srcs_bzl",
         ":semantics_bzl",
+        "@bazel_skylib//lib:paths",
     ],
 )
 
@@ -200,6 +188,18 @@
 )
 
 bzl_library(
+    name = "precompile_bzl",
+    srcs = ["precompile.bzl"],
+    deps = [
+        ":attributes_bzl",
+        ":py_internal_bzl",
+        ":py_interpreter_program_bzl",
+        ":toolchain_types_bzl",
+        "@bazel_skylib//lib:paths",
+    ],
+)
+
+bzl_library(
     name = "python_bzl",
     srcs = ["python.bzl"],
     deps = [
@@ -265,8 +265,8 @@
     name = "py_binary_macro_bzl",
     srcs = ["py_binary_macro.bzl"],
     deps = [
-        ":common_bzl",
         ":py_binary_rule_bzl",
+        ":py_executable_bzl",
     ],
 )
 
@@ -275,7 +275,7 @@
     srcs = ["py_binary_rule.bzl"],
     deps = [
         ":attributes_bzl",
-        ":py_executable_bazel_bzl",
+        ":py_executable_bzl",
         ":semantics_bzl",
         "@bazel_skylib//lib:dicts",
     ],
@@ -344,20 +344,6 @@
 )
 
 bzl_library(
-    name = "py_executable_bazel_bzl",
-    srcs = ["py_executable_bazel.bzl"],
-    deps = [
-        ":attributes_bzl",
-        ":common_bazel_bzl",
-        ":common_bzl",
-        ":py_executable_bzl",
-        ":py_internal_bzl",
-        ":py_runtime_info_bzl",
-        ":semantics_bzl",
-    ],
-)
-
-bzl_library(
     name = "py_executable_bzl",
     srcs = ["py_executable.bzl"],
     deps = [
@@ -365,6 +351,7 @@
         ":cc_helper_bzl",
         ":common_bzl",
         ":flags_bzl",
+        ":precompile_bzl",
         ":py_cc_link_params_info_bzl",
         ":py_executable_info_bzl",
         ":py_info_bzl",
@@ -373,6 +360,7 @@
         ":rules_cc_srcs_bzl",
         ":toolchain_types_bzl",
         "@bazel_skylib//lib:dicts",
+        "@bazel_skylib//lib:paths",
         "@bazel_skylib//lib:structs",
         "@bazel_skylib//rules:common_settings",
     ],
@@ -431,8 +419,8 @@
     name = "py_library_rule_bzl",
     srcs = ["py_library_rule.bzl"],
     deps = [
-        ":common_bazel_bzl",
         ":common_bzl",
+        ":precompile_bzl",
         ":py_library_bzl",
     ],
 )
@@ -508,7 +496,7 @@
     name = "py_test_macro_bzl",
     srcs = ["py_test_macro.bzl"],
     deps = [
-        ":common_bazel_bzl",
+        ":py_executable_bzl",
         ":py_test_rule_bzl",
     ],
 )
@@ -519,7 +507,7 @@
     deps = [
         ":attributes_bzl",
         ":common_bzl",
-        ":py_executable_bazel_bzl",
+        ":py_executable_bzl",
         ":semantics_bzl",
         "@bazel_skylib//lib:dicts",
     ],
diff --git a/python/private/common.bzl b/python/private/common.bzl
index 97fabce..9c285f9 100644
--- a/python/private/common.bzl
+++ b/python/private/common.bzl
@@ -11,9 +11,13 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-"""Various things common to Bazel and Google rule implementations."""
+"""Various things common to rule implementations."""
 
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
 load(":cc_helper.bzl", "cc_helper")
+load(":py_cc_link_params_info.bzl", "PyCcLinkParamsInfo")
 load(":py_info.bzl", "PyInfo", "PyInfoBuilder")
 load(":py_internal.bzl", "py_internal")
 load(":reexports.bzl", "BuiltinPyInfo")
@@ -262,6 +266,30 @@
     # as a valid extension.
     return [f for f in srcs if f.extension == "py"]
 
+def collect_cc_info(ctx, extra_deps = []):
+    """Collect C++ information from dependencies for Bazel.
+
+    Args:
+        ctx: Rule ctx; must have `deps` attribute.
+        extra_deps: list of Target to also collect C+ information from.
+
+    Returns:
+        CcInfo provider of merged information.
+    """
+    deps = ctx.attr.deps
+    if extra_deps:
+        deps = list(deps)
+        deps.extend(extra_deps)
+    cc_infos = []
+    for dep in deps:
+        if CcInfo in dep:
+            cc_infos.append(dep[CcInfo])
+
+        if PyCcLinkParamsInfo in dep:
+            cc_infos.append(dep[PyCcLinkParamsInfo].cc_info)
+
+    return cc_common.merge_cc_infos(cc_infos = cc_infos)
+
 def collect_imports(ctx, semantics):
     """Collect the direct and transitive `imports` strings.
 
@@ -280,6 +308,37 @@
             transitive.append(dep[BuiltinPyInfo].imports)
     return depset(direct = semantics.get_imports(ctx), transitive = transitive)
 
+def get_imports(ctx):
+    """Gets the imports from a rule's `imports` attribute.
+
+    See create_binary_semantics_struct for details about this function.
+
+    Args:
+        ctx: Rule ctx.
+
+    Returns:
+        List of strings.
+    """
+    prefix = "{}/{}".format(
+        ctx.workspace_name,
+        py_internal.get_label_repo_runfiles_path(ctx.label),
+    )
+    result = []
+    for import_str in ctx.attr.imports:
+        import_str = ctx.expand_make_variables("imports", import_str, {})
+        if import_str.startswith("/"):
+            continue
+
+        # To prevent "escaping" out of the runfiles tree, we normalize
+        # the path and ensure it doesn't have up-level references.
+        import_path = paths.normalize("{}/{}".format(prefix, import_str))
+        if import_path.startswith("../") or import_path == "..":
+            fail("Path '{}' references a path above the execution root".format(
+                import_str,
+            ))
+        result.append(import_path)
+    return result
+
 def collect_runfiles(ctx, files = depset()):
     """Collects the necessary files from the rule's context.
 
diff --git a/python/private/common_bazel.bzl b/python/private/precompile.bzl
similarity index 77%
rename from python/private/common_bazel.bzl
rename to python/private/precompile.bzl
index efbebd0..23e8f81 100644
--- a/python/private/common_bazel.bzl
+++ b/python/private/precompile.bzl
@@ -13,44 +13,12 @@
 # limitations under the License.
 """Common functions that are specific to Bazel rule implementation"""
 
-load("@bazel_skylib//lib:paths.bzl", "paths")
 load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
-load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
-load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
 load(":attributes.bzl", "PrecompileAttr", "PrecompileInvalidationModeAttr", "PrecompileSourceRetentionAttr")
-load(":common.bzl", "is_bool")
 load(":flags.bzl", "PrecompileFlag")
-load(":py_cc_link_params_info.bzl", "PyCcLinkParamsInfo")
-load(":py_internal.bzl", "py_internal")
 load(":py_interpreter_program.bzl", "PyInterpreterProgramInfo")
 load(":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE")
 
-_py_builtins = py_internal
-
-def collect_cc_info(ctx, extra_deps = []):
-    """Collect C++ information from dependencies for Bazel.
-
-    Args:
-        ctx: Rule ctx; must have `deps` attribute.
-        extra_deps: list of Target to also collect C+ information from.
-
-    Returns:
-        CcInfo provider of merged information.
-    """
-    deps = ctx.attr.deps
-    if extra_deps:
-        deps = list(deps)
-        deps.extend(extra_deps)
-    cc_infos = []
-    for dep in deps:
-        if CcInfo in dep:
-            cc_infos.append(dep[CcInfo])
-
-        if PyCcLinkParamsInfo in dep:
-            cc_infos.append(dep[PyCcLinkParamsInfo].cc_info)
-
-    return cc_common.merge_cc_infos(cc_infos = cc_infos)
-
 def maybe_precompile(ctx, srcs):
     """Computes all the outputs (maybe precompiled) from the input srcs.
 
@@ -237,44 +205,3 @@
         toolchain = EXEC_TOOLS_TOOLCHAIN_TYPE,
     )
     return pyc
-
-def get_imports(ctx):
-    """Gets the imports from a rule's `imports` attribute.
-
-    See create_binary_semantics_struct for details about this function.
-
-    Args:
-        ctx: Rule ctx.
-
-    Returns:
-        List of strings.
-    """
-    prefix = "{}/{}".format(
-        ctx.workspace_name,
-        _py_builtins.get_label_repo_runfiles_path(ctx.label),
-    )
-    result = []
-    for import_str in ctx.attr.imports:
-        import_str = ctx.expand_make_variables("imports", import_str, {})
-        if import_str.startswith("/"):
-            continue
-
-        # To prevent "escaping" out of the runfiles tree, we normalize
-        # the path and ensure it doesn't have up-level references.
-        import_path = paths.normalize("{}/{}".format(prefix, import_str))
-        if import_path.startswith("../") or import_path == "..":
-            fail("Path '{}' references a path above the execution root".format(
-                import_str,
-            ))
-        result.append(import_path)
-    return result
-
-def convert_legacy_create_init_to_int(kwargs):
-    """Convert "legacy_create_init" key to int, in-place.
-
-    Args:
-        kwargs: The kwargs to modify. The key "legacy_create_init", if present
-            and bool, will be converted to its integer value, in place.
-    """
-    if is_bool(kwargs.get("legacy_create_init")):
-        kwargs["legacy_create_init"] = 1 if kwargs["legacy_create_init"] else 0
diff --git a/python/private/py_binary_macro.bzl b/python/private/py_binary_macro.bzl
index 83b3c18..d1269f2 100644
--- a/python/private/py_binary_macro.bzl
+++ b/python/private/py_binary_macro.bzl
@@ -13,8 +13,8 @@
 # limitations under the License.
 """Implementation of macro-half of py_binary rule."""
 
-load(":common_bazel.bzl", "convert_legacy_create_init_to_int")
 load(":py_binary_rule.bzl", py_binary_rule = "py_binary")
+load(":py_executable.bzl", "convert_legacy_create_init_to_int")
 
 def py_binary(**kwargs):
     convert_legacy_create_init_to_int(kwargs)
diff --git a/python/private/py_binary_rule.bzl b/python/private/py_binary_rule.bzl
index 9ce0726..f1c8eb1 100644
--- a/python/private/py_binary_rule.bzl
+++ b/python/private/py_binary_rule.bzl
@@ -16,9 +16,9 @@
 load("@bazel_skylib//lib:dicts.bzl", "dicts")
 load(":attributes.bzl", "AGNOSTIC_BINARY_ATTRS")
 load(
-    ":py_executable_bazel.bzl",
+    ":py_executable.bzl",
     "create_executable_rule",
-    "py_executable_bazel_impl",
+    "py_executable_impl",
 )
 
 _PY_TEST_ATTRS = {
@@ -39,7 +39,7 @@
 }
 
 def _py_binary_impl(ctx):
-    return py_executable_bazel_impl(
+    return py_executable_impl(
         ctx = ctx,
         is_test = False,
         inherited_environment = [],
diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl
index 8c0487d..40c7410 100644
--- a/python/private/py_executable.bzl
+++ b/python/private/py_executable.bzl
@@ -14,6 +14,7 @@
 """Common functionality between test/binary executables."""
 
 load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load("@bazel_skylib//lib:paths.bzl", "paths")
 load("@bazel_skylib//lib:structs.bzl", "structs")
 load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
 load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
@@ -21,6 +22,7 @@
     ":attributes.bzl",
     "AGNOSTIC_EXECUTABLE_ATTRS",
     "COMMON_ATTRS",
+    "IMPORTS_ATTRS",
     "PY_SRCS_ATTRS",
     "PrecompileAttr",
     "PycCollectionAttr",
@@ -33,21 +35,29 @@
 load(":cc_helper.bzl", "cc_helper")
 load(
     ":common.bzl",
+    "collect_cc_info",
     "collect_imports",
     "collect_runfiles",
+    "create_binary_semantics_struct",
+    "create_cc_details_struct",
+    "create_executable_result_struct",
     "create_instrumented_files_info",
     "create_output_group_info",
     "create_py_info",
     "csv",
     "filter_to_py_srcs",
+    "get_imports",
+    "is_bool",
     "target_platform_has_any_constraint",
     "union_attrs",
 )
+load(":flags.bzl", "BootstrapImplFlag")
+load(":precompile.bzl", "maybe_precompile")
 load(":py_cc_link_params_info.bzl", "PyCcLinkParamsInfo")
 load(":py_executable_info.bzl", "PyExecutableInfo")
 load(":py_info.bzl", "PyInfo")
 load(":py_internal.bzl", "py_internal")
-load(":py_runtime_info.bzl", "PyRuntimeInfo")
+load(":py_runtime_info.bzl", "DEFAULT_STUB_SHEBANG", "PyRuntimeInfo")
 load(":reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo")
 load(
     ":semantics.bzl",
@@ -59,10 +69,13 @@
 load(
     ":toolchain_types.bzl",
     "EXEC_TOOLS_TOOLCHAIN_TYPE",
+    "TARGET_TOOLCHAIN_TYPE",
     TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE",
 )
 
 _py_builtins = py_internal
+_EXTERNAL_PATH_PREFIX = "external"
+_ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
 
 # Bazel 5.4 doesn't have config_common.toolchain_type
 _CC_TOOLCHAINS = [config_common.toolchain_type(
@@ -76,7 +89,21 @@
     COMMON_ATTRS,
     AGNOSTIC_EXECUTABLE_ATTRS,
     PY_SRCS_ATTRS,
+    IMPORTS_ATTRS,
     {
+        "legacy_create_init": attr.int(
+            default = -1,
+            values = [-1, 0, 1],
+            doc = """\
+Whether to implicitly create empty `__init__.py` files in the runfiles tree.
+These are created in every directory containing Python source code or shared
+libraries, and every parent directory of those directories, excluding the repo
+root directory. The default, `-1` (auto), means true unless
+`--incompatible_default_to_explicit_init_py` is used. If false, the user is
+responsible for creating (possibly empty) `__init__.py` files and adding them to
+the `srcs` of Python targets as required.
+                                       """,
+        ),
         # TODO(b/203567235): In the Java impl, any file is allowed. While marked
         # label, it is more treated as a string, and doesn't have to refer to
         # anything that exists because it gets treated as suffix-search string
@@ -120,17 +147,732 @@
             default = "//python/config_settings:bootstrap_impl",
             providers = [BuildSettingInfo],
         ),
+        "_bootstrap_template": attr.label(
+            allow_single_file = True,
+            default = "@bazel_tools//tools/python:python_bootstrap_template.txt",
+        ),
+        "_launcher": attr.label(
+            cfg = "target",
+            # NOTE: This is an executable, but is only used for Windows. It
+            # can't have executable=True because the backing target is an
+            # empty target for other platforms.
+            default = "//tools/launcher:launcher",
+        ),
+        "_py_interpreter": attr.label(
+            # The configuration_field args are validated when called;
+            # we use the precense of py_internal to indicate this Bazel
+            # build has that fragment and name.
+            default = configuration_field(
+                fragment = "bazel_py",
+                name = "python_top",
+            ) if py_internal else None,
+        ),
+        # TODO: This appears to be vestigial. It's only added because
+        # GraphlessQueryTest.testLabelsOperator relies on it to test for
+        # query behavior of implicit dependencies.
+        "_py_toolchain_type": attr.label(
+            default = TARGET_TOOLCHAIN_TYPE,
+        ),
+        "_python_version_flag": attr.label(
+            default = "//python/config_settings:python_version",
+        ),
         "_windows_constraints": attr.label_list(
             default = [
                 "@platforms//os:windows",
             ],
         ),
+        "_windows_launcher_maker": attr.label(
+            default = "@bazel_tools//tools/launcher:launcher_maker",
+            cfg = "exec",
+            executable = True,
+        ),
+        "_zipper": attr.label(
+            cfg = "exec",
+            executable = True,
+            default = "@bazel_tools//tools/zip:zipper",
+        ),
     },
     create_srcs_version_attr(values = SRCS_VERSION_ALL_VALUES),
     create_srcs_attr(mandatory = True),
     allow_none = True,
 )
 
+def convert_legacy_create_init_to_int(kwargs):
+    """Convert "legacy_create_init" key to int, in-place.
+
+    Args:
+        kwargs: The kwargs to modify. The key "legacy_create_init", if present
+            and bool, will be converted to its integer value, in place.
+    """
+    if is_bool(kwargs.get("legacy_create_init")):
+        kwargs["legacy_create_init"] = 1 if kwargs["legacy_create_init"] else 0
+
+def py_executable_impl(ctx, *, is_test, inherited_environment):
+    return py_executable_base_impl(
+        ctx = ctx,
+        semantics = create_binary_semantics(),
+        is_test = is_test,
+        inherited_environment = inherited_environment,
+    )
+
+def create_binary_semantics():
+    return create_binary_semantics_struct(
+        # keep-sorted start
+        create_executable = _create_executable,
+        get_cc_details_for_binary = _get_cc_details_for_binary,
+        get_central_uncachable_version_file = lambda ctx: None,
+        get_coverage_deps = _get_coverage_deps,
+        get_debugger_deps = _get_debugger_deps,
+        get_extra_common_runfiles_for_binary = lambda ctx: ctx.runfiles(),
+        get_extra_providers = _get_extra_providers,
+        get_extra_write_build_data_env = lambda ctx: {},
+        get_imports = get_imports,
+        get_interpreter_path = _get_interpreter_path,
+        get_native_deps_dso_name = _get_native_deps_dso_name,
+        get_native_deps_user_link_flags = _get_native_deps_user_link_flags,
+        get_stamp_flag = _get_stamp_flag,
+        maybe_precompile = maybe_precompile,
+        should_build_native_deps_dso = lambda ctx: False,
+        should_create_init_files = _should_create_init_files,
+        should_include_build_data = lambda ctx: False,
+        # keep-sorted end
+    )
+
+def _get_coverage_deps(ctx, runtime_details):
+    _ = ctx, runtime_details  # @unused
+    return []
+
+def _get_debugger_deps(ctx, runtime_details):
+    _ = ctx, runtime_details  # @unused
+    return []
+
+def _get_extra_providers(ctx, main_py, runtime_details):
+    _ = ctx, main_py, runtime_details  # @unused
+    return []
+
+def _get_stamp_flag(ctx):
+    # NOTE: Undocumented API; private to builtins
+    return ctx.configuration.stamp_binaries
+
+def _should_create_init_files(ctx):
+    if ctx.attr.legacy_create_init == -1:
+        return not ctx.fragments.py.default_to_explicit_init_py
+    else:
+        return bool(ctx.attr.legacy_create_init)
+
+def _create_executable(
+        ctx,
+        *,
+        executable,
+        main_py,
+        imports,
+        is_test,
+        runtime_details,
+        cc_details,
+        native_deps_details,
+        runfiles_details):
+    _ = is_test, cc_details, native_deps_details  # @unused
+
+    is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints)
+
+    if is_windows:
+        if not executable.extension == "exe":
+            fail("Should not happen: somehow we are generating a non-.exe file on windows")
+        base_executable_name = executable.basename[0:-4]
+    else:
+        base_executable_name = executable.basename
+
+    venv = None
+
+    # The check for stage2_bootstrap_template is to support legacy
+    # BuiltinPyRuntimeInfo providers, which is likely to come from
+    # @bazel_tools//tools/python:autodetecting_toolchain, the toolchain used
+    # for workspace builds when no rules_python toolchain is configured.
+    if (BootstrapImplFlag.get_value(ctx) == BootstrapImplFlag.SCRIPT and
+        runtime_details.effective_runtime and
+        hasattr(runtime_details.effective_runtime, "stage2_bootstrap_template")):
+        venv = _create_venv(
+            ctx,
+            output_prefix = base_executable_name,
+            imports = imports,
+            runtime_details = runtime_details,
+        )
+
+        stage2_bootstrap = _create_stage2_bootstrap(
+            ctx,
+            output_prefix = base_executable_name,
+            output_sibling = executable,
+            main_py = main_py,
+            imports = imports,
+            runtime_details = runtime_details,
+        )
+        extra_runfiles = ctx.runfiles([stage2_bootstrap] + venv.files_without_interpreter)
+        zip_main = _create_zip_main(
+            ctx,
+            stage2_bootstrap = stage2_bootstrap,
+            runtime_details = runtime_details,
+            venv = venv,
+        )
+    else:
+        stage2_bootstrap = None
+        extra_runfiles = ctx.runfiles()
+        zip_main = ctx.actions.declare_file(base_executable_name + ".temp", sibling = executable)
+        _create_stage1_bootstrap(
+            ctx,
+            output = zip_main,
+            main_py = main_py,
+            imports = imports,
+            is_for_zip = True,
+            runtime_details = runtime_details,
+        )
+
+    zip_file = ctx.actions.declare_file(base_executable_name + ".zip", sibling = executable)
+    _create_zip_file(
+        ctx,
+        output = zip_file,
+        original_nonzip_executable = executable,
+        zip_main = zip_main,
+        runfiles = runfiles_details.default_runfiles.merge(extra_runfiles),
+    )
+
+    extra_files_to_build = []
+
+    # NOTE: --build_python_zip defaults to true on Windows
+    build_zip_enabled = ctx.fragments.py.build_python_zip
+
+    # When --build_python_zip is enabled, then the zip file becomes
+    # one of the default outputs.
+    if build_zip_enabled:
+        extra_files_to_build.append(zip_file)
+
+    # The logic here is a bit convoluted. Essentially, there are 3 types of
+    # executables produced:
+    # 1. (non-Windows) A bootstrap template based program.
+    # 2. (non-Windows) A self-executable zip file of a bootstrap template based program.
+    # 3. (Windows) A native Windows executable that finds and launches
+    #    the actual underlying Bazel program (one of the above). Note that
+    #    it implicitly assumes one of the above is located next to it, and
+    #    that --build_python_zip defaults to true for Windows.
+
+    should_create_executable_zip = False
+    bootstrap_output = None
+    if not is_windows:
+        if build_zip_enabled:
+            should_create_executable_zip = True
+        else:
+            bootstrap_output = executable
+    else:
+        _create_windows_exe_launcher(
+            ctx,
+            output = executable,
+            use_zip_file = build_zip_enabled,
+            python_binary_path = runtime_details.executable_interpreter_path,
+        )
+        if not build_zip_enabled:
+            # On Windows, the main executable has an "exe" extension, so
+            # here we re-use the un-extensioned name for the bootstrap output.
+            bootstrap_output = ctx.actions.declare_file(base_executable_name)
+
+            # The launcher looks for the non-zip executable next to
+            # itself, so add it to the default outputs.
+            extra_files_to_build.append(bootstrap_output)
+
+    if should_create_executable_zip:
+        if bootstrap_output != None:
+            fail("Should not occur: bootstrap_output should not be used " +
+                 "when creating an executable zip")
+        _create_executable_zip_file(
+            ctx,
+            output = executable,
+            zip_file = zip_file,
+            stage2_bootstrap = stage2_bootstrap,
+            runtime_details = runtime_details,
+            venv = venv,
+        )
+    elif bootstrap_output:
+        _create_stage1_bootstrap(
+            ctx,
+            output = bootstrap_output,
+            stage2_bootstrap = stage2_bootstrap,
+            runtime_details = runtime_details,
+            is_for_zip = False,
+            imports = imports,
+            main_py = main_py,
+            venv = venv,
+        )
+    else:
+        # Otherwise, this should be the Windows case of launcher + zip.
+        # Double check this just to make sure.
+        if not is_windows or not build_zip_enabled:
+            fail(("Should not occur: The non-executable-zip and " +
+                  "non-bootstrap-template case should have windows and zip " +
+                  "both true, but got " +
+                  "is_windows={is_windows} " +
+                  "build_zip_enabled={build_zip_enabled}").format(
+                is_windows = is_windows,
+                build_zip_enabled = build_zip_enabled,
+            ))
+
+    # The interpreter is added this late in the process so that it isn't
+    # added to the zipped files.
+    if venv:
+        extra_runfiles = extra_runfiles.merge(ctx.runfiles([venv.interpreter]))
+    return create_executable_result_struct(
+        extra_files_to_build = depset(extra_files_to_build),
+        output_groups = {"python_zip_file": depset([zip_file])},
+        extra_runfiles = extra_runfiles,
+    )
+
+def _create_zip_main(ctx, *, stage2_bootstrap, runtime_details, venv):
+    python_binary = _runfiles_root_path(ctx, venv.interpreter.short_path)
+    python_binary_actual = venv.interpreter_actual_path
+
+    # The location of this file doesn't really matter. It's added to
+    # the zip file as the top-level __main__.py file and not included
+    # elsewhere.
+    output = ctx.actions.declare_file(ctx.label.name + "_zip__main__.py")
+    ctx.actions.expand_template(
+        template = runtime_details.effective_runtime.zip_main_template,
+        output = output,
+        substitutions = {
+            "%python_binary%": python_binary,
+            "%python_binary_actual%": python_binary_actual,
+            "%stage2_bootstrap%": "{}/{}".format(
+                ctx.workspace_name,
+                stage2_bootstrap.short_path,
+            ),
+            "%workspace_name%": ctx.workspace_name,
+        },
+    )
+    return output
+
+def relative_path(from_, to):
+    """Compute a relative path from one path to another.
+
+    Args:
+        from_: {type}`str` the starting directory. Note that it should be
+            a directory because relative-symlinks are relative to the
+            directory the symlink resides in.
+        to: {type}`str` the path that `from_` wants to point to
+
+    Returns:
+        {type}`str` a relative path
+    """
+    from_parts = from_.split("/")
+    to_parts = to.split("/")
+
+    # Strip common leading parts from both paths
+    n = min(len(from_parts), len(to_parts))
+    for _ in range(n):
+        if from_parts[0] == to_parts[0]:
+            from_parts.pop(0)
+            to_parts.pop(0)
+        else:
+            break
+
+    # Impossible to compute a relative path without knowing what ".." is
+    if from_parts and from_parts[0] == "..":
+        fail("cannot compute relative path from '%s' to '%s'", from_, to)
+
+    parts = ([".."] * len(from_parts)) + to_parts
+    return paths.join(*parts)
+
+# Create a venv the executable can use.
+# For venv details and the venv startup process, see:
+# * https://docs.python.org/3/library/venv.html
+# * https://snarky.ca/how-virtual-environments-work/
+# * https://github.com/python/cpython/blob/main/Modules/getpath.py
+# * https://github.com/python/cpython/blob/main/Lib/site.py
+def _create_venv(ctx, output_prefix, imports, runtime_details):
+    venv = "_{}.venv".format(output_prefix.lstrip("_"))
+
+    # The pyvenv.cfg file must be present to trigger the venv site hooks.
+    # Because it's paths are expected to be absolute paths, we can't reliably
+    # put much in it. See https://github.com/python/cpython/issues/83650
+    pyvenv_cfg = ctx.actions.declare_file("{}/pyvenv.cfg".format(venv))
+    ctx.actions.write(pyvenv_cfg, "")
+
+    runtime = runtime_details.effective_runtime
+    if runtime.interpreter:
+        py_exe_basename = paths.basename(runtime.interpreter.short_path)
+
+        # Even though ctx.actions.symlink() is used, using
+        # declare_symlink() is required to ensure that the resulting file
+        # in runfiles is always a symlink. An RBE implementation, for example,
+        # may choose to write what symlink() points to instead.
+        interpreter = ctx.actions.declare_symlink("{}/bin/{}".format(venv, py_exe_basename))
+
+        interpreter_actual_path = _runfiles_root_path(ctx, runtime.interpreter.short_path)
+        rel_path = relative_path(
+            # dirname is necessary because a relative symlink is relative to
+            # the directory the symlink resides within.
+            from_ = paths.dirname(_runfiles_root_path(ctx, interpreter.short_path)),
+            to = interpreter_actual_path,
+        )
+
+        ctx.actions.symlink(output = interpreter, target_path = rel_path)
+    else:
+        py_exe_basename = paths.basename(runtime.interpreter_path)
+        interpreter = ctx.actions.declare_symlink("{}/bin/{}".format(venv, py_exe_basename))
+        ctx.actions.symlink(output = interpreter, target_path = runtime.interpreter_path)
+        interpreter_actual_path = runtime.interpreter_path
+
+    if runtime.interpreter_version_info:
+        version = "{}.{}".format(
+            runtime.interpreter_version_info.major,
+            runtime.interpreter_version_info.minor,
+        )
+    else:
+        version_flag = ctx.attr._python_version_flag[config_common.FeatureFlagInfo].value
+        version_flag_parts = version_flag.split(".")[0:2]
+        version = "{}.{}".format(*version_flag_parts)
+
+    # See site.py logic: free-threaded builds append "t" to the venv lib dir name
+    if "t" in runtime.abi_flags:
+        version += "t"
+
+    site_packages = "{}/lib/python{}/site-packages".format(venv, version)
+    pth = ctx.actions.declare_file("{}/bazel.pth".format(site_packages))
+    ctx.actions.write(pth, "import _bazel_site_init\n")
+
+    site_init = ctx.actions.declare_file("{}/_bazel_site_init.py".format(site_packages))
+    computed_subs = ctx.actions.template_dict()
+    computed_subs.add_joined("%imports%", imports, join_with = ":", map_each = _map_each_identity)
+    ctx.actions.expand_template(
+        template = runtime.site_init_template,
+        output = site_init,
+        substitutions = {
+            "%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
+            "%site_init_runfiles_path%": "{}/{}".format(ctx.workspace_name, site_init.short_path),
+            "%workspace_name%": ctx.workspace_name,
+        },
+        computed_substitutions = computed_subs,
+    )
+
+    return struct(
+        interpreter = interpreter,
+        # Runfiles root relative path or absolute path
+        interpreter_actual_path = interpreter_actual_path,
+        files_without_interpreter = [pyvenv_cfg, pth, site_init],
+    )
+
+def _map_each_identity(v):
+    return v
+
+def _create_stage2_bootstrap(
+        ctx,
+        *,
+        output_prefix,
+        output_sibling,
+        main_py,
+        imports,
+        runtime_details):
+    output = ctx.actions.declare_file(
+        # Prepend with underscore to prevent pytest from trying to
+        # process the bootstrap for files starting with `test_`
+        "_{}_stage2_bootstrap.py".format(output_prefix),
+        sibling = output_sibling,
+    )
+    runtime = runtime_details.effective_runtime
+    if (ctx.configuration.coverage_enabled and
+        runtime and
+        runtime.coverage_tool):
+        coverage_tool_runfiles_path = "{}/{}".format(
+            ctx.workspace_name,
+            runtime.coverage_tool.short_path,
+        )
+    else:
+        coverage_tool_runfiles_path = ""
+
+    template = runtime.stage2_bootstrap_template
+
+    ctx.actions.expand_template(
+        template = template,
+        output = output,
+        substitutions = {
+            "%coverage_tool%": coverage_tool_runfiles_path,
+            "%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
+            "%imports%": ":".join(imports.to_list()),
+            "%main%": "{}/{}".format(ctx.workspace_name, main_py.short_path),
+            "%target%": str(ctx.label),
+            "%workspace_name%": ctx.workspace_name,
+        },
+        is_executable = True,
+    )
+    return output
+
+def _runfiles_root_path(ctx, short_path):
+    """Compute a runfiles-root relative path from `File.short_path`
+
+    Args:
+        ctx: current target ctx
+        short_path: str, a main-repo relative path from `File.short_path`
+
+    Returns:
+        {type}`str`, a runflies-root relative path
+    """
+
+    # The ../ comes from short_path is for files in other repos.
+    if short_path.startswith("../"):
+        return short_path[3:]
+    else:
+        return "{}/{}".format(ctx.workspace_name, short_path)
+
+def _create_stage1_bootstrap(
+        ctx,
+        *,
+        output,
+        main_py = None,
+        stage2_bootstrap = None,
+        imports = None,
+        is_for_zip,
+        runtime_details,
+        venv = None):
+    runtime = runtime_details.effective_runtime
+
+    if venv:
+        python_binary_path = _runfiles_root_path(ctx, venv.interpreter.short_path)
+    else:
+        python_binary_path = runtime_details.executable_interpreter_path
+
+    if is_for_zip and venv:
+        python_binary_actual = venv.interpreter_actual_path
+    else:
+        python_binary_actual = ""
+
+    subs = {
+        "%is_zipfile%": "1" if is_for_zip else "0",
+        "%python_binary%": python_binary_path,
+        "%python_binary_actual%": python_binary_actual,
+        "%target%": str(ctx.label),
+        "%workspace_name%": ctx.workspace_name,
+    }
+
+    if stage2_bootstrap:
+        subs["%stage2_bootstrap%"] = "{}/{}".format(
+            ctx.workspace_name,
+            stage2_bootstrap.short_path,
+        )
+        template = runtime.bootstrap_template
+        subs["%shebang%"] = runtime.stub_shebang
+    else:
+        if (ctx.configuration.coverage_enabled and
+            runtime and
+            runtime.coverage_tool):
+            coverage_tool_runfiles_path = "{}/{}".format(
+                ctx.workspace_name,
+                runtime.coverage_tool.short_path,
+            )
+        else:
+            coverage_tool_runfiles_path = ""
+        if runtime:
+            subs["%shebang%"] = runtime.stub_shebang
+            template = runtime.bootstrap_template
+        else:
+            subs["%shebang%"] = DEFAULT_STUB_SHEBANG
+            template = ctx.file._bootstrap_template
+
+        subs["%coverage_tool%"] = coverage_tool_runfiles_path
+        subs["%import_all%"] = ("True" if ctx.fragments.bazel_py.python_import_all_repositories else "False")
+        subs["%imports%"] = ":".join(imports.to_list())
+        subs["%main%"] = "{}/{}".format(ctx.workspace_name, main_py.short_path)
+
+    ctx.actions.expand_template(
+        template = template,
+        output = output,
+        substitutions = subs,
+    )
+
+def _create_windows_exe_launcher(
+        ctx,
+        *,
+        output,
+        python_binary_path,
+        use_zip_file):
+    launch_info = ctx.actions.args()
+    launch_info.use_param_file("%s", use_always = True)
+    launch_info.set_param_file_format("multiline")
+    launch_info.add("binary_type=Python")
+    launch_info.add(ctx.workspace_name, format = "workspace_name=%s")
+    launch_info.add(
+        "1" if py_internal.runfiles_enabled(ctx) else "0",
+        format = "symlink_runfiles_enabled=%s",
+    )
+    launch_info.add(python_binary_path, format = "python_bin_path=%s")
+    launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s")
+
+    launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable
+    ctx.actions.run(
+        executable = ctx.executable._windows_launcher_maker,
+        arguments = [launcher.path, launch_info, output.path],
+        inputs = [launcher],
+        outputs = [output],
+        mnemonic = "PyBuildLauncher",
+        progress_message = "Creating launcher for %{label}",
+        # Needed to inherit PATH when using non-MSVC compilers like MinGW
+        use_default_shell_env = True,
+    )
+
+def _create_zip_file(ctx, *, output, original_nonzip_executable, zip_main, runfiles):
+    """Create a Python zipapp (zip with __main__.py entry point)."""
+    workspace_name = ctx.workspace_name
+    legacy_external_runfiles = _py_builtins.get_legacy_external_runfiles(ctx)
+
+    manifest = ctx.actions.args()
+    manifest.use_param_file("@%s", use_always = True)
+    manifest.set_param_file_format("multiline")
+
+    manifest.add("__main__.py={}".format(zip_main.path))
+    manifest.add("__init__.py=")
+    manifest.add(
+        "{}=".format(
+            _get_zip_runfiles_path("__init__.py", workspace_name, legacy_external_runfiles),
+        ),
+    )
+    for path in runfiles.empty_filenames.to_list():
+        manifest.add("{}=".format(_get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles)))
+
+    def map_zip_runfiles(file):
+        if file != original_nonzip_executable and file != output:
+            return "{}={}".format(
+                _get_zip_runfiles_path(file.short_path, workspace_name, legacy_external_runfiles),
+                file.path,
+            )
+        else:
+            return None
+
+    manifest.add_all(runfiles.files, map_each = map_zip_runfiles, allow_closure = True)
+
+    inputs = [zip_main]
+    if _py_builtins.is_bzlmod_enabled(ctx):
+        zip_repo_mapping_manifest = ctx.actions.declare_file(
+            output.basename + ".repo_mapping",
+            sibling = output,
+        )
+        _py_builtins.create_repo_mapping_manifest(
+            ctx = ctx,
+            runfiles = runfiles,
+            output = zip_repo_mapping_manifest,
+        )
+        manifest.add("{}/_repo_mapping={}".format(
+            _ZIP_RUNFILES_DIRECTORY_NAME,
+            zip_repo_mapping_manifest.path,
+        ))
+        inputs.append(zip_repo_mapping_manifest)
+
+    for artifact in runfiles.files.to_list():
+        # Don't include the original executable because it isn't used by the
+        # zip file, so no need to build it for the action.
+        # Don't include the zipfile itself because it's an output.
+        if artifact != original_nonzip_executable and artifact != output:
+            inputs.append(artifact)
+
+    zip_cli_args = ctx.actions.args()
+    zip_cli_args.add("cC")
+    zip_cli_args.add(output)
+
+    ctx.actions.run(
+        executable = ctx.executable._zipper,
+        arguments = [zip_cli_args, manifest],
+        inputs = depset(inputs),
+        outputs = [output],
+        use_default_shell_env = True,
+        mnemonic = "PythonZipper",
+        progress_message = "Building Python zip: %{label}",
+    )
+
+def _get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles):
+    if legacy_external_runfiles and path.startswith(_EXTERNAL_PATH_PREFIX):
+        zip_runfiles_path = paths.relativize(path, _EXTERNAL_PATH_PREFIX)
+    else:
+        # NOTE: External runfiles (artifacts in other repos) will have a leading
+        # path component of "../" so that they refer outside the main workspace
+        # directory and into the runfiles root. By normalizing, we simplify e.g.
+        # "workspace/../foo/bar" to simply "foo/bar".
+        zip_runfiles_path = paths.normalize("{}/{}".format(workspace_name, path))
+    return "{}/{}".format(_ZIP_RUNFILES_DIRECTORY_NAME, zip_runfiles_path)
+
+def _create_executable_zip_file(
+        ctx,
+        *,
+        output,
+        zip_file,
+        stage2_bootstrap,
+        runtime_details,
+        venv):
+    prelude = ctx.actions.declare_file(
+        "{}_zip_prelude.sh".format(output.basename),
+        sibling = output,
+    )
+    if stage2_bootstrap:
+        _create_stage1_bootstrap(
+            ctx,
+            output = prelude,
+            stage2_bootstrap = stage2_bootstrap,
+            runtime_details = runtime_details,
+            is_for_zip = True,
+            venv = venv,
+        )
+    else:
+        ctx.actions.write(prelude, "#!/usr/bin/env python3\n")
+
+    ctx.actions.run_shell(
+        command = "cat {prelude} {zip} > {output}".format(
+            prelude = prelude.path,
+            zip = zip_file.path,
+            output = output.path,
+        ),
+        inputs = [prelude, zip_file],
+        outputs = [output],
+        use_default_shell_env = True,
+        mnemonic = "PyBuildExecutableZip",
+        progress_message = "Build Python zip executable: %{label}",
+    )
+
+def _get_cc_details_for_binary(ctx, extra_deps):
+    cc_info = collect_cc_info(ctx, extra_deps = extra_deps)
+    return create_cc_details_struct(
+        cc_info_for_propagating = cc_info,
+        cc_info_for_self_link = cc_info,
+        cc_info_with_extra_link_time_libraries = None,
+        extra_runfiles = ctx.runfiles(),
+        # Though the rules require the CcToolchain, it isn't actually used.
+        cc_toolchain = None,
+        feature_config = None,
+    )
+
+def _get_interpreter_path(ctx, *, runtime, flag_interpreter_path):
+    if runtime:
+        if runtime.interpreter_path:
+            interpreter_path = runtime.interpreter_path
+        else:
+            interpreter_path = "{}/{}".format(
+                ctx.workspace_name,
+                runtime.interpreter.short_path,
+            )
+
+            # NOTE: External runfiles (artifacts in other repos) will have a
+            # leading path component of "../" so that they refer outside the
+            # main workspace directory and into the runfiles root. By
+            # normalizing, we simplify e.g. "workspace/../foo/bar" to simply
+            # "foo/bar"
+            interpreter_path = paths.normalize(interpreter_path)
+
+    elif flag_interpreter_path:
+        interpreter_path = flag_interpreter_path
+    else:
+        fail("Unable to determine interpreter path")
+
+    return interpreter_path
+
+def _get_native_deps_dso_name(ctx):
+    _ = ctx  # @unused
+    fail("Building native deps DSO not supported.")
+
+def _get_native_deps_user_link_flags(ctx):
+    _ = ctx  # @unused
+    fail("Building native deps DSO not supported.")
+
 def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = []):
     """Base rule implementation for a Python executable.
 
@@ -949,6 +1691,14 @@
         inherited_environment = inherited_environment,
     )
 
+def create_executable_rule(*, attrs, **kwargs):
+    return create_base_executable_rule(
+        ##attrs = dicts.add(EXECUTABLE_ATTRS, attrs),
+        attrs = attrs,
+        fragments = ["py", "bazel_py"],
+        **kwargs
+    )
+
 def create_base_executable_rule(*, attrs, fragments = [], **kwargs):
     """Create a function for defining for Python binary/test targets.
 
diff --git a/python/private/py_executable_bazel.bzl b/python/private/py_executable_bazel.bzl
deleted file mode 100644
index 3778c19..0000000
--- a/python/private/py_executable_bazel.bzl
+++ /dev/null
@@ -1,772 +0,0 @@
-# Copyright 2022 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Implementation for Bazel Python executable."""
-
-load("@bazel_skylib//lib:dicts.bzl", "dicts")
-load("@bazel_skylib//lib:paths.bzl", "paths")
-load(":attributes.bzl", "IMPORTS_ATTRS")
-load(
-    ":common.bzl",
-    "create_binary_semantics_struct",
-    "create_cc_details_struct",
-    "create_executable_result_struct",
-    "target_platform_has_any_constraint",
-    "union_attrs",
-)
-load(":common_bazel.bzl", "collect_cc_info", "get_imports", "maybe_precompile")
-load(":flags.bzl", "BootstrapImplFlag")
-load(
-    ":py_executable.bzl",
-    "create_base_executable_rule",
-    "py_executable_base_impl",
-)
-load(":py_internal.bzl", "py_internal")
-load(":py_runtime_info.bzl", "DEFAULT_STUB_SHEBANG")
-load(":toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE")
-
-_py_builtins = py_internal
-_EXTERNAL_PATH_PREFIX = "external"
-_ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
-
-BAZEL_EXECUTABLE_ATTRS = union_attrs(
-    IMPORTS_ATTRS,
-    {
-        "legacy_create_init": attr.int(
-            default = -1,
-            values = [-1, 0, 1],
-            doc = """\
-Whether to implicitly create empty `__init__.py` files in the runfiles tree.
-These are created in every directory containing Python source code or shared
-libraries, and every parent directory of those directories, excluding the repo
-root directory. The default, `-1` (auto), means true unless
-`--incompatible_default_to_explicit_init_py` is used. If false, the user is
-responsible for creating (possibly empty) `__init__.py` files and adding them to
-the `srcs` of Python targets as required.
-                                       """,
-        ),
-        "_bootstrap_template": attr.label(
-            allow_single_file = True,
-            default = "@bazel_tools//tools/python:python_bootstrap_template.txt",
-        ),
-        "_launcher": attr.label(
-            cfg = "target",
-            # NOTE: This is an executable, but is only used for Windows. It
-            # can't have executable=True because the backing target is an
-            # empty target for other platforms.
-            default = "//tools/launcher:launcher",
-        ),
-        "_py_interpreter": attr.label(
-            # The configuration_field args are validated when called;
-            # we use the precense of py_internal to indicate this Bazel
-            # build has that fragment and name.
-            default = configuration_field(
-                fragment = "bazel_py",
-                name = "python_top",
-            ) if py_internal else None,
-        ),
-        # TODO: This appears to be vestigial. It's only added because
-        # GraphlessQueryTest.testLabelsOperator relies on it to test for
-        # query behavior of implicit dependencies.
-        "_py_toolchain_type": attr.label(
-            default = TARGET_TOOLCHAIN_TYPE,
-        ),
-        "_python_version_flag": attr.label(
-            default = "//python/config_settings:python_version",
-        ),
-        "_windows_launcher_maker": attr.label(
-            default = "@bazel_tools//tools/launcher:launcher_maker",
-            cfg = "exec",
-            executable = True,
-        ),
-        "_zipper": attr.label(
-            cfg = "exec",
-            executable = True,
-            default = "@bazel_tools//tools/zip:zipper",
-        ),
-    },
-)
-
-def create_executable_rule(*, attrs, **kwargs):
-    return create_base_executable_rule(
-        attrs = dicts.add(BAZEL_EXECUTABLE_ATTRS, attrs),
-        fragments = ["py", "bazel_py"],
-        **kwargs
-    )
-
-def py_executable_bazel_impl(ctx, *, is_test, inherited_environment):
-    """Common code for executables for Bazel."""
-    return py_executable_base_impl(
-        ctx = ctx,
-        semantics = create_binary_semantics_bazel(),
-        is_test = is_test,
-        inherited_environment = inherited_environment,
-    )
-
-def create_binary_semantics_bazel():
-    return create_binary_semantics_struct(
-        # keep-sorted start
-        create_executable = _create_executable,
-        get_cc_details_for_binary = _get_cc_details_for_binary,
-        get_central_uncachable_version_file = lambda ctx: None,
-        get_coverage_deps = _get_coverage_deps,
-        get_debugger_deps = _get_debugger_deps,
-        get_extra_common_runfiles_for_binary = lambda ctx: ctx.runfiles(),
-        get_extra_providers = _get_extra_providers,
-        get_extra_write_build_data_env = lambda ctx: {},
-        get_imports = get_imports,
-        get_interpreter_path = _get_interpreter_path,
-        get_native_deps_dso_name = _get_native_deps_dso_name,
-        get_native_deps_user_link_flags = _get_native_deps_user_link_flags,
-        get_stamp_flag = _get_stamp_flag,
-        maybe_precompile = maybe_precompile,
-        should_build_native_deps_dso = lambda ctx: False,
-        should_create_init_files = _should_create_init_files,
-        should_include_build_data = lambda ctx: False,
-        # keep-sorted end
-    )
-
-def _get_coverage_deps(ctx, runtime_details):
-    _ = ctx, runtime_details  # @unused
-    return []
-
-def _get_debugger_deps(ctx, runtime_details):
-    _ = ctx, runtime_details  # @unused
-    return []
-
-def _get_extra_providers(ctx, main_py, runtime_details):
-    _ = ctx, main_py, runtime_details  # @unused
-    return []
-
-def _get_stamp_flag(ctx):
-    # NOTE: Undocumented API; private to builtins
-    return ctx.configuration.stamp_binaries
-
-def _should_create_init_files(ctx):
-    if ctx.attr.legacy_create_init == -1:
-        return not ctx.fragments.py.default_to_explicit_init_py
-    else:
-        return bool(ctx.attr.legacy_create_init)
-
-def _create_executable(
-        ctx,
-        *,
-        executable,
-        main_py,
-        imports,
-        is_test,
-        runtime_details,
-        cc_details,
-        native_deps_details,
-        runfiles_details):
-    _ = is_test, cc_details, native_deps_details  # @unused
-
-    is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints)
-
-    if is_windows:
-        if not executable.extension == "exe":
-            fail("Should not happen: somehow we are generating a non-.exe file on windows")
-        base_executable_name = executable.basename[0:-4]
-    else:
-        base_executable_name = executable.basename
-
-    venv = None
-
-    # The check for stage2_bootstrap_template is to support legacy
-    # BuiltinPyRuntimeInfo providers, which is likely to come from
-    # @bazel_tools//tools/python:autodetecting_toolchain, the toolchain used
-    # for workspace builds when no rules_python toolchain is configured.
-    if (BootstrapImplFlag.get_value(ctx) == BootstrapImplFlag.SCRIPT and
-        runtime_details.effective_runtime and
-        hasattr(runtime_details.effective_runtime, "stage2_bootstrap_template")):
-        venv = _create_venv(
-            ctx,
-            output_prefix = base_executable_name,
-            imports = imports,
-            runtime_details = runtime_details,
-        )
-
-        stage2_bootstrap = _create_stage2_bootstrap(
-            ctx,
-            output_prefix = base_executable_name,
-            output_sibling = executable,
-            main_py = main_py,
-            imports = imports,
-            runtime_details = runtime_details,
-        )
-        extra_runfiles = ctx.runfiles([stage2_bootstrap] + venv.files_without_interpreter)
-        zip_main = _create_zip_main(
-            ctx,
-            stage2_bootstrap = stage2_bootstrap,
-            runtime_details = runtime_details,
-            venv = venv,
-        )
-    else:
-        stage2_bootstrap = None
-        extra_runfiles = ctx.runfiles()
-        zip_main = ctx.actions.declare_file(base_executable_name + ".temp", sibling = executable)
-        _create_stage1_bootstrap(
-            ctx,
-            output = zip_main,
-            main_py = main_py,
-            imports = imports,
-            is_for_zip = True,
-            runtime_details = runtime_details,
-        )
-
-    zip_file = ctx.actions.declare_file(base_executable_name + ".zip", sibling = executable)
-    _create_zip_file(
-        ctx,
-        output = zip_file,
-        original_nonzip_executable = executable,
-        zip_main = zip_main,
-        runfiles = runfiles_details.default_runfiles.merge(extra_runfiles),
-    )
-
-    extra_files_to_build = []
-
-    # NOTE: --build_python_zip defaults to true on Windows
-    build_zip_enabled = ctx.fragments.py.build_python_zip
-
-    # When --build_python_zip is enabled, then the zip file becomes
-    # one of the default outputs.
-    if build_zip_enabled:
-        extra_files_to_build.append(zip_file)
-
-    # The logic here is a bit convoluted. Essentially, there are 3 types of
-    # executables produced:
-    # 1. (non-Windows) A bootstrap template based program.
-    # 2. (non-Windows) A self-executable zip file of a bootstrap template based program.
-    # 3. (Windows) A native Windows executable that finds and launches
-    #    the actual underlying Bazel program (one of the above). Note that
-    #    it implicitly assumes one of the above is located next to it, and
-    #    that --build_python_zip defaults to true for Windows.
-
-    should_create_executable_zip = False
-    bootstrap_output = None
-    if not is_windows:
-        if build_zip_enabled:
-            should_create_executable_zip = True
-        else:
-            bootstrap_output = executable
-    else:
-        _create_windows_exe_launcher(
-            ctx,
-            output = executable,
-            use_zip_file = build_zip_enabled,
-            python_binary_path = runtime_details.executable_interpreter_path,
-        )
-        if not build_zip_enabled:
-            # On Windows, the main executable has an "exe" extension, so
-            # here we re-use the un-extensioned name for the bootstrap output.
-            bootstrap_output = ctx.actions.declare_file(base_executable_name)
-
-            # The launcher looks for the non-zip executable next to
-            # itself, so add it to the default outputs.
-            extra_files_to_build.append(bootstrap_output)
-
-    if should_create_executable_zip:
-        if bootstrap_output != None:
-            fail("Should not occur: bootstrap_output should not be used " +
-                 "when creating an executable zip")
-        _create_executable_zip_file(
-            ctx,
-            output = executable,
-            zip_file = zip_file,
-            stage2_bootstrap = stage2_bootstrap,
-            runtime_details = runtime_details,
-            venv = venv,
-        )
-    elif bootstrap_output:
-        _create_stage1_bootstrap(
-            ctx,
-            output = bootstrap_output,
-            stage2_bootstrap = stage2_bootstrap,
-            runtime_details = runtime_details,
-            is_for_zip = False,
-            imports = imports,
-            main_py = main_py,
-            venv = venv,
-        )
-    else:
-        # Otherwise, this should be the Windows case of launcher + zip.
-        # Double check this just to make sure.
-        if not is_windows or not build_zip_enabled:
-            fail(("Should not occur: The non-executable-zip and " +
-                  "non-bootstrap-template case should have windows and zip " +
-                  "both true, but got " +
-                  "is_windows={is_windows} " +
-                  "build_zip_enabled={build_zip_enabled}").format(
-                is_windows = is_windows,
-                build_zip_enabled = build_zip_enabled,
-            ))
-
-    # The interpreter is added this late in the process so that it isn't
-    # added to the zipped files.
-    if venv:
-        extra_runfiles = extra_runfiles.merge(ctx.runfiles([venv.interpreter]))
-    return create_executable_result_struct(
-        extra_files_to_build = depset(extra_files_to_build),
-        output_groups = {"python_zip_file": depset([zip_file])},
-        extra_runfiles = extra_runfiles,
-    )
-
-def _create_zip_main(ctx, *, stage2_bootstrap, runtime_details, venv):
-    python_binary = _runfiles_root_path(ctx, venv.interpreter.short_path)
-    python_binary_actual = venv.interpreter_actual_path
-
-    # The location of this file doesn't really matter. It's added to
-    # the zip file as the top-level __main__.py file and not included
-    # elsewhere.
-    output = ctx.actions.declare_file(ctx.label.name + "_zip__main__.py")
-    ctx.actions.expand_template(
-        template = runtime_details.effective_runtime.zip_main_template,
-        output = output,
-        substitutions = {
-            "%python_binary%": python_binary,
-            "%python_binary_actual%": python_binary_actual,
-            "%stage2_bootstrap%": "{}/{}".format(
-                ctx.workspace_name,
-                stage2_bootstrap.short_path,
-            ),
-            "%workspace_name%": ctx.workspace_name,
-        },
-    )
-    return output
-
-def relative_path(from_, to):
-    """Compute a relative path from one path to another.
-
-    Args:
-        from_: {type}`str` the starting directory. Note that it should be
-            a directory because relative-symlinks are relative to the
-            directory the symlink resides in.
-        to: {type}`str` the path that `from_` wants to point to
-
-    Returns:
-        {type}`str` a relative path
-    """
-    from_parts = from_.split("/")
-    to_parts = to.split("/")
-
-    # Strip common leading parts from both paths
-    n = min(len(from_parts), len(to_parts))
-    for _ in range(n):
-        if from_parts[0] == to_parts[0]:
-            from_parts.pop(0)
-            to_parts.pop(0)
-        else:
-            break
-
-    # Impossible to compute a relative path without knowing what ".." is
-    if from_parts and from_parts[0] == "..":
-        fail("cannot compute relative path from '%s' to '%s'", from_, to)
-
-    parts = ([".."] * len(from_parts)) + to_parts
-    return paths.join(*parts)
-
-# Create a venv the executable can use.
-# For venv details and the venv startup process, see:
-# * https://docs.python.org/3/library/venv.html
-# * https://snarky.ca/how-virtual-environments-work/
-# * https://github.com/python/cpython/blob/main/Modules/getpath.py
-# * https://github.com/python/cpython/blob/main/Lib/site.py
-def _create_venv(ctx, output_prefix, imports, runtime_details):
-    venv = "_{}.venv".format(output_prefix.lstrip("_"))
-
-    # The pyvenv.cfg file must be present to trigger the venv site hooks.
-    # Because it's paths are expected to be absolute paths, we can't reliably
-    # put much in it. See https://github.com/python/cpython/issues/83650
-    pyvenv_cfg = ctx.actions.declare_file("{}/pyvenv.cfg".format(venv))
-    ctx.actions.write(pyvenv_cfg, "")
-
-    runtime = runtime_details.effective_runtime
-    if runtime.interpreter:
-        py_exe_basename = paths.basename(runtime.interpreter.short_path)
-
-        # Even though ctx.actions.symlink() is used, using
-        # declare_symlink() is required to ensure that the resulting file
-        # in runfiles is always a symlink. An RBE implementation, for example,
-        # may choose to write what symlink() points to instead.
-        interpreter = ctx.actions.declare_symlink("{}/bin/{}".format(venv, py_exe_basename))
-
-        interpreter_actual_path = _runfiles_root_path(ctx, runtime.interpreter.short_path)
-        rel_path = relative_path(
-            # dirname is necessary because a relative symlink is relative to
-            # the directory the symlink resides within.
-            from_ = paths.dirname(_runfiles_root_path(ctx, interpreter.short_path)),
-            to = interpreter_actual_path,
-        )
-
-        ctx.actions.symlink(output = interpreter, target_path = rel_path)
-    else:
-        py_exe_basename = paths.basename(runtime.interpreter_path)
-        interpreter = ctx.actions.declare_symlink("{}/bin/{}".format(venv, py_exe_basename))
-        ctx.actions.symlink(output = interpreter, target_path = runtime.interpreter_path)
-        interpreter_actual_path = runtime.interpreter_path
-
-    if runtime.interpreter_version_info:
-        version = "{}.{}".format(
-            runtime.interpreter_version_info.major,
-            runtime.interpreter_version_info.minor,
-        )
-    else:
-        version_flag = ctx.attr._python_version_flag[config_common.FeatureFlagInfo].value
-        version_flag_parts = version_flag.split(".")[0:2]
-        version = "{}.{}".format(*version_flag_parts)
-
-    # See site.py logic: free-threaded builds append "t" to the venv lib dir name
-    if "t" in runtime.abi_flags:
-        version += "t"
-
-    site_packages = "{}/lib/python{}/site-packages".format(venv, version)
-    pth = ctx.actions.declare_file("{}/bazel.pth".format(site_packages))
-    ctx.actions.write(pth, "import _bazel_site_init\n")
-
-    site_init = ctx.actions.declare_file("{}/_bazel_site_init.py".format(site_packages))
-    computed_subs = ctx.actions.template_dict()
-    computed_subs.add_joined("%imports%", imports, join_with = ":", map_each = _map_each_identity)
-    ctx.actions.expand_template(
-        template = runtime.site_init_template,
-        output = site_init,
-        substitutions = {
-            "%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
-            "%site_init_runfiles_path%": "{}/{}".format(ctx.workspace_name, site_init.short_path),
-            "%workspace_name%": ctx.workspace_name,
-        },
-        computed_substitutions = computed_subs,
-    )
-
-    return struct(
-        interpreter = interpreter,
-        # Runfiles root relative path or absolute path
-        interpreter_actual_path = interpreter_actual_path,
-        files_without_interpreter = [pyvenv_cfg, pth, site_init],
-    )
-
-def _map_each_identity(v):
-    return v
-
-def _create_stage2_bootstrap(
-        ctx,
-        *,
-        output_prefix,
-        output_sibling,
-        main_py,
-        imports,
-        runtime_details):
-    output = ctx.actions.declare_file(
-        # Prepend with underscore to prevent pytest from trying to
-        # process the bootstrap for files starting with `test_`
-        "_{}_stage2_bootstrap.py".format(output_prefix),
-        sibling = output_sibling,
-    )
-    runtime = runtime_details.effective_runtime
-    if (ctx.configuration.coverage_enabled and
-        runtime and
-        runtime.coverage_tool):
-        coverage_tool_runfiles_path = "{}/{}".format(
-            ctx.workspace_name,
-            runtime.coverage_tool.short_path,
-        )
-    else:
-        coverage_tool_runfiles_path = ""
-
-    template = runtime.stage2_bootstrap_template
-
-    ctx.actions.expand_template(
-        template = template,
-        output = output,
-        substitutions = {
-            "%coverage_tool%": coverage_tool_runfiles_path,
-            "%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
-            "%imports%": ":".join(imports.to_list()),
-            "%main%": "{}/{}".format(ctx.workspace_name, main_py.short_path),
-            "%target%": str(ctx.label),
-            "%workspace_name%": ctx.workspace_name,
-        },
-        is_executable = True,
-    )
-    return output
-
-def _runfiles_root_path(ctx, short_path):
-    """Compute a runfiles-root relative path from `File.short_path`
-
-    Args:
-        ctx: current target ctx
-        short_path: str, a main-repo relative path from `File.short_path`
-
-    Returns:
-        {type}`str`, a runflies-root relative path
-    """
-
-    # The ../ comes from short_path is for files in other repos.
-    if short_path.startswith("../"):
-        return short_path[3:]
-    else:
-        return "{}/{}".format(ctx.workspace_name, short_path)
-
-def _create_stage1_bootstrap(
-        ctx,
-        *,
-        output,
-        main_py = None,
-        stage2_bootstrap = None,
-        imports = None,
-        is_for_zip,
-        runtime_details,
-        venv = None):
-    runtime = runtime_details.effective_runtime
-
-    if venv:
-        python_binary_path = _runfiles_root_path(ctx, venv.interpreter.short_path)
-    else:
-        python_binary_path = runtime_details.executable_interpreter_path
-
-    if is_for_zip and venv:
-        python_binary_actual = venv.interpreter_actual_path
-    else:
-        python_binary_actual = ""
-
-    subs = {
-        "%is_zipfile%": "1" if is_for_zip else "0",
-        "%python_binary%": python_binary_path,
-        "%python_binary_actual%": python_binary_actual,
-        "%target%": str(ctx.label),
-        "%workspace_name%": ctx.workspace_name,
-    }
-
-    if stage2_bootstrap:
-        subs["%stage2_bootstrap%"] = "{}/{}".format(
-            ctx.workspace_name,
-            stage2_bootstrap.short_path,
-        )
-        template = runtime.bootstrap_template
-        subs["%shebang%"] = runtime.stub_shebang
-    else:
-        if (ctx.configuration.coverage_enabled and
-            runtime and
-            runtime.coverage_tool):
-            coverage_tool_runfiles_path = "{}/{}".format(
-                ctx.workspace_name,
-                runtime.coverage_tool.short_path,
-            )
-        else:
-            coverage_tool_runfiles_path = ""
-        if runtime:
-            subs["%shebang%"] = runtime.stub_shebang
-            template = runtime.bootstrap_template
-        else:
-            subs["%shebang%"] = DEFAULT_STUB_SHEBANG
-            template = ctx.file._bootstrap_template
-
-        subs["%coverage_tool%"] = coverage_tool_runfiles_path
-        subs["%import_all%"] = ("True" if ctx.fragments.bazel_py.python_import_all_repositories else "False")
-        subs["%imports%"] = ":".join(imports.to_list())
-        subs["%main%"] = "{}/{}".format(ctx.workspace_name, main_py.short_path)
-
-    ctx.actions.expand_template(
-        template = template,
-        output = output,
-        substitutions = subs,
-    )
-
-def _create_windows_exe_launcher(
-        ctx,
-        *,
-        output,
-        python_binary_path,
-        use_zip_file):
-    launch_info = ctx.actions.args()
-    launch_info.use_param_file("%s", use_always = True)
-    launch_info.set_param_file_format("multiline")
-    launch_info.add("binary_type=Python")
-    launch_info.add(ctx.workspace_name, format = "workspace_name=%s")
-    launch_info.add(
-        "1" if py_internal.runfiles_enabled(ctx) else "0",
-        format = "symlink_runfiles_enabled=%s",
-    )
-    launch_info.add(python_binary_path, format = "python_bin_path=%s")
-    launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s")
-
-    launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable
-    ctx.actions.run(
-        executable = ctx.executable._windows_launcher_maker,
-        arguments = [launcher.path, launch_info, output.path],
-        inputs = [launcher],
-        outputs = [output],
-        mnemonic = "PyBuildLauncher",
-        progress_message = "Creating launcher for %{label}",
-        # Needed to inherit PATH when using non-MSVC compilers like MinGW
-        use_default_shell_env = True,
-    )
-
-def _create_zip_file(ctx, *, output, original_nonzip_executable, zip_main, runfiles):
-    """Create a Python zipapp (zip with __main__.py entry point)."""
-    workspace_name = ctx.workspace_name
-    legacy_external_runfiles = _py_builtins.get_legacy_external_runfiles(ctx)
-
-    manifest = ctx.actions.args()
-    manifest.use_param_file("@%s", use_always = True)
-    manifest.set_param_file_format("multiline")
-
-    manifest.add("__main__.py={}".format(zip_main.path))
-    manifest.add("__init__.py=")
-    manifest.add(
-        "{}=".format(
-            _get_zip_runfiles_path("__init__.py", workspace_name, legacy_external_runfiles),
-        ),
-    )
-    for path in runfiles.empty_filenames.to_list():
-        manifest.add("{}=".format(_get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles)))
-
-    def map_zip_runfiles(file):
-        if file != original_nonzip_executable and file != output:
-            return "{}={}".format(
-                _get_zip_runfiles_path(file.short_path, workspace_name, legacy_external_runfiles),
-                file.path,
-            )
-        else:
-            return None
-
-    manifest.add_all(runfiles.files, map_each = map_zip_runfiles, allow_closure = True)
-
-    inputs = [zip_main]
-    if _py_builtins.is_bzlmod_enabled(ctx):
-        zip_repo_mapping_manifest = ctx.actions.declare_file(
-            output.basename + ".repo_mapping",
-            sibling = output,
-        )
-        _py_builtins.create_repo_mapping_manifest(
-            ctx = ctx,
-            runfiles = runfiles,
-            output = zip_repo_mapping_manifest,
-        )
-        manifest.add("{}/_repo_mapping={}".format(
-            _ZIP_RUNFILES_DIRECTORY_NAME,
-            zip_repo_mapping_manifest.path,
-        ))
-        inputs.append(zip_repo_mapping_manifest)
-
-    for artifact in runfiles.files.to_list():
-        # Don't include the original executable because it isn't used by the
-        # zip file, so no need to build it for the action.
-        # Don't include the zipfile itself because it's an output.
-        if artifact != original_nonzip_executable and artifact != output:
-            inputs.append(artifact)
-
-    zip_cli_args = ctx.actions.args()
-    zip_cli_args.add("cC")
-    zip_cli_args.add(output)
-
-    ctx.actions.run(
-        executable = ctx.executable._zipper,
-        arguments = [zip_cli_args, manifest],
-        inputs = depset(inputs),
-        outputs = [output],
-        use_default_shell_env = True,
-        mnemonic = "PythonZipper",
-        progress_message = "Building Python zip: %{label}",
-    )
-
-def _get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles):
-    if legacy_external_runfiles and path.startswith(_EXTERNAL_PATH_PREFIX):
-        zip_runfiles_path = paths.relativize(path, _EXTERNAL_PATH_PREFIX)
-    else:
-        # NOTE: External runfiles (artifacts in other repos) will have a leading
-        # path component of "../" so that they refer outside the main workspace
-        # directory and into the runfiles root. By normalizing, we simplify e.g.
-        # "workspace/../foo/bar" to simply "foo/bar".
-        zip_runfiles_path = paths.normalize("{}/{}".format(workspace_name, path))
-    return "{}/{}".format(_ZIP_RUNFILES_DIRECTORY_NAME, zip_runfiles_path)
-
-def _create_executable_zip_file(
-        ctx,
-        *,
-        output,
-        zip_file,
-        stage2_bootstrap,
-        runtime_details,
-        venv):
-    prelude = ctx.actions.declare_file(
-        "{}_zip_prelude.sh".format(output.basename),
-        sibling = output,
-    )
-    if stage2_bootstrap:
-        _create_stage1_bootstrap(
-            ctx,
-            output = prelude,
-            stage2_bootstrap = stage2_bootstrap,
-            runtime_details = runtime_details,
-            is_for_zip = True,
-            venv = venv,
-        )
-    else:
-        ctx.actions.write(prelude, "#!/usr/bin/env python3\n")
-
-    ctx.actions.run_shell(
-        command = "cat {prelude} {zip} > {output}".format(
-            prelude = prelude.path,
-            zip = zip_file.path,
-            output = output.path,
-        ),
-        inputs = [prelude, zip_file],
-        outputs = [output],
-        use_default_shell_env = True,
-        mnemonic = "PyBuildExecutableZip",
-        progress_message = "Build Python zip executable: %{label}",
-    )
-
-def _get_cc_details_for_binary(ctx, extra_deps):
-    cc_info = collect_cc_info(ctx, extra_deps = extra_deps)
-    return create_cc_details_struct(
-        cc_info_for_propagating = cc_info,
-        cc_info_for_self_link = cc_info,
-        cc_info_with_extra_link_time_libraries = None,
-        extra_runfiles = ctx.runfiles(),
-        # Though the rules require the CcToolchain, it isn't actually used.
-        cc_toolchain = None,
-        feature_config = None,
-    )
-
-def _get_interpreter_path(ctx, *, runtime, flag_interpreter_path):
-    if runtime:
-        if runtime.interpreter_path:
-            interpreter_path = runtime.interpreter_path
-        else:
-            interpreter_path = "{}/{}".format(
-                ctx.workspace_name,
-                runtime.interpreter.short_path,
-            )
-
-            # NOTE: External runfiles (artifacts in other repos) will have a
-            # leading path component of "../" so that they refer outside the
-            # main workspace directory and into the runfiles root. By
-            # normalizing, we simplify e.g. "workspace/../foo/bar" to simply
-            # "foo/bar"
-            interpreter_path = paths.normalize(interpreter_path)
-
-    elif flag_interpreter_path:
-        interpreter_path = flag_interpreter_path
-    else:
-        fail("Unable to determine interpreter path")
-
-    return interpreter_path
-
-def _get_native_deps_dso_name(ctx):
-    _ = ctx  # @unused
-    fail("Building native deps DSO not supported.")
-
-def _get_native_deps_user_link_flags(ctx):
-    _ = ctx  # @unused
-    fail("Building native deps DSO not supported.")
diff --git a/python/private/py_library_rule.bzl b/python/private/py_library_rule.bzl
index ed64716..8a8d6cf 100644
--- a/python/private/py_library_rule.bzl
+++ b/python/private/py_library_rule.bzl
@@ -13,8 +13,8 @@
 # limitations under the License.
 """Implementation of py_library rule."""
 
-load(":common.bzl", "create_library_semantics_struct")
-load(":common_bazel.bzl", "collect_cc_info", "get_imports", "maybe_precompile")
+load(":common.bzl", "collect_cc_info", "create_library_semantics_struct", "get_imports")
+load(":precompile.bzl", "maybe_precompile")
 load(":py_library.bzl", "create_py_library_rule", "py_library_impl")
 
 def _py_library_impl_with_semantics(ctx):
diff --git a/python/private/py_test_macro.bzl b/python/private/py_test_macro.bzl
index 1f9330f..348e877 100644
--- a/python/private/py_test_macro.bzl
+++ b/python/private/py_test_macro.bzl
@@ -13,7 +13,7 @@
 # limitations under the License.
 """Implementation of macro-half of py_test rule."""
 
-load(":common_bazel.bzl", "convert_legacy_create_init_to_int")
+load(":py_executable.bzl", "convert_legacy_create_init_to_int")
 load(":py_test_rule.bzl", py_test_rule = "py_test")
 
 def py_test(**kwargs):
diff --git a/python/private/py_test_rule.bzl b/python/private/py_test_rule.bzl
index 64d5f21..63000c7 100644
--- a/python/private/py_test_rule.bzl
+++ b/python/private/py_test_rule.bzl
@@ -17,9 +17,9 @@
 load(":attributes.bzl", "AGNOSTIC_TEST_ATTRS")
 load(":common.bzl", "maybe_add_test_execution_info")
 load(
-    ":py_executable_bazel.bzl",
+    ":py_executable.bzl",
     "create_executable_rule",
-    "py_executable_bazel_impl",
+    "py_executable_impl",
 )
 
 _BAZEL_PY_TEST_ATTRS = {
@@ -40,7 +40,7 @@
 }
 
 def _py_test_impl(ctx):
-    providers = py_executable_bazel_impl(
+    providers = py_executable_impl(
         ctx = ctx,
         is_test = True,
         inherited_environment = ctx.attr.env_inherit,
diff --git a/tests/bootstrap_impls/venv_relative_path_tests.bzl b/tests/bootstrap_impls/venv_relative_path_tests.bzl
index b21f220..ad4870f 100644
--- a/tests/bootstrap_impls/venv_relative_path_tests.bzl
+++ b/tests/bootstrap_impls/venv_relative_path_tests.bzl
@@ -15,7 +15,7 @@
 "Unit tests for relative_path computation"
 
 load("@rules_testing//lib:test_suite.bzl", "test_suite")
-load("//python/private:py_executable_bazel.bzl", "relative_path")  # buildifier: disable=bzl-visibility
+load("//python/private:py_executable.bzl", "relative_path")  # buildifier: disable=bzl-visibility
 
 _tests = []