python: Make Starlark implementation of Python rules build/pass most tests

This makes the Starlark implementation load without errors and pass almost all
of the Java unit tests for the Python rules.

* Expose `--python_path` flag via fragments to Starlark
* Remove load of Google-only toplevel_aliases to get CcInfo symbol
* Implement `imports` attribute completely/correctly; the semantics
  `get_imports()` function is only for processing the `imports` attribute
  itself, not all imports for a target. That is done by (the new) common
  function `collect_imports()`.
* Only return OutputGroupInfo once (the `output_groups` arg of
  `_create_providers()` would cause it to be returned twice.
* Make `PyCcLinkParamsProvider` usable: bind this global name to the actual
  Python rule implementation instead of the C++ no-op implementation.
* Move the Python-specific `PyWrapCcInfo` and `py_wrap_cc_helper_do_not_use`
  symbols out of the C++ rules and into the Python rules. This is necessary do
  avoid a circular dependency between the two rule sets after moving the
  `PyCcLinkParamsProvider` into the Python rules. These two symbols are still
  bound to no-ops in Bazel because they are unused in the Bazel parts of the
  Python rules.
* Fix an undefined variable usage in the Bazel `get_imports` function.
* Various Bazel-specific functions were slightly renamed to better distinguish
  them from their common counterparts.
* Pass missing `inherited_environment` arg in `py_binary`
* Make `py_binary`, `py_test`, and `py_library` macros load and call their
  respective rule implementations.
* Fix `_create_providers` returns doc.
* Implement `legacy_create_init` attribute
* Implement and fixes for `--build_python_zip`, boostrap-template-executables,
  and Windows launcher executables.
  * Fix computing the base executable name: `.basename` attribute, not `.name`
  * Create zip and supporting files next to the executable (use the `sibling`
    arg when declaring the artifacts)
  * Add the zip file to files to build outputs.
  * Fix undefined variable usage (`bootstrap_output`, not `bootstrap_template`)
  * Add stub intended for the zip to the zip action's inputs
* Add missing methods to Bazel binary semantics object. Note that a few
  features are still unimplemented (i.e. coverage).
* Fix `PLATFORMS_LOCATION` prefix (missing leading slash)
* Fix fragment name for getting `--python_path` flag.

Closes #17188. Work towards #15897.

PiperOrigin-RevId: 502656401
Change-Id: I2cbf103ca1159f8e78dacbe91fd24dd1c60e5dab
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index bee8e13..728538f 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -102,6 +102,7 @@
 import com.google.devtools.build.lib.rules.cpp.CcSharedLibraryPermissionsRule;
 import com.google.devtools.build.lib.rules.cpp.CcSharedLibraryRule;
 import com.google.devtools.build.lib.rules.cpp.CcStarlarkInternal;
+import com.google.devtools.build.lib.rules.cpp.GoogleLegacyStubs;
 import com.google.devtools.build.lib.rules.cpp.proto.CcProtoLibraryRule;
 import com.google.devtools.build.lib.rules.objc.BazelObjcStarlarkInternal;
 import com.google.devtools.build.lib.rules.objc.ObjcStarlarkInternal;
@@ -111,6 +112,7 @@
 import com.google.devtools.build.lib.rules.proto.ProtoConfiguration;
 import com.google.devtools.build.lib.rules.proto.ProtoInfo;
 import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainRule;
+import com.google.devtools.build.lib.rules.python.PyCcLinkParamsProvider;
 import com.google.devtools.build.lib.rules.python.PyInfo;
 import com.google.devtools.build.lib.rules.python.PyRuleClasses.PySymlink;
 import com.google.devtools.build.lib.rules.python.PyRuntimeInfo;
@@ -471,7 +473,12 @@
 
           builder.addStarlarkBootstrap(
               new PyBootstrap(
-                  PyInfo.PROVIDER, PyRuntimeInfo.PROVIDER, PyStarlarkTransitions.INSTANCE));
+                  PyInfo.PROVIDER,
+                  PyRuntimeInfo.PROVIDER,
+                  PyStarlarkTransitions.INSTANCE,
+                  new GoogleLegacyStubs.PyWrapCcHelper(),
+                  new GoogleLegacyStubs.PyWrapCcInfoProvider(),
+                  PyCcLinkParamsProvider.PROVIDER));
 
           builder.addSymlinkDefinition(PySymlink.PY2);
           builder.addSymlinkDefinition(PySymlink.PY3);
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java
index b9a75e2..506b6af 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java
@@ -41,7 +41,6 @@
 import com.google.devtools.build.lib.rules.cpp.DebugPackageProvider;
 import com.google.devtools.build.lib.rules.cpp.FdoPrefetchHintsRule;
 import com.google.devtools.build.lib.rules.cpp.FdoProfileRule;
