blob: a044c1156e8dfc4bf40de5158eb949f33bbb2026 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.parser;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.FlavorParser;
import com.facebook.buck.model.ImmutableBuildTarget;
import com.facebook.buck.model.ImmutableFlavor;
import com.facebook.buck.model.ImmutableUnflavoredBuildTarget;
import com.facebook.buck.model.UnflavoredBuildTarget;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.HashSet;
import java.util.List;
public class BuildTargetParser {
private static final String BUILD_RULE_PREFIX = "//";
private static final String BUILD_RULE_SEPARATOR = ":";
private static final String REPOSITORY_STARTER = "@";
private static final Splitter BUILD_RULE_SEPARATOR_SPLITTER = Splitter.on(BUILD_RULE_SEPARATOR);
private static final List<String> INVALID_BUILD_RULE_SUBSTRINGS = ImmutableList.of("..", "./");
private final ImmutableMap<Optional<String>, Optional<String>> localToCanonicalRepoNamesMap;
private final FlavorParser flavorParser = new FlavorParser();
public BuildTargetParser() {
// By default, use a canonical names map that only allows targets with no repo name.
this(ImmutableMap.of(Optional.<String>absent(), Optional.<String>absent()));
}
/**
*
* @param localToCanonicalRepoNamesMap
* Internally, buck uses a unique, global name to refer to each repository. All targets
* that live in repo X are represented internally using this unique name. However, other
* repos might have their own names for X, and rules inside of X will refer to each other
* with no explicit repo at all. This map provides the translation from local repo names
* used within the current repo to their unique, global repo names.
*/
public BuildTargetParser(
ImmutableMap<Optional<String>, Optional<String>> localToCanonicalRepoNamesMap) {
this.localToCanonicalRepoNamesMap = localToCanonicalRepoNamesMap;
}
/**
* @param buildTargetName either a fully-qualified name or relative to the {@link BuildTargetPatternParser}.
* For example, inside {@code first-party/orca/orcaapp/BUILD}, which can be obtained by
* calling {@code ParseContext.forBaseName("first-party/orca/orcaapp")},
* {@code //first-party/orca/orcaapp:assets} and {@code :assets} refer to the same target.
* However, from the command line the context is obtained by calling
* {@link BuildTargetPatternParser#fullyQualified(BuildTargetParser)} and relative names are
* not recognized.
* @param buildTargetPatternParser how targets should be interpreted, such in the context of a
* specific build file or only as fully-qualified names (as is the case for targets from the
* command line).
*/
public BuildTarget parse(
String buildTargetName,
BuildTargetPatternParser buildTargetPatternParser) {
for (String invalidSubstring : INVALID_BUILD_RULE_SUBSTRINGS) {
if (buildTargetName.contains(invalidSubstring)) {
throw new BuildTargetParseException(
String.format("%s cannot contain %s", buildTargetName, invalidSubstring));
}
}
if (buildTargetName.endsWith(BUILD_RULE_SEPARATOR) &&
!buildTargetPatternParser.isWildCardAllowed()) {
throw new BuildTargetParseException(
String.format("%s cannot end with a colon", buildTargetName));
}
Optional<String> givenRepoName = Optional.absent();
String targetAfterRepo = buildTargetName;
if (buildTargetName.startsWith(REPOSITORY_STARTER)) {
if (!buildTargetName.contains(BUILD_RULE_PREFIX)) {
throw new BuildTargetParseException(
String.format(
"Cross-repo paths must contain %s (found %s)",
BUILD_RULE_PREFIX,
buildTargetName));
}
int slashIndex = buildTargetName.indexOf(BUILD_RULE_PREFIX);
givenRepoName = Optional.of(
buildTargetName.substring(REPOSITORY_STARTER.length(), slashIndex));
targetAfterRepo = buildTargetName.substring(slashIndex);
}
if (givenRepoName.isPresent() && givenRepoName.get().isEmpty()) {
throw new BuildTargetParseException("Repo name must not be empty.");
}
if (!localToCanonicalRepoNamesMap.containsKey(givenRepoName)) {
throw new HumanReadableException(String.format(
"In build target '%s', repo '%s' is not defined.",
buildTargetName,
givenRepoName));
}
List<String> parts = BUILD_RULE_SEPARATOR_SPLITTER.splitToList(targetAfterRepo);
if (parts.size() != 2) {
throw new BuildTargetParseException(String.format(
"%s must contain exactly one colon (found %d)", buildTargetName, parts.size() - 1));
}
String baseName =
parts.get(0).isEmpty() ? buildTargetPatternParser.getBaseName() : parts.get(0);
String shortName = parts.get(1);
Iterable<String> flavorNames = new HashSet<>();
int hashIndex = shortName.indexOf("#");
if (hashIndex != -1 && hashIndex < shortName.length()) {
flavorNames = flavorParser.parseFlavorString(shortName.substring(hashIndex + 1));
shortName = shortName.substring(0, hashIndex);
}
Preconditions.checkNotNull(baseName);
// On Windows, baseName may contain backslashes, which are not permitted by BuildTarget.
baseName = baseName.replace("\\", "/");
String fullyQualifiedName = baseName + ':' + shortName;
if (!fullyQualifiedName.startsWith(BUILD_RULE_PREFIX)) {
throw new BuildTargetParseException(
String.format("%s must start with %s", fullyQualifiedName, BUILD_RULE_PREFIX));
}
ImmutableUnflavoredBuildTarget.Builder unflavoredBuilder =
UnflavoredBuildTarget.builder(baseName, shortName);
Optional<String> canonicalRepoName = Preconditions.checkNotNull(
localToCanonicalRepoNamesMap.get(givenRepoName));
if (canonicalRepoName.isPresent()) {
unflavoredBuilder.setRepository(canonicalRepoName.get());
}
ImmutableBuildTarget.Builder builder = BuildTarget.builder(unflavoredBuilder.build());
for (String flavor : flavorNames) {
builder.addFlavors(ImmutableFlavor.of(flavor));
}
return builder.build();
}
}