Eliminate PackageArgument

The package() function is now implemented by a @StarlarkMethod rather than an anonymous subclass of StarlarkCallable.

The PackageArgument machinery is deleted. The convert-and-process approach is instead inlined into a series of if-elses for each argument (with the convert step using a helper to reduce repetition). The big if-else is factored out into an overridable method, so other implementations of package() can add their own parameters on top.

The package() symbol is now registered on the ConfiguredRuleClassProvider like any other BUILD toplevel. Removed its special casing in BazelStarlarkEnvironment, and the ability to register PackageArguments with the CRCP.

Fixed a copy-paste error where the "default_applicable_licenses" param would claim to be "default_package_metadata" for the purposes of error messages.

PiperOrigin-RevId: 532911239
Change-Id: I957354e0a4e32f7922b510c5537f56c6aff72ab9
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageCallable.java b/src/main/java/com/google/devtools/build/lib/packages/PackageCallable.java
index 6390926..3f8db53 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageCallable.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageCallable.java
@@ -14,276 +14,124 @@
 
 package com.google.devtools.build.lib.packages;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.License.DistributionType;
 import com.google.devtools.build.lib.server.FailureDetails.PackageLoading.Code;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import net.starlark.java.eval.Dict;
+import net.starlark.java.annot.Param;
+import net.starlark.java.annot.StarlarkMethod;
 import net.starlark.java.eval.EvalException;
-import net.starlark.java.eval.Printer;
 import net.starlark.java.eval.Starlark;
-import net.starlark.java.eval.StarlarkCallable;
 import net.starlark.java.eval.StarlarkThread;
-import net.starlark.java.eval.Tuple;
 import net.starlark.java.syntax.Location;
 
 /**
- * Utility class encapsulating the definition of the {@code package()} function of BUILD files.
- *
- * <p>Also includes the definitions of those arguments to {@code package()} that are available in
- * all Bazel environments.
+ * Utility class encapsulating the standard definition of the {@code package()} function of BUILD
+ * files.
  */
 public class PackageCallable {
 
-  private PackageCallable() {}
+  protected PackageCallable() {}
+
+  public static final PackageCallable INSTANCE = new PackageCallable();
+
+  @StarlarkMethod(
+      name = "package",
+      documented = false, // documented in docgen/templates/be/functions.vm
+      extraKeywords = @Param(name = "kwargs", defaultValue = "{}"),
+      useStarlarkThread = true)
+  public Object packageCallable(Map<String, Object> kwargs, StarlarkThread thread)
+      throws EvalException {
+    Package.Builder pkgBuilder = PackageFactory.getContext(thread).pkgBuilder;
+    if (pkgBuilder.isPackageFunctionUsed()) {
+      throw new EvalException("'package' can only be used once per BUILD file");
+    }
+    pkgBuilder.setPackageFunctionUsed();
+
+    if (kwargs.isEmpty()) {
+      throw new EvalException("at least one argument must be given to the 'package' function");
+    }
+
+    Location loc = thread.getCallerLocation();
+    for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
+      String name = kwarg.getKey();
+      Object rawValue = kwarg.getValue();
+      processParam(name, rawValue, pkgBuilder, loc);
+    }
+    return Starlark.NONE;
+  }
 
   /**
-   * Returns a {@link StarlarkCallable} that implements the {@code package()} functionality.
-   *
-   * @param packageArgs a list of {@link PackageArgument}s to support, beyond the standard ones
-   *     included in every Bazel environment
+   * Handles one parameter. Subclasses can add new parameters by overriding this method and falling
+   * back on the super method when the parameter does not match.
    */