-import com.google.devtools.build.lib.rules.cpp.GoogleLegacyStubs;
 import com.google.devtools.build.lib.rules.cpp.GraphNodeAspect;
 import com.google.devtools.build.lib.rules.cpp.PropellerOptimizeRule;
 import com.google.devtools.build.lib.rules.platform.PlatformRules;
@@ -97,10 +96,7 @@
             new BazelCcModule(),
             CcInfo.PROVIDER,
             DebugPackageProvider.PROVIDER,
-            CcToolchainConfigInfo.PROVIDER,
-            new GoogleLegacyStubs.PyWrapCcHelper(),
-            new GoogleLegacyStubs.PyWrapCcInfoProvider(),
-            new GoogleLegacyStubs.PyCcLinkParamsProvider()));
+            CcToolchainConfigInfo.PROVIDER));
 
     try {
       builder.addWorkspaceFileSuffix(
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java
index 1c4766a..e6afbd5 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java
@@ -148,6 +148,10 @@
     return options.pythonTop;
   }
 
+  @StarlarkMethod(
+      name = "python_path",
+      structField = true,
+      doc = "The value of the --python_path flag.")
   public String getPythonPath() {
     return options.pythonPath == null ? "python" : options.pythonPath;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/BUILD b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/BUILD
index 8892c7b..96c2878 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/BUILD
@@ -30,7 +30,6 @@
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/core",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/go",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/platform",
-        "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python",
         "//src/main/java/net/starlark/java/annot",
         "//src/main/java/net/starlark/java/eval",
         "//third_party:guava",
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcBootstrap.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcBootstrap.java
index ddf936b..33afd77 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcBootstrap.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcBootstrap.java
@@ -24,8 +24,6 @@
 import com.google.devtools.build.lib.starlarkbuildapi.core.Bootstrap;
 import com.google.devtools.build.lib.starlarkbuildapi.core.ContextAndFlagGuardedValue;
 import com.google.devtools.build.lib.starlarkbuildapi.platform.ConstraintValueInfoApi;
-import com.google.devtools.build.lib.starlarkbuildapi.python.PyBootstrap;
-import net.starlark.java.eval.FlagGuardedValue;
 
 /** {@link Bootstrap} for Starlark objects related to cpp rules. */
 public class CcBootstrap implements Bootstrap {
@@ -70,9 +68,6 @@
   private final CcInfoApi.Provider<? extends FileApi> ccInfoProvider;
   private final DebugPackageInfoApi.Provider<? extends FileApi> debugPackageInfoProvider;
   private final CcToolchainConfigInfoApi.Provider ccToolchainConfigInfoProvider;
-  private final PyWrapCcHelperApi<?, ?, ?, ?, ?, ?, ?, ?, ?> pyWrapCcHelper;
-  private final PyWrapCcInfoApi.Provider pyWrapCcInfoProvider;
-  private final PyCcLinkParamsProviderApi.Provider pyCcLinkInfoParamsInfoProvider;
 
   public CcBootstrap(
       CcModuleApi<
@@ -107,17 +102,11 @@
           ccModule,
       CcInfoApi.Provider<? extends FileApi> ccInfoProvider,
       DebugPackageInfoApi.Provider<? extends FileApi> debugPackageInfoProvider,
-      CcToolchainConfigInfoApi.Provider ccToolchainConfigInfoProvider,
-      PyWrapCcHelperApi<?, ?, ?, ?, ?, ?, ?, ?, ?> pyWrapCcHelper,
-      PyWrapCcInfoApi.Provider pyWrapCcInfoProvider,
-      PyCcLinkParamsProviderApi.Provider pyCcLinkInfoParamsInfoProvider) {
+      CcToolchainConfigInfoApi.Provider ccToolchainConfigInfoProvider) {
     this.ccModule = ccModule;
     this.ccInfoProvider = ccInfoProvider;
     this.debugPackageInfoProvider = debugPackageInfoProvider;
     this.ccToolchainConfigInfoProvider = ccToolchainConfigInfoProvider;
-    this.pyWrapCcHelper = pyWrapCcHelper;
-    this.pyWrapCcInfoProvider = pyWrapCcInfoProvider;
-    this.pyCcLinkInfoParamsInfoProvider = pyCcLinkInfoParamsInfoProvider;
   }
 
   @Override
@@ -146,21 +135,5 @@
             BuildLanguageOptions.INCOMPATIBLE_STOP_EXPORTING_LANGUAGE_MODULES,
             ccToolchainConfigInfoProvider,
             allowedRepositories));
-    builder.put(
-        "py_wrap_cc_helper_do_not_use",
-        FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
-            BuildLanguageOptions.EXPERIMENTAL_GOOGLE_LEGACY_API, pyWrapCcHelper));
-    builder.put(
-        "PyWrapCcInfo",
-        ContextAndFlagGuardedValue.onlyInAllowedReposOrWhenIncompatibleFlagIsFalse(
-            BuildLanguageOptions.INCOMPATIBLE_STOP_EXPORTING_LANGUAGE_MODULES,
-            pyWrapCcInfoProvider,
-            PyBootstrap.allowedRepositories));
-    builder.put(
-        "PyCcLinkParamsProvider",
-        ContextAndFlagGuardedValue.onlyInAllowedReposOrWhenIncompatibleFlagIsFalse(
-            BuildLanguageOptions.INCOMPATIBLE_STOP_EXPORTING_LANGUAGE_MODULES,
-            pyCcLinkInfoParamsInfoProvider,
-            PyBootstrap.allowedRepositories));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/BUILD b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/BUILD
index 24e8a68..6bea525 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/BUILD
@@ -26,6 +26,7 @@
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/core",
+        "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp",
         "//src/main/java/net/starlark/java/annot",
         "//src/main/java/net/starlark/java/eval",
         "//third_party:guava",
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/PyBootstrap.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/PyBootstrap.java
index 1b6c95a..fcfe03e 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/PyBootstrap.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/python/PyBootstrap.java
@@ -20,6 +20,9 @@
 import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
 import com.google.devtools.build.lib.starlarkbuildapi.core.Bootstrap;
 import com.google.devtools.build.lib.starlarkbuildapi.core.ContextAndFlagGuardedValue;
