bazel analysis: move analysis-related classes here

This change moves RuleErrorConsumer and AnalysisIssues from lib.packages,
where they *obviously* do not belong, to lib.analysis.

RuleClass.checkAttributesNonEmpty, which again doesn't belong, because
of its dependency on RuleErrorConsumer, was inlined to its sole caller
(RuleContext).

PiperOrigin-RevId: 325437765
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisIssues.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisIssues.java
new file mode 100644
index 0000000..ca591bd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisIssues.java
@@ -0,0 +1,134 @@
+// Copyright 2014 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.analysis;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * Checked exception for analysis-time errors, which can store the errors for later reporting.
+ *
+ * <p>It's more robust for a method to throw this exception than expecting a
+ * {@link RuleErrorConsumer} object (which may be null).
+ */
+public final class AnalysisIssues extends Exception {
+
+  /**
+   * An error entry.
+   *
+   * <p>{@link AnalysisIssues} can accumulate multiple of these, and report all of them at once.
+   */
+  public static final class Entry {
+    private final String attribute;
+    private final String messageTemplate;
+    private final Object[] arguments;
+
+    private Entry(@Nullable String attribute, String messageTemplate, Object... arguments) {
+      this.attribute = attribute;
+      this.messageTemplate = messageTemplate;
+      this.arguments = arguments;
+    }
+
+    private void reportTo(RuleErrorConsumer errors) {
+      String msg = String.format(messageTemplate, arguments);
+      if (attribute == null) {
+        errors.ruleError(msg);
+      } else {
+        errors.attributeError(attribute, msg);
+      }
+    }
+
+    private void reportTo(StringBuilder sb) {
+      String msg = String.format(messageTemplate, arguments);
+      if (attribute == null) {
+        sb.append("ERROR: ").append(msg);
+      } else {
+        sb.append("ERROR: in attribute \"").append(attribute).append("\": ").append(msg);
+      }
+    }
+
+    @Override
+    public String toString() {
+      if (attribute == null) {
+        return String.format("ERROR: " + messageTemplate, arguments);
+      } else {
+        List<Object> args = new ArrayList<>();
+        args.add(attribute);
+        Collections.addAll(args, arguments);
+        return String.format("ERROR in '%s': " + messageTemplate, args.toArray());
+      }
+    }
+  }
+
+  private final ImmutableList<Entry> entries;
+
+  public AnalysisIssues(Entry entry) {
+    this.entries = ImmutableList.of(Preconditions.checkNotNull(entry));
+  }
+
+  public AnalysisIssues(Collection<Entry> entries) {
+    this.entries = ImmutableList.copyOf(Preconditions.checkNotNull(entries));
+  }
+
+  /**
+   * Creates a attribute error entry that will be added to a {@link AnalysisIssues} later.
+   */
+  public static Entry attributeError(String attribute, String messageTemplate,
+      Object... arguments) {
+    return new Entry(attribute, messageTemplate, arguments);
+  }
+
+  public static Entry ruleError(String messageTemplate, Object... arguments) {
+    return new Entry(null, messageTemplate, arguments);
+  }
+
+  /**
+   * Report all accumulated errors and warnings to the given consumer object.
+   */
+  public void reportTo(RuleErrorConsumer errors) {
+    Preconditions.checkNotNull(errors);
+    for (Entry e : entries) {
+      e.reportTo(errors);
+    }
+  }
+
+  @Nullable
+  private String asString() {
+    if (entries == null) {
+      return null;
+    }
+
+    StringBuilder sb = new StringBuilder();
+    for (Entry e : entries) {
+      e.reportTo(sb);
+    }
+    return sb.toString();
+  }
+
+  @Override
+  public String getMessage() {
+    return asString();
+  }
+
+  @Override
+  public String toString() {
+    String s = asString();
+    return s == null ? "" : s;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index cb29326..d3c509a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -169,6 +169,7 @@
         "Allowlist.java",
         "AnalysisEnvironment.java",
         "AnalysisFailureEvent.java",
+        "AnalysisIssues.java",
         "AnalysisResult.java",
         "AnalysisRootCauseEvent.java",
         "AnalysisUtils.java",
@@ -205,6 +206,7 @@
         "RuleConfiguredTargetFactory.java",
         "RuleContext.java",
         "RuleDefinition.java",
+        "RuleErrorConsumer.java",
         "Runfiles.java",
         "RunfilesProvider.java",
         "RunfilesSupplierImpl.java",
@@ -2110,6 +2112,7 @@
     name = "starlark/starlark_error_reporter",
     srcs = ["starlark/StarlarkErrorReporter.java"],
     deps = [
+        ":analysis_cluster",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/syntax:evaluator",
     ],
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CommonPrerequisiteValidator.java b/src/main/java/com/google/devtools/build/lib/analysis/CommonPrerequisiteValidator.java
index d6f90dc..ffd5d5d 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/CommonPrerequisiteValidator.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CommonPrerequisiteValidator.java
@@ -25,7 +25,6 @@
 import com.google.devtools.build.lib.packages.PackageGroup;
 import com.google.devtools.build.lib.packages.RawAttributeMapper;
 import com.google.devtools.build.lib.packages.Rule;
-import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/EventHandlingErrorReporter.java b/src/main/java/com/google/devtools/build/lib/analysis/EventHandlingErrorReporter.java
index c06ba04..f138abc 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/EventHandlingErrorReporter.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/EventHandlingErrorReporter.java
@@ -17,12 +17,11 @@
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.packages.Attribute;
-import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.syntax.Location;
 
 /**
  * Base class for implementations of {@link
- * com.google.devtools.build.lib.packages.RuleErrorConsumer}.
+ * com.google.devtools.build.lib.analysis.RuleErrorConsumer}.
  *
  * <p>Do not create new implementations of this class - instead, use {@link RuleContext} in Native
  * rule definitions, and {@link StarlarkErrorReporter} in Starlark API definitions. For use in
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java
index fafc011..e5a1c95 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java
@@ -31,7 +31,6 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.OutputFile;
-import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.util.ShellEscaper;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index 72edea4..002e1fd 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -85,7 +85,6 @@
 import com.google.devtools.build.lib.packages.RequiredProviders;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.RuleClass;
-import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.packages.SymbolGenerator;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.TargetUtils;
@@ -1813,7 +1812,7 @@
       Preconditions.checkNotNull(constraintSemantics);
       AttributeMap attributes =
           ConfiguredAttributeMapper.of(target.getAssociatedRule(), configConditions);
-      validateAttributes(attributes);
+      checkAttributesNonEmpty(attributes);
       ListMultimap<String, ConfiguredTargetAndData> targetMap = createTargetMap();
       ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap =
           createFilesetEntryMap(target.getAssociatedRule(), configConditions);
@@ -1840,11 +1839,25 @@
           requiredConfigFragments);
     }
 
-    private void validateAttributes(AttributeMap attributes) {
-      target
-          .getAssociatedRule()
-          .getRuleClassObject()
-          .checkAttributesNonEmpty(reporter, attributes);
+    private void checkAttributesNonEmpty(AttributeMap attributes) {
+      for (String attributeName : attributes.getAttributeNames()) {
+        Attribute attr = attributes.getAttributeDefinition(attributeName);
+        if (!attr.isNonEmpty()) {
+          continue;
+        }
+        Object attributeValue = attributes.get(attributeName, attr.getType());
+
+        // TODO(adonovan): define in terms of Starlark.len?
+        boolean isEmpty = false;
+        if (attributeValue instanceof List<?>) {
+          isEmpty = ((List) attributeValue).isEmpty();
+        } else if (attributeValue instanceof Map<?, ?>) {
+          isEmpty = ((Map) attributeValue).isEmpty();
+        }
+        if (isEmpty) {
+          reporter.attributeError(attr.getName(), "attribute must be non empty");
+        }
+      }
     }
 
     public Builder setVisibility(NestedSet<PackageGroupContents> visibility) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleErrorConsumer.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleErrorConsumer.java
new file mode 100644
index 0000000..cf4ae88
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleErrorConsumer.java
@@ -0,0 +1,104 @@
+// Copyright 2014 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.analysis;
+
+import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
+
+/**
+ * A thin interface exposing only the warning and error reporting functionality
+ * of a rule.
+ *
+ * <p>When a class or a method needs only this functionality but not the whole
+ * {@code RuleContext}, it can use this thin interface instead.
+ *
+ * <p>This interface should only be implemented by {@code RuleContext}.
+ */
+public interface RuleErrorConsumer {
+
+  /**
+   * Consume a non-attribute-specific warning in a rule.
+   */
+  void ruleWarning(String message);
+
+  /**
+   * Consume a non-attribute-specific error in a rule.
+   */
+  void ruleError(String message);
+
+  /**
+   * Consume an attribute-specific warning in a rule.
+   */
+  void attributeWarning(String attrName, String message);
+
+  /**
+   * Consume an attribute-specific error in a rule.
+   */
+  void attributeError(String attrName, String message);
+
+  /**
+   * Convenience function to report non-attribute-specific errors in the current rule and then throw
+   * a {@link RuleErrorException}, immediately exiting the current rule, and shutting down the
+   * invocation in a no-keep-going build. If multiple errors are present, invoke {@link #ruleError}
+   * to collect additional error information before calling this method.
+   */
+  default RuleErrorException throwWithRuleError(String message) throws RuleErrorException {
+    ruleError(message);
+    throw new RuleErrorException(message);
+  }
+
+  /** See {@link #throwWithRuleError(String)}. */
+  default RuleErrorException throwWithRuleError(Throwable cause) throws RuleErrorException {
+    ruleError(cause.getMessage());
+    throw new RuleErrorException(cause);
+  }
+
+  /** See {@link #throwWithRuleError(String)}. */
+  default RuleErrorException throwWithRuleError(String message, Throwable cause)
+      throws RuleErrorException {
+    ruleError(message);
+    throw new RuleErrorException(message, cause);
+  }
+
+  /**
+   * Convenience function to report attribute-specific errors in the current rule, and then throw a
+   * {@link RuleErrorException}, immediately exiting the build invocation. Alternatively, invoke
+   * {@link #attributeError} instead to collect additional error information before ending the
+   * invocation.
+   *
+   * <p>If the name of the attribute starts with <code>$</code>
+   * it is replaced with a string <code>(an implicit dependency)</code>.
+   */
+  default RuleErrorException throwWithAttributeError(String attrName, String message)
+      throws RuleErrorException {
+    attributeError(attrName, message);
+    throw new RuleErrorException(message);
+  }
+
+  /**
+   * Returns whether this instance is known to have errors at this point during analysis. Do not
+   * call this method after the initializationHook has returned.
+   */
+  boolean hasErrors();
+
+  /**
+   * No-op if {@link #hasErrors} is false, throws {@link RuleErrorException} if it is true.
+   * This provides a convenience to early-exit of configured target creation if there are errors.
+   */
+  default void assertNoErrors() throws RuleErrorException {
+    if (hasErrors()) {
+      throw new RuleErrorException();
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
index eb8fc9a..59d14ea 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
@@ -20,10 +20,10 @@
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.RuleErrorConsumer;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
 import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
-import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import javax.annotation.Nullable;
 
@@ -180,7 +180,7 @@
   PlatformInfo getExecutionPlatform(String execGroup);
 
   /**
-   * Returns the {@link com.google.devtools.build.lib.packages.RuleErrorConsumer} for reporting rule
+   * Returns the {@link com.google.devtools.build.lib.analysis.RuleErrorConsumer} for reporting rule
    * errors.
    */
   RuleErrorConsumer getRuleErrorConsumer();
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkErrorReporter.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkErrorReporter.java
index 79abec0..80d70a3 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkErrorReporter.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkErrorReporter.java
@@ -13,8 +13,8 @@
 // limitations under the License
 package com.google.devtools.build.lib.analysis.starlark;
 
+import com.google.devtools.build.lib.analysis.RuleErrorConsumer;
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
-import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.syntax.EvalException;
 
 /**