Make `--incompatible_disallow_legacy_py_provider` a no-op

The legacy `py` provider struct is now silently ignored instead of causing
an error.

Cleanup for #7741

PiperOrigin-RevId: 449568980
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java
index 435914a..fedc65c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java
@@ -32,12 +32,10 @@
 import com.google.devtools.build.lib.packages.Attribute.LabelLateBoundDefault;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
-import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier;
 import com.google.devtools.build.lib.packages.TriState;
 import com.google.devtools.build.lib.rules.python.PyCommon;
 import com.google.devtools.build.lib.rules.python.PyInfo;
 import com.google.devtools.build.lib.rules.python.PyRuleClasses;
-import com.google.devtools.build.lib.rules.python.PyStructUtils;
 import com.google.devtools.build.lib.rules.python.PythonVersion;
 
 /**
@@ -69,17 +67,7 @@
           <a href="${link py_library}"><code>py_library</code></a> rules.
           <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
           .override(
-              builder
-                  .copy("deps")
-                  .mandatoryProvidersList(
-                      ImmutableList.of(
-                          // Legacy provider.
-                          // TODO(b/153363654): Remove this legacy set.
-                          ImmutableList.of(
-                              StarlarkProviderIdentifier.forLegacy(PyStructUtils.PROVIDER_NAME)),
-                          // Modern provider.
-                          ImmutableList.of(PyInfo.PROVIDER.id())))
-                  .allowedFileTypes())
+              builder.copy("deps").mandatoryProviders(PyInfo.PROVIDER.id()).allowedFileTypes())
           /* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(imports) -->
           List of import directories to be added to the <code>PYTHONPATH</code>.
           <p>
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
index 8a76a4a..a1b5ce1 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
@@ -207,7 +207,6 @@
     this.hasPy3OnlySources = initHasPy3OnlySources(ruleContext, this.sourcesVersion);
     this.runtimeFromToolchain = initRuntimeFromToolchain(ruleContext, this.version);
     validatePythonVersionAttr();
-    validateLegacyProviderNotUsedIfDisabled();
   }
 
   /** Returns the parsed value of the "srcs_version" attribute. */
@@ -244,17 +243,28 @@
   /**
    * Gathers transitive .py files from {@code deps} (not including this target's {@code srcs} and
    * adds them to {@code builder}.
+   *
+   * <p>If a target has the PyInfo provider, the value from that provider is used. Otherwise, we
+   * fall back on collecting .py source files from the target's filesToBuild.
    */
+  // TODO(bazel-team): Eliminate the fallback behavior by returning an appropriate py provider from
+  // the relevant rules.
   private static void collectTransitivePythonSourcesFromDeps(
       RuleContext ruleContext, NestedSetBuilder<Artifact> builder) {
     for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("deps")) {
-      try {
-        builder.addTransitive(PyProviderUtils.getTransitiveSources(dep));
-      } catch (EvalException e) {
-        // Either the provider type or field type is bad.
-        ruleContext.attributeError(
-            "deps", String.format("In dep '%s': %s", dep.getLabel(), e.getMessage()));
+      NestedSet<Artifact> sources;
+      if (dep.get(PyInfo.PROVIDER) != null) {
+        sources = dep.get(PyInfo.PROVIDER).getTransitiveSourcesSet();
+      } else {
+        sources =
+            NestedSetBuilder.<Artifact>compileOrder()
+                .addAll(
+                    FileType.filter(
+                        dep.getProvider(FileProvider.class).getFilesToBuild().toList(),
+                        PyRuleClasses.PYTHON_SOURCE))
+                .build();
       }
+      builder.addTransitive(sources);
     }
   }
 