+import com.google.devtools.build.lib.starlarkbuildapi.cpp.PyCcLinkParamsProviderApi;
+import com.google.devtools.build.lib.starlarkbuildapi.cpp.PyWrapCcHelperApi;
+import com.google.devtools.build.lib.starlarkbuildapi.cpp.PyWrapCcInfoApi;
 import com.google.devtools.build.lib.starlarkbuildapi.python.PyInfoApi.PyInfoProviderApi;
 import com.google.devtools.build.lib.starlarkbuildapi.python.PyRuntimeInfoApi.PyRuntimeInfoProviderApi;
 import net.starlark.java.eval.FlagGuardedValue;
@@ -36,14 +39,23 @@
   private final PyInfoProviderApi pyInfoProviderApi;
   private final PyRuntimeInfoProviderApi pyRuntimeInfoProviderApi;
   private final PyStarlarkTransitionsApi pyStarlarkTransitionsApi;
+  private final PyWrapCcHelperApi<?, ?, ?, ?, ?, ?, ?, ?, ?> pyWrapCcHelper;
+  private final PyWrapCcInfoApi.Provider pyWrapCcInfoProvider;
+  private final PyCcLinkParamsProviderApi.Provider pyCcLinkInfoParamsInfoProvider;
 
   public PyBootstrap(
       PyInfoProviderApi pyInfoProviderApi,
       PyRuntimeInfoProviderApi pyRuntimeInfoProviderApi,
-      PyStarlarkTransitionsApi pyStarlarkTransitionsApi) {
+      PyStarlarkTransitionsApi pyStarlarkTransitionsApi,
+      PyWrapCcHelperApi<?, ?, ?, ?, ?, ?, ?, ?, ?> pyWrapCcHelper,
+      PyWrapCcInfoApi.Provider pyWrapCcInfoProvider,
+      PyCcLinkParamsProviderApi.Provider pyCcLinkInfoParamsInfoProvider) {
     this.pyInfoProviderApi = pyInfoProviderApi;
     this.pyRuntimeInfoProviderApi = pyRuntimeInfoProviderApi;
     this.pyStarlarkTransitionsApi = pyStarlarkTransitionsApi;
+    this.pyWrapCcHelper = pyWrapCcHelper;
+    this.pyWrapCcInfoProvider = pyWrapCcInfoProvider;
+    this.pyCcLinkInfoParamsInfoProvider = pyCcLinkInfoParamsInfoProvider;
   }
 
   @Override
@@ -65,5 +77,21 @@
         "py_transitions",
         FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
             BuildLanguageOptions.EXPERIMENTAL_GOOGLE_LEGACY_API, pyStarlarkTransitionsApi));
