Introduce optional flavor projection to BuildTarget.
Summary:
For now, this will be reserved for synthetic build rules that
are inserted via graph-enhancement.
Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/model/BuildTarget.java b/src/com/facebook/buck/model/BuildTarget.java
index d7d4b58..56e4a9b 100644
--- a/src/com/facebook/buck/model/BuildTarget.java
+++ b/src/com/facebook/buck/model/BuildTarget.java
@@ -19,11 +19,16 @@
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;
@@ -34,22 +39,47 @@
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);
- // On Windows, baseName may contain backslashes, which are not permitted by BuildTarget.
- baseName = baseName.replace("\\", "/");
+ 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;
+ }
- this.baseName = baseName;
- this.shortName = Preconditions.checkNotNull(shortName);
- this.fullyQualifiedName = String.format("%s:%s", baseName, shortName);
+ // 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;
}
/**
@@ -82,7 +112,7 @@
}
public Path getBuildFilePath() {
- return java.nio.file.Paths.get(getBaseNameWithSlash() + BuckConstant.BUILD_RULES_FILE_NAME);
+ return Paths.get(getBaseNameWithSlash() + BuckConstant.BUILD_RULES_FILE_NAME);
}
/**
@@ -94,6 +124,15 @@
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".
@@ -149,6 +188,11 @@
return fullyQualifiedName;
}
+ @JsonIgnore
+ public boolean isFlavored() {
+ return flavor.isPresent();
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof BuildTarget)) {
diff --git a/test/com/facebook/buck/model/BuildTargetTest.java b/test/com/facebook/buck/model/BuildTargetTest.java
index 0c29f0f..710a4a3 100644
--- a/test/com/facebook/buck/model/BuildTargetTest.java
+++ b/test/com/facebook/buck/model/BuildTargetTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import org.junit.Rule;
import org.junit.Test;
@@ -87,4 +88,40 @@
BuildTarget ioTarget = new BuildTarget("//src/com/facebook/buck/util", "io");
assertFalse(utilTarget.equals(ioTarget));
}
+
+ @Test
+ public void testBuildTargetWithFlavor() {
+ BuildTarget target = new BuildTarget("//foo/bar", "baz", "dex");
+ assertEquals(target.getShortName(), "baz#dex");
+ assertEquals(target.getShortNameWithoutFlavor(), "baz");
+ assertTrue(target.isFlavored());
+ }
+
+ @Test
+ public void testBuildTargetWithoutFlavor() {
+ BuildTarget target = new BuildTarget("//foo/bar", "baz");
+ assertEquals(target.getShortName(), "baz");
+ assertEquals(target.getShortNameWithoutFlavor(), "baz");
+ assertFalse(target.isFlavored());
+ }
+
+ @Test
+ public void testFlavorIsValid() {
+ try {
+ new BuildTarget("//foo/bar", "baz", "d3x");
+ fail("Should have thrown IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid flavor: d3x", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testShortNameCannotContainHash() {
+ try {
+ new BuildTarget("//foo/bar", "baz#dex");
+ fail("Should have thrown IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ assertEquals("Build target name cannot contain '#' but was: baz#dex.", e.getMessage());
+ }
+ }
}