blob: a20a2b7220def0ac4e2c396ada30974c5988bdd8 [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.java;
import static com.facebook.buck.java.JavaCompilationConstants.DEFAULT_JAVAC_OPTIONS;
import static com.facebook.buck.util.BuckConstant.BIN_PATH;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.facebook.buck.android.AndroidLibrary;
import com.facebook.buck.android.AndroidLibraryBuilder;
import com.facebook.buck.android.AndroidLibraryDescription;
import com.facebook.buck.android.AndroidPlatformTarget;
import com.facebook.buck.android.AndroidResourceDescription;
import com.facebook.buck.event.BuckEventBusFactory;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.abi.AbiWriterProtocol;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.ActionGraph;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildDependencies;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.BuildTargetSourcePath;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.FakeBuildRuleParamsBuilder;
import com.facebook.buck.rules.FakeBuildableContext;
import com.facebook.buck.rules.FakeRuleKeyBuilderFactory;
import com.facebook.buck.rules.ImmutableBuildContext;
import com.facebook.buck.rules.ImmutableSha1HashCode;
import com.facebook.buck.rules.NoopArtifactCache;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.RuleKeyBuilderFactory;
import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePaths;
import com.facebook.buck.rules.TestSourcePath;
import com.facebook.buck.shell.GenruleBuilder;
import com.facebook.buck.shell.ShellStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepRunner;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.testutil.AllExistingProjectFilesystem;
import com.facebook.buck.testutil.FakeFileHashCache;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.testutil.MoreAsserts;
import com.facebook.buck.testutil.RuleMap;
import com.facebook.buck.timing.DefaultClock;
import com.facebook.buck.util.Ansi;
import com.facebook.buck.util.BuckConstant;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.Verbosity;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
public class DefaultJavaLibraryTest {
private static final String ANNOTATION_SCENARIO_TARGET =
"//android/java/src/com/facebook:fb";
private static final String ANNOTATION_SCENARIO_GEN_PATH_POSTIX =
BuckConstant.ANNOTATION_DIR + "/android/java/src/com/facebook/__fb_gen__";
@Rule
public TemporaryFolder tmp = new TemporaryFolder();
private String annotationScenarioGenPath;
@Before
public void stubOutBuildContext() {
StepRunner stepRunner = createNiceMock(StepRunner.class);
JavaPackageFinder packageFinder = createNiceMock(JavaPackageFinder.class);
replay(packageFinder, stepRunner);
annotationScenarioGenPath = new File(
tmp.getRoot(),
ANNOTATION_SCENARIO_GEN_PATH_POSTIX).getAbsolutePath();
}
/** Make sure that when isAndroidLibrary is true, that the Android bootclasspath is used. */
@Test
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
public void testBuildInternalWithAndroidBootclasspath() throws IOException {
String folder = "android/java/src/com/facebook";
tmp.newFolder(folder.split("/"));
BuildTarget buildTarget = BuildTargetFactory.newInstance("//" + folder + ":fb");
Path src = Paths.get(folder, "Main.java");
tmp.newFile(src.toString());
BuildRuleResolver ruleResolver = new BuildRuleResolver(
ImmutableMap.<BuildTarget, BuildRule>of());
ProjectFilesystem projectFilesystem = new ProjectFilesystem(tmp.getRoot().toPath());
BuildRule libraryRule = AndroidLibraryBuilder
.createBuilder(buildTarget)
.addSrc(src)
.build(ruleResolver);
DefaultJavaLibrary javaLibrary = (DefaultJavaLibrary) libraryRule;
String bootclasspath = "effects.jar:maps.jar:usb.jar:";
BuildContext context = createBuildContext(libraryRule, bootclasspath, projectFilesystem);
List<Step> steps = javaLibrary.getBuildSteps(context, new FakeBuildableContext());
// Find the JavacStep and verify its bootclasspath.
Step step = Iterables.find(steps, new Predicate<Step>() {
@Override
public boolean apply(Step command) {
return command instanceof JavacStep;
}
});
assertNotNull("Expected a JavacStep in the steplist.", step);
JavacStep javac = (JavacStep) step;
assertEquals("Should compile Main.java rather than generated R.java.",
ImmutableSet.of(src),
javac.getSrcs());
}
@Test
public void testGetInputsToCompareToOutputWhenAResourceAsSourcePathExists() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget genruleBuildTarget = BuildTargetFactory.newInstance("//generated:stuff");
BuildRule genrule = GenruleBuilder
.newGenruleBuilder(genruleBuildTarget)
.setBash("echo 'aha' > $OUT")
.setOut("stuff.txt")
.build(ruleResolver);
ProjectFilesystem filesystem = new AllExistingProjectFilesystem() {
@Override
public boolean isDirectory(Path path, LinkOption... linkOptionsk) {
return false;
}
};
DefaultJavaLibrary javaRule = (DefaultJavaLibrary) JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//library:code"))
.addResource(new BuildTargetSourcePath(filesystem, genrule.getBuildTarget()))
.addResource(new TestSourcePath("library/data.txt"))
.build(ruleResolver, filesystem);
assertEquals(
"Generated files should not be included in getInputsToCompareToOutput() because they " +
"should not be part of the RuleKey computation.",
ImmutableList.of(Paths.get("library/data.txt")),
javaRule.getInputsToCompareToOutput());
}
@Test
public void testJavaLibaryThrowsIfResourceIsDirectory() {
ProjectFilesystem filesystem = new AllExistingProjectFilesystem() {
@Override
public boolean isDirectory(Path path, LinkOption... linkOptionsk) {
return true;
}
};
try {
JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//library:code"))
.addResource(new TestSourcePath("library"))
.build(new BuildRuleResolver(), filesystem);
fail("An exception should have been thrown because a directory was passed as a resource.");
} catch (HumanReadableException e) {
assertTrue(e.getHumanReadableErrorMessage().contains("a directory is not a valid input"));
}
}
/**
* Verify adding an annotation processor java binary.
*/
@Test
public void testAddAnnotationProcessorJavaBinary() throws IOException {
AnnotationProcessingScenario scenario = new AnnotationProcessingScenario();
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_JAVA_BINARY);
scenario.getAnnotationProcessingParamsBuilder()
.addAllProcessors(ImmutableList.of("MyProcessor"));
ImmutableList<String> parameters = scenario.buildAndGetCompileParameters();
MoreAsserts.assertContainsOne(parameters, "-processorpath");
MoreAsserts.assertContainsOne(parameters, "-processor");
assertHasProcessor(parameters, "MyProcessor");
MoreAsserts.assertContainsOne(parameters, "-s");
MoreAsserts.assertContainsOne(parameters, annotationScenarioGenPath);
assertEquals(
"Expected '-processor MyProcessor' parameters",
parameters.indexOf("-processor") + 1,
parameters.indexOf("MyProcessor"));
assertEquals(
"Expected '-s " + annotationScenarioGenPath + "' parameters",
parameters.indexOf("-s") + 1,
parameters.indexOf(annotationScenarioGenPath));
for (String parameter : parameters) {
assertTrue("Expected no custom annotation options.", !parameter.startsWith("-A") ||
parameter.startsWith("-A" + AbiWriterProtocol.PARAM_ABI_OUTPUT_FILE));
}
}
/**
* Verify adding an annotation processor prebuilt jar.
*/
@Test
public void testAddAnnotationProcessorPrebuiltJar() throws IOException {
AnnotationProcessingScenario scenario = new AnnotationProcessingScenario();
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_PREBUILT_JAR);
scenario.getAnnotationProcessingParamsBuilder()
.addAllProcessors(ImmutableList.of("MyProcessor"));
ImmutableList<String> parameters = scenario.buildAndGetCompileParameters();
MoreAsserts.assertContainsOne(parameters, "-processorpath");
MoreAsserts.assertContainsOne(parameters, "-processor");
assertHasProcessor(parameters, "MyProcessor");
MoreAsserts.assertContainsOne(parameters, "-s");
MoreAsserts.assertContainsOne(parameters, annotationScenarioGenPath);
}
/**
* Verify adding an annotation processor java library.
*/
@Test
public void testAddAnnotationProcessorJavaLibrary() throws IOException {
AnnotationProcessingScenario scenario = new AnnotationProcessingScenario();
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_PREBUILT_JAR);
scenario.getAnnotationProcessingParamsBuilder()
.addAllProcessors(ImmutableList.of("MyProcessor"));
ImmutableList<String> parameters = scenario.buildAndGetCompileParameters();
MoreAsserts.assertContainsOne(parameters, "-processorpath");
MoreAsserts.assertContainsOne(parameters, "-processor");
assertHasProcessor(parameters, "MyProcessor");
MoreAsserts.assertContainsOne(parameters, "-s");
MoreAsserts.assertContainsOne(parameters, annotationScenarioGenPath);
}
/**
* Verify adding multiple annotation processors.
*/
@Test
public void testAddAnnotationProcessorJar() throws IOException {
AnnotationProcessingScenario scenario = new AnnotationProcessingScenario();
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_PREBUILT_JAR);
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_JAVA_BINARY);
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_JAVA_LIBRARY);
scenario.getAnnotationProcessingParamsBuilder()
.addAllProcessors(ImmutableList.of("MyProcessor"));
ImmutableList<String> parameters = scenario.buildAndGetCompileParameters();
MoreAsserts.assertContainsOne(parameters, "-processorpath");
MoreAsserts.assertContainsOne(parameters, "-processor");
assertHasProcessor(parameters, "MyProcessor");
MoreAsserts.assertContainsOne(parameters, "-s");
MoreAsserts.assertContainsOne(parameters, annotationScenarioGenPath);
}
@Test
public void testGetClasspathEntriesMap() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
BuildRule libraryOne = JavaLibraryBuilder.createBuilder(libraryOneTarget)
.addSrc(Paths.get("java/src/com/libone/Bar.java"))
.build(ruleResolver);
BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo");
BuildRule libraryTwo = JavaLibraryBuilder
.createBuilder(libraryTwoTarget)
.addSrc(Paths.get("java/src/com/libtwo/Foo.java"))
.addDep(libraryOne.getBuildTarget())
.build(ruleResolver);
BuildTarget parentTarget = BuildTargetFactory.newInstance("//:parent");
BuildRule parent = JavaLibraryBuilder
.createBuilder(parentTarget)
.addSrc(Paths.get("java/src/com/parent/Meh.java"))
.addDep(libraryTwo.getBuildTarget())
.build(ruleResolver);
assertEquals(
ImmutableSetMultimap.of(
getJavaLibrary(libraryOne),
Paths.get("buck-out/gen/lib__libone__output/libone.jar"),
getJavaLibrary(libraryTwo),
Paths.get("buck-out/gen/lib__libtwo__output/libtwo.jar"),
getJavaLibrary(parent),
Paths.get("buck-out/gen/lib__parent__output/parent.jar")),
((HasClasspathEntries) parent).getTransitiveClasspathEntries());
}
@Test
public void testClasspathForJavacCommand() throws IOException {
// libraryOne responds like an ordinary prebuilt_jar with no dependencies. We have to use a
// FakeJavaLibraryRule so that we can override the behavior of getAbiKey().
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
FakeJavaLibrary libraryOne = new FakeJavaLibrary(
libraryOneTarget,
new SourcePathResolver(new BuildRuleResolver())) {
@Override
public Sha1HashCode getAbiKey() {
return ImmutableSha1HashCode.of(Strings.repeat("cafebabe", 5));
}
@Override
public ImmutableSetMultimap<JavaLibrary, Path> getDeclaredClasspathEntries() {
return ImmutableSetMultimap.of(
(JavaLibrary) this,
Paths.get("java/src/com/libone/bar.jar"));
}
@Override
public ImmutableSetMultimap<JavaLibrary, Path> getOutputClasspathEntries() {
return ImmutableSetMultimap.of(
(JavaLibrary) this,
Paths.get("java/src/com/libone/bar.jar"));
}
@Override
public ImmutableSetMultimap<JavaLibrary, Path> getTransitiveClasspathEntries() {
return ImmutableSetMultimap.of();
}
};
Map<BuildTarget, BuildRule> buildRuleIndex = Maps.newHashMap();
buildRuleIndex.put(libraryOneTarget, libraryOne);
BuildRuleResolver ruleResolver = new BuildRuleResolver(buildRuleIndex);
BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo");
BuildRule libraryTwo = JavaLibraryBuilder
.createBuilder(libraryTwoTarget)
.addSrc(Paths.get("java/src/com/libtwo/Foo.java"))
.addDep(libraryOne.getBuildTarget())
.build(ruleResolver);
BuildContext buildContext = EasyMock.createMock(BuildContext.class);
expect(buildContext.getBuildDependencies()).andReturn(BuildDependencies.FIRST_ORDER_ONLY)
.times(2);
JavaPackageFinder javaPackageFinder = EasyMock.createMock(JavaPackageFinder.class);
expect(buildContext.getJavaPackageFinder()).andReturn(javaPackageFinder);
replay(buildContext, javaPackageFinder);
List<Step> steps = libraryTwo.getBuildSteps(buildContext, new FakeBuildableContext());
EasyMock.verify(buildContext, javaPackageFinder);
ImmutableList<JavacStep> javacSteps = FluentIterable
.from(steps)
.filter(JavacStep.class)
.toList();
assertEquals("There should be only one javac step.", 1, javacSteps.size());
JavacStep javacStep = javacSteps.get(0);
assertEquals(
"The classpath to use when compiling //:libtwo according to getDeclaredClasspathEntries()" +
" should contain only bar.jar.",
ImmutableSet.of(Paths.get("java/src/com/libone/bar.jar")),
ImmutableSet.copyOf(
((JavaLibrary) libraryTwo).getDeclaredClasspathEntries().values()));
assertEquals(
"The classpath for the javac step to compile //:libtwo should contain only bar.jar.",
ImmutableSet.of(Paths.get("java/src/com/libone/bar.jar")),
javacStep.getClasspathEntries());
}
/**
* Verify adding an annotation processor java binary with options.
*/
@Test
public void testAddAnnotationProcessorWithOptions() throws IOException {
AnnotationProcessingScenario scenario = new AnnotationProcessingScenario();
scenario.addAnnotationProcessorTarget(AnnotationProcessorTarget.VALID_JAVA_BINARY);
scenario.getAnnotationProcessingParamsBuilder().addAllProcessors(
ImmutableList.of("MyProcessor"));
scenario.getAnnotationProcessingParamsBuilder().addParameter("MyParameter");
scenario.getAnnotationProcessingParamsBuilder().addParameter("MyKey=MyValue");
scenario.getAnnotationProcessingParamsBuilder().setProcessOnly(true);
ImmutableList<String> parameters = scenario.buildAndGetCompileParameters();
MoreAsserts.assertContainsOne(parameters, "-processorpath");
MoreAsserts.assertContainsOne(parameters, "-processor");
assertHasProcessor(parameters, "MyProcessor");
MoreAsserts.assertContainsOne(parameters, "-s");
MoreAsserts.assertContainsOne(parameters, annotationScenarioGenPath);
MoreAsserts.assertContainsOne(parameters, "-proc:only");
assertEquals(
"Expected '-processor MyProcessor' parameters",
parameters.indexOf("-processor") + 1,
parameters.indexOf("MyProcessor"));
assertEquals(
"Expected '-s " + annotationScenarioGenPath + "' parameters",
parameters.indexOf("-s") + 1,
parameters.indexOf(annotationScenarioGenPath));
MoreAsserts.assertContainsOne(parameters, "-AMyParameter");
MoreAsserts.assertContainsOne(parameters, "-AMyKey=MyValue");
}
private void assertHasProcessor(List<String> params, String processor) {
int index = params.indexOf("-processor");
if (index >= params.size()) {
fail(String.format("No processor argument found in %s.", params));
}
Set<String> processors = ImmutableSet.copyOf(Splitter.on(',').split(params.get(index + 1)));
if (!processors.contains(processor)) {
fail(String.format("Annotation processor %s not found in %s.", processor, params));
}
}
@Test
public void testExportedDeps() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget nonIncludedTarget = BuildTargetFactory.newInstance("//:not_included");
BuildRule notIncluded = JavaLibraryBuilder
.createBuilder(nonIncludedTarget)
.addSrc(Paths.get("java/src/com/not_included/Raz.java"))
.build(ruleResolver);
BuildTarget includedTarget = BuildTargetFactory.newInstance("//:included");
BuildRule included = JavaLibraryBuilder
.createBuilder(includedTarget)
.addSrc(Paths.get("java/src/com/included/Rofl.java"))
.build(ruleResolver);
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
BuildRule libraryOne = JavaLibraryBuilder
.createBuilder(libraryOneTarget)
.addDep(notIncluded.getBuildTarget())
.addDep(included.getBuildTarget())
.addExportedDep(included.getBuildTarget())
.addSrc(Paths.get("java/src/com/libone/Bar.java"))
.build(ruleResolver);
BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo");
BuildRule libraryTwo = JavaLibraryBuilder
.createBuilder(libraryTwoTarget)
.addSrc(Paths.get("java/src/com/libtwo/Foo.java"))
.addDep(libraryOne.getBuildTarget())
.addExportedDep(libraryOne.getBuildTarget())
.build(ruleResolver);
BuildTarget parentTarget = BuildTargetFactory.newInstance("//:parent");
BuildRule parent = JavaLibraryBuilder
.createBuilder(parentTarget)
.addSrc(Paths.get("java/src/com/parent/Meh.java"))
.addDep(libraryTwo.getBuildTarget())
.build(ruleResolver);
assertEquals(
"A java_library that depends on //:libone should include only libone.jar in its " +
"classpath when compiling itself.",
ImmutableSetMultimap.of(
getJavaLibrary(notIncluded),
Paths.get("buck-out/gen/lib__not_included__output/not_included.jar")),
getJavaLibrary(notIncluded).getOutputClasspathEntries());
assertEquals(
ImmutableSetMultimap.of(
getJavaLibrary(included),
Paths.get("buck-out/gen/lib__included__output/included.jar")),
getJavaLibrary(included).getOutputClasspathEntries());
assertEquals(
ImmutableSetMultimap.of(
getJavaLibrary(included),
Paths.get("buck-out/gen/lib__included__output/included.jar"),
getJavaLibrary(libraryOne),
Paths.get("buck-out/gen/lib__libone__output/libone.jar"),
getJavaLibrary(libraryOne),
Paths.get("buck-out/gen/lib__included__output/included.jar")),
getJavaLibrary(libraryOne).getOutputClasspathEntries());
assertEquals(
"//:libtwo exports its deps, so a java_library that depends on //:libtwo should include " +
"both libone.jar and libtwo.jar in its classpath when compiling itself.",
ImmutableSetMultimap.of(
getJavaLibrary(libraryOne),
Paths.get("buck-out/gen/lib__libone__output/libone.jar"),
getJavaLibrary(libraryOne),
Paths.get("buck-out/gen/lib__included__output/included.jar"),
getJavaLibrary(libraryTwo),
Paths.get("buck-out/gen/lib__libone__output/libone.jar"),
getJavaLibrary(libraryTwo),
Paths.get("buck-out/gen/lib__libtwo__output/libtwo.jar"),
getJavaLibrary(libraryTwo),
Paths.get("buck-out/gen/lib__included__output/included.jar")),
getJavaLibrary(libraryTwo).getOutputClasspathEntries());
assertEquals(
"A java_binary that depends on //:parent should include libone.jar, libtwo.jar and " +
"parent.jar.",
ImmutableSetMultimap.<JavaLibrary, Path>builder()
.put(
getJavaLibrary(included),
Paths.get("buck-out/gen/lib__included__output/included.jar"))
.put(
getJavaLibrary(notIncluded),
Paths.get("buck-out/gen/lib__not_included__output/not_included.jar"))
.putAll(
getJavaLibrary(libraryOne),
Paths.get("buck-out/gen/lib__included__output/included.jar"),
Paths.get("buck-out/gen/lib__libone__output/libone.jar"))
.putAll(
getJavaLibrary(libraryTwo),
Paths.get("buck-out/gen/lib__included__output/included.jar"),
Paths.get("buck-out/gen/lib__not_included__output/not_included.jar"),
Paths.get("buck-out/gen/lib__libone__output/libone.jar"),
Paths.get("buck-out/gen/lib__libtwo__output/libtwo.jar"))
.put(getJavaLibrary(parent), Paths.get("buck-out/gen/lib__parent__output/parent.jar"))
.build(),
getJavaLibrary(parent).getTransitiveClasspathEntries());
assertEquals(
"A java_library that depends on //:parent should include only parent.jar in its " +
"-classpath when compiling itself.",
ImmutableSetMultimap.of(
getJavaLibrary(parent),
Paths.get("buck-out/gen/lib__parent__output/parent.jar")),
getJavaLibrary(parent).getOutputClasspathEntries());
}
/**
* Tests that an error is thrown when non-java library rules are listed in the exported deps
* parameter.
*/
@Test
public void testExportedDepsShouldOnlyContainJavaLibraryRules() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget genruleBuildTarget = BuildTargetFactory.newInstance("//generated:stuff");
BuildRule genrule = GenruleBuilder
.newGenruleBuilder(genruleBuildTarget)
.setBash("echo 'aha' > $OUT")
.setOut("stuff.txt")
.build(ruleResolver);
String commonLibAbiKeyHash = Strings.repeat("a", 40);
BuildTarget buildTarget = BuildTargetFactory.newInstance("//:lib");
try {
createDefaultJavaLibaryRuleWithAbiKey(
commonLibAbiKeyHash,
buildTarget,
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSortedSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.<BuildRule>of(),
/* exportedDeps */ ImmutableSortedSet.of(genrule));
fail("A non-java library listed as exported dep should have thrown.");
} catch (HumanReadableException e) {
String expected =
buildTarget + ": exported dep " +
genruleBuildTarget + " (" + genrule.getType() + ") " +
"must be a type of java library.";
assertEquals(expected, e.getMessage());
}
}
/**
* Tests DefaultJavaLibraryRule#getAbiKeyForDeps() when the dependencies contain a JavaAbiRule
* that is not a JavaLibraryRule.
*/
@Test
public void testGetAbiKeyForDepsWithMixedDeps() throws IOException {
String tinyLibAbiKeyHash = Strings.repeat("a", 40);
BuildRule tinyLibrary = createDefaultJavaLibaryRuleWithAbiKey(
tinyLibAbiKeyHash,
BuildTargetFactory.newInstance("//:tinylib"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.<BuildRule>of(),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
String javaAbiRuleKeyHash = Strings.repeat("b", 40);
FakeJavaAbiRule fakeJavaAbiRule = new FakeJavaAbiRule(
AndroidResourceDescription.TYPE,
BuildTargetFactory.newInstance("//:tinylibfakejavaabi"),
javaAbiRuleKeyHash);
BuildRule defaultJavaLibary = createDefaultJavaLibaryRuleWithAbiKey(
Strings.repeat("c", 40),
BuildTargetFactory.newInstance("//:javalib"),
/* srcs */ ImmutableSet.<String>of(),
/* deps */ ImmutableSortedSet.of(tinyLibrary, fakeJavaAbiRule),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
Hasher hasher = Hashing.sha1().newHasher();
hasher.putUnencodedChars(tinyLibAbiKeyHash);
hasher.putUnencodedChars(javaAbiRuleKeyHash);
assertEquals(
ImmutableSha1HashCode.of(hasher.hash().toString()),
((AbiRule) defaultJavaLibary).getAbiKeyForDeps());
}
/**
* @see com.facebook.buck.rules.AbiRule#getAbiKeyForDeps()
*/
@Test
public void testGetAbiKeyForDepsInThePresenceOfExportedDeps() throws IOException {
// Create a java_library named //:tinylib with a hardcoded ABI key.
String tinyLibAbiKeyHash = Strings.repeat("a", 40);
BuildRule tinyLibrary = createDefaultJavaLibaryRuleWithAbiKey(
tinyLibAbiKeyHash,
BuildTargetFactory.newInstance("//:tinylib"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.<BuildRule>of(),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
// Create two java_library rules, each of which depends on //:tinylib, but only one of which
// exports its deps.
String commonWithExportAbiKeyHash = Strings.repeat("b", 40);
BuildRule commonWithExport = createDefaultJavaLibaryRuleWithAbiKey(
commonWithExportAbiKeyHash,
BuildTargetFactory.newInstance("//:common_with_export"),
/* srcs */ ImmutableSet.<String>of(),
/* deps */ ImmutableSortedSet.of(tinyLibrary),
/* exportedDeps */ ImmutableSortedSet.of(tinyLibrary));
BuildRule commonNoExport = createDefaultJavaLibaryRuleWithAbiKey(
/* abiHash */ null,
BuildTargetFactory.newInstance("//:common_no_export"),
/* srcs */ ImmutableSet.<String>of(),
/* deps */ ImmutableSortedSet.of(tinyLibrary),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
// Verify getAbiKeyForDeps() for the two //:common_XXX rules.
assertEquals(
"getAbiKeyForDeps() should be the same for both rules because they have the same deps.",
((AbiRule) commonNoExport).getAbiKeyForDeps(),
((AbiRule) commonWithExport).getAbiKeyForDeps());
String expectedAbiKeyForDepsHash = Hashing.sha1().newHasher()
.putUnencodedChars(tinyLibAbiKeyHash).hash().toString();
String observedAbiKeyForDepsHash =
((AbiRule) commonNoExport).getAbiKeyForDeps().getHash();
assertEquals(expectedAbiKeyForDepsHash, observedAbiKeyForDepsHash);
// Create a BuildRuleResolver populated with the three build rules we created thus far.
Map<BuildTarget, BuildRule> buildRuleIndex = Maps.newHashMap();
buildRuleIndex.put(tinyLibrary.getBuildTarget(), tinyLibrary);
buildRuleIndex.put(commonWithExport.getBuildTarget(), commonWithExport);
buildRuleIndex.put(commonNoExport.getBuildTarget(), commonNoExport);
BuildRuleResolver ruleResolver = new BuildRuleResolver(buildRuleIndex);
// Create two rules, each of which depends on one of the //:common_XXX rules.
BuildRule consumerNoExport = JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//:consumer_no_export"))
.addDep(commonNoExport.getBuildTarget())
.build(ruleResolver);
BuildRule consumerWithExport = JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//:consumer_with_export"))
.addDep(commonWithExport.getBuildTarget())
.build(ruleResolver);
// Verify getAbiKeyForDeps() for the two //:consumer_XXX rules.
// This differs from the EMPTY_ABI_KEY in that the value of that comes from the SHA1 of an empty
// jar file, whereas this is constructed from the empty set of values.
Sha1HashCode noAbiDeps = ImmutableSha1HashCode.of(Hashing.sha1().newHasher().hash().toString());
assertEquals(
"The ABI of the deps of //:consumer_no_export should be the empty ABI.",
noAbiDeps,
((AbiRule) consumerNoExport).getAbiKeyForDeps());
assertThat(
"Although //:consumer_no_export and //:consumer_with_export have the same deps, " +
"the ABIs of their deps will differ because of the use of exported_deps is non-empty",
((AbiRule) consumerNoExport).getAbiKeyForDeps(),
not(equalTo(((AbiRule) consumerWithExport).getAbiKeyForDeps())));
String expectedAbiKeyNoDepsHashForConsumerWithExport = Hashing.sha1().newHasher()
.putUnencodedChars(((HasJavaAbi) commonWithExport).getAbiKey().getHash())
.putUnencodedChars(tinyLibAbiKeyHash)
.hash()
.toString();
String observedAbiKeyNoDepsHashForConsumerWithExport =
((AbiRule) consumerWithExport).getAbiKeyForDeps()
.getHash();
assertEquals(
"By hardcoding the ABI keys for the deps, we made getAbiKeyForDeps() a predictable value.",
expectedAbiKeyNoDepsHashForConsumerWithExport,
observedAbiKeyNoDepsHashForConsumerWithExport);
}
/**
* @see DefaultJavaLibrary#getAbiKey()
*/
@Test
public void testGetAbiKeyInThePresenceOfExportedDeps() throws IOException {
// Create a java_library named //:commonlib with a hardcoded ABI key.
String commonLibAbiKeyHash = Strings.repeat("a", 40);
BuildRule commonLibrary = createDefaultJavaLibaryRuleWithAbiKey(
commonLibAbiKeyHash,
BuildTargetFactory.newInstance("//:commonlib"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSortedSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.<BuildRule>of(),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
// Create two java_library rules, each of which depends on //:commonlib, but only one of which
// exports its deps.
String libWithExportAbiKeyHash = Strings.repeat("b", 40);
BuildRule libWithExport = createDefaultJavaLibaryRuleWithAbiKey(
libWithExportAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_with_export"),
/* srcs */ ImmutableSet.<String>of(),
/* deps */ ImmutableSortedSet.of(commonLibrary),
/* exportedDeps */ ImmutableSortedSet.of(commonLibrary));
String libNoExportAbiKeyHash = Strings.repeat("c", 40);
BuildRule libNoExport = createDefaultJavaLibaryRuleWithAbiKey(
/* abiHash */ libNoExportAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_no_export"),
/* srcs */ ImmutableSet.<String>of(),
/* deps */ ImmutableSortedSet.of(commonLibrary),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
// Verify getAbiKey() for the two //:lib_XXX rules.
String expectedLibWithExportAbiKeyHash = Hashing.sha1().newHasher()
.putUnencodedChars(commonLibAbiKeyHash)
.putUnencodedChars(libWithExportAbiKeyHash)
.hash()
.toString();
assertEquals(
"getAbiKey() should include the dependencies' ABI keys for the rule with export_deps=true.",
expectedLibWithExportAbiKeyHash,
((HasJavaAbi) libWithExport).getAbiKey().getHash());
assertEquals(
"getAbiKey() should not include the dependencies' ABI keys for the rule with " +
"export_deps=false.",
libNoExportAbiKeyHash,
((HasJavaAbi) libNoExport).getAbiKey().getHash());
}
/**
* @see DefaultJavaLibrary#getAbiKey()
*/
@Test
public void testGetAbiKeyRecursiveExportedDeps() throws IOException {
String libAAbiKeyHash = Strings.repeat("a", 40);
String libBAbiKeyHash = Strings.repeat("b", 40);
String libCAbiKeyHash = Strings.repeat("c", 40);
String libDAbiKeyHash = Strings.repeat("d", 40);
// Test the following dependency graph:
// a(export_deps=true) -> b(export_deps=true) -> c(export_deps=true) -> d(export_deps=true)
BuildRule libD = createDefaultJavaLibaryRuleWithAbiKey(
libDAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_d"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.<BuildRule>of(),
/* exporedtDeps */ ImmutableSortedSet.<BuildRule>of());
BuildRule libC = createDefaultJavaLibaryRuleWithAbiKey(
libCAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_c"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.of(libD),
/* exporedtDeps */ ImmutableSortedSet.of(libD));
BuildRule libB = createDefaultJavaLibaryRuleWithAbiKey(
libBAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_b"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.of(libC),
/* exportedDeps */ ImmutableSortedSet.of(libC));
BuildRule libA = createDefaultJavaLibaryRuleWithAbiKey(
libAAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_a"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.of(libB),
/* exportedDeps */ ImmutableSortedSet.of(libB));
assertEquals(
"If a rule has no dependencies its final ABI key should be the rule's own ABI key.",
libDAbiKeyHash,
((HasJavaAbi) libD).getAbiKey().getHash());
String expectedLibCAbiKeyHash = Hashing.sha1().newHasher()
.putUnencodedChars(libDAbiKeyHash)
.putUnencodedChars(libCAbiKeyHash)
.hash()
.toString();
assertEquals(
"The ABI key for lib_c should contain lib_d's ABI key.",
expectedLibCAbiKeyHash,
((HasJavaAbi) libC).getAbiKey().getHash());
String expectedLibBAbiKeyHash = Hashing.sha1().newHasher()
.putUnencodedChars(expectedLibCAbiKeyHash)
.putUnencodedChars(libDAbiKeyHash)
.putUnencodedChars(libBAbiKeyHash)
.hash()
.toString();
assertEquals(
"The ABI key for lib_b should contain lib_c's and lib_d's ABI keys.",
expectedLibBAbiKeyHash,
((HasJavaAbi) libB).getAbiKey().getHash());
String expectedLibAAbiKeyHash = Hashing.sha1().newHasher()
.putUnencodedChars(expectedLibBAbiKeyHash)
.putUnencodedChars(expectedLibCAbiKeyHash)
.putUnencodedChars(libAAbiKeyHash)
.hash()
.toString();
assertEquals(
"The ABI key for lib_a should contain lib_b's, lib_c's and lib_d's ABI keys.",
expectedLibAAbiKeyHash,
((HasJavaAbi) libA).getAbiKey().getHash());
// Test the following dependency graph:
// a(export_deps=true) -> b(export_deps=true) -> c(export_deps=false) -> d(export_deps=false)
libD = createDefaultJavaLibaryRuleWithAbiKey(
libDAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_d2"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.<BuildRule>of(),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of());
libC = createDefaultJavaLibaryRuleWithAbiKey(
libCAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_c2"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.of(libD),
/* exportDeps */ ImmutableSortedSet.<BuildRule>of());
libB = createDefaultJavaLibaryRuleWithAbiKey(
libBAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_b2"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.of(libC),
/* exportedDeps */ ImmutableSortedSet.of(libC));
libA = createDefaultJavaLibaryRuleWithAbiKey(
libAAbiKeyHash,
BuildTargetFactory.newInstance("//:lib_a2"),
// Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
/* srcs */ ImmutableSet.of("foo/Bar.java"),
/* deps */ ImmutableSortedSet.of(libB),
/* exportedDeps */ ImmutableSortedSet.of(libB));
assertEquals(
"If export_deps is false, the final ABI key should be the rule's own ABI key.",
libDAbiKeyHash,
((HasJavaAbi) libD).getAbiKey().getHash());
assertEquals(
"If export_deps is false, the final ABI key should be the rule's own ABI key.",
libCAbiKeyHash,
((HasJavaAbi) libC).getAbiKey().getHash());
expectedLibBAbiKeyHash = Hashing.sha1().newHasher()
.putUnencodedChars(libCAbiKeyHash)
.putUnencodedChars(libBAbiKeyHash)
.hash()
.toString();
assertEquals(
"The ABI key for lib_b should contain lib_c's ABI key.",
expectedLibBAbiKeyHash,
((HasJavaAbi) libB).getAbiKey().getHash());
expectedLibAAbiKeyHash = Hashing.sha1().newHasher()
.putUnencodedChars(expectedLibBAbiKeyHash)
.putUnencodedChars(libCAbiKeyHash)
.putUnencodedChars(libAAbiKeyHash)
.hash()
.toString();
assertEquals(
"The ABI key for lib_a should contain lib_b's, lib_c's and lib_d's ABI keys.",
expectedLibAAbiKeyHash,
((HasJavaAbi) libA).getAbiKey().getHash());
}
private static BuildRule createDefaultJavaLibaryRuleWithAbiKey(
@Nullable final String partialAbiHash,
BuildTarget buildTarget,
ImmutableSet<String> srcs,
ImmutableSortedSet<BuildRule> deps,
ImmutableSortedSet<BuildRule> exportedDeps) {
ProjectFilesystem projectFilesystem = new FakeProjectFilesystem();
ImmutableSortedSet<SourcePath> srcsAsPaths = FluentIterable.from(srcs)
.transform(MorePaths.TO_PATH)
.transform(SourcePaths.toSourcePath(projectFilesystem))
.toSortedSet(Ordering.natural());
BuildRuleParams buildRuleParams = new FakeBuildRuleParamsBuilder(buildTarget)
.setDeps(ImmutableSortedSet.copyOf(deps))
.setType(JavaLibraryDescription.TYPE)
.build();
return new DefaultJavaLibrary(
buildRuleParams,
new SourcePathResolver(new BuildRuleResolver()),
srcsAsPaths,
/* resources */ ImmutableSet.<SourcePath>of(),
/* proguardConfig */ Optional.<Path>absent(),
/* postprocessClassesCommands */ ImmutableList.<String>of(),
exportedDeps,
/* providedDeps */ ImmutableSortedSet.<BuildRule>of(),
/* additionalClasspathEntries */ ImmutableSet.<Path>of(),
DEFAULT_JAVAC_OPTIONS,
/* resourcesRoot */ Optional.<Path>absent()) {
@Override
public Sha1HashCode getAbiKey() {
if (partialAbiHash == null) {
return super.getAbiKey();
} else {
return createTotalAbiKey(ImmutableSha1HashCode.of(partialAbiHash));
}
}
};
}
@Test
public void testEmptySuggestBuildFunction() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
JavaLibrary libraryOne = (JavaLibrary) JavaLibraryBuilder
.createBuilder(libraryOneTarget)
.addSrc(Paths.get("java/src/com/libone/bar.java"))
.build(ruleResolver);
BuildContext context = createSuggestContext(ruleResolver,
BuildDependencies.FIRST_ORDER_ONLY);
ImmutableSetMultimap<JavaLibrary, Path> classpathEntries =
libraryOne.getTransitiveClasspathEntries();
assertEquals(
Optional.<JavacStep.SuggestBuildRules>absent(),
((DefaultJavaLibrary) libraryOne).createSuggestBuildFunction(
context,
classpathEntries,
classpathEntries,
createJarResolver(/* classToSymbols */ImmutableMap.<Path, String>of())));
EasyMock.verify(context);
}
@Test
public void testSuggsetDepsReverseTopoSortRespected() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
ProjectFilesystem projectFilesystem = new ProjectFilesystem(tmp.getRoot().toPath());
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
BuildRule libraryOne = JavaLibraryBuilder
.createBuilder(libraryOneTarget)
.addSrc(Paths.get("java/src/com/libone/Bar.java"))
.build(ruleResolver);
BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo");
BuildRule libraryTwo = JavaLibraryBuilder
.createBuilder(libraryTwoTarget)
.addSrc(Paths.get("java/src/com/libtwo/Foo.java"))
.addDep(libraryOne.getBuildTarget())
.build(ruleResolver);
BuildTarget parentTarget = BuildTargetFactory.newInstance("//:parent");
BuildRule parent = JavaLibraryBuilder
.createBuilder(parentTarget)
.addSrc(Paths.get("java/src/com/parent/Meh.java"))
.addDep(libraryTwo.getBuildTarget())
.build(ruleResolver);
BuildTarget grandparentTarget = BuildTargetFactory.newInstance("//:grandparent");
BuildRule grandparent = JavaLibraryBuilder
.createBuilder(grandparentTarget)
.addSrc(Paths.get("java/src/com/parent/OldManRiver.java"))
.addDep(parent.getBuildTarget())
.build(ruleResolver);
BuildContext context = createSuggestContext(ruleResolver,
BuildDependencies.WARN_ON_TRANSITIVE);
ImmutableSetMultimap<JavaLibrary, Path> transitive =
((HasClasspathEntries) parent).getTransitiveClasspathEntries();
ImmutableMap<Path, String> classToSymbols = ImmutableMap.of(
Iterables.getFirst(transitive.get((JavaLibrary) parent), null),
"com.facebook.Foo",
Iterables.getFirst(transitive.get((JavaLibrary) libraryOne), null),
"com.facebook.Bar",
Iterables.getFirst(transitive.get((JavaLibrary) libraryTwo), null),
"com.facebook.Foo");
Optional<JavacStep.SuggestBuildRules> suggestFn =
((DefaultJavaLibrary) grandparent).createSuggestBuildFunction(
context,
transitive,
/* declaredClasspathEntries */ ImmutableSetMultimap.<JavaLibrary, Path>of(),
createJarResolver(classToSymbols));
assertTrue(suggestFn.isPresent());
assertEquals(ImmutableSet.of("//:parent", "//:libone"),
suggestFn.get().suggest(
projectFilesystem,
ImmutableSet.of("com.facebook.Foo", "com.facebook.Bar")));
EasyMock.verify(context);
}
@Test
public void testRuleKeyIsOrderInsensitiveForSourcesAndResources() throws IOException {
// Note that these filenames were deliberately chosen to have identical hashes to maximize
// the chance of order-sensitivity when being inserted into a HashMap. Just using
// {foo,bar}.{java,txt} resulted in a passing test even for the old broken code.
ProjectFilesystem filesystem = new AllExistingProjectFilesystem() {
@Override
public boolean isDirectory(Path path, LinkOption... linkOptionsk) {
return false;
}
};
BuildRuleResolver resolver1 = new BuildRuleResolver();
SourcePathResolver pathResolver1 = new SourcePathResolver(resolver1);
DefaultJavaLibrary rule1 = (DefaultJavaLibrary) JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//lib:lib"))
.addSrc(Paths.get("agifhbkjdec.java"))
.addSrc(Paths.get("bdeafhkgcji.java"))
.addSrc(Paths.get("bdehgaifjkc.java"))
.addSrc(Paths.get("cfiabkjehgd.java"))
.addResource(new TestSourcePath("becgkaifhjd.txt"))
.addResource(new TestSourcePath("bkhajdifcge.txt"))
.addResource(new TestSourcePath("cabfghjekid.txt"))
.addResource(new TestSourcePath("chkdbafijge.txt"))
.build(resolver1, filesystem);
BuildRuleResolver resolver2 = new BuildRuleResolver();
SourcePathResolver pathResolver2 = new SourcePathResolver(resolver2);
DefaultJavaLibrary rule2 = (DefaultJavaLibrary) JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//lib:lib"))
.addSrc(Paths.get("cfiabkjehgd.java"))
.addSrc(Paths.get("bdehgaifjkc.java"))
.addSrc(Paths.get("bdeafhkgcji.java"))
.addSrc(Paths.get("agifhbkjdec.java"))
.addResource(new TestSourcePath("chkdbafijge.txt"))
.addResource(new TestSourcePath("cabfghjekid.txt"))
.addResource(new TestSourcePath("bkhajdifcge.txt"))
.addResource(new TestSourcePath("becgkaifhjd.txt"))
.build(resolver2, filesystem);
Iterable<Path> inputs1 = rule1.getInputsToCompareToOutput();
Iterable<Path> inputs2 = rule2.getInputsToCompareToOutput();
assertEquals(ImmutableList.copyOf(inputs1), ImmutableList.copyOf(inputs2));
ImmutableMap.Builder<String, String> fileHashes = ImmutableMap.builder();
for (String filename : ImmutableList.of(
"agifhbkjdec.java", "bdeafhkgcji.java", "bdehgaifjkc.java", "cfiabkjehgd.java",
"becgkaifhjd.txt", "bkhajdifcge.txt", "cabfghjekid.txt", "chkdbafijge.txt")) {
fileHashes.put(filename, Hashing.sha1().hashString(filename, Charsets.UTF_8).toString());
}
RuleKeyBuilderFactory ruleKeyBuilderFactory =
new FakeRuleKeyBuilderFactory(FakeFileHashCache.createFromStrings(fileHashes.build()));
RuleKey.Builder builder1 = ruleKeyBuilderFactory.newInstance(rule1, pathResolver1);
RuleKey.Builder builder2 = ruleKeyBuilderFactory.newInstance(rule2, pathResolver2);
rule1.appendToRuleKey(builder1);
rule2.appendToRuleKey(builder2);
RuleKey.Builder.RuleKeyPair pair1 = builder1.build();
RuleKey.Builder.RuleKeyPair pair2 = builder2.build();
assertEquals(pair1.getTotalRuleKey(), pair2.getTotalRuleKey());
assertEquals(pair1.getRuleKeyWithoutDeps(), pair2.getRuleKeyWithoutDeps());
}
@Test
public void testWhenNoJavacIsProvidedAJavacInMemoryStepIsAdded() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
BuildRule rule = JavaLibraryBuilder
.createBuilder(libraryOneTarget)
.addSrc(Paths.get("java/src/com/libone/Bar.java"))
.build(ruleResolver);
DefaultJavaLibrary buildable = (DefaultJavaLibrary) rule;
ImmutableList.Builder<Step> stepsBuilder = ImmutableList.builder();
buildable.createCommandsForJavac(
buildable.getPathToOutputFile(),
ImmutableSet.copyOf(buildable.getTransitiveClasspathEntries().values()),
ImmutableSet.copyOf(buildable.getDeclaredClasspathEntries().values()),
DEFAULT_JAVAC_OPTIONS,
BuildDependencies.FIRST_ORDER_ONLY,
Optional.<JavacStep.SuggestBuildRules>absent(),
stepsBuilder,
libraryOneTarget);
List<Step> steps = stepsBuilder.build();
assertEquals(steps.size(), 3);
assertTrue(((JavacStep) steps.get(2)).getJavac() instanceof Jsr199Javac);
}
@Test
public void testWhenJavacJarIsProvidedAJavacInMemoryStepIsAdded() {
BuildRuleResolver ruleResolver = new BuildRuleResolver();
BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone");
Path javacJarPath = Paths.get("java/src/com/libone/JavacJar.jar");
BuildRule rule = JavaLibraryBuilder
.createBuilder(libraryOneTarget)
.addSrc(Paths.get("java/src/com/libone/Bar.java"))
.setJavacJar(javacJarPath)
.build(ruleResolver);
DefaultJavaLibrary buildable = (DefaultJavaLibrary) rule;
ImmutableList.Builder<Step> stepsBuilder = ImmutableList.builder();
buildable.createCommandsForJavac(
buildable.getPathToOutputFile(),
ImmutableSet.copyOf(buildable.getTransitiveClasspathEntries().values()),
ImmutableSet.copyOf(buildable.getDeclaredClasspathEntries().values()),
buildable.getJavacOptions(),
BuildDependencies.FIRST_ORDER_ONLY,
Optional.<JavacStep.SuggestBuildRules>absent(),
stepsBuilder,
libraryOneTarget);
List<Step> steps = stepsBuilder.build();
assertEquals(steps.size(), 3);
assertTrue(((JavacStep) steps.get(2)).getJavac() instanceof Jsr199Javac);
Jsr199Javac jsrJavac = ((Jsr199Javac) (((JavacStep) steps.get(2)).getJavac()));
assertTrue(jsrJavac.getJavacJar().isPresent());
assertEquals(jsrJavac.getJavacJar().get(), javacJarPath);
}
@Test
public void testAddPostprocessClassesCommands() {
ImmutableList<String> postprocessClassesCommands = ImmutableList.of("tool arg1", "tool2");
Path outputDirectory = BIN_PATH.resolve("android/java/lib__java__classes");
ExecutionContext executionContext = EasyMock.createMock(ExecutionContext.class);
ImmutableList.Builder<Step> commands = ImmutableList.builder();
DefaultJavaLibrary.addPostprocessClassesCommands(
commands,
postprocessClassesCommands,
outputDirectory);
ImmutableList<Step> steps = commands.build();
assertEquals(2, steps.size());
assertTrue(steps.get(0) instanceof ShellStep);
ShellStep step0 = (ShellStep) steps.get(0);
assertEquals(
ImmutableList.of("bash", "-c", "tool arg1 " + outputDirectory),
step0.getShellCommand(executionContext));
assertTrue(steps.get(1) instanceof ShellStep);
ShellStep step1 = (ShellStep) steps.get(1);
assertEquals(
ImmutableList.of("bash", "-c", "tool2 " + outputDirectory),
step1.getShellCommand(executionContext));
}
// Utilities
private JavaLibrary getJavaLibrary(BuildRule rule) {
return (JavaLibrary) rule;
}
private DefaultJavaLibrary.JarResolver createJarResolver(
final ImmutableMap<Path, String> classToSymbols) {
ImmutableSetMultimap.Builder<Path, String> resolveMapBuilder =
ImmutableSetMultimap.builder();
for (Map.Entry<Path, String> entry : classToSymbols.entrySet()) {
String fullyQualified = entry.getValue();
String packageName = fullyQualified.substring(0, fullyQualified.lastIndexOf('.'));
String className = fullyQualified.substring(fullyQualified.lastIndexOf('.'));
resolveMapBuilder.putAll(entry.getKey(), fullyQualified, packageName, className);
}
final ImmutableSetMultimap<Path, String> resolveMap = resolveMapBuilder.build();
return new DefaultJavaLibrary.JarResolver() {
@Override
public ImmutableSet<String> resolve(ProjectFilesystem filesystem, Path relativeClassPath) {
if (resolveMap.containsKey(relativeClassPath)) {
return resolveMap.get(relativeClassPath);
} else {
return ImmutableSet.of();
}
}
};
}
private BuildContext createSuggestContext(BuildRuleResolver ruleResolver,
BuildDependencies buildDependencies) {
ActionGraph graph = RuleMap.createGraphFromBuildRules(ruleResolver);
BuildContext context = EasyMock.createMock(BuildContext.class);
expect(context.getActionGraph()).andReturn(graph).anyTimes();
expect(context.getBuildDependencies()).andReturn(buildDependencies).anyTimes();
replay(context);
return context;
}
// TODO(mbolin): Eliminate the bootclasspath parameter, as it is completely misused in this test.
private BuildContext createBuildContext(BuildRule javaLibrary,
@Nullable String bootclasspath,
@Nullable ProjectFilesystem projectFilesystem) {
AndroidPlatformTarget platformTarget = EasyMock.createMock(AndroidPlatformTarget.class);
ImmutableList<Path> bootclasspathEntries = (bootclasspath == null)
? ImmutableList.of(Paths.get("I am not used"))
: ImmutableList.of(Paths.get(bootclasspath));
expect(platformTarget.getBootclasspathEntries()).andReturn(bootclasspathEntries)
.anyTimes();
replay(platformTarget);
if (projectFilesystem == null) {
projectFilesystem = EasyMock.createMock(ProjectFilesystem.class);
}
// TODO(mbolin): Create a utility that populates a BuildContext.Builder with fakes.
return ImmutableBuildContext.builder()
.setActionGraph(RuleMap.createGraphFromSingleRule(javaLibrary))
.setStepRunner(EasyMock.createMock(StepRunner.class))
.setProjectFilesystem(projectFilesystem)
.setClock(new DefaultClock())
.setBuildId(new BuildId())
.setArtifactCache(new NoopArtifactCache())
.setBuildDependencies(BuildDependencies.TRANSITIVE)
.setJavaPackageFinder(EasyMock.createMock(JavaPackageFinder.class))
.setAndroidBootclasspathSupplier(
BuildContext.createBootclasspathSupplier(Suppliers.ofInstance(platformTarget)))
.setEventBus(BuckEventBusFactory.newInstance())
.build();
}
private enum AnnotationProcessorTarget {
VALID_PREBUILT_JAR("//tools/java/src/com/facebook/library:prebuilt-processors") {
@Override
public BuildRule createRule(BuildTarget target) {
return PrebuiltJarBuilder.createBuilder(target)
.setBinaryJar(Paths.get("MyJar"))
.build(new BuildRuleResolver());
}
},
VALID_JAVA_BINARY("//tools/java/src/com/facebook/annotations:custom-processors") {
@Override
public BuildRule createRule(BuildTarget target) {
return new JavaBinaryRuleBuilder(target)
.setMainClass("com.facebook.Main")
.build(new BuildRuleResolver());
}
},
VALID_JAVA_LIBRARY("//tools/java/src/com/facebook/somejava:library") {
@Override
public BuildRule createRule(BuildTarget target) {
return JavaLibraryBuilder.createBuilder(target)
.addSrc(Paths.get("MyClass.java"))
.setProguardConfig(Paths.get("MyProguardConfig"))
.build(new BuildRuleResolver());
}
};
private final String targetName;
private AnnotationProcessorTarget(String targetName) {
this.targetName = targetName;
}
public BuildTarget createTarget() {
return BuildTargetFactory.newInstance(targetName);
}
public abstract BuildRule createRule(BuildTarget target);
}
// Captures all the common code between the different annotation processing test scenarios.
private class AnnotationProcessingScenario {
private final AnnotationProcessingParams.Builder annotationProcessingParamsBuilder;
private final Map<BuildTarget, BuildRule> buildRuleIndex;
private ExecutionContext executionContext;
private BuildContext buildContext;
public AnnotationProcessingScenario() throws IOException {
annotationProcessingParamsBuilder = new AnnotationProcessingParams.Builder();
buildRuleIndex = Maps.newHashMap();
}
public AnnotationProcessingParams.Builder getAnnotationProcessingParamsBuilder() {
return annotationProcessingParamsBuilder;
}
public void addAnnotationProcessorTarget(AnnotationProcessorTarget processor) {
BuildTarget target = processor.createTarget();
BuildRule rule = processor.createRule(target);
annotationProcessingParamsBuilder.addProcessorBuildTarget(rule);
buildRuleIndex.put(target, rule);
}
public ImmutableList<String> buildAndGetCompileParameters() throws IOException {
ProjectFilesystem projectFilesystem = new ProjectFilesystem(tmp.getRoot().toPath());
BuildRule javaLibrary = createJavaLibraryRule(projectFilesystem);
buildContext = createBuildContext(javaLibrary, /* bootclasspath */ null, projectFilesystem);
List<Step> steps = javaLibrary.getBuildSteps(
buildContext, new FakeBuildableContext());
JavacStep javacCommand = lastJavacCommand(steps);
executionContext = TestExecutionContext.newBuilder()
.setProjectFilesystem(projectFilesystem)
.setConsole(new Console(Verbosity.SILENT, System.out, System.err, Ansi.withoutTty()))
.setDebugEnabled(true)
.build();
ImmutableList<String> options = javacCommand.getOptions(executionContext,
/* buildClasspathEntries */ ImmutableSet.<Path>of());
return options;
}
// TODO(simons): Actually generate a java library rule, rather than an android one.
private BuildRule createJavaLibraryRule(ProjectFilesystem projectFilesystem)
throws IOException {
BuildTarget buildTarget = BuildTargetFactory.newInstance(ANNOTATION_SCENARIO_TARGET);
annotationProcessingParamsBuilder.setOwnerTarget(buildTarget);
annotationProcessingParamsBuilder.setProjectFilesystem(projectFilesystem);
tmp.newFolder("android", "java", "src", "com", "facebook");
String src = "android/java/src/com/facebook/Main.java";
tmp.newFile(src);
AnnotationProcessingParams params = annotationProcessingParamsBuilder.build();
ImmutableJavacOptions.Builder options = JavacOptions.builder(DEFAULT_JAVAC_OPTIONS)
.setAnnotationProcessingParams(params);
BuildRuleParams buildRuleParams = new FakeBuildRuleParamsBuilder(buildTarget)
.setProjectFilesystem(projectFilesystem)
.setType(AndroidLibraryDescription.TYPE)
.build();
return new AndroidLibrary(
buildRuleParams,
new SourcePathResolver(new BuildRuleResolver()),
ImmutableSet.of(new TestSourcePath(src)),
/* resources */ ImmutableSet.<SourcePath>of(),
/* proguardConfig */ Optional.<Path>absent(),
/* postprocessClassesCommands */ ImmutableList.<String>of(),
/* exportedDeps */ ImmutableSortedSet.<BuildRule>of(),
/* providedDeps */ ImmutableSortedSet.<BuildRule>of(),
/* additionalClasspathEntries */ ImmutableSet.<Path>of(),
options.build(),
/* resourcesRoot */ Optional.<Path>absent(),
/* manifestFile */ Optional.<SourcePath>absent(),
/* isPrebuiltAar */ false);
}
private JavacStep lastJavacCommand(Iterable<Step> commands) {
Step javac = null;
for (Step step : commands) {
if (step instanceof JavacStep) {
javac = step;
// Intentionally no break here, since we want the last one.
}
}
assertNotNull("Expected a JavacStep in step list", javac);
return (JavacStep) javac;
}
}
private static class FakeJavaAbiRule extends FakeBuildRule implements HasJavaAbi {
private final String abiKeyHash;
public FakeJavaAbiRule(BuildRuleType type, BuildTarget buildTarget, String abiKeyHash) {
super(type, buildTarget, new SourcePathResolver(new BuildRuleResolver()));
this.abiKeyHash = abiKeyHash;
}
@Override
public Sha1HashCode getAbiKey() {
return ImmutableSha1HashCode.of(abiKeyHash);
}
}
}