+    builder.put(
+        "py_wrap_cc_helper_do_not_use",
+        FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
+            BuildLanguageOptions.EXPERIMENTAL_GOOGLE_LEGACY_API, pyWrapCcHelper));
+    builder.put(
+        "PyWrapCcInfo",
+        ContextAndFlagGuardedValue.onlyInAllowedReposOrWhenIncompatibleFlagIsFalse(
+            BuildLanguageOptions.INCOMPATIBLE_STOP_EXPORTING_LANGUAGE_MODULES,
+            pyWrapCcInfoProvider,
+            allowedRepositories));
+    builder.put(
+        "PyCcLinkParamsProvider",
+        ContextAndFlagGuardedValue.onlyInAllowedReposOrWhenIncompatibleFlagIsFalse(
+            BuildLanguageOptions.INCOMPATIBLE_STOP_EXPORTING_LANGUAGE_MODULES,
+            pyCcLinkInfoParamsInfoProvider,
+            allowedRepositories));
   }
 }
diff --git a/src/main/starlark/builtins_bzl/common/python/attributes.bzl b/src/main/starlark/builtins_bzl/common/python/attributes.bzl
index 987f4d5..f00b575 100644
--- a/src/main/starlark/builtins_bzl/common/python/attributes.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/attributes.bzl
@@ -15,7 +15,6 @@
 
 load(":common/python/common.bzl", "union_attrs")
 load(":common/python/providers.bzl", "PyInfo")
-load(":blaze/common/toplevel_aliases.bzl", "CcInfo")
 load(
     ":common/python/semantics.bzl",
     "DEPS_ATTR_ALLOW_RULES",
@@ -24,6 +23,7 @@
     "TOOLS_REPO",
 )
 
+_CcInfo = _builtins.toplevel.CcInfo
 _STAMP_VALUES = [-1, 0, 1]
 
 def create_stamp_attr(**kwargs):