@@ -308,34 +318,42 @@
       targets = ruleContext.getPrerequisites("deps");
     }
     for (TransitiveInfoCollection target : targets) {
-      try {
-        if (PyProviderUtils.getUsesSharedLibraries(target)) {
+      if (target.get(PyInfo.PROVIDER) != null) {
+        if (target.get(PyInfo.PROVIDER).getUsesSharedLibraries()) {
           return true;
         }
-      } catch (EvalException e) {
-        ruleContext.ruleError(String.format("In dep '%s': %s", target.getLabel(), e.getMessage()));
+      } else if (FileType.contains(
+          target.getProvider(FileProvider.class).getFilesToBuild().toList(),
+          CppFileTypes.SHARED_LIBRARY)) {
+        return true;
       }
     }
     return false;
   }
 
+  /**
+   * Returns the transitive import paths of a target.
+   *
+   * <p>For targets with the PyInfo provider, the value from that provider is used. Otherwise, we
+   * default to an empty set.
+   */
   private static NestedSet<String> initImports(RuleContext ruleContext, PythonSemantics semantics) {
     NestedSetBuilder<String> builder = NestedSetBuilder.compileOrder();
     builder.addAll(semantics.getImports(ruleContext));
     for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("deps")) {
-      try {
-        NestedSet<String> imports = PyProviderUtils.getImports(dep);
-        if (!builder.getOrder().isCompatible(imports.getOrder())) {
-          // TODO(brandjon): We should make order an invariant of the Python provider, and move this
-          // check into PyInfo/PyStructUtils.
-          ruleContext.ruleError(
-              getOrderErrorMessage(PyStructUtils.IMPORTS, builder.getOrder(), imports.getOrder()));
-        } else {
-          builder.addTransitive(imports);
-        }
-      } catch (EvalException e) {
-        ruleContext.attributeError(
-            "deps", String.format("In dep '%s': %s", dep.getLabel(), e.getMessage()));
+      NestedSet<String> imports;
+      if (dep.get(PyInfo.PROVIDER) != null) {
+        imports = dep.get(PyInfo.PROVIDER).getImportsSet();
+      } else {
+        imports = NestedSetBuilder.emptySet(Order.COMPILE_ORDER);
+      }
+      if (!builder.getOrder().isCompatible(imports.getOrder())) {
+        // TODO(brandjon): We should make order an invariant of the Python provider, and move this
+        // check into PyInfo.
+        ruleContext.ruleError(
+            getOrderErrorMessage("imports", builder.getOrder(), imports.getOrder()));
+      } else {
+        builder.addTransitive(imports);
       }
     }
     return builder.build();
@@ -350,14 +368,9 @@
     if (sourcesVersion == PythonVersion.PY2 || sourcesVersion == PythonVersion.PY2ONLY) {
       return true;
     }
-    for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("deps")) {
-      try {
-        if (PyProviderUtils.getHasPy2OnlySources(dep)) {
-          return true;
-        }
-      } catch (EvalException e) {
-        ruleContext.attributeError(
-            "deps", String.format("In dep '%s': %s", dep.getLabel(), e.getMessage()));
+    for (PyInfo depInfo : ruleContext.getPrerequisites("deps", PyInfo.PROVIDER)) {
+      if (depInfo.getHasPy2OnlySources()) {
+        return true;
       }
     }
     return false;
@@ -372,14 +385,9 @@
     if (sourcesVersion == PythonVersion.PY3 || sourcesVersion == PythonVersion.PY3ONLY) {
       return true;
     }
-    for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("deps")) {
-      try {
-        if (PyProviderUtils.getHasPy3OnlySources(dep)) {
-          return true;
-        }
-      } catch (EvalException e) {
-        ruleContext.attributeError(
-            "deps", String.format("In dep '%s': %s", dep.getLabel(), e.getMessage()));
+    for (PyInfo depInfo : ruleContext.getPrerequisites("deps", PyInfo.PROVIDER)) {
+      if (depInfo.getHasPy3OnlySources()) {
+        return true;
       }
     }
     return false;
@@ -524,27 +532,6 @@
   }
 
   /**
-   * Reports an attribute error if a target in {@code deps} passes the legacy "py" provider but this
-   * is disallowed by the configuration.
-   */
-  private void validateLegacyProviderNotUsedIfDisabled() {
-    if (!ruleContext.getFragment(PythonConfiguration.class).disallowLegacyPyProvider()) {
-      return;
-    }
-    for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("deps")) {
-      if (PyProviderUtils.hasLegacyProvider(dep)) {
-        ruleContext.attributeError(
-            "deps",
-            String.format(
-                "In dep '%s': The legacy 'py' provider is disallowed. Migrate to the PyInfo "
-                    + "provider instead. You can temporarily disable this failure with "
-                    + "--incompatible_disallow_legacy_py_provider=false.",
-                dep.getLabel()));
-      }
-    }
-  }
-
-  /**
    * If the Python version (as determined by the configuration) is inconsistent with {@link
    * #hasPy2OnlySources} or {@link #hasPy3OnlySources}, emits a {@link FailAction} that "generates"
    * the executable.
@@ -752,7 +739,9 @@
     addPyExtraActionPseudoAction();
   }
 
-  /** @return an artifact next to the executable file with a given suffix. */
+  /**
+   * @return an artifact next to the executable file with a given suffix.
+   */
   private Artifact getArtifactWithExtension(Artifact executable, String extension) {
     // On Windows, the Python executable has .exe extension on Windows,
     // On Linux, the Python executable has no extension.
@@ -793,21 +782,20 @@
   }
 
   /**
-   * Adds a PyInfo or legacy "py" provider.
+   * Adds a PyInfo provider.
    *
    * <p>This is a public method because some rules just want a PyInfo provider without the other
    * things py_library needs.
    */
   public void addPyInfoProvider(RuleConfiguredTargetBuilder builder) {
-    boolean createLegacyPyProvider =
-        !ruleContext.getFragment(PythonConfiguration.class).disallowLegacyPyProvider();
-    PyProviderUtils.builder(createLegacyPyProvider)
-        .setTransitiveSources(transitivePythonSources)
-        .setUsesSharedLibraries(usesSharedLibraries)
-        .setImports(imports)
-        .setHasPy2OnlySources(hasPy2OnlySources)
-        .setHasPy3OnlySources(hasPy3OnlySources)
-        .buildAndAddToTarget(builder);
+    builder.addNativeDeclaredProvider(
+        PyInfo.builder()
+            .setTransitiveSources(transitivePythonSources)
+            .setUsesSharedLibraries(usesSharedLibraries)
+            .setImports(imports)
+            .setHasPy2OnlySources(hasPy2OnlySources)
+            .setHasPy3OnlySources(hasPy3OnlySources)
+            .build());
   }
 
   public void addCommonTransitiveInfoProviders(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java
deleted file mode 100644
index 7325ed2..0000000
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2019 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.
-
-package com.google.devtools.build.lib.rules.python;
-
-import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.analysis.FileProvider;
-import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
-import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
-import com.google.devtools.build.lib.collect.nestedset.NestedSet;
-import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
-import com.google.devtools.build.lib.collect.nestedset.Order;
-import com.google.devtools.build.lib.packages.StructImpl;
-import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
-import com.google.devtools.build.lib.util.FileType;
-import net.starlark.java.eval.EvalException;
-import net.starlark.java.eval.Starlark;
-
-/**
- * Static helper class for creating and accessing Python provider information.
- *
- * <p>This class exposes a unified view over both the legacy and modern Python providers.
- */
-// TODO(b/153363654): Delete this class, go directly through PyInfo instead.
-public class PyProviderUtils {
-
-  // Disable construction.
-  private PyProviderUtils() {}
-
-  /** Returns whether a given target has the {@code PyInfo} provider. */
-  public static boolean hasModernProvider(TransitiveInfoCollection target) {
-    return target.get(PyInfo.PROVIDER) != null;
-  }
-
-  /**
-   * Returns the {@code PyInfo} provider from the given target info, or null if the provider is not
-   * present.
-   */
-  public static PyInfo getModernProvider(TransitiveInfoCollection target) {
-    return target.get(PyInfo.PROVIDER);
-  }
-
-  /** Returns whether a given target has the legacy "py" provider. */
-  public static boolean hasLegacyProvider(TransitiveInfoCollection target) {
-    return target.get(PyStructUtils.PROVIDER_NAME) != null;
-  }
-
-  /**
-   * Returns the struct representing the legacy "py" provider, from the given target info.
-   *
-   * @throws EvalException if the provider does not exist or has the wrong type.
-   */
-  public static StructImpl getLegacyProvider(TransitiveInfoCollection target) throws EvalException {
-    Object info = target.get(PyStructUtils.PROVIDER_NAME);
-    if (info instanceof StructImpl) {
-      return (StructImpl) info;
-    }
-    if (info == null) {
-      throw Starlark.errorf("Target does not have '%s' provider", PyStructUtils.PROVIDER_NAME);
-    }
-    throw Starlark.errorf(
-        "'%s' provider should be a struct (got %s)",
-        PyStructUtils.PROVIDER_NAME, Starlark.type(info));
-  }
-
-  /**
-   * Returns the transitive sources of a given target.
-   *
-   * <p>If the target has a py provider, the value from that provider is used. Otherwise, we fall
-   * back on collecting .py source files from the target's filesToBuild.
-   *
-   * @throws EvalException if the legacy struct provider is present but malformed
-   */
-  // TODO(bazel-team): Eliminate the fallback behavior by returning an appropriate py provider from
-  // the relevant rules.
-  public static NestedSet<Artifact> getTransitiveSources(TransitiveInfoCollection target)
-      throws EvalException {
-    if (hasModernProvider(target)) {
-      return getModernProvider(target).getTransitiveSourcesSet();
-    } else if (hasLegacyProvider(target)) {
-      return PyStructUtils.getTransitiveSources(getLegacyProvider(target));
-    } else {
-      NestedSet<Artifact> files = target.getProvider(FileProvider.class).getFilesToBuild();
-      return NestedSetBuilder.<Artifact>compileOrder()
-          .addAll(FileType.filter(files.toList(), PyRuleClasses.PYTHON_SOURCE))
-          .build();
-    }
-  }
-
-  /**
-   * Returns whether a target uses shared libraries.
-   *
-   * <p>If the target has a py provider, the value from that provider is used. Otherwise, we fall
-   * back on checking whether the target's filesToBuild contains a shared library file type (e.g., a
-   * .so file).
-   *
-   * @throws EvalException if the legacy struct provider is present but malformed
-   */
-  public static boolean getUsesSharedLibraries(TransitiveInfoCollection target)
-      throws EvalException {
-    if (hasModernProvider(target)) {
-      return getModernProvider(target).getUsesSharedLibraries();
-    } else if (hasLegacyProvider(target)) {
-      return PyStructUtils.getUsesSharedLibraries(getLegacyProvider(target));
-    } else {
-      NestedSet<Artifact> files = target.getProvider(FileProvider.class).getFilesToBuild();
-      return FileType.contains(files.toList(), CppFileTypes.SHARED_LIBRARY);
-    }
-  }
-
-  /**
-   * Returns the transitive import paths of a target.
-   *
-   * <p>If the target has a py provider, the value from that provider is used. Otherwise, we default
-   * to an empty set.
-   *
-   * @throws EvalException if the legacy struct provider is present but malformed
-   */
-  public static NestedSet<String> getImports(TransitiveInfoCollection target) throws EvalException {
-    if (hasModernProvider(target)) {
-      return getModernProvider(target).getImportsSet();
-    } else if (hasLegacyProvider(target)) {
-      return PyStructUtils.getImports(getLegacyProvider(target));
-    } else {
-      return NestedSetBuilder.emptySet(Order.COMPILE_ORDER);
-    }
-  }
-
-  /**
-   * Returns whether the target has transitive sources requiring Python 2.
-   *
-   * <p>If the target has a py provider, the value from that provider is used. Otherwise, we default
-   * to false.
-   */
-  public static boolean getHasPy2OnlySources(TransitiveInfoCollection target) throws EvalException {
-    if (hasModernProvider(target)) {
-      return getModernProvider(target).getHasPy2OnlySources();
-    } else if (hasLegacyProvider(target)) {
-      return PyStructUtils.getHasPy2OnlySources(getLegacyProvider(target));
-    } else {
-      return false;
-    }
-  }
-
-  /**
-   * Returns whether the target has transitive sources requiring Python 3.
-   *
-   * <p>If the target has a py provider, the value from that provider is used. Otherwise, we default
-   * to false.
-   */
-  public static boolean getHasPy3OnlySources(TransitiveInfoCollection target) throws EvalException {
-    if (hasModernProvider(target)) {
-      return getModernProvider(target).getHasPy3OnlySources();
-    } else if (hasLegacyProvider(target)) {
-      return PyStructUtils.getHasPy3OnlySources(getLegacyProvider(target));
-    } else {
-      return false;
-    }
-  }
-
-  /**
-   * Returns a builder to construct the legacy and/or modern Python providers and add them to a
-   * configured target.
-   *
-   * <p>If {@code createLegacy} is false, only the modern {@code PyInfo} provider is produced.
-   * Otherwise both {@code PyInfo} and the "py" provider are produced.
-   */
-  public static Builder builder(boolean createLegacy) {
-    return new Builder(createLegacy);
-  }
-
-  /** A builder to add both the legacy and modern providers to a configured target. */
-  public static class Builder {
-    private final PyInfo.Builder modernBuilder = PyInfo.builder();
-    private final PyStructUtils.Builder legacyBuilder = PyStructUtils.builder();
-    private final boolean createLegacy;
-
-    // Use the static builder() method instead.
-    private Builder(boolean createLegacy) {
-      this.createLegacy = createLegacy;
-    }
-
-    public Builder setTransitiveSources(NestedSet<Artifact> transitiveSources) {
-      modernBuilder.setTransitiveSources(transitiveSources);
-      legacyBuilder.setTransitiveSources(transitiveSources);
-      return this;
-    }
-
-    public Builder setUsesSharedLibraries(boolean usesSharedLibraries) {
-      modernBuilder.setUsesSharedLibraries(usesSharedLibraries);
-      legacyBuilder.setUsesSharedLibraries(usesSharedLibraries);
-      return this;
-    }
-
-    public Builder setImports(NestedSet<String> imports) {
-      modernBuilder.setImports(imports);
-      legacyBuilder.setImports(imports);
-      return this;
-    }
-
-    public Builder setHasPy2OnlySources(boolean hasPy2OnlySources) {
-      modernBuilder.setHasPy2OnlySources(hasPy2OnlySources);
-      legacyBuilder.setHasPy2OnlySources(hasPy2OnlySources);
-      return this;
-    }
-
-    public Builder setHasPy3OnlySources(boolean hasPy3OnlySources) {
-      modernBuilder.setHasPy3OnlySources(hasPy3OnlySources);
-      legacyBuilder.setHasPy3OnlySources(hasPy3OnlySources);
-      return this;
-    }
-
-    public RuleConfiguredTargetBuilder buildAndAddToTarget(
-        RuleConfiguredTargetBuilder targetBuilder) {
-      targetBuilder.addNativeDeclaredProvider(modernBuilder.build());
-      if (createLegacy) {
-        targetBuilder.addStarlarkTransitiveInfo(PyStructUtils.PROVIDER_NAME, legacyBuilder.build());
-      }
-      return targetBuilder;
-    }
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java
deleted file mode 100644
index ec01775..0000000
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2018 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.
-
-package com.google.devtools.build.lib.rules.python;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.collect.nestedset.Depset;
-import com.google.devtools.build.lib.collect.nestedset.NestedSet;
-import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
-import com.google.devtools.build.lib.collect.nestedset.Order;
-import com.google.devtools.build.lib.packages.StructImpl;
-import com.google.devtools.build.lib.packages.StructProvider;
-import net.starlark.java.eval.EvalException;
-import net.starlark.java.eval.Starlark;
-
-/** Static helper class for creating and accessing instances of the legacy "py" struct provider. */
-// TODO(b/153363654): Remove this file.
-public class PyStructUtils {
-
-  // Disable construction.
-  private PyStructUtils() {}
-
-  /** Name of the Python provider in Starlark code (as a field of a {@code Target}. */
-  public static final String PROVIDER_NAME = "py";
-
-  /**
-   * Name of field holding a postorder-compatible depset of transitive sources (i.e., .py files in
-   * {@code srcs} and in {@code srcs} of transitive {@code deps}).
-   */
-  public static final String TRANSITIVE_SOURCES = "transitive_sources";
-
-  /**
-   * Name of field holding a boolean indicating whether any transitive dep uses shared libraries.
-   */
-  public static final String USES_SHARED_LIBRARIES = "uses_shared_libraries";
-
-  /**
-   * Name of field holding a depset of import paths added by the transitive deps (including this
-   * target).
-   */
-  // TODO(brandjon): Make this a pre-order depset, since higher-level targets should get precedence
-  // on PYTHONPATH. Add assertions on its order compatibility.
-  public static final String IMPORTS = "imports";
-
-  /**
-   * Name of field holding a boolean indicating whether there are any transitive sources that
-   * require a Python 2 runtime.
-   */
-  public static final String HAS_PY2_ONLY_SOURCES = "has_py2_only_sources";
-
-  /**
-   * Name of field holding a boolean indicating whether there are any transitive sources that
-   * require a Python 3 runtime.
-   */
-  public static final String HAS_PY3_ONLY_SOURCES = "has_py3_only_sources";
-
-  private static final ImmutableMap<String, Object> DEFAULTS;
-
-  static {
-    ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
-    // TRANSITIVE_SOURCES is mandatory
-    builder.put(USES_SHARED_LIBRARIES, false);
-    builder.put(
-        IMPORTS,
-        Depset.of(
-            Depset.ElementType.STRING, NestedSetBuilder.<String>emptySet(Order.COMPILE_ORDER)));
-    builder.put(HAS_PY2_ONLY_SOURCES, false);
-    builder.put(HAS_PY3_ONLY_SOURCES, false);
-    DEFAULTS = builder.buildOrThrow();
-  }
-
-  private static Object getValue(StructImpl info, String fieldName) throws EvalException {
-    Object fieldValue = info.getValue(fieldName);
-    if (fieldValue == null) {
-      fieldValue = DEFAULTS.get(fieldName);
-      if (fieldValue == null) {
-        throw Starlark.errorf("'py' provider missing '%s' field", fieldName);
-      }
-    }
-    return fieldValue;
-  }
-
-  /**
-   * Casts and returns the transitive sources field.
-   *
-   * @throws EvalException if the field does not exist or is not a depset of {@link Artifact}
-   */
-  public static NestedSet<Artifact> getTransitiveSources(StructImpl info) throws EvalException {
-    Object x = getValue(info, TRANSITIVE_SOURCES);
-    if (x == null) {
-      throw Starlark.errorf(
-          "'%s' provider's '%s' field is missing, want depset", PROVIDER_NAME, TRANSITIVE_SOURCES);
-    }
-    NestedSet<Artifact> set = Depset.cast(x, Artifact.class, TRANSITIVE_SOURCES);
-    if (!set.getOrder().isCompatible(Order.COMPILE_ORDER)) {
-      throw Starlark.errorf(
-          "Incompatible depset order for '%s': expected 'default' or 'postorder', but got '%s'",
-          TRANSITIVE_SOURCES, set.getOrder().getStarlarkName());
-    }
-    return set;
-  }
-
-  /**
-   * Casts and returns the uses-shared-libraries field (or its default value).
-   *
-   * @throws EvalException if the field exists and is not a boolean
-   */
-  public static boolean getUsesSharedLibraries(StructImpl info) throws EvalException {
-    Object v = getValue(info, USES_SHARED_LIBRARIES);
-    if (v instanceof Boolean) {
-      return (Boolean) v;
-    }
-    throw Starlark.errorf(
-        "'%s' provider's '%s' field was %s, want bool",
-        PROVIDER_NAME, USES_SHARED_LIBRARIES, Starlark.type(v));
-  }
-
-  /**
-   * Casts and returns the imports field (or its default value).
-   *
-   * @throws EvalException if the field exists and is not a depset of strings
-   */
-  public static NestedSet<String> getImports(StructImpl info) throws EvalException {
-    Object x = getValue(info, IMPORTS);
-    if (x == null) {
-      throw Starlark.errorf(
-          "'%s' provider's '%s' field is missing, want depset", PROVIDER_NAME, IMPORTS);
-    }
-    return Depset.cast(x, String.class, IMPORTS);
-  }
-
-  /**
-   * Casts and returns the py2-only-sources field (or its default value).
-   *
-   * @throws EvalException if the field exists and is not a boolean
-   */
-  public static boolean getHasPy2OnlySources(StructImpl info) throws EvalException {
-    Object v = getValue(info, HAS_PY2_ONLY_SOURCES);
-    if (v instanceof Boolean) {
-      return (Boolean) v;
-    }
-    throw Starlark.errorf(
-        "'%s' provider's '%s' field was %s, want bool",
-        PROVIDER_NAME, HAS_PY2_ONLY_SOURCES, Starlark.type(v));
-  }
-
-  /**
-   * Casts and returns the py3-only-sources field (or its default value).
-   *
-   * @throws EvalException if the field exists and is not a boolean
-   */
-  public static boolean getHasPy3OnlySources(StructImpl info) throws EvalException {
-    Object v = getValue(info, HAS_PY3_ONLY_SOURCES);
-    if (v instanceof Boolean) {
-      return (Boolean) v;
-    }
-    throw Starlark.errorf(
-        "'%s' provider's '%s' field was %s, want bool",
-        PROVIDER_NAME, HAS_PY3_ONLY_SOURCES, Starlark.type(v));
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  /** Builder for a legacy py provider struct. */
-  public static class Builder {
-    Depset transitiveSources = null;
-    Boolean usesSharedLibraries = null;
-    Depset imports = null;
-    Boolean hasPy2OnlySources = null;
-    Boolean hasPy3OnlySources = null;
-
-    // Use the static builder() method instead.
-    private Builder() {}
-
-    public Builder setTransitiveSources(NestedSet<Artifact> transitiveSources) {
-      this.transitiveSources = Depset.of(Artifact.TYPE, transitiveSources);
-      return this;
-    }
-
-    public Builder setUsesSharedLibraries(boolean usesSharedLibraries) {
-      this.usesSharedLibraries = usesSharedLibraries;
-      return this;
-    }
-
-    public Builder setImports(NestedSet<String> imports) {
-      this.imports = Depset.of(Depset.ElementType.STRING, imports);
-      return this;
-    }
-
-    public Builder setHasPy2OnlySources(boolean hasPy2OnlySources) {
-      this.hasPy2OnlySources = hasPy2OnlySources;
-      return this;
-    }
-
-    public Builder setHasPy3OnlySources(boolean hasPy3OnlySources) {
-      this.hasPy3OnlySources = hasPy3OnlySources;
-      return this;
-    }
-
-    private static void put(
-        ImmutableMap.Builder<String, Object> fields, String fieldName, Object value) {
-      fields.put(fieldName, value != null ? value : DEFAULTS.get(fieldName));
-    }
-
-    public StructImpl build() {
-      ImmutableMap.Builder<String, Object> fields = ImmutableMap.builder();
-      Preconditions.checkNotNull(transitiveSources, "setTransitiveSources is required");
-      put(fields, TRANSITIVE_SOURCES, transitiveSources);
-      put(fields, USES_SHARED_LIBRARIES, usesSharedLibraries);
-      put(fields, IMPORTS, imports);
-      put(fields, HAS_PY2_ONLY_SOURCES, hasPy2OnlySources);
-      put(fields, HAS_PY3_ONLY_SOURCES, hasPy3OnlySources);
-      return StructProvider.STRUCT.create(fields.buildOrThrow(), "No such attribute '%s'");
-    }
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonConfiguration.java
index 7cfaea2..170580c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PythonConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonConfiguration.java
@@ -46,9 +46,6 @@
   // TODO(brandjon): Remove this once migration to PY3-as-default is complete.
   private final boolean py2OutputsAreSuffixed;
 
-  // TODO(brandjon): Remove this once migration to the new provider is complete (#7010).
-  private final boolean disallowLegacyPyProvider;
-
   // TODO(brandjon): Remove this once migration to Python toolchains is complete.
   private final boolean useToolchains;
 
@@ -63,7 +60,6 @@
     this.buildPythonZip = pythonOptions.buildPythonZip;
     this.buildTransitiveRunfilesTrees = pythonOptions.buildTransitiveRunfilesTrees;
     this.py2OutputsAreSuffixed = pythonOptions.incompatiblePy2OutputsAreSuffixed;
-    this.disallowLegacyPyProvider = pythonOptions.incompatibleDisallowLegacyPyProvider;
     this.useToolchains = pythonOptions.incompatibleUsePythonToolchains;
     this.defaultToExplicitInitPy = pythonOptions.incompatibleDefaultToExplicitInitPy;
   }
@@ -139,16 +135,6 @@
   }
 
   /**
-   * Returns true if Python rules should omit the legacy "py" provider and fail-fast when given this
-   * provider from their {@code deps}.
-   *
-   * <p>Any rules that pass this provider should be updated to pass {@code PyInfo} instead.
-   */
-  public boolean disallowLegacyPyProvider() {
-    return disallowLegacyPyProvider;
-  }
-
-  /**
    * Returns true if executable Python rules should obtain their runtime from the Python toolchain
    * rather than legacy flags.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java
index 14aa2fe..6795f35 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java
@@ -199,16 +199,14 @@
   private static final OptionDefinition HOST_FORCE_PYTHON_DEFINITION =
       OptionsParser.getOptionDefinitionByName(PythonOptions.class, "host_force_python");
 
+  // TODO(b/230490091): Delete this flag (see also bazelbuild issue #7741)
   @Option(
       name = "incompatible_disallow_legacy_py_provider",
       defaultValue = "true",
       documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
       effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
       metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
-      help =
-          "If set to true, native Python rules will neither produce nor consume the legacy \"py\" "
-              + "provider. Use PyInfo instead. Under this flag, passing the legacy provider to a "
-              + "Python target will be an error.")
+      help = "No-op, will be removed soon.")
   public boolean incompatibleDisallowLegacyPyProvider;
 
   // TODO(b/153369373): Delete this flag.
@@ -326,7 +324,6 @@
     hostPythonOptions.incompatiblePy3IsDefault = incompatiblePy3IsDefault;
     hostPythonOptions.incompatiblePy2OutputsAreSuffixed = incompatiblePy2OutputsAreSuffixed;
     hostPythonOptions.buildPythonZip = buildPythonZip;
-    hostPythonOptions.incompatibleDisallowLegacyPyProvider = incompatibleDisallowLegacyPyProvider;
     hostPythonOptions.incompatibleUsePythonToolchains = incompatibleUsePythonToolchains;
 
     // Save host options in case of a further exec->host transition.
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/BUILD b/src/test/java/com/google/devtools/build/lib/rules/python/BUILD
index 9edc440..8e8b97b 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/BUILD
@@ -18,11 +18,9 @@
         ":PyBinaryConfiguredTargetTest",
         ":PyInfoTest",
         ":PyLibraryConfiguredTargetTest",
-        ":PyProviderUtilsTest",
         ":PyRuntimeConfiguredTargetTest",
         ":PyRuntimeInfoTest",
         ":PyStarlarkTransitionsTest",
-        ":PyStructUtilsTest",
         ":PyTestConfiguredTargetTest",
         ":PythonConfigurationTest",
         ":PythonSrcsVersionAspectTest",
@@ -185,37 +183,6 @@
 )
 
 java_test(
-    name = "PyProviderUtilsTest",
-    srcs = ["PyProviderUtilsTest.java"],
-    deps = [
-        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
-        "//src/main/java/com/google/devtools/build/lib/rules/python",
-        "//src/main/java/net/starlark/java/eval",
-        "//src/test/java/com/google/devtools/build/lib/analysis/util",
-        "//third_party:junit4",
-        "//third_party:truth",
-    ],
-)
-
-java_test(
-    name = "PyStructUtilsTest",
-    srcs = ["PyStructUtilsTest.java"],
-    deps = [
-        "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
-        "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
-        "//src/main/java/com/google/devtools/build/lib/packages",
-        "//src/main/java/com/google/devtools/build/lib/rules/python",
-        "//src/main/java/com/google/devtools/build/lib/vfs",
-        "//src/main/java/net/starlark/java/eval",
-        "//src/test/java/com/google/devtools/build/lib/actions/util",
-        "//src/test/java/com/google/devtools/build/lib/testutil",
-        "//third_party:guava",
-        "//third_party:junit4",
-        "//third_party:truth",
-    ],
-)
-
-java_test(
     name = "PyInfoTest",
     srcs = ["PyInfoTest.java"],
     deps = [
@@ -267,7 +234,6 @@
     deps = [
         "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
-        "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/rules/python",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyBaseConfiguredTargetTestBase.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyBaseConfiguredTargetTestBase.java
index 610926a..3f7452b 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PyBaseConfiguredTargetTestBase.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/PyBaseConfiguredTargetTestBase.java
@@ -79,8 +79,7 @@
   }
 
   @Test
-  public void producesBothModernAndLegacyProviders_WithoutIncompatibleFlag() throws Exception {
-    useConfiguration("--incompatible_disallow_legacy_py_provider=false");
+  public void producesProvider() throws Exception {
     scratch.file(
         "pkg/BUILD", //
         ruleName + "(",
@@ -88,77 +87,10 @@
         "    srcs = ['foo.py'])");
     ConfiguredTarget target = getConfiguredTarget("//pkg:foo");
     assertThat(target.get(PyInfo.PROVIDER)).isNotNull();
-    assertThat(target.get(PyStructUtils.PROVIDER_NAME)).isNotNull();
   }
 
   @Test
-  public void producesOnlyModernProvider_WithIncompatibleFlag() throws Exception {
-    useConfiguration("--incompatible_disallow_legacy_py_provider=true");
-    scratch.file(
-        "pkg/BUILD", //
-        ruleName + "(",
-        "    name = 'foo',",
-        "    srcs = ['foo.py'])");
-    ConfiguredTarget target = getConfiguredTarget("//pkg:foo");
-    assertThat(target.get(PyInfo.PROVIDER)).isNotNull();
-    assertThat(target.get(PyStructUtils.PROVIDER_NAME)).isNull();
-  }
-
-  @Test
-  public void consumesLegacyProvider_WithoutIncompatibleFlag() throws Exception {
-    useConfiguration("--incompatible_disallow_legacy_py_provider=false");
-    scratch.file(
-        "pkg/rules.bzl",
-        "def _myrule_impl(ctx):",
-        "    return struct(py=struct(transitive_sources=depset([])))",
-        "myrule = rule(",
-        "    implementation = _myrule_impl,",
-        ")");
-    scratch.file(
-        "pkg/BUILD",
-        "load(':rules.bzl', 'myrule')",
-        "myrule(",
-        "    name = 'dep',",
-        ")",
-        ruleName + "(",
-        "    name = 'foo',",
-        "    srcs = ['foo.py'],",
-        "    deps = [':dep'],",
-        ")");
-    ConfiguredTarget target = getConfiguredTarget("//pkg:foo");
-    assertThat(target).isNotNull();
-    assertNoEvents();
-  }
-
-  @Test
-  public void rejectsLegacyProvider_WithIncompatibleFlag() throws Exception {
-    useConfiguration("--incompatible_disallow_legacy_py_provider=true");
-    scratch.file(
-        "pkg/rules.bzl",
-        "def _myrule_impl(ctx):",
-        "    return struct(py=struct(transitive_sources=depset([])))",
-        "myrule = rule(",
-        "    implementation = _myrule_impl,",
-        ")");
-    checkError(
-        "pkg",
-        "foo",
-        // error:
-        "In dep '//pkg:dep': The legacy 'py' provider is disallowed.",
-        // build file:
-        "load(':rules.bzl', 'myrule')",
-        "myrule(",
-        "    name = 'dep',",
-        ")",
-        ruleName + "(",
-        "    name = 'foo',",
-        "    srcs = ['foo.py'],",
-        "    deps = [':dep'],",
-        ")");
-  }
-
-  @Test
-  public void consumesModernProvider() throws Exception {
+  public void consumesProvider() throws Exception {
     scratch.file(
         "pkg/rules.bzl",
         "def _myrule_impl(ctx):",
@@ -207,4 +139,17 @@
         "    deps = [':dep'],",
         ")");
   }
+
+  @Test
+  public void dataSetsUsesSharedLibrary() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        ruleName + "(",
+        "    name = 'foo',",
+        "    srcs = ['foo.py'],",
+        "    data = ['lib.so']",
+        ")");
+    ConfiguredTarget target = getConfiguredTarget("//pkg:foo");
+    assertThat(target.get(PyInfo.PROVIDER).getUsesSharedLibraries()).isTrue();
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyProviderUtilsTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyProviderUtilsTest.java
deleted file mode 100644
index b005a7a..0000000
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PyProviderUtilsTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2019 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.
-
-package com.google.devtools.build.lib.rules.python;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-
-import com.google.devtools.build.lib.analysis.ConfiguredTarget;
-import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
-import net.starlark.java.eval.EvalException;
-import org.junit.Test;
-import org.junit.function.ThrowingRunnable;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for {@link PyProviderUtils}. */
-@RunWith(JUnit4.class)
-public class PyProviderUtilsTest extends BuildViewTestCase {
-
-  private static String join(String... lines) {
-    return String.join("\n", lines);
-  }
-
-  private static void assertThrowsEvalExceptionContaining(
-      ThrowingRunnable runnable, String message) {
-    assertThat(assertThrows(EvalException.class, runnable)).hasMessageThat().contains(message);
-  }
-
-  /**
-   * Creates {@code //pkg:target} as an instance of a trivial rule having the given implementation
-   * function body.
-   *
-   * <p>The body is formed by joining the input strings with newlines, then inserting 4 spaces of
-   * indentation before the first line and each newline. This allows a single string argument to
-   * contain multiple pre-joined lines and still be indented correctly.
-   */
-  private void declareTargetWithImplementation(String... input) throws Exception {
-    String indentedBody;
-    if (input.length == 0) {
-      indentedBody = "    pass";
-    } else {
-      indentedBody = "    " + String.join("\n", input).replace("\n", "\n    ");
-    }
-    scratch.file(
-        "pkg/rules.bzl",
-        "def _myrule_impl(ctx):",
-        indentedBody,
-        "myrule = rule(",
-        "    implementation = _myrule_impl,",
-        ")");
-    scratch.file(
-        "pkg/BUILD", //
-        "load(':rules.bzl', 'myrule')", //
-        "myrule(", //
-        "    name = 'target',", //
-        ")");
-  }
-
-  /** Retrieves the target defined by {@link #declareTargetWithImplementation}. */
-  private ConfiguredTarget getTarget() throws Exception {
-    ConfiguredTarget target = getConfiguredTarget("//pkg:target");
-    assertThat(target).isNotNull();
-    return target;
-  }
-
-  @Test
-  public void getAndHasModernProvider_Present() throws Exception {
-    declareTargetWithImplementation( //
-        "return [PyInfo(transitive_sources=depset([]))]");
-    assertThat(PyProviderUtils.hasModernProvider(getTarget())).isTrue();
-    assertThat(PyProviderUtils.getModernProvider(getTarget())).isNotNull();
-  }
-
-  @Test
-  public void getAndHasModernProvider_Absent() throws Exception {
-    declareTargetWithImplementation( //
-        "return []");
-    assertThat(PyProviderUtils.hasModernProvider(getTarget())).isFalse();
-    assertThat(PyProviderUtils.getModernProvider(getTarget())).isNull();
-  }
-
-  @Test
-  public void getAndHasLegacyProvider_Present() throws Exception {
-    declareTargetWithImplementation( //
-        "return struct(py=struct())"); // Accessor doesn't actually check fields
-    assertThat(PyProviderUtils.hasLegacyProvider(getTarget())).isTrue();
-    assertThat(PyProviderUtils.getLegacyProvider(getTarget())).isNotNull();
-  }
-
-  @Test
-  public void getAndHasLegacyProvider_Absent() throws Exception {
-    declareTargetWithImplementation( //
-        "return []");
-    assertThat(PyProviderUtils.hasLegacyProvider(getTarget())).isFalse();
-    assertThrowsEvalExceptionContaining(
-        () -> PyProviderUtils.getLegacyProvider(getTarget()), "Target does not have 'py' provider");
-  }
-
-  @Test
-  public void getLegacyProvider_NotAStruct() throws Exception {
-    declareTargetWithImplementation( //
-        "return struct(py=123)");
-    assertThrowsEvalExceptionContaining(
-        () -> PyProviderUtils.getLegacyProvider(getTarget()), "'py' provider should be a struct");
-  }
-
-  private static final String TRANSITIVE_SOURCES_SETUP_CODE =
-      join(
-          "afile = ctx.actions.declare_file('a.py')",
-          "ctx.actions.write(output=afile, content='a')",
-          "bfile = ctx.actions.declare_file('b.py')",
-          "ctx.actions.write(output=bfile, content='b')",
-          "modern_info = PyInfo(transitive_sources = depset(direct=[afile]))",
-          "legacy_info = struct(transitive_sources = depset(direct=[bfile]))");
-
-  @Test
-  public void getTransitiveSources_ModernProvider() throws Exception {
-    declareTargetWithImplementation( //
-        TRANSITIVE_SOURCES_SETUP_CODE, //
-        "return [modern_info]");
-    assertThat(PyProviderUtils.getTransitiveSources(getTarget()).toList())
-        .containsExactly(getBinArtifact("a.py", getTarget()));
-  }
-
-  @Test
-  public void getTransitiveSources_LegacyProvider() throws Exception {
-    declareTargetWithImplementation( //
-        TRANSITIVE_SOURCES_SETUP_CODE, //
-        "return struct(py=legacy_info)");
-    assertThat(PyProviderUtils.getTransitiveSources(getTarget()).toList())
-        .containsExactly(getBinArtifact("b.py", getTarget()));
-  }
-
-  @Test
-  public void getTransitiveSources_BothProviders() throws Exception {
-    declareTargetWithImplementation( //
-        TRANSITIVE_SOURCES_SETUP_CODE, //
-        "return struct(py=legacy_info, providers=[modern_info])");
-    assertThat(PyProviderUtils.getTransitiveSources(getTarget()).toList())
-        .containsExactly(getBinArtifact("a.py", getTarget()));
-  }
-
-  @Test
-  public void getTransitiveSources_NoProvider() throws Exception {
-    declareTargetWithImplementation( //
-        TRANSITIVE_SOURCES_SETUP_CODE, //
-        "return [DefaultInfo(files=depset(direct=[afile]))]");
-    assertThat(PyProviderUtils.getTransitiveSources(getTarget()).toList())
-        .containsExactly(getBinArtifact("a.py", getTarget()));
-  }
-
-  private static final String USES_SHARED_LIBRARIES_SETUP_CODE =
-      join(
-          "modern_info = PyInfo(transitive_sources = depset([]), uses_shared_libraries=False)",
-          "legacy_info = struct(transitive_sources = depset([]), uses_shared_libraries=True)");
-
-  @Test
-  public void getUsesSharedLibraries_ModernProvider() throws Exception {
-    declareTargetWithImplementation( //
-        USES_SHARED_LIBRARIES_SETUP_CODE, //
-        "return [modern_info]");
-    assertThat(PyProviderUtils.getUsesSharedLibraries(getTarget())).isFalse();
-  }
-
-  @Test
-  public void getUsesSharedLibraries_LegacyProvider() throws Exception {
-    declareTargetWithImplementation( //
-        USES_SHARED_LIBRARIES_SETUP_CODE, //
-        "return struct(py=legacy_info)");
-    assertThat(PyProviderUtils.getUsesSharedLibraries(getTarget())).isTrue();
-  }
-
-  @Test
-  public void getUsesSharedLibraries_BothProviders() throws Exception {
-    declareTargetWithImplementation( //
-        USES_SHARED_LIBRARIES_SETUP_CODE, //
-        "return struct(py=legacy_info, providers=[modern_info])");
-    assertThat(PyProviderUtils.getUsesSharedLibraries(getTarget())).isFalse();
-  }
-
-  @Test
-  public void getUsesSharedLibraries_NoProvider() throws Exception {
-    declareTargetWithImplementation(
-        "afile = ctx.actions.declare_file('a.so')",
-        "ctx.actions.write(output=afile, content='a')",
-        "return [DefaultInfo(files=depset(direct=[afile]))]");
-    assertThat(PyProviderUtils.getUsesSharedLibraries(getTarget())).isTrue();
-  }
-
-  private static final String IMPORTS_SETUP_CODE =
-      join(
-          "modern_info = PyInfo(transitive_sources = depset([]), imports = depset(direct=['abc']))",
-          "legacy_info = struct(",
-          "    transitive_sources = depset([]),",
-          "    imports = depset(direct=['def']))");
-
-  @Test
-  public void getImports_ModernProvider() throws Exception {
-    declareTargetWithImplementation( //
-        IMPORTS_SETUP_CODE, //
-        "return [modern_info]");
-    assertThat(PyProviderUtils.getImports(getTarget()).toList()).containsExactly("abc");
-  }
-
-  @Test
-  public void getImports_LegacyProvider() throws Exception {
-    declareTargetWithImplementation( //
-        IMPORTS_SETUP_CODE, //
-        "return struct(py=legacy_info)");
-    assertThat(PyProviderUtils.getImports(getTarget()).toList()).containsExactly("def");
-  }
-
-  @Test
-  public void getImports_BothProviders() throws Exception {
-    declareTargetWithImplementation( //
-        IMPORTS_SETUP_CODE, //
-        "return struct(py=legacy_info, providers=[modern_info])");
-    assertThat(PyProviderUtils.getImports(getTarget()).toList()).containsExactly("abc");
-  }
-
-  @Test
-  public void getImports_NoProvider() throws Exception {
-    declareTargetWithImplementation( //
-        IMPORTS_SETUP_CODE, //
-        "return []");
-    assertThat(PyProviderUtils.getImports(getTarget()).toList()).isEmpty();
-  }
-
-  private static final String HAS_PY2_ONLY_SOURCES_SETUP_CODE =
-      join(
-          "modern_info = PyInfo(transitive_sources = depset([]), has_py2_only_sources = False)",
-          "legacy_info = struct(transitive_sources = depset([]), has_py2_only_sources = True)");
-
-  @Test
-  public void getHasPy2OnlySources_ModernProvider() throws Exception {
-    declareTargetWithImplementation( //
-        HAS_PY2_ONLY_SOURCES_SETUP_CODE, //
-        "return [modern_info]");
-    assertThat(PyProviderUtils.getHasPy2OnlySources(getTarget())).isFalse();
-  }
-
-  @Test
-  public void getHasPy2OnlySources_LegacyProvider() throws Exception {
-    declareTargetWithImplementation( //
-        HAS_PY2_ONLY_SOURCES_SETUP_CODE, //
-        "return struct(py=legacy_info)");
-    assertThat(PyProviderUtils.getHasPy2OnlySources(getTarget())).isTrue();
-  }
-
-  @Test
-  public void getHasPy2OnlySources_BothProviders() throws Exception {
-    declareTargetWithImplementation( //
-        HAS_PY2_ONLY_SOURCES_SETUP_CODE, //
-        "return struct(py=legacy_info, providers=[modern_info])");
-    assertThat(PyProviderUtils.getHasPy2OnlySources(getTarget())).isFalse();
-  }
-
-  @Test
-  public void getHasPy2OnlySources_NoProvider() throws Exception {
-    declareTargetWithImplementation( //
-        "return []");
-    assertThat(PyProviderUtils.getHasPy2OnlySources(getTarget())).isFalse();
-  }
-
-  private static final String HAS_PY3_ONLY_SOURCES_SETUP_CODE =
-      join(
-          "modern_info = PyInfo(transitive_sources = depset([]), has_py3_only_sources = False)",
-          "legacy_info = struct(transitive_sources = depset([]), has_py3_only_sources = True)");
-
-  @Test
-  public void getHasPy3OnlySources_ModernProvider() throws Exception {
-    declareTargetWithImplementation( //
-        HAS_PY3_ONLY_SOURCES_SETUP_CODE, //
-        "return [modern_info]");
-    assertThat(PyProviderUtils.getHasPy3OnlySources(getTarget())).isFalse();
-  }
-
-  @Test
-  public void getHasPy3OnlySources_LegacyProvider() throws Exception {
-    declareTargetWithImplementation( //
-        HAS_PY3_ONLY_SOURCES_SETUP_CODE, //
-        "return struct(py=legacy_info)");
-    assertThat(PyProviderUtils.getHasPy3OnlySources(getTarget())).isTrue();
-  }
-
-  @Test
-  public void getHasPy3OnlySources_BothProviders() throws Exception {
-    declareTargetWithImplementation( //
-        HAS_PY3_ONLY_SOURCES_SETUP_CODE, //
-        "return struct(py=legacy_info, providers=[modern_info])");
-    assertThat(PyProviderUtils.getHasPy3OnlySources(getTarget())).isFalse();
-  }
-
-  @Test
-  public void getHasPy3OnlySources_NoProvider() throws Exception {
-    declareTargetWithImplementation( //
-        "return []");
-    assertThat(PyProviderUtils.getHasPy3OnlySources(getTarget())).isFalse();
-  }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java
deleted file mode 100644
index 9a3a906..0000000
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2018 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.
-
-package com.google.devtools.build.lib.rules.python;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.actions.ArtifactRoot;
-import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
-import com.google.devtools.build.lib.collect.nestedset.Depset;
-import com.google.devtools.build.lib.collect.nestedset.NestedSet;
-import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
-import com.google.devtools.build.lib.collect.nestedset.Order;
-import com.google.devtools.build.lib.packages.StarlarkInfo;
-import com.google.devtools.build.lib.packages.StructImpl;
-import com.google.devtools.build.lib.packages.StructProvider;
-import com.google.devtools.build.lib.testutil.FoundationTestCase;
-import com.google.devtools.build.lib.vfs.Root;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import net.starlark.java.eval.EvalException;
-import net.starlark.java.eval.StarlarkInt;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.function.ThrowingRunnable;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for {@link PyStructUtils}. */
-@RunWith(JUnit4.class)
-public class PyStructUtilsTest extends FoundationTestCase {
-
-  private Artifact dummyArtifact;
-
-  @Before
-  public void setUp() {
-    this.dummyArtifact =
-        ActionsTestUtil.createArtifact(
-            ArtifactRoot.asSourceRoot(Root.fromPath(rootDirectory)), "dummy");
-  }
-
-  /**
-   * Constructs a py provider struct with the given field values and with default values for any
-   * field not specified.
-   *
-   * <p>The struct is constructed directly, rather than using {@link PyStructUtils.Builder}, so that
-   * the resulting instance is suitable for asserting on {@code PyStructUtils}'s operations over
-   * structs with known contents. {@code overrides} is applied directly without validating the
-   * fields' names or types.
-   */
-  private StructImpl makeStruct(Map<String, Object> overrides) {
-    Map<String, Object> fields = new LinkedHashMap<>();
-    fields.put(
-        PyStructUtils.TRANSITIVE_SOURCES,
-        Depset.of(Artifact.TYPE, NestedSetBuilder.emptySet(Order.COMPILE_ORDER)));
-    fields.put(PyStructUtils.USES_SHARED_LIBRARIES, false);
-    fields.put(
-        PyStructUtils.IMPORTS,
-        Depset.of(Depset.ElementType.STRING, NestedSetBuilder.emptySet(Order.COMPILE_ORDER)));
-    fields.put(PyStructUtils.HAS_PY2_ONLY_SOURCES, false);
-    fields.put(PyStructUtils.HAS_PY3_ONLY_SOURCES, false);
-    fields.putAll(overrides);
-    return StructProvider.STRUCT.create(fields, "No such attribute '%s'");
-  }
-
-  private static void assertThrowsEvalExceptionContaining(
-      ThrowingRunnable runnable, String message) {
-    assertThat(assertThrows(EvalException.class, runnable)).hasMessageThat().contains(message);
-  }
-
-  private static void assertHasMissingFieldMessage(ThrowingRunnable access, String fieldName) {
-    assertThrowsEvalExceptionContaining(
-        access, String.format("\'py' provider missing '%s' field", fieldName));
-  }
-
-  private static void assertHasWrongTypeMessage(
-      ThrowingRunnable access, String fieldName, String expectedType) {
-    assertThrowsEvalExceptionContaining(
-        access,
-        String.format("\'py' provider's '%s' field was int, want %s", fieldName, expectedType));
-  }
-
-  /** We need this because {@code NestedSet}s don't have value equality. */
-  private static void assertHasOrderAndContainsExactly(
-      NestedSet<?> set, Order order, Object... values) {
-    assertThat(set.getOrder()).isEqualTo(order);
-    assertThat(set.toList()).containsExactly(values);
-  }
-
-  @Test
-  public void getTransitiveSources_Good() throws Exception {
-    NestedSet<Artifact> sources = NestedSetBuilder.create(Order.COMPILE_ORDER, dummyArtifact);
-    StructImpl info =
-        makeStruct(
-            ImmutableMap.of(PyStructUtils.TRANSITIVE_SOURCES, Depset.of(Artifact.TYPE, sources)));
-    assertThat(PyStructUtils.getTransitiveSources(info)).isSameInstanceAs(sources);
-  }
-
-  private static final StructImpl emptyInfo =
-      StarlarkInfo.create(StructProvider.STRUCT, ImmutableMap.of(), /* loc= */ null);
-
-  @Test
-  public void getTransitiveSources_Missing() {
-    assertHasMissingFieldMessage(
-        () -> PyStructUtils.getTransitiveSources(emptyInfo), "transitive_sources");
-  }
-
-  @Test
-  public void getTransitiveSources_WrongType() {
-    StructImpl info =
-        makeStruct(ImmutableMap.of(PyStructUtils.TRANSITIVE_SOURCES, StarlarkInt.of(123)));
-    assertThrowsEvalExceptionContaining(
-        () -> PyStructUtils.getTransitiveSources(info),
-        "for transitive_sources, got int, want a depset of File");
-  }
-
-  @Test
-  public void getTransitiveSources_OrderMismatch() throws Exception {
-    NestedSet<Artifact> sources = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
-    StructImpl info =
-        makeStruct(
-            ImmutableMap.of(PyStructUtils.TRANSITIVE_SOURCES, Depset.of(Artifact.TYPE, sources)));
-    assertThrowsEvalExceptionContaining(
-        () -> PyStructUtils.getTransitiveSources(info),
-        "Incompatible depset order for 'transitive_sources': expected 'default' or 'postorder', "
-            + "but got 'preorder'");
-  }
-
-  @Test
-  public void getUsesSharedLibraries_Good() throws Exception {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.USES_SHARED_LIBRARIES, true));
-    assertThat(PyStructUtils.getUsesSharedLibraries(info)).isTrue();
-  }
-
-  @Test
-  public void getUsesSharedLibraries_Missing() throws Exception {
-    assertThat(PyStructUtils.getUsesSharedLibraries(emptyInfo)).isFalse();
-  }
-
-  @Test
-  public void getUsesSharedLibraries_WrongType() {
-    StructImpl info =
-        makeStruct(ImmutableMap.of(PyStructUtils.USES_SHARED_LIBRARIES, StarlarkInt.of(123)));
-    assertHasWrongTypeMessage(
-        () -> PyStructUtils.getUsesSharedLibraries(info), "uses_shared_libraries", "bool");
-  }
-
-  @Test
-  public void getImports_Good() throws Exception {
-    NestedSet<String> imports = NestedSetBuilder.create(Order.COMPILE_ORDER, "abc");
-    StructImpl info =
-        makeStruct(
-            ImmutableMap.of(PyStructUtils.IMPORTS, Depset.of(Depset.ElementType.STRING, imports)));
-    assertThat(PyStructUtils.getImports(info)).isSameInstanceAs(imports);
-  }
-
-  @Test
-  public void getImports_Missing() throws Exception {
-    assertHasOrderAndContainsExactly(PyStructUtils.getImports(emptyInfo), Order.COMPILE_ORDER);
-  }
-
-  @Test
-  public void getImports_WrongType() {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.IMPORTS, StarlarkInt.of(123)));
-    assertThrowsEvalExceptionContaining(
-        () -> PyStructUtils.getImports(info), "for imports, got int, want a depset of string");
-  }
-
-  @Test
-  public void getHasPy2OnlySources_Good() throws Exception {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY2_ONLY_SOURCES, true));
-    assertThat(PyStructUtils.getHasPy2OnlySources(info)).isTrue();
-  }
-
-  @Test
-  public void getHasPy2OnlySources_Missing() throws Exception {
-    assertThat(PyStructUtils.getHasPy2OnlySources(emptyInfo)).isFalse();
-  }
-
-  @Test
-  public void getHasPy2OnlySources_WrongType() {
-    StructImpl info =
-        makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY2_ONLY_SOURCES, StarlarkInt.of(123)));
-    assertHasWrongTypeMessage(
-        () -> PyStructUtils.getHasPy2OnlySources(info), "has_py2_only_sources", "bool");
-  }
-
-  @Test
-  public void getHasPy3OnlySources_Good() throws Exception {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY3_ONLY_SOURCES, true));
-    assertThat(PyStructUtils.getHasPy3OnlySources(info)).isTrue();
-  }
-
-  @Test
-  public void getHasPy3OnlySources_Missing() throws Exception {
-    assertThat(PyStructUtils.getHasPy3OnlySources(emptyInfo)).isFalse();
-  }
-
-  @Test
-  public void getHasPy3OnlySources_WrongType() {
-    StructImpl info =
-        makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY3_ONLY_SOURCES, StarlarkInt.of(123)));
-    assertHasWrongTypeMessage(
-        () -> PyStructUtils.getHasPy3OnlySources(info), "has_py3_only_sources", "bool");
-  }
-
-  /** Checks values set by the builder. */
-  @Test
-  public void builder() throws Exception {
-    NestedSet<Artifact> sources = NestedSetBuilder.create(Order.COMPILE_ORDER, dummyArtifact);
-    NestedSet<String> imports = NestedSetBuilder.create(Order.COMPILE_ORDER, "abc");
-    StructImpl info =
-        PyStructUtils.builder()
-            .setTransitiveSources(sources)
-            .setUsesSharedLibraries(true)
-            .setImports(imports)
-            .setHasPy2OnlySources(true)
-            .setHasPy3OnlySources(true)
-            .build();
-    // Assert using struct operations, not PyStructUtils accessors, which aren't necessarily trusted
-    // to be correct.
-    assertHasOrderAndContainsExactly(
-        ((Depset) info.getValue(PyStructUtils.TRANSITIVE_SOURCES)).getSet(Artifact.class),
-        Order.COMPILE_ORDER,
-        dummyArtifact);
-    assertThat((Boolean) info.getValue(PyStructUtils.USES_SHARED_LIBRARIES)).isTrue();
-    assertHasOrderAndContainsExactly(
-        ((Depset) info.getValue(PyStructUtils.IMPORTS)).getSet(String.class),
-        Order.COMPILE_ORDER,
-        "abc");
-    assertThat((Boolean) info.getValue(PyStructUtils.HAS_PY2_ONLY_SOURCES)).isTrue();
-    assertThat((Boolean) info.getValue(PyStructUtils.HAS_PY3_ONLY_SOURCES)).isTrue();
-  }
-
-  /** Checks the defaults set by the builder. */
-  @Test
-  public void builderDefaults() throws Exception {
-    // transitive_sources is mandatory, so create a dummy value but no need to assert on it.
-    NestedSet<Artifact> sources = NestedSetBuilder.create(Order.COMPILE_ORDER, dummyArtifact);
-    StructImpl info = PyStructUtils.builder().setTransitiveSources(sources).build();
-    // Assert using struct operations, not PyStructUtils accessors, which aren't necessarily trusted
-    // to be correct.
-    assertThat((Boolean) info.getValue(PyStructUtils.USES_SHARED_LIBRARIES)).isFalse();
-    assertHasOrderAndContainsExactly(
-        ((Depset) info.getValue(PyStructUtils.IMPORTS)).getSet(String.class), Order.COMPILE_ORDER);
-    assertThat((Boolean) info.getValue(PyStructUtils.HAS_PY2_ONLY_SOURCES)).isFalse();
-    assertThat((Boolean) info.getValue(PyStructUtils.HAS_PY3_ONLY_SOURCES)).isFalse();
-  }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PythonConfigurationTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PythonConfigurationTest.java
index a009109..54b1987 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PythonConfigurationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/PythonConfigurationTest.java
@@ -96,13 +96,11 @@
             "--incompatible_py3_is_default=true",
             "--incompatible_py2_outputs_are_suffixed=true",
             "--build_python_zip=true",
