blob: dc94e9f7ae4e3f1fb928903e872a62a3bd6b6052 [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 static com.facebook.buck.util.BuckConstant.BUILD_RULES_FILE_NAME;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.util.ProjectFilesystem;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import java.util.List;
public class BuildTargetParser {
private static final String BUILD_RULE_PREFIX = "//";
private static final String BUILD_RULE_SEPARATOR = ":";
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 ProjectFilesystem projectFilesystem;
public BuildTargetParser(ProjectFilesystem projectFilesystem) {
this.projectFilesystem = Preconditions.checkNotNull(projectFilesystem);
}
/**
* @param buildTargetName either a fully-qualified name or relative to the {@link ParseContext}.
* 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 ParseContext#fullyQualified()} and relative names are not recognized.
* @param parseContext how targets should be interpretted, 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, ParseContext parseContext)
throws NoSuchBuildTargetException {
Preconditions.checkNotNull(buildTargetName);
Preconditions.checkNotNull(parseContext);
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) &&
parseContext.getType() != ParseContext.Type.VISIBILITY) {
throw new BuildTargetParseException(
String.format("%s cannot end with a colon", buildTargetName));
}
List<String> parts = ImmutableList.copyOf(BUILD_RULE_SEPARATOR_SPLITTER.split(buildTargetName));
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() ? parseContext.getBaseName() : parts.get(0);
String shortName = parts.get(1);
String fullyQualifiedName = String.format("%s:%s", baseName, shortName);
if (!fullyQualifiedName.startsWith(BUILD_RULE_PREFIX)) {
throw new BuildTargetParseException(
String.format("%s must start with %s", fullyQualifiedName, BUILD_RULE_PREFIX));
}
// Make sure the directory that contains the build file exists.
String buildFileDirectory = baseName.substring(BUILD_RULE_PREFIX.length());
String buildFilePath = (buildFileDirectory.isEmpty() ? "" : buildFileDirectory + "/")
+ BUILD_RULES_FILE_NAME;
if (!projectFilesystem.exists(buildFileDirectory)) {
if (parseContext.getType() == ParseContext.Type.BUILD_FILE &&
baseName.equals(parseContext.getBaseName())) {
throw new BuildTargetParseException(String.format(
"Internal error: Parsing in the context of %s, but %s does not exist",
buildFilePath,
buildFileDirectory));
} else {
throw NoSuchBuildTargetException.createForMissingDirectory(buildFileDirectory,
buildTargetName,
parseContext);
}
}
// Make sure the build file exists.
if (!projectFilesystem.exists(buildFilePath)) {
if (parseContext.getType() == ParseContext.Type.BUILD_FILE &&
baseName.equals(parseContext.getBaseName())) {
throw new BuildTargetParseException(String.format(
"Internal error: Parsing in the context of %s, but %s does not exist",
buildFilePath,
buildFilePath));
} else {
throw NoSuchBuildTargetException.createForMissingBuildFile(buildFilePath,
buildTargetName,
parseContext);
}
}
return new BuildTarget(baseName, shortName);
}
}