@@ -104,7 +104,7 @@
         # Use create_srcs_attr to create one.
         "srcs": None,
         "deps": attr.label_list(
-            providers = [[PyInfo], [CcInfo]],
+            providers = [[PyInfo], [_CcInfo]],
             # TODO(b/228692666): Google-specific; remove these allowances once
             # the depot is cleaned up.
             allow_rules = DEPS_ATTR_ALLOW_RULES,
diff --git a/src/main/starlark/builtins_bzl/common/python/common.bzl b/src/main/starlark/builtins_bzl/common/python/common.bzl
index 8224ac3..a05b774 100644
--- a/src/main/starlark/builtins_bzl/common/python/common.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/common.bzl
@@ -29,7 +29,6 @@
 # Extensions without the dot
 _PYTHON_SOURCE_EXTENSIONS = ["py"]
 
-# Todo: rename this to "binary" and split off library-parts
 def create_binary_semantics_struct(
         *,
         create_executable,
@@ -74,8 +73,9 @@
             of additional environment variable to pass to build data generation.
         get_interpreter_path: Callable that returns an optional string, which is
             the path to the Python interpreter to use for running the binary.
-        get_imports: Callable that returns a depset of the target's import
-            paths.
+        get_imports: Callable that returns a list of the target's import
+            paths (from the `imports` attribute, so just the target's own import
+            path strings, not from dependencies).
         get_native_deps_dso_name: Callable that returns a string, which is the
             basename (with extension) of the native deps DSO library.
         get_native_deps_user_link_flags: Callable that returns a list of strings,
@@ -253,6 +253,13 @@
     # as a valid extension.
     return [f for f in srcs if f.extension == "py"]
 
+def collect_imports(ctx, semantics):
+    return depset(direct = semantics.get_imports(ctx), transitive = [
+        dep[PyInfo].imports
+        for dep in ctx.attr.deps
+        if PyInfo in dep
+    ])
+
 def collect_runfiles(ctx, files):
     """Collects the necessary files from the rule's context.
 
@@ -402,10 +409,11 @@
         extensions = _PYTHON_SOURCE_EXTENSIONS,
     )
 
-def create_output_group_info(transitive_sources):
+def create_output_group_info(transitive_sources, extra_groups):
     return OutputGroupInfo(
         compilation_prerequisites_INTERNAL_ = transitive_sources,
         compilation_outputs = transitive_sources,
+        **extra_groups
     )
 
 def maybe_add_test_execution_info(providers, ctx):
diff --git a/src/main/starlark/builtins_bzl/common/python/common_bazel.bzl b/src/main/starlark/builtins_bzl/common/python/common_bazel.bzl
index 1ae55e3..125bcc3 100644
--- a/src/main/starlark/builtins_bzl/common/python/common_bazel.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/common_bazel.bzl
@@ -13,6 +13,8 @@
 # limitations under the License.
 """Common functions that are specific to Bazel rule implementation"""
 
+load(":common/python/providers.bzl", "PyCcLinkParamsProvider")
+
 _py_builtins = _builtins.internal.py_builtins
 _CcInfo = _builtins.toplevel.CcInfo
 _cc_common = _builtins.toplevel.cc_common
@@ -35,8 +37,10 @@
     for dep in deps:
         if _CcInfo in dep:
             cc_infos.append(dep[_CcInfo])
+
         if PyCcLinkParamsProvider in dep:
             cc_infos.append(dep[PyCcLinkParamsProvider].cc_info)
+
     return _cc_common.merge_cc_infos(cc_infos = cc_infos)
 
 def maybe_precompile(ctx, srcs):
@@ -72,12 +76,15 @@
     )
     result = []
     for import_str in ctx.attr.imports:
+        # todo: check if $(location) expansion is done here
         import_str = ctx.expand_make_variables(import_str)
         if import_str.startswith("/"):
             continue
-        import_path = "{}/{}".format(prefix, expanded)
+        import_path = "{}/{}".format(prefix, import_str)
 
         # TODO: Reject paths with uplevel references (see
-        # PathFragment#containsUplevelReferences)
+        # PathFragment#containsUplevelReferences). Basically, we want to
+        # prevent imports="../../../../../" (or similar) from "escaping" out
+        # of the runfiles tree.
         result.append(import_path)
     return result
diff --git a/src/main/starlark/builtins_bzl/common/python/py_binary_bazel.bzl b/src/main/starlark/builtins_bzl/common/python/py_binary_bazel.bzl
index 726676f..abd9a29 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_binary_bazel.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_binary_bazel.bzl
@@ -17,7 +17,7 @@
 load(
     ":common/python/py_executable_bazel.bzl",
     "create_executable_rule",
-    "py_executable_impl",
+    "py_executable_bazel_impl",
 )
 load(":common/python/attributes.bzl", "AGNOSTIC_BINARY_ATTRS")
 
@@ -35,7 +35,11 @@
 }
 
 def _py_binary_impl(ctx):
-    return py_executable_impl(ctx = ctx, is_test = False)
+    return py_executable_bazel_impl(
+        ctx = ctx,
+        is_test = False,
+        inherited_environment = [],
+    )
 
 py_binary = create_executable_rule(
     implementation = _py_binary_impl,
diff --git a/src/main/starlark/builtins_bzl/common/python/py_binary_macro.bzl b/src/main/starlark/builtins_bzl/common/python/py_binary_macro.bzl
index 82f15a26..833c98f 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_binary_macro.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_binary_macro.bzl
@@ -13,6 +13,7 @@
 # limitations under the License.
 """Implementation of macro-half of py_binary rule."""
 
+load(":common/python/py_binary_bazel.bzl", py_binary_rule = "py_binary")
+
 def py_binary(**kwargs):
-    _ = kwargs  # @unused
-    pass
+    py_binary_rule(**kwargs)
diff --git a/src/main/starlark/builtins_bzl/common/python/py_executable.bzl b/src/main/starlark/builtins_bzl/common/python/py_executable.bzl
index 1591a65..bc8b63c 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_executable.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_executable.bzl
@@ -17,6 +17,7 @@
 load(
     ":common/python/common.bzl",
     "TOOLCHAIN_TYPE",
+    "collect_imports",
     "collect_runfiles",
     "create_instrumented_files_info",
     "create_output_group_info",
@@ -108,7 +109,7 @@
     main_py = determine_main(ctx)
     direct_sources = filter_to_py_srcs(ctx.files.srcs)
     output_sources = semantics.maybe_precompile(ctx, direct_sources)
-    imports = semantics.get_imports(ctx)
+    imports = collect_imports(ctx, semantics)
     executable, files_to_build = _compute_outputs(ctx, output_sources)
 
     runtime_details = _get_runtime_details(ctx, semantics)
@@ -671,8 +672,9 @@
         semantics: BinarySemantics struct; see create_binary_semantics()
 
     Returns:
-        A value that rules can return. i.e. a list of providers or
-        a struct of providers.
+        A two-tuple of:
+        1. A dict of legacy providers.
+        2. A list of modern providers.
     """
     providers = [
         DefaultInfo(
@@ -707,7 +709,7 @@
         )
 
     providers.append(py_info)
-    providers.append(create_output_group_info(py_info.transitive_sources))
+    providers.append(create_output_group_info(py_info.transitive_sources, output_groups))
 
     extra_legacy_providers, extra_providers = semantics.get_extra_providers(
         ctx,
@@ -715,9 +717,6 @@
         runtime_details = runtime_details,
     )
     providers.extend(extra_providers)
-    if output_groups:
-        providers.append(OutputGroupInfo(**output_groups))
-
     return extra_legacy_providers, providers
 
 def _create_run_environment_info(ctx, inherited_environment):
diff --git a/src/main/starlark/builtins_bzl/common/python/py_executable_bazel.bzl b/src/main/starlark/builtins_bzl/common/python/py_executable_bazel.bzl
index 7c6a660..eca06cd 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_executable_bazel.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_executable_bazel.bzl
@@ -13,16 +13,49 @@
 # limitations under the License.
 """Implementation for Bazel Python executable."""
 
+load(":common/paths.bzl", "paths")
 load(":common/python/attributes_bazel.bzl", "IMPORTS_ATTRS")
-load(":common/python/common_bazel.bzl", "collect_cc_info")
+load(
+    ":common/python/common.bzl",
+    "create_binary_semantics_struct",
+    "create_cc_details_struct",
+    "create_executable_result_struct",
+    "union_attrs",
+)
+load(":common/python/common_bazel.bzl", "collect_cc_info", "get_imports", "maybe_precompile")
 load(":common/python/providers.bzl", "DEFAULT_STUB_SHEBANG")
-load(":common/python/py_executable.bzl", "py_executable_base_impl")
+load(
+    ":common/python/py_executable.bzl",
+    "create_base_executable_rule",
+    "py_executable_base_impl",
+)
 load(":common/python/semantics.bzl", "TOOLS_REPO")
 
+_py_builtins = _builtins.internal.py_builtins
+_EXTERNAL_PATH_PREFIX = "external"
+_ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
+
 BAZEL_EXECUTABLE_ATTRS = union_attrs(
     IMPORTS_ATTRS,
     {
-        "_zipper": attr.label(cfg = "exec"),
+        "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.
+                                       """,
+        ),
+        "_zipper": attr.label(
+            cfg = "exec",
+            executable = True,
+            default = "@" + TOOLS_REPO + "//tools/zip:zipper",
+        ),
         "_launcher": attr.label(
             cfg = "target",
             default = "@" + TOOLS_REPO + "//tools/launcher:launcher",
@@ -35,6 +68,10 @@
         # TODO: Remove this attribute; it's basically a no-op in Bazel due
         # to toolchain resolution.
         "_py_interpreter": attr.label(),
+        "_bootstrap_template": attr.label(
+            allow_single_file = True,
+            default = "@" + TOOLS_REPO + "//tools/python:python_bootstrap_template.txt",
+        ),
     },
 )
 
@@ -45,7 +82,8 @@
         **kwargs
     )
 
-def py_executable_impl(ctx, *, is_test, inherited_environment):
+def py_executable_bazel_impl(ctx, *, is_test, inherited_environment):
+    """Common code for executables for Baze."""
     result = py_executable_base_impl(
         ctx = ctx,
         semantics = create_binary_semantics_bazel(),
@@ -58,17 +96,51 @@
     )
 
 def create_binary_semantics_bazel():
-    return create_binary_semantics_bazel_common(
+    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,
-        should_build_native_deps_dso = _should_build_native_deps_dso,
+        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
     )
 
+# todo: implement by getting coverage files from the runtime
+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 ctx.fragments.py.default_to_explicit_init_py
+    else:
+        return bool(ctx.attr.legacy_create_init)
+
 def _create_executable(
         ctx,
         *,
@@ -95,12 +167,12 @@
     if is_windows:
         if not executable.name.extension == "exe":
             fail("Should not happen: somehow we are generating a non-.exe file on windows")
-        base_executable_name = executable.name[0:-4]
+        base_executable_name = executable.basename[0:-4]
     else:
-        base_executable_name = executable.name
+        base_executable_name = executable.basename
 
-    zip_bootstrap = ctx.actions.declare(base_executable_name + ".temp")
-    zip_file = ctx.actions.declare(base_executable_name + ".zip")
+    zip_bootstrap = ctx.actions.declare_file(base_executable_name + ".temp", sibling = executable)
+    zip_file = ctx.actions.declare_file(base_executable_name + ".zip", sibling = executable)
 
     _expand_bootstrap_template(
         ctx,
@@ -112,10 +184,11 @@
         ctx,
         output = zip_file,
         original_nonzip_executable = executable,
-        zipfile_executable = zip_bootstrap,
+        executable_for_zip_file = zip_bootstrap,
         runfiles = runfiles_details.default_runfiles,
     )
 
+    extra_files_to_build = []
     build_zip_enabled = ctx.fragments.py.build_python_zip
 
     # When --build_python_zip is enabled, then the zip file becomes
@@ -155,17 +228,17 @@
             extra_files_to_build.append(bootstrap_output)
 
     if should_create_executable_zip:
-        if bootstrap_template != None:
-            fail("Should not occur: bootstrap_template should not be used " +
+        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)
     else:
-        if bootstrap_template == None:
-            fail("Should not occur: bootstrap_template should set when " +
+        if bootstrap_output == None:
+            fail("Should not occur: bootstrap_output should set when " +
                  "build a bootstrap-template-based executable")
         _expand_bootstrap_template(
             ctx,
-            output = bootstrap_template,
+            output = bootstrap_output,
             is_for_zip = build_zip_enabled,
             **common_bootstrap_template_kwargs
         )
@@ -243,7 +316,7 @@
         progress_message = "Creating launcher for %{label}",
     )
 
-def _create_zip_file(ctx, *, zip_file, original_nonzip_executable, zip_executable, runfiles):
+def _create_zip_file(ctx, *, output, original_nonzip_executable, executable_for_zip_file, runfiles):
     workspace_name = ctx.workspace_name
     legacy_external_runfiles = _py_builtins.get_legacy_external_runfiles(ctx)
 
@@ -251,7 +324,7 @@
     manifest.use_param_file("%s", use_always = True)
     manifest.set_param_file_format("multiline")
 
-    manifest.add("__main__.py=%s", zip_executable)
+    manifest.add("__main__.py=%s", executable_for_zip_file)
     manifest.add("__init__.py=")
     manifest.add(
         "%s=",
@@ -261,7 +334,7 @@
         manifest.add("%s=", _get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles))
 
     def map_zip_runfiles(file):
-        if file != original_nonzip_executable and file != zip_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,
@@ -270,22 +343,22 @@
             return None
 
     manifest.add_all(runfiles.files, map_each = map_zip_runfiles, allow_closure = True)
-    inputs = []
+    inputs = [executable_for_zip_file]
     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 != zip_file:
+        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(zip_file)
+    zip_cli_args.add(output)
     ctx.actions.run(
         executable = ctx.executable._zipper,
         arguments = [zip_cli_args, manifest],
         inputs = depset(inputs),
-        outputs = [zip_file],
+        outputs = [output],
         use_default_shell_env = True,
         mnemonic = "PythonZipper",
         progress_message = "Building Python zip: %{label}",
@@ -293,10 +366,10 @@
 
 def _get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles):
     if legacy_external_runfiles and path.startswith(_EXTERNAL_PATH_PREFIX):
-        zip_runfiles_path = path.relativeTo(EXTERNAL_PATH_PREFIX)
+        zip_runfiles_path = paths.relativize(_EXTERNAL_PATH_PREFIX, path)
     else:
         zip_runfiles_path = "{}/{}".format(workspace_name, path)
-    return ZIP_RUNFILES_DIRECTORY_NAME.getRelative(zip_runfiles_path)
+    return "{}/{}".format(_ZIP_RUNFILES_DIRECTORY_NAME, zip_runfiles_path)
 
 def _create_executable_zip_file(ctx, *, output, zip_file):
     ctx.actions.run_shell(
diff --git a/src/main/starlark/builtins_bzl/common/python/py_library.bzl b/src/main/starlark/builtins_bzl/common/python/py_library.bzl
index c0ebbf3..a9b36fd 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_library.bzl
@@ -23,6 +23,7 @@
 )
 load(
     ":common/python/common.bzl",
+    "collect_imports",
     "collect_runfiles",
     "create_instrumented_files_info",
     "create_output_group_info",
@@ -58,7 +59,7 @@
     py_info, deps_transitive_sources = create_py_info(
         ctx,
         direct_sources = depset(direct_sources),
-        imports = semantics.get_imports(ctx),
+        imports = collect_imports(ctx, semantics),
     )
 
     # TODO(b/253059598): Remove support for extra actions; https://github.com/bazelbuild/bazel/issues/16455
@@ -74,7 +75,7 @@
         py_info,
         create_instrumented_files_info(ctx),
         _py_builtins.new_py_cc_link_params_provider(cc_info = cc_info),
-        create_output_group_info(py_info.transitive_sources),
+        create_output_group_info(py_info.transitive_sources, extra_groups = {}),
     ]
 
 def create_py_library_rule(*, attrs = {}, **kwargs):
diff --git a/src/main/starlark/builtins_bzl/common/python/py_library_bazel.bzl b/src/main/starlark/builtins_bzl/common/python/py_library_bazel.bzl
index 6d306d3e..b844b97 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_library_bazel.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_library_bazel.bzl
@@ -14,16 +14,19 @@
 """Implementation of py_library for Bazel."""
 
 load(
-    ":common/python/attributes.bzl",
-    "IMPORTS_ATTR",
+    ":common/python/attributes_bazel.bzl",
+    "IMPORTS_ATTRS",
 )
 load(
     ":common/python/common.bzl",
-    "collect_cc_info",
     "create_library_semantics_struct",
+    "union_attrs",
+)
+load(
+    ":common/python/common_bazel.bzl",
+    "collect_cc_info",
     "get_imports",
     "maybe_precompile",
-    "union_attrs",
 )
 load(
     ":common/python/py_library.bzl",
@@ -34,7 +37,7 @@
 
 _BAZEL_LIBRARY_ATTRS = union_attrs(
     LIBRARY_ATTRS,
-    IMPORTS_ATTR,
+    IMPORTS_ATTRS,
 )
 
 def create_library_semantics_bazel():
diff --git a/src/main/starlark/builtins_bzl/common/python/py_library_macro.bzl b/src/main/starlark/builtins_bzl/common/python/py_library_macro.bzl
index a93edfa..729c426 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_library_macro.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_library_macro.bzl
@@ -13,6 +13,7 @@
 # limitations under the License.
 """Implementation of macro-half of py_library rule."""
 
+load(":common/python/py_library_bazel.bzl", py_library_rule = "py_library")
+
 def py_library(**kwargs):
-    _ = kwargs  # @unused
-    pass
+    py_library_rule(**kwargs)
diff --git a/src/main/starlark/builtins_bzl/common/python/py_test_bazel.bzl b/src/main/starlark/builtins_bzl/common/python/py_test_bazel.bzl
index 2310620..0a9f3ff 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_test_bazel.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_test_bazel.bzl
@@ -13,11 +13,13 @@
 # limitations under the License.
 """Rule implementation of py_test for Bazel."""
 
+load(":common/python/semantics.bzl", "TOOLS_REPO")
 load(
     ":common/python/py_executable_bazel.bzl",
     "create_executable_rule",
-    "py_executable_impl",
+    "py_executable_bazel_impl",
 )
+load(":common/python/common.bzl", "maybe_add_test_execution_info")
 load(":common/python/attributes.bzl", "AGNOSTIC_TEST_ATTRS")
 
 _BAZEL_PY_TEST_ATTRS = {
@@ -38,7 +40,7 @@
 }
 
 def _py_test_impl(ctx):
-    providers = py_executable_impl(
+    providers = py_executable_bazel_impl(
         ctx = ctx,
         is_test = True,
         inherited_environment = ctx.attr.env_inherit,
diff --git a/src/main/starlark/builtins_bzl/common/python/py_test_macro.bzl b/src/main/starlark/builtins_bzl/common/python/py_test_macro.bzl
index 700ea8e..9a3b491 100644
--- a/src/main/starlark/builtins_bzl/common/python/py_test_macro.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/py_test_macro.bzl
@@ -13,6 +13,7 @@
 # limitations under the License.
 """Implementation of macro-half of py_test rule."""
 
+load(":common/python/py_test_bazel.bzl", py_test_rule = "py_test")
+
 def py_test(**kwargs):
-    _ = kwargs  # @unused
-    pass
+    py_test_rule(**kwargs)
diff --git a/src/main/starlark/builtins_bzl/common/python/semantics.bzl b/src/main/starlark/builtins_bzl/common/python/semantics.bzl
index d60bb4c..af8848e 100644
--- a/src/main/starlark/builtins_bzl/common/python/semantics.bzl
+++ b/src/main/starlark/builtins_bzl/common/python/semantics.bzl
@@ -16,14 +16,14 @@
 IMPORTS_ATTR_SUPPORTED = True
 
 TOOLS_REPO = "bazel_tools"
-PLATFORMS_LOCATION = "@platforms"
+PLATFORMS_LOCATION = "@platforms/"
 
 SRCS_ATTR_ALLOW_FILES = [".py", ".py3"]
 
 DEPS_ATTR_ALLOW_RULES = None
 
 PY_RUNTIME_ATTR_NAME = "_py_interpreter"
-PY_RUNTIME_FRAGMENT_NAME = "py"
+PY_RUNTIME_FRAGMENT_NAME = "bazel_py"
 PY_RUNTIME_FRAGMENT_ATTR_NAME = "python_path"
 
 BUILD_DATA_SYMLINK_PATH = "pyglib/build_data.txt"