-            "--incompatible_disallow_legacy_py_provider=true",
             "--incompatible_use_python_toolchains=true");
     PythonOptions hostOpts = (PythonOptions) opts.getHost();
     assertThat(hostOpts.incompatiblePy3IsDefault).isTrue();
     assertThat(hostOpts.incompatiblePy2OutputsAreSuffixed).isTrue();
     assertThat(hostOpts.buildPythonZip).isEqualTo(TriState.YES);
-    assertThat(hostOpts.incompatibleDisallowLegacyPyProvider).isTrue();
     assertThat(hostOpts.incompatibleUsePythonToolchains).isTrue();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PythonStarlarkApiTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PythonStarlarkApiTest.java
index 97bffef..3ba118e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PythonStarlarkApiTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/PythonStarlarkApiTest.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
-import com.google.devtools.build.lib.packages.StructImpl;
 import com.google.devtools.build.lib.testutil.TestConstants;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,16 +28,8 @@
 @RunWith(JUnit4.class)
 public class PythonStarlarkApiTest extends BuildViewTestCase {
 
-  /**
-   * Defines userlib in //pkg:rules.bzl, which acts as a Starlark-defined version of py_library.
-   *
-   * <p>If {@code legacyProviderAllowed} is true, both the legacy and modern providers are returned.
-   * Otherwise only the modern provider is returned. In both cases the modern provider of the
-   * dependency is consumed while the native one is ignored.
-   */
-  private void defineUserlibRule(boolean legacyProviderAllowed) throws Exception {
-    String returnExpr =
-        legacyProviderAllowed ? "struct(py=legacy_info, providers=[modern_info])" : "[modern_info]";
+  /** Defines userlib in //pkg:rules.bzl, which acts as a Starlark-defined version of py_library. */
+  private void defineUserlibRule() throws Exception {
     scratch.file(
         "pkg/rules.bzl",
         "def _userlib_impl(ctx):",
@@ -59,19 +50,13 @@
         "    has_py3_only_sources = \\",
         "        any([py.has_py3_only_sources for py in dep_infos]) or \\",
         "        ctx.attr.has_py3_only_sources",
-        "    legacy_info = struct(",
+        "    info = PyInfo(",
         "        transitive_sources = transitive_sources,",
         "        uses_shared_libraries = uses_shared_libraries,",
         "        imports = imports,",
         "        has_py2_only_sources = has_py2_only_sources,",
         "        has_py3_only_sources = has_py3_only_sources)",
-        "    modern_info = PyInfo(",
-        "        transitive_sources = transitive_sources,",
-        "        uses_shared_libraries = uses_shared_libraries,",
-        "        imports = imports,",
-        "        has_py2_only_sources = has_py2_only_sources,",
-        "        has_py3_only_sources = has_py3_only_sources)",
-        "    return " + returnExpr,
+        "    return [info]",
         "",
         "userlib = rule(",
         "    implementation = _userlib_impl,",
@@ -87,19 +72,8 @@
   }
 
   @Test
-  public void librarySandwich_LegacyProviderAllowed() throws Exception {
-    doLibrarySandwichTest(/*legacyProviderAllowed=*/ true);
-  }
-
-  @Test
-  public void librarySandwich_LegacyProviderDisallowed() throws Exception {
-    doLibrarySandwichTest(/*legacyProviderAllowed=*/ false);
-  }
-
-  private void doLibrarySandwichTest(boolean legacyProviderAllowed) throws Exception {
-    useConfiguration(
-        "--incompatible_disallow_legacy_py_provider=" + (legacyProviderAllowed ? "false" : "true"));
-    defineUserlibRule(legacyProviderAllowed);
+  public void librarySandwich() throws Exception {
+    defineUserlibRule();
     scratch.file(
         "pkg/BUILD",
         "load(':rules.bzl', 'userlib')",
@@ -125,31 +99,17 @@
         ")");
     ConfiguredTarget target = getConfiguredTarget("//pkg:upperuserlib");
 
-    if (legacyProviderAllowed) {
-      StructImpl legacyInfo = PyProviderUtils.getLegacyProvider(target);
-      assertThat(PyStructUtils.getTransitiveSources(legacyInfo).toList())
-          .containsExactly(
-              getSourceArtifact("pkg/loweruserlib.py"),
-              getSourceArtifact("pkg/pylib.py"),
-              getSourceArtifact("pkg/upperuserlib.py"));
-      assertThat(PyStructUtils.getUsesSharedLibraries(legacyInfo)).isTrue();
-      assertThat(PyStructUtils.getImports(legacyInfo).toList())
-          .containsExactly("loweruserlib_path", "upperuserlib_path");
-      assertThat(PyStructUtils.getHasPy2OnlySources(legacyInfo)).isTrue();
-      assertThat(PyStructUtils.getHasPy3OnlySources(legacyInfo)).isTrue();
-    }
-
-    PyInfo modernInfo = PyProviderUtils.getModernProvider(target);
-    assertThat(modernInfo.getTransitiveSources().toList(Artifact.class))
+    PyInfo info = target.get(PyInfo.PROVIDER);
+    assertThat(info.getTransitiveSources().toList(Artifact.class))
         .containsExactly(
             getSourceArtifact("pkg/loweruserlib.py"),
             getSourceArtifact("pkg/pylib.py"),
             getSourceArtifact("pkg/upperuserlib.py"));
-    assertThat(modernInfo.getUsesSharedLibraries()).isTrue();
-    assertThat(modernInfo.getImports().toList(String.class))
+    assertThat(info.getUsesSharedLibraries()).isTrue();
+    assertThat(info.getImports().toList(String.class))
         .containsExactly("loweruserlib_path", "upperuserlib_path");
-    assertThat(modernInfo.getHasPy2OnlySources()).isTrue();
-    assertThat(modernInfo.getHasPy3OnlySources()).isTrue();
+    assertThat(info.getHasPy2OnlySources()).isTrue();
+    assertThat(info.getHasPy3OnlySources()).isTrue();
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
index 075fd3f..b8a9b1d 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
@@ -52,7 +52,6 @@
 import com.google.devtools.build.lib.packages.StructImpl;
 import com.google.devtools.build.lib.rules.java.JavaInfo;
 import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
-import com.google.devtools.build.lib.rules.python.PyProviderUtils;
 import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import com.google.devtools.build.lib.util.FileTypeSet;
@@ -500,7 +499,7 @@
     TransitiveInfoCollection tic1 = (TransitiveInfoCollection) ((Sequence) result).get(0);
     assertThat(JavaInfo.getProvider(JavaSourceJarsProvider.class, tic1)).isNotNull();
     // Check an unimplemented provider too
-    assertThat(PyProviderUtils.hasLegacyProvider(tic1)).isFalse();
+    assertThat(tic1.get("not_implemented_provider")).isNull();
   }
 
   @Test