-  // TODO(b/280446865): Consider eliminating the package() extensibility mechanism altogether.
-  // There is currently only one use case: distinguishing the set of package() arguments available
-  // in OSS Bazel vs internally to Google. Instead of registering these arguments and passing them
-  // to this factory method to obtain a package() callable, we could instead define two
-  // @StarlarkMethod-annotated Java functions implementing the two versions of package(), and
-  // register the appropriate one with the ConfiguredRuleClassProvider.Builder.
-  public static StarlarkCallable newPackageCallable(List<PackageArgument<?>> packageArgs) {
-    // Construct a map of PackageArguments, which the returned callable closes over.
-    ImmutableMap.Builder<String, PackageArgument<?>> argsBuilder = ImmutableMap.builder();
-    for (PackageArgument<?> arg : getCommonArguments()) {
-      argsBuilder.put(arg.getName(), arg);
-    }
-    for (PackageArgument<?> arg : packageArgs) {
-      argsBuilder.put(arg.getName(), arg);
-    }
-    final ImmutableMap<String, PackageArgument<?>> packageArguments = argsBuilder.buildOrThrow();
-
-    return new StarlarkCallable() {
-      @Override
-      public String getName() {
-        return "package";
-      }
-
-      @Override
-      public String toString() {
-        return "package(...)";
-      }
-
-      @Override
-      public boolean isImmutable() {
-        return true;
-      }
-
-      @Override
-      public void repr(Printer printer) {
-        printer.append("<built-in function package>");
-      }
-
-      @Override
-      public Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwargs)
-          throws EvalException {
-        if (!args.isEmpty()) {
-          throw new EvalException("unexpected positional arguments");
-        }
-        Package.Builder pkgBuilder = PackageFactory.getContext(thread).pkgBuilder;
-
-        // Validate parameter list
-        if (pkgBuilder.isPackageFunctionUsed()) {
-          throw new EvalException("'package' can only be used once per BUILD file");
-        }
-        pkgBuilder.setPackageFunctionUsed();
-
-        // Each supplied argument must name a PackageArgument.
-        if (kwargs.isEmpty()) {
-          throw new EvalException("at least one argument must be given to the 'package' function");
-        }
-        Location loc = thread.getCallerLocation();
-        for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
-          String name = kwarg.getKey();
-          PackageArgument<?> pkgarg = packageArguments.get(name);
-          if (pkgarg == null) {
-            throw Starlark.errorf("unexpected keyword argument: %s", name);
-          }
-          pkgarg.convertAndProcess(pkgBuilder, loc, kwarg.getValue());
-        }
-        return Starlark.NONE;
-      }
-    };
-  }
-
-  /** Returns the basic set of {@link PackageArgument}s. */
-  private static ImmutableList<PackageArgument<?>> getCommonArguments() {
-    return ImmutableList.of(
-        new DefaultDeprecation(),
-        new DefaultDistribs(),
-        new DefaultApplicableLicenses(),
-        new DefaultPackageMetadata(),
-        new DefaultLicenses(),
-        new DefaultTestOnly(),
-        new DefaultVisibility(),
-        new Features(),
-        new DefaultCompatibleWith(),
-        new DefaultRestrictedTo());
-  }
-
-  private static class DefaultVisibility extends PackageArgument<List<Label>> {
-    private DefaultVisibility() {
-      super("default_visibility", BuildType.LABEL_LIST);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, List<Label> value)
-        throws EvalException {
+  protected void processParam(
+      String name, Object rawValue, Package.Builder pkgBuilder, Location loc) throws EvalException {
+    if (name.equals("default_visibility")) {
+      List<Label> value = convert(BuildType.LABEL_LIST, rawValue, pkgBuilder);
       pkgBuilder.setDefaultVisibility(RuleVisibility.parse(value));
-    }
-  }
 
-  private static class DefaultTestOnly extends PackageArgument<Boolean> {
-    private DefaultTestOnly() {
-      super("default_testonly", Type.BOOLEAN);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, Boolean value) {
+    } else if (name.equals("default_testonly")) {
+      Boolean value = convert(Type.BOOLEAN, rawValue, pkgBuilder);
       pkgBuilder.setDefaultTestonly(value);
-    }
-  }
 
-  private static class DefaultDeprecation extends PackageArgument<String> {
-    private DefaultDeprecation() {
-      super("default_deprecation", Type.STRING);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, String value) {
+    } else if (name.equals("default_deprecation")) {
+      String value = convert(Type.STRING, rawValue, pkgBuilder);
       pkgBuilder.setDefaultDeprecation(value);
-    }
-  }
 
-  private static class Features extends PackageArgument<List<String>> {
-    private Features() {
-      super("features", Type.STRING_LIST);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, List<String> value) {
+    } else if (name.equals("features")) {
+      List<String> value = convert(Type.STRING_LIST, rawValue, pkgBuilder);
       pkgBuilder.addFeatures(value);
-    }
-  }
 
-  /**
-   * Declares the package() attribute specifying the default value for {@link
-   * com.google.devtools.build.lib.packages.RuleClass#APPLICABLE_LICENSES_ATTR} when not explicitly
-   * specified.
-   */
-  private static class DefaultApplicableLicenses extends PackageArgument<List<Label>> {
-    private DefaultApplicableLicenses() {
-      super("default_applicable_licenses", BuildType.LABEL_LIST);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, List<Label> value) {
-      if (!pkgBuilder.getDefaultPackageMetadata().isEmpty()) {
-        pkgBuilder.addEvent(
-            Package.error(
-                location,
-                "Can not set both default_package_metadata and default_applicable_licenses."
-                    + " Move all declarations to default_package_metadata.",
-                Code.INVALID_PACKAGE_SPECIFICATION));
-      }
-
-      pkgBuilder.setDefaultPackageMetadata(value, "default_package_metadata", location);
-    }
-  }
-
-  /**
-   * Declares the package() attribute specifying the default value for {@link
-   * com.google.devtools.build.lib.packages.RuleClass#APPLICABLE_LICENSES_ATTR} when not explicitly
-   * specified.
-   */
-  private static class DefaultPackageMetadata extends PackageArgument<List<Label>> {
-    private static final String DEFAULT_PACKAGE_METADATA_ATTRIBUTE = "default_package_metadata";
-
-    private DefaultPackageMetadata() {
-      super(DEFAULT_PACKAGE_METADATA_ATTRIBUTE, BuildType.LABEL_LIST);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, List<Label> value) {
-      if (!pkgBuilder.getDefaultPackageMetadata().isEmpty()) {
-        pkgBuilder.addEvent(
-            Package.error(
-                location,
-                "Can not set both default_package_metadata and default_applicable_licenses."
-                    + " Move all declarations to default_package_metadata.",
-                Code.INVALID_PACKAGE_SPECIFICATION));
-      }
-      pkgBuilder.setDefaultPackageMetadata(value, DEFAULT_PACKAGE_METADATA_ATTRIBUTE, location);
-    }
-  }
-
-  private static class DefaultLicenses extends PackageArgument<License> {
-    private DefaultLicenses() {
-      super("licenses", BuildType.LICENSE);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, License value) {
+    } else if (name.equals("licenses")) {
+      License value = convert(BuildType.LICENSE, rawValue, pkgBuilder);
       pkgBuilder.setDefaultLicense(value);
-    }
-  }
 
-  private static class DefaultDistribs extends PackageArgument<Set<DistributionType>> {
-    private DefaultDistribs() {
-      super("distribs", BuildType.DISTRIBUTIONS);
-    }
-
-    @Override
-    protected void process(
-        Package.Builder pkgBuilder, Location location, Set<DistributionType> value) {
+    } else if (name.equals("distribs")) {
+      Set<DistributionType> value = convert(BuildType.DISTRIBUTIONS, rawValue, pkgBuilder);
       pkgBuilder.setDefaultDistribs(value);
+
+    } else if (name.equals("default_compatible_with")) {
+      List<Label> value = convert(BuildType.LABEL_LIST, rawValue, pkgBuilder);
+      pkgBuilder.setDefaultCompatibleWith(value, name, loc);
+
+    } else if (name.equals("default_restricted_to")) {
+      List<Label> value = convert(BuildType.LABEL_LIST, rawValue, pkgBuilder);
+      pkgBuilder.setDefaultRestrictedTo(value, name, loc);
+
+    } else if (name.equals("default_applicable_licenses")) {
+      List<Label> value = convert(BuildType.LABEL_LIST, rawValue, pkgBuilder);
+      if (!pkgBuilder.getDefaultPackageMetadata().isEmpty()) {
+        pkgBuilder.addEvent(
+            Package.error(
+                loc,
+                "Can not set both default_package_metadata and default_applicable_licenses."
+                    + " Move all declarations to default_package_metadata.",
+                Code.INVALID_PACKAGE_SPECIFICATION));
+      }
+      pkgBuilder.setDefaultPackageMetadata(value, name, loc);
+
+    } else if (name.equals("default_package_metadata")) {
+      List<Label> value = convert(BuildType.LABEL_LIST, rawValue, pkgBuilder);
+      if (!pkgBuilder.getDefaultPackageMetadata().isEmpty()) {
+        pkgBuilder.addEvent(
+            Package.error(
+                loc,
+                "Can not set both default_package_metadata and default_applicable_licenses."
+                    + " Move all declarations to default_package_metadata.",
+                Code.INVALID_PACKAGE_SPECIFICATION));
+      }
+      pkgBuilder.setDefaultPackageMetadata(value, name, loc);
+
+    } else {
+      throw Starlark.errorf("unexpected keyword argument: %s", name);
     }
   }
 
-  /**
-   * Declares the package() attribute specifying the default value for {@link
-   * com.google.devtools.build.lib.packages.RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} when not
-   * explicitly specified.
-   */
-  private static class DefaultCompatibleWith extends PackageArgument<List<Label>> {
-    private static final String DEFAULT_COMPATIBLE_WITH_ATTRIBUTE = "default_compatible_with";
-
-    private DefaultCompatibleWith() {
-      super(DEFAULT_COMPATIBLE_WITH_ATTRIBUTE, BuildType.LABEL_LIST);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, List<Label> value) {
-      pkgBuilder.setDefaultCompatibleWith(value, DEFAULT_COMPATIBLE_WITH_ATTRIBUTE, location);
-    }
-  }
-
-  /**
-   * Declares the package() attribute specifying the default value for {@link
-   * com.google.devtools.build.lib.packages.RuleClass#RESTRICTED_ENVIRONMENT_ATTR} when not
-   * explicitly specified.
-   */
-  private static class DefaultRestrictedTo extends PackageArgument<List<Label>> {
-    private static final String DEFAULT_RESTRICTED_TO_ATTRIBUTE = "default_restricted_to";
-
-    private DefaultRestrictedTo() {
-      super(DEFAULT_RESTRICTED_TO_ATTRIBUTE, BuildType.LABEL_LIST);
-    }
-
-    @Override
-    protected void process(Package.Builder pkgBuilder, Location location, List<Label> value) {
-      pkgBuilder.setDefaultRestrictedTo(value, DEFAULT_RESTRICTED_TO_ATTRIBUTE, location);
-    }
+  protected static <T> T convert(Type<T> type, Object value, Package.Builder pkgBuilder)
+      throws EvalException {
+    return type.convert(value, "'package' argument", pkgBuilder.getLabelConverter());
   }
 }