Move def of package() out of PackageFactory

The definition of the package() symbol was the last remaining piece of business logic where PackageFactory had a say in what the Starlark environment for BUILD/.bzl files looks like. This CL moves the definition of package() to DefaultPackageArgs (now renamed PackageCallable) and stores the symbol in the rule class provider.

It should now be possible to create a complete Starlark environment for BzlLoadFunction, etc., without knowing the PackageFactory.

PackageCallable now holds both the core package() implementation and the definitions of those package() params that are universal to all Bazel environments. I also simplified by inlining createPackageArguments() into newPackageFunction (renamed newPackageCallable to avoid confusion with, well, PackageFunction).

PiperOrigin-RevId: 531529822
Change-Id: I5f6942717ff439cac85ac612b8e4f92c51c140db
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
new file mode 100644
index 0000000..21cfffe
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageCallable.java
@@ -0,0 +1,293 @@
+// Copyright 2020 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.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.packages.PackageFactory.EnvironmentExtension;
+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.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.
+ */
+public class PackageCallable {
+
+  private PackageCallable() {}
+
+  /**
+   * Returns a {@link StarlarkCallable} that implements the {@code package()} functionality.
+   *
+   * @param environmentExtensions a list of extensions that define additional arguments for {@code
+   *     package()}, beyond the standard ones included in every Bazel environment
+   */
+  // 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<EnvironmentExtension> environmentExtensions) {
+    // 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 (EnvironmentExtension ext : environmentExtensions) {
+      for (PackageArgument<?> arg : ext.getPackageArguments()) {
+        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 {
+      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) {
+      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) {
+      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) {
+      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) {
+      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) {
+      pkgBuilder.setDefaultDistribs(value);
+    }
+  }
+
+  /**
+   * 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);
+    }
+  }
+}