|  | // 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; | 
|  | } | 
|  | } | 
|  | } |