Update from Google.
--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java
new file mode 100644
index 0000000..89ce2e7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java
@@ -0,0 +1,181 @@
+// Copyright 2014 Google Inc. 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.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Helper class encapsulating string scanning state used during "heuristic"
+ * expansion of labels embedded within rules.
+ */
+public final class LabelExpander {
+ /**
+ * An exception that is thrown when a label is expanded to zero or multiple
+ * files during expansion.
+ */
+ public static class NotUniqueExpansionException extends Exception {
+ public NotUniqueExpansionException(int sizeOfResultSet, String labelText) {
+ super("heuristic label expansion found '" + labelText + "', which expands to "
+ + sizeOfResultSet + " files"
+ + (sizeOfResultSet > 1
+ ? ", please use $(locations " + labelText + ") instead"
+ : ""));
+ }
+ }
+
+ // This is a utility class, no need to instantiate.
+ private LabelExpander() {}
+
+ /**
+ * CharMatcher to determine if a given character is valid for labels.
+ *
+ * <p>The Build Concept Reference additionally allows '=' and ',' to appear in labels,
+ * but for the purposes of the heuristic, this function does not, as it would cause
+ * "--foo=:rule1,:rule2" to scan as a single possible label, instead of three
+ * ("--foo", ":rule1", ":rule2").
+ */
+ private static final CharMatcher LABEL_CHAR_MATCHER =
+ CharMatcher.inRange('a', 'z')
+ .or(CharMatcher.inRange('A', 'Z'))
+ .or(CharMatcher.inRange('0', '9'))
+ .or(CharMatcher.anyOf(":/_.-+" + PathFragment.SEPARATOR_CHAR));
+
+ /**
+ * Expands all references to labels embedded within a string using the
+ * provided expansion mapping from labels to artifacts.
+ *
+ * <p>Since this pass is heuristic, references to non-existent labels (such
+ * as arbitrary words) or invalid labels are simply ignored and are unchanged
+ * in the output. However, if the heuristic discovers a label, which
+ * identifies an existing target producing zero or multiple files, an error
+ * is reported.
+ *
+ * @param expression the expression to expand.
+ * @param labelMap the mapping from labels to artifacts, whose relative path
+ * is to be used as the expansion.
+ * @param labelResolver the {@code Label} that can resolve label strings
+ * to {@code Label} objects. The resolved label is either relative to
+ * {@code labelResolver} or is a global label (i.e. starts with "//").
+ * @return the expansion of the string.
+ * @throws NotUniqueExpansionException if a label that is present in the
+ * mapping expands to zero or multiple files.
+ */
+ public static <T extends Iterable<Artifact>> String expand(@Nullable String expression,
+ Map<Label, T> labelMap, Label labelResolver) throws NotUniqueExpansionException {
+ if (Strings.isNullOrEmpty(expression)) {
+ return "";
+ }
+ Preconditions.checkNotNull(labelMap);
+ Preconditions.checkNotNull(labelResolver);
+
+ int offset = 0;
+ StringBuilder result = new StringBuilder();
+ while (offset < expression.length()) {
+ String labelText = scanLabel(expression, offset);
+ if (labelText != null) {
+ offset += labelText.length();
+ result.append(tryResolvingLabelTextToArtifactPath(labelText, labelMap, labelResolver));
+ } else {
+ result.append(expression.charAt(offset));
+ offset++;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Tries resolving a label text to a full label for the associated {@code
+ * Artifact}, using the provided mapping.
+ *
+ * <p>The method succeeds if the label text can be resolved to a {@code
+ * Label} object, which is present in the {@code labelMap} and maps to
+ * exactly one {@code Artifact}.
+ *
+ * @param labelText the text to resolve.
+ * @param labelMap the mapping from labels to artifacts, whose relative path
+ * is to be used as the expansion.
+ * @param labelResolver the {@code Label} that can resolve label strings
+ * to {@code Label} objects. The resolved label is either relative to
+ * {@code labelResolver} or is a global label (i.e. starts with "//").
+ * @return an absolute label to an {@code Artifact} if the resolving was
+ * successful or the original label text.
+ * @throws NotUniqueExpansionException if a label that is present in the
+ * mapping expands to zero or multiple files.
+ */
+ private static <T extends Iterable<Artifact>> String tryResolvingLabelTextToArtifactPath(
+ String labelText, Map<Label, T> labelMap, Label labelResolver)
+ throws NotUniqueExpansionException {
+ Label resolvedLabel = resolveLabelText(labelText, labelResolver);
+ if (resolvedLabel != null) {
+ Iterable<Artifact> artifacts = labelMap.get(resolvedLabel);
+ if (artifacts != null) { // resolvedLabel identifies an existing target
+ List<String> locations = new ArrayList<>();
+ Artifact.addExecPaths(artifacts, locations);
+ int resultSetSize = locations.size();
+ if (resultSetSize == 1) {
+ return Iterables.getOnlyElement(locations); // success!
+ } else {
+ throw new NotUniqueExpansionException(resultSetSize, labelText);
+ }
+ }
+ }
+ return labelText;
+ }
+
+ /**
+ * Resolves a string to a label text. Uses {@code labelResolver} to do so.
+ * The result is either relative to {@code labelResolver} or is an absolute
+ * label. In case of an invalid label text, the return value is null.
+ */
+ private static Label resolveLabelText(String labelText, Label labelResolver) {
+ try {
+ return labelResolver.getRelative(labelText);
+ } catch (Label.SyntaxException e) {
+ // It's a heuristic, so quietly ignore "errors". Because Label.getRelative never
+ // returns null, we can use null to indicate an error.
+ return null;
+ }
+ }
+
+ /**
+ * Scans the argument string from a given start position until the name of a
+ * potential label has been consumed, then returns the label text. If
+ * the expression contains no possible label starting at the start position,
+ * the return value is null.
+ */
+ private static String scanLabel(String expression, int start) {
+ int offset = start;
+ while (offset < expression.length() && LABEL_CHAR_MATCHER.matches(expression.charAt(offset))) {
+ ++offset;
+ }
+ if (offset > start) {
+ return expression.substring(start, offset);
+ } else {
+ return null;
+ }
+ }
+}