| /* |
| * 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.model; |
| |
| import com.facebook.buck.util.BuckConstant; |
| import com.facebook.buck.util.ProjectFilesystem; |
| import com.fasterxml.jackson.annotation.JsonAutoDetect; |
| import com.fasterxml.jackson.annotation.JsonIgnore; |
| import com.fasterxml.jackson.annotation.JsonProperty; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| |
| import java.io.File; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.regex.Pattern; |
| |
| import javax.annotation.Nullable; |
| |
| @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, |
| getterVisibility = JsonAutoDetect.Visibility.NONE, |
| setterVisibility = JsonAutoDetect.Visibility.NONE) |
| public final class BuildTarget implements Comparable<BuildTarget> { |
| |
| public static final String BUILD_TARGET_PREFIX = "//"; |
| |
| private static final Pattern VALID_FLAVOR_PATTERN = Pattern.compile("[a-zA-Z_]+"); |
| |
| private final String baseName; |
| private final String shortName; |
| private final Optional<String> flavor; |
| private final String fullyQualifiedName; |
| |
| public BuildTarget(String baseName, String shortName) { |
| this(baseName, shortName, /* flavor */ Optional.<String>absent()); |
| } |
| |
| public BuildTarget(String baseName, String shortName, String flavor) { |
| this(baseName, shortName, Optional.of(flavor)); |
| } |
| |
| private BuildTarget(String baseName, String shortName, Optional<String> flavor) { |
| Preconditions.checkNotNull(baseName); |
| // shortName may be the empty string when parsing visibility patterns. |
| Preconditions.checkNotNull(shortName); |
| Preconditions.checkNotNull(flavor); |
| |
| Preconditions.checkArgument(baseName.startsWith(BUILD_TARGET_PREFIX), |
| "baseName must start with // but was %s", |
| baseName); |
| |
| Preconditions.checkArgument(!shortName.contains("#"), |
| "Build target name cannot contain '#' but was: %s.", |
| shortName); |
| if (flavor.isPresent()) { |
| String flavorName = flavor.get(); |
| if (!VALID_FLAVOR_PATTERN.matcher(flavorName).matches()) { |
| throw new IllegalArgumentException("Invalid flavor: " + flavorName); |
| } |
| shortName += "#" + flavorName; |
| } |
| |
| // On Windows, baseName may contain backslashes, which are not permitted by BuildTarget. |
| this.baseName = baseName.replace("\\", "/"); |
| this.shortName = shortName; |
| this.flavor = flavor; |
| this.fullyQualifiedName = baseName + ":" + shortName; |
| } |
| |
| /** |
| * The build file in which this rule was defined. |
| * @throws MissingBuildFileException if the build file for the target does not exist. |
| */ |
| public File getBuildFile(ProjectFilesystem projectFilesystem) throws MissingBuildFileException { |
| String pathToBuildFile = getBasePathWithSlash() + BuckConstant.BUILD_RULES_FILE_NAME; |
| File buildFile = projectFilesystem.getFileForRelativePath(pathToBuildFile); |
| if (buildFile.isFile()) { |
| return buildFile; |
| } else { |
| throw new MissingBuildFileException(this); |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| public static class MissingBuildFileException extends BuildTargetException { |
| |
| private MissingBuildFileException(BuildTarget buildTarget) { |
| super(String.format("No build file at %s when resolving target %s.", |
| buildTarget.getBasePathWithSlash() + BuckConstant.BUILD_RULES_FILE_NAME, |
| buildTarget.getFullyQualifiedName())); |
| } |
| |
| @Override |
| public String getHumanReadableErrorMessage() { |
| return getMessage(); |
| } |
| } |
| |
| public Path getBuildFilePath() { |
| return Paths.get(getBaseNameWithSlash() + BuckConstant.BUILD_RULES_FILE_NAME); |
| } |
| |
| /** |
| * If this build target were //third_party/java/guava:guava-latest, then this would return |
| * "guava-latest". |
| */ |
| @JsonProperty("shortName") |
| public String getShortName() { |
| return shortName; |
| } |
| |
| @VisibleForTesting |
| String getShortNameWithoutFlavor() { |
| if (!isFlavored()) { |
| return shortName; |
| } else { |
| return shortName.substring(0, shortName.length() - flavor.get().length() - 1); |
| } |
| } |
| |
| /** |
| * If this build target were //third_party/java/guava:guava-latest, then this would return |
| * "//third_party/java/guava". |
| */ |
| @JsonProperty("baseName") |
| public String getBaseName() { |
| return baseName; |
| } |
| |
| /** |
| * If this build target were //third_party/java/guava:guava-latest, then this would return |
| * "//third_party/java/guava/". |
| */ |
| public String getBaseNameWithSlash() { |
| return getBaseNameWithSlash(baseName); |
| } |
| |
| /** |
| * Helper function for getting BuildTarget base names with a trailing slash if needed. |
| * |
| * If baseName were //third_party/java/guava, then this would return "//third_party/java/guava/". |
| * If it were //, it would return //. |
| */ |
| @Nullable |
| public static String getBaseNameWithSlash(@Nullable String baseName) { |
| return baseName == null || baseName.equals(BUILD_TARGET_PREFIX) ? baseName : baseName + "/"; |
| } |
| |
| /** |
| * If this build target were //third_party/java/guava:guava-latest, then this would return |
| * "third_party/java/guava". This does not contain the "//" prefix so that it can be appended to |
| * a file path. |
| */ |
| public String getBasePath() { |
| return baseName.substring(BUILD_TARGET_PREFIX.length()); |
| } |
| |
| /** |
| * @return the value of {@link #getBasePath()} with a trailing slash, unless |
| * {@link #getBasePath()} returns the empty string, in which case this also returns the empty |
| * string |
| */ |
| public String getBasePathWithSlash() { |
| String basePath = getBasePath(); |
| return basePath.isEmpty() ? "" : basePath + "/"; |
| } |
| |
| /** |
| * If this build target is //third_party/java/guava:guava-latest, then this would return |
| * "//third_party/java/guava:guava-latest". |
| */ |
| public String getFullyQualifiedName() { |
| return fullyQualifiedName; |
| } |
| |
| @JsonIgnore |
| public boolean isFlavored() { |
| return flavor.isPresent(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof BuildTarget)) { |
| return false; |
| } |
| BuildTarget that = (BuildTarget)o; |
| return this.fullyQualifiedName.equals(that.fullyQualifiedName); |
| } |
| |
| @Override |
| public int hashCode() { |
| return fullyQualifiedName.hashCode(); |
| } |
| |
| /** @return {@link #getFullyQualifiedName()} */ |
| @Override |
| public String toString() { |
| return getFullyQualifiedName(); |
| } |
| |
| @Override |
| public int compareTo(BuildTarget target) { |
| Preconditions.checkNotNull(target); |
| return getFullyQualifiedName().compareTo(target.getFullyQualifiedName()); |
| } |
| } |