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()); + } + } }