/* * 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.jvm.java; import static com.facebook.buck.jvm.java.JavaCompilationConstants.DEFAULT_JAVAC_OPTIONS; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; 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.AndroidLibraryBuilder; import com.facebook.buck.android.AndroidPlatformTarget; import com.facebook.buck.event.BuckEventBusFactory; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.core.JavaPackageFinder; import com.facebook.buck.jvm.java.testutil.AbiCompilationModeTest; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.ActionGraph; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.FakeBuildContext; import com.facebook.buck.rules.FakeBuildRuleParamsBuilder; import com.facebook.buck.rules.FakeBuildableContext; import com.facebook.buck.rules.FakeSourcePath; import com.facebook.buck.rules.PathSourcePath; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.rules.keys.DefaultRuleKeyFactory; import com.facebook.buck.rules.keys.InputBasedRuleKeyFactory; import com.facebook.buck.shell.GenruleBuilder; 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.TargetGraphFactory; import com.facebook.buck.util.Ansi; import com.facebook.buck.util.Console; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.RichStream; import com.facebook.buck.util.Verbosity; import com.facebook.buck.util.cache.DefaultFileHashCache; import com.facebook.buck.util.cache.FileHashCache; import com.facebook.buck.util.cache.StackedFileHashCache; import com.facebook.buck.zip.CustomJarOutputStream; import com.facebook.buck.zip.ZipOutputStreams; import com.google.common.base.Charsets; import com.google.common.base.Splitter; 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.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.hash.Hashing; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import org.easymock.EasyMock; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class DefaultJavaLibraryTest extends AbiCompilationModeTest { private static final String ANNOTATION_SCENARIO_TARGET = "//android/java/src/com/facebook:fb"; @Rule public TemporaryFolder tmp = new TemporaryFolder(); private String annotationScenarioGenPath; private BuildRuleResolver ruleResolver; private JavaBuckConfig testJavaBuckConfig; @Before public void setUp() throws InterruptedException { ruleResolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); testJavaBuckConfig = getJavaBuckConfigWithCompilationMode(); ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot().toPath()); StepRunner stepRunner = createNiceMock(StepRunner.class); JavaPackageFinder packageFinder = createNiceMock(JavaPackageFinder.class); replay(packageFinder, stepRunner); annotationScenarioGenPath = filesystem .resolve(filesystem.getBuckPaths().getAnnotationDir()) .resolve("android/java/src/com/facebook/__fb_gen__") .toAbsolutePath() .toString(); } /** Make sure that when isAndroidLibrary is true, that the Android bootclasspath is used. */ @Test @SuppressWarnings("PMD.AvoidUsingHardCodedIP") public void testBuildInternalWithAndroidBootclasspath() throws Exception { 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()); BuildRule libraryRule = AndroidLibraryBuilder.createBuilder(buildTarget).addSrc(src).build(ruleResolver); DefaultJavaLibrary javaLibrary = (DefaultJavaLibrary) libraryRule; String bootclasspath = "effects.jar" + File.pathSeparator + "maps.jar" + File.pathSeparator + "usb.jar" + File.pathSeparator; BuildContext context = createBuildContext(libraryRule, bootclasspath); List<Step> steps = javaLibrary.getBuildSteps(context, new FakeBuildableContext()); // Find the JavacStep and verify its bootclasspath. Step step = Iterables.find(steps, command -> 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 testJavaLibaryThrowsIfResourceIsDirectory() throws Exception { ProjectFilesystem filesystem = new AllExistingProjectFilesystem() { @Override public boolean isDirectory(Path path, LinkOption... linkOptionsk) { return true; } }; try { createJavaLibraryBuilder(BuildTargetFactory.newInstance("//library:code")) .addResource(new FakeSourcePath("library")) .build(ruleResolver, 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 Exception { AnnotationProcessingScenario scenario = new AnnotationProcessingScenario(); scenario.addAnnotationProcessorTarget(validJavaBinary); scenario .getAnnotationProcessingParamsBuilder() .setLegacyAnnotationProcessorNames(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) { assertThat("Expected no custom annotation options.", parameter.startsWith("-A"), is(false)); } } /** Verify adding an annotation processor prebuilt jar. */ @Test public void testAddAnnotationProcessorPrebuiltJar() throws Exception { AnnotationProcessingScenario scenario = new AnnotationProcessingScenario(); scenario.addAnnotationProcessorTarget(validPrebuiltJar); scenario .getAnnotationProcessingParamsBuilder() .setLegacyAnnotationProcessorNames(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 Exception { AnnotationProcessingScenario scenario = new AnnotationProcessingScenario(); scenario.addAnnotationProcessorTarget(validPrebuiltJar); scenario .getAnnotationProcessingParamsBuilder() .setLegacyAnnotationProcessorNames(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 Exception { AnnotationProcessingScenario scenario = new AnnotationProcessingScenario(); scenario.addAnnotationProcessorTarget(validPrebuiltJar); scenario.addAnnotationProcessorTarget(validJavaBinary); scenario.addAnnotationProcessorTarget(validJavaLibrary); scenario .getAnnotationProcessingParamsBuilder() .setLegacyAnnotationProcessorNames(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() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone"); TargetNode<?, ?> libraryOne = createJavaLibraryBuilder(libraryOneTarget) .addSrc(Paths.get("java/src/com/libone/Bar.java")) .build(); BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo"); TargetNode<?, ?> libraryTwo = createJavaLibraryBuilder(libraryTwoTarget) .addSrc(Paths.get("java/src/com/libtwo/Foo.java")) .addDep(libraryOne.getBuildTarget()) .build(); BuildTarget parentTarget = BuildTargetFactory.newInstance("//:parent"); TargetNode<?, ?> parent = createJavaLibraryBuilder(parentTarget) .addSrc(Paths.get("java/src/com/parent/Meh.java")) .addDep(libraryTwo.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(libraryOne, libraryTwo, parent); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); JavaLibrary libraryOneRule = (JavaLibrary) ruleResolver.requireRule(libraryOneTarget); JavaLibrary parentRule = (JavaLibrary) ruleResolver.requireRule(parentTarget); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(ruleResolver)); Path root = libraryOneRule.getProjectFilesystem().getRootPath(); assertEquals( ImmutableSet.of( root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryOneTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryTwoTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(parentTarget, filesystem))), resolve(parentRule.getTransitiveClasspaths(), pathResolver)); } @Test public void testGetClasspathDeps() throws Exception { BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone"); TargetNode<?, ?> libraryOne = createJavaLibraryBuilder(libraryOneTarget) .addSrc(Paths.get("java/src/com/libone/Bar.java")) .build(); BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo"); TargetNode<?, ?> libraryTwo = createJavaLibraryBuilder(libraryTwoTarget) .addSrc(Paths.get("java/src/com/libtwo/Foo.java")) .addDep(libraryOne.getBuildTarget()) .build(); BuildTarget parentTarget = BuildTargetFactory.newInstance("//:parent"); TargetNode<?, ?> parent = createJavaLibraryBuilder(parentTarget) .addSrc(Paths.get("java/src/com/parent/Meh.java")) .addDep(libraryTwo.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(libraryOne, libraryTwo, parent); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); BuildRule libraryOneRule = ruleResolver.requireRule(libraryOneTarget); BuildRule libraryTwoRule = ruleResolver.requireRule(libraryTwoTarget); BuildRule parentRule = ruleResolver.requireRule(parentTarget); assertThat( ((HasClasspathEntries) parentRule).getTransitiveClasspathDeps(), equalTo( ImmutableSet.of( getJavaLibrary(libraryOneRule), getJavaLibrary(libraryTwoRule), getJavaLibrary(parentRule)))); } @Test public void testClasspathForJavacCommand() throws Exception { BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone"); TargetNode<?, ?> libraryOne = createJavaLibraryBuilder(libraryOneTarget) .addSrc(Paths.get("java/src/com/libone/Bar.java")) .build(); BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo"); TargetNode<?, ?> libraryTwo = createJavaLibraryBuilder(libraryTwoTarget) .addSrc(Paths.get("java/src/com/libtwo/Foo.java")) .addDep(libraryOne.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(libraryOne, libraryTwo); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); DefaultJavaLibrary libraryOneRule = (DefaultJavaLibrary) ruleResolver.requireRule(libraryOneTarget); DefaultJavaLibrary libraryTwoRule = (DefaultJavaLibrary) ruleResolver.requireRule(libraryTwoTarget); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(ruleResolver)); List<Step> steps = libraryTwoRule.getBuildSteps( FakeBuildContext.withSourcePathResolver(pathResolver), new FakeBuildableContext()); 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); final BuildRule expectedRule; if (compileAgainstAbis.equals(TRUE)) { expectedRule = ruleResolver.getRule(libraryOneRule.getAbiJar().get()); } else { expectedRule = libraryOneRule; } String expectedName = expectedRule.getFullyQualifiedName(); assertEquals( "The classpath for the javac step to compile //:libtwo should contain only " + expectedName, ImmutableSet.of(pathResolver.getAbsolutePath(expectedRule.getSourcePathToOutput())), javacStep.getClasspathEntries()); } @Test public void testDepFilePredicateForNoAnnotationProcessorDeps() throws Exception { BuildTarget annotationProcessorTarget = validJavaLibrary.createTarget(); BuildTarget annotationProcessorAbiTarget = validJavaLibraryAbi.createTarget(); BuildRule annotationProcessorRule = validJavaLibrary.createRule(annotationProcessorTarget); BuildRule annotationProcessorAbiRule = validJavaLibraryAbi.createRule(annotationProcessorAbiTarget); ruleResolver.addToIndex(annotationProcessorRule); ruleResolver.addToIndex(annotationProcessorAbiRule); BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libone"); DefaultJavaLibrary libraryTwo = createJavaLibraryBuilder(libraryTwoTarget) .addSrc(Paths.get("java/src/com/libtwo/Foo.java")) .addAnnotationProcessorDep(annotationProcessorTarget) .build(ruleResolver); SourcePath sourcePath = annotationProcessorAbiRule.getSourcePathToOutput(); assertFalse( "The predicate for dep file shouldn't contain annotation processor deps", libraryTwo.getCoveredByDepFilePredicate().test(sourcePath)); } /** Verify adding an annotation processor java binary with options. */ @Test public void testAddAnnotationProcessorWithOptions() throws Exception { AnnotationProcessingScenario scenario = new AnnotationProcessingScenario(); scenario.addAnnotationProcessorTarget(validJavaBinary); scenario .getAnnotationProcessingParamsBuilder() .setLegacyAnnotationProcessorNames(ImmutableList.of("MyProcessor")); scenario.getAnnotationProcessingParamsBuilder().addParameters("MyParameter"); scenario.getAnnotationProcessingParamsBuilder().addParameters("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() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); BuildTarget nonIncludedTarget = BuildTargetFactory.newInstance("//:not_included"); TargetNode<?, ?> notIncludedNode = createJavaLibraryBuilder(nonIncludedTarget) .addSrc(Paths.get("java/src/com/not_included/Raz.java")) .build(); BuildTarget includedTarget = BuildTargetFactory.newInstance("//:included"); TargetNode<?, ?> includedNode = createJavaLibraryBuilder(includedTarget) .addSrc(Paths.get("java/src/com/included/Rofl.java")) .build(); BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone"); TargetNode<?, ?> libraryOneNode = createJavaLibraryBuilder(libraryOneTarget) .addDep(notIncludedNode.getBuildTarget()) .addDep(includedNode.getBuildTarget()) .addExportedDep(includedNode.getBuildTarget()) .addSrc(Paths.get("java/src/com/libone/Bar.java")) .build(); BuildTarget libraryTwoTarget = BuildTargetFactory.newInstance("//:libtwo"); TargetNode<?, ?> libraryTwoNode = createJavaLibraryBuilder(libraryTwoTarget) .addSrc(Paths.get("java/src/com/libtwo/Foo.java")) .addDep(libraryOneNode.getBuildTarget()) .addExportedDep(libraryOneNode.getBuildTarget()) .build(); BuildTarget parentTarget = BuildTargetFactory.newInstance("//:parent"); TargetNode<?, ?> parentNode = createJavaLibraryBuilder(parentTarget) .addSrc(Paths.get("java/src/com/parent/Meh.java")) .addDep(libraryTwoNode.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance( notIncludedNode, includedNode, libraryOneNode, libraryTwoNode, parentNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(ruleResolver)); BuildRule notIncluded = ruleResolver.requireRule(notIncludedNode.getBuildTarget()); BuildRule included = ruleResolver.requireRule(includedNode.getBuildTarget()); BuildRule libraryOne = ruleResolver.requireRule(libraryOneTarget); BuildRule libraryTwo = ruleResolver.requireRule(libraryTwoTarget); BuildRule parent = ruleResolver.requireRule(parentTarget); Path root = parent.getProjectFilesystem().getRootPath(); assertEquals( "A java_library that depends on //:libone should include only libone.jar in its " + "classpath when compiling itself.", ImmutableSet.of( root.resolve(DefaultJavaLibrary.getOutputJarPath(nonIncludedTarget, filesystem))), resolve(getJavaLibrary(notIncluded).getOutputClasspaths(), pathResolver)); assertEquals( ImmutableSet.of( root.resolve(DefaultJavaLibrary.getOutputJarPath(includedTarget, filesystem))), resolve(getJavaLibrary(included).getOutputClasspaths(), pathResolver)); assertEquals( ImmutableSet.of( root.resolve(DefaultJavaLibrary.getOutputJarPath(includedTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryOneTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(includedTarget, filesystem))), resolve(getJavaLibrary(libraryOne).getOutputClasspaths(), pathResolver)); 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.", ImmutableSet.of( root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryOneTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(includedTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryOneTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryTwoTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(includedTarget, filesystem))), resolve(getJavaLibrary(libraryTwo).getOutputClasspaths(), pathResolver)); assertEquals( "A java_binary that depends on //:parent should include libone.jar, libtwo.jar and " + "parent.jar.", ImmutableSet.<Path>builder() .add( root.resolve(DefaultJavaLibrary.getOutputJarPath(includedTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(nonIncludedTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryOneTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(libraryTwoTarget, filesystem)), root.resolve(DefaultJavaLibrary.getOutputJarPath(parentTarget, filesystem))) .build(), resolve(getJavaLibrary(parent).getTransitiveClasspaths(), pathResolver)); assertThat( getJavaLibrary(parent).getTransitiveClasspathDeps(), equalTo( ImmutableSet.<JavaLibrary>builder() .add(getJavaLibrary(included)) .add(getJavaLibrary(notIncluded)) .add(getJavaLibrary(libraryOne)) .add(getJavaLibrary(libraryTwo)) .add(getJavaLibrary(parent)) .build())); assertEquals( "A java_library that depends on //:parent should include only parent.jar in its " + "-classpath when compiling itself.", ImmutableSet.of( root.resolve(DefaultJavaLibrary.getOutputJarPath(parentTarget, filesystem))), resolve(getJavaLibrary(parent).getOutputClasspaths(), pathResolver)); } /** * Tests that an error is thrown when non-java library rules are listed in the exported deps * parameter. */ @Test public void testExportedDepsShouldOnlyContainJavaLibraryRules() throws Exception { BuildTarget genruleBuildTarget = BuildTargetFactory.newInstance("//generated:stuff"); BuildRule genrule = GenruleBuilder.newGenruleBuilder(genruleBuildTarget) .setBash("echo 'aha' > $OUT") .setOut("stuff.txt") .build(ruleResolver); BuildTarget buildTarget = BuildTargetFactory.newInstance("//:lib"); try { createDefaultJavaLibraryRuleWithAbiKey( buildTarget, /* srcs */ ImmutableSortedSet.of("foo/Bar.java"), /* deps */ ImmutableSortedSet.of(), /* exportedDeps */ ImmutableSortedSet.of(genrule), /* spoolMode */ Optional.empty(), /* postprocessClassesCommands */ ImmutableList.of()); 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()); } } @Test public void testStepsPresenceForForDirectJarSpooling() throws NoSuchBuildTargetException { BuildTarget buildTarget = BuildTargetFactory.newInstance("//:lib"); DefaultJavaLibrary javaLibraryBuildRule = createDefaultJavaLibraryRuleWithAbiKey( buildTarget, /* srcs */ ImmutableSortedSet.of("foo/Bar.java"), /* deps */ ImmutableSortedSet.of(), /* exportedDeps */ ImmutableSortedSet.of(), Optional.of(AbstractJavacOptions.SpoolMode.DIRECT_TO_JAR), /* postprocessClassesCommands */ ImmutableList.of()); BuildContext buildContext = createBuildContext(javaLibraryBuildRule, /* bootclasspath */ null); ImmutableList<Step> steps = javaLibraryBuildRule.getBuildSteps(buildContext, new FakeBuildableContext()); assertThat(steps, Matchers.hasItem(Matchers.instanceOf(JavacDirectToJarStep.class))); assertThat(steps, Matchers.not(Matchers.hasItem(Matchers.instanceOf(JavacStep.class)))); assertThat(steps, Matchers.not(Matchers.hasItem(Matchers.instanceOf(JarDirectoryStep.class)))); } @Test public void testJavacDirectToJarStepIsNotPresentWhenPostprocessClassesCommandsPresent() throws NoSuchBuildTargetException { BuildTarget buildTarget = BuildTargetFactory.newInstance("//:lib"); DefaultJavaLibrary javaLibraryBuildRule = createDefaultJavaLibraryRuleWithAbiKey( buildTarget, /* srcs */ ImmutableSortedSet.of("foo/Bar.java"), /* deps */ ImmutableSortedSet.of(), /* exportedDeps */ ImmutableSortedSet.of(), Optional.of(AbstractJavacOptions.SpoolMode.DIRECT_TO_JAR), /* postprocessClassesCommands */ ImmutableList.of("process_class_files.py")); BuildContext buildContext = createBuildContext(javaLibraryBuildRule, /* bootclasspath */ null); ImmutableList<Step> steps = javaLibraryBuildRule.getBuildSteps(buildContext, new FakeBuildableContext()); assertThat( steps, Matchers.not(Matchers.hasItem(Matchers.instanceOf(JavacDirectToJarStep.class)))); assertThat(steps, Matchers.hasItem(Matchers.instanceOf(JavacStep.class))); assertThat(steps, Matchers.hasItem(Matchers.instanceOf(JarDirectoryStep.class))); } @Test public void testStepsPresenceForIntermediateOutputToDiskSpooling() throws NoSuchBuildTargetException { BuildTarget buildTarget = BuildTargetFactory.newInstance("//:lib"); DefaultJavaLibrary javaLibraryBuildRule = createDefaultJavaLibraryRuleWithAbiKey( buildTarget, /* srcs */ ImmutableSortedSet.of("foo/Bar.java"), /* deps */ ImmutableSortedSet.of(), /* exportedDeps */ ImmutableSortedSet.of(), Optional.of(AbstractJavacOptions.SpoolMode.INTERMEDIATE_TO_DISK), /* postprocessClassesCommands */ ImmutableList.of()); BuildContext buildContext = createBuildContext(javaLibraryBuildRule, /* bootclasspath */ null); ImmutableList<Step> steps = javaLibraryBuildRule.getBuildSteps(buildContext, new FakeBuildableContext()); assertThat( steps, Matchers.not(Matchers.hasItem(Matchers.instanceOf(JavacDirectToJarStep.class)))); assertThat(steps, Matchers.hasItem(Matchers.instanceOf(JavacStep.class))); assertThat(steps, Matchers.hasItem(Matchers.instanceOf(JarDirectoryStep.class))); } /** Tests that input-based rule keys work properly with generated sources. */ @Test public void testInputBasedRuleKeySourceChange() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); // Setup a Java library consuming a source generated by a genrule and grab its rule key. BuildRule genSrc = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:gen_srcs")) .setOut("Test.java") .setCmd("something") .build(ruleResolver, filesystem); filesystem.writeContentsToPath( "class Test {}", pathResolver.getRelativePath(genSrc.getSourcePathToOutput())); JavaLibrary library = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:lib")) .addSrc(genSrc.getSourcePathToOutput()) .build(ruleResolver, filesystem); FileHashCache originalHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); InputBasedRuleKeyFactory factory = new InputBasedRuleKeyFactory(0, originalHashCache, pathResolver, ruleFinder); RuleKey originalRuleKey = factory.build(library); // Now change the genrule such that its rule key changes, but it's output stays the same (since // we don't change it). This should *not* affect the input-based rule key of the consuming // java library, since it only cares about the contents of the source. ruleResolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); genSrc = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:gen_srcs")) .setOut("Test.java") .setCmd("something else") .build(ruleResolver, filesystem); library = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:lib")) .addSrc(genSrc.getSourcePathToOutput()) .build(ruleResolver, filesystem); FileHashCache unaffectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, unaffectedHashCache, pathResolver, ruleFinder); RuleKey unaffectedRuleKey = factory.build(library); assertThat(originalRuleKey, equalTo(unaffectedRuleKey)); // Now actually modify the source, which should make the input-based rule key change. ruleResolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); genSrc = GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:gen_srcs")) .setOut("Test.java") .setCmd("something else") .build(ruleResolver, filesystem); filesystem.writeContentsToPath( "class Test2 {}", pathResolver.getRelativePath(genSrc.getSourcePathToOutput())); library = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:lib")) .addSrc(genSrc.getSourcePathToOutput()) .build(ruleResolver, filesystem); FileHashCache affectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, affectedHashCache, pathResolver, ruleFinder); RuleKey affectedRuleKey = factory.build(library); assertThat(originalRuleKey, Matchers.not(equalTo(affectedRuleKey))); } /** Tests that input-based rule keys work properly with simple Java library deps. */ @Test public void testInputBasedRuleKeyWithJavaLibraryDep() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); // Setup a Java library which builds against another Java library dep. TargetNode<JavaLibraryDescriptionArg, ?> depNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:dep"), filesystem) .addSrc(Paths.get("Source.java")) .build(); TargetNode<?, ?> libraryNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:lib"), filesystem) .addDep(depNode.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(depNode, libraryNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); JavaLibrary dep = (JavaLibrary) ruleResolver.requireRule(depNode.getBuildTarget()); JavaLibrary library = (JavaLibrary) ruleResolver.requireRule(libraryNode.getBuildTarget()); filesystem.writeContentsToPath( "JAR contents", pathResolver.getRelativePath(dep.getSourcePathToOutput())); writeAbiJar( filesystem, pathResolver.getRelativePath( ruleResolver.requireRule(dep.getAbiJar().get()).getSourcePathToOutput()), "Source.class", "ABI JAR contents"); FileHashCache originalHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); InputBasedRuleKeyFactory factory = new InputBasedRuleKeyFactory(0, originalHashCache, pathResolver, ruleFinder); RuleKey originalRuleKey = factory.build(library); // Now change the Java library dependency such that its rule key changes, and change its JAR // contents, but keep its ABI JAR the same. This should *not* affect the input-based rule key // of the consuming java library, since it only cares about the contents of the ABI JAR. depNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:dep")) .addSrc(Paths.get("Source.java")) .setResourcesRoot(Paths.get("some root that changes the rule key")) .build(); targetGraph = TargetGraphFactory.newInstance(depNode, libraryNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); ruleFinder = new SourcePathRuleFinder(ruleResolver); pathResolver = new SourcePathResolver(ruleFinder); dep = (JavaLibrary) ruleResolver.requireRule(depNode.getBuildTarget()); library = (JavaLibrary) ruleResolver.requireRule(libraryNode.getBuildTarget()); filesystem.writeContentsToPath( "different JAR contents", pathResolver.getRelativePath(dep.getSourcePathToOutput())); FileHashCache unaffectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, unaffectedHashCache, pathResolver, ruleFinder); RuleKey unaffectedRuleKey = factory.build(library); assertThat(originalRuleKey, equalTo(unaffectedRuleKey)); // Now actually change the Java library dependency's ABI JAR. This *should* affect the // input-based rule key of the consuming java library. ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); ruleFinder = new SourcePathRuleFinder(ruleResolver); pathResolver = new SourcePathResolver(ruleFinder); dep = (JavaLibrary) ruleResolver.requireRule(depNode.getBuildTarget()); library = (JavaLibrary) ruleResolver.requireRule(libraryNode.getBuildTarget()); writeAbiJar( filesystem, pathResolver.getRelativePath( ruleResolver.requireRule(dep.getAbiJar().get()).getSourcePathToOutput()), "Source.class", "changed ABI JAR contents"); FileHashCache affectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, affectedHashCache, pathResolver, ruleFinder); RuleKey affectedRuleKey = factory.build(library); assertThat(originalRuleKey, Matchers.not(equalTo(affectedRuleKey))); } /** * Tests that input-based rule keys work properly with a Java library dep exported by a * first-order dep. */ @Test public void testInputBasedRuleKeyWithExportedDeps() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); // Setup a Java library which builds against another Java library dep exporting another Java // library dep. TargetNode<JavaLibraryDescriptionArg, ?> exportedDepNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:edep"), filesystem) .addSrc(Paths.get("Source1.java")) .build(); TargetNode<?, ?> depNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:dep"), filesystem) .addExportedDep(exportedDepNode.getBuildTarget()) .build(); TargetNode<?, ?> libraryNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:lib"), filesystem) .addDep(depNode.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(exportedDepNode, depNode, libraryNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); JavaLibrary exportedDep = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:edep")); JavaLibrary library = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:lib")); filesystem.writeContentsToPath( "JAR contents", pathResolver.getRelativePath(exportedDep.getSourcePathToOutput())); writeAbiJar( filesystem, pathResolver.getRelativePath( ruleResolver.requireRule(exportedDep.getAbiJar().get()).getSourcePathToOutput()), "Source1.class", "ABI JAR contents"); FileHashCache originalHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); InputBasedRuleKeyFactory factory = new InputBasedRuleKeyFactory(0, originalHashCache, pathResolver, ruleFinder); RuleKey originalRuleKey = factory.build(library); // Now change the exported Java library dependency such that its rule key changes, and change // its JAR contents, but keep its ABI JAR the same. This should *not* affect the input-based // rule key of the consuming java library, since it only cares about the contents of the ABI // JAR. exportedDepNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:edep"), filesystem) .addSrc(Paths.get("Source1.java")) .setResourcesRoot(Paths.get("some root that changes the rule key")) .build(); targetGraph = TargetGraphFactory.newInstance(exportedDepNode, depNode, libraryNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); ruleFinder = new SourcePathRuleFinder(ruleResolver); pathResolver = new SourcePathResolver(ruleFinder); exportedDep = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:edep")); library = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:lib")); filesystem.writeContentsToPath( "different JAR contents", pathResolver.getRelativePath(exportedDep.getSourcePathToOutput())); FileHashCache unaffectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, unaffectedHashCache, pathResolver, ruleFinder); RuleKey unaffectedRuleKey = factory.build(library); assertThat(originalRuleKey, equalTo(unaffectedRuleKey)); // Now actually change the exproted Java library dependency's ABI JAR. This *should* affect // the input-based rule key of the consuming java library. ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); ruleFinder = new SourcePathRuleFinder(ruleResolver); pathResolver = new SourcePathResolver(ruleFinder); exportedDep = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:edep")); library = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:lib")); writeAbiJar( filesystem, pathResolver.getRelativePath( ruleResolver.requireRule(exportedDep.getAbiJar().get()).getSourcePathToOutput()), "Source1.class", "changed ABI JAR contents"); FileHashCache affectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, affectedHashCache, pathResolver, ruleFinder); RuleKey affectedRuleKey = factory.build(library); assertThat(originalRuleKey, Matchers.not(equalTo(affectedRuleKey))); } /** * Tests that input-based rule keys work properly with a Java library dep exported through * multiple Java library dependencies. */ @Test public void testInputBasedRuleKeyWithRecursiveExportedDeps() throws Exception { ProjectFilesystem filesystem = new FakeProjectFilesystem(); // Setup a Java library which builds against another Java library dep exporting another Java // library dep. TargetNode<JavaLibraryDescriptionArg, ?> exportedDepNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:edep"), filesystem) .addSrc(Paths.get("Source1.java")) .build(); TargetNode<?, ?> dep2Node = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:dep2"), filesystem) .addExportedDep(exportedDepNode.getBuildTarget()) .build(); TargetNode<?, ?> dep1Node = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:dep1"), filesystem) .addExportedDep(dep2Node.getBuildTarget()) .build(); TargetNode<?, ?> libraryNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:lib"), filesystem) .addDep(dep1Node.getBuildTarget()) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(exportedDepNode, dep2Node, dep1Node, libraryNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); JavaLibrary exportedDep = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:edep")); JavaLibrary library = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:lib")); filesystem.writeContentsToPath( "JAR contents", pathResolver.getRelativePath(exportedDep.getSourcePathToOutput())); writeAbiJar( filesystem, pathResolver.getRelativePath( ruleResolver.requireRule(exportedDep.getAbiJar().get()).getSourcePathToOutput()), "Source1.class", "ABI JAR contents"); FileHashCache originalHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); InputBasedRuleKeyFactory factory = new InputBasedRuleKeyFactory(0, originalHashCache, pathResolver, ruleFinder); RuleKey originalRuleKey = factory.build(library); // Now change the exported Java library dependency such that its rule key changes, and change // its JAR contents, but keep its ABI JAR the same. This should *not* affect the input-based // rule key of the consuming java library, since it only cares about the contents of the ABI // JAR. exportedDepNode = createJavaLibraryBuilder(BuildTargetFactory.newInstance("//:edep"), filesystem) .addSrc(Paths.get("Source1.java")) .setResourcesRoot(Paths.get("some root that changes the rule key")) .build(); targetGraph = TargetGraphFactory.newInstance(exportedDepNode, dep2Node, dep1Node, libraryNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); ruleFinder = new SourcePathRuleFinder(ruleResolver); pathResolver = new SourcePathResolver(ruleFinder); exportedDep = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:edep")); library = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:lib")); filesystem.writeContentsToPath( "different JAR contents", pathResolver.getRelativePath(exportedDep.getSourcePathToOutput())); FileHashCache unaffectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, unaffectedHashCache, pathResolver, ruleFinder); RuleKey unaffectedRuleKey = factory.build(library); assertThat(originalRuleKey, equalTo(unaffectedRuleKey)); // Now actually change the exproted Java library dependency's ABI JAR. This *should* affect // the input-based rule key of the consuming java library. ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); ruleFinder = new SourcePathRuleFinder(ruleResolver); pathResolver = new SourcePathResolver(ruleFinder); exportedDep = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:edep")); library = (JavaLibrary) ruleResolver.requireRule(BuildTargetFactory.newInstance("//:lib")); writeAbiJar( filesystem, pathResolver.getRelativePath( ruleResolver.requireRule(exportedDep.getAbiJar().get()).getSourcePathToOutput()), "Source1.class", "changed ABI JAR contents"); FileHashCache affectedHashCache = new StackedFileHashCache( ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))); factory = new InputBasedRuleKeyFactory(0, affectedHashCache, pathResolver, ruleFinder); RuleKey affectedRuleKey = factory.build(library); assertThat(originalRuleKey, Matchers.not(equalTo(affectedRuleKey))); } private DefaultJavaLibrary createDefaultJavaLibraryRuleWithAbiKey( BuildTarget buildTarget, ImmutableSet<String> srcs, ImmutableSortedSet<BuildRule> deps, ImmutableSortedSet<BuildRule> exportedDeps, Optional<AbstractJavacOptions.SpoolMode> spoolMode, ImmutableList<String> postprocessClassesCommands) throws NoSuchBuildTargetException { ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); ImmutableSortedSet<SourcePath> srcsAsPaths = FluentIterable.from(srcs) .transform(Paths::get) .transform(p -> (SourcePath) new PathSourcePath(projectFilesystem, p)) .toSortedSet(Ordering.natural()); BuildRuleParams buildRuleParams = new FakeBuildRuleParamsBuilder(buildTarget) .setDeclaredDeps(ImmutableSortedSet.copyOf(deps)) .build(); JavacOptions javacOptions = spoolMode.isPresent() ? JavacOptions.builder(DEFAULT_JAVAC_OPTIONS).setSpoolMode(spoolMode.get()).build() : DEFAULT_JAVAC_OPTIONS; DefaultJavaLibrary defaultJavaLibrary = DefaultJavaLibrary.builder(buildRuleParams, ruleResolver, testJavaBuckConfig) .setJavacOptions(javacOptions) .setSrcs(srcsAsPaths) .setGeneratedSourceFolder(javacOptions.getGeneratedSourceFolderName()) .setPostprocessClassesCommands(postprocessClassesCommands) .setExportedDepRules(exportedDeps) .setTrackClassUsage(javacOptions.trackClassUsage()) .build(); ruleResolver.addToIndex(defaultJavaLibrary); return defaultJavaLibrary; } @Test public void testRuleKeyIsOrderInsensitiveForSourcesAndResources() throws Exception { // 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(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder1 = new SourcePathRuleFinder(resolver1); SourcePathResolver pathResolver1 = new SourcePathResolver(ruleFinder1); DefaultJavaLibrary rule1 = createJavaLibraryBuilder(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 FakeSourcePath("becgkaifhjd.txt")) .addResource(new FakeSourcePath("bkhajdifcge.txt")) .addResource(new FakeSourcePath("cabfghjekid.txt")) .addResource(new FakeSourcePath("chkdbafijge.txt")) .build(resolver1, filesystem); BuildRuleResolver resolver2 = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathRuleFinder ruleFinder2 = new SourcePathRuleFinder(resolver2); SourcePathResolver pathResolver2 = new SourcePathResolver(ruleFinder2); DefaultJavaLibrary rule2 = createJavaLibraryBuilder(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 FakeSourcePath("chkdbafijge.txt")) .addResource(new FakeSourcePath("cabfghjekid.txt")) .addResource(new FakeSourcePath("bkhajdifcge.txt")) .addResource(new FakeSourcePath("becgkaifhjd.txt")) .build(resolver2, filesystem); 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()); } DefaultRuleKeyFactory ruleKeyFactory = new DefaultRuleKeyFactory( 0, FakeFileHashCache.createFromStrings(fileHashes.build()), pathResolver1, ruleFinder1); DefaultRuleKeyFactory ruleKeyFactory2 = new DefaultRuleKeyFactory( 0, FakeFileHashCache.createFromStrings(fileHashes.build()), pathResolver2, ruleFinder2); RuleKey key1 = ruleKeyFactory.build(rule1); RuleKey key2 = ruleKeyFactory2.build(rule2); assertEquals(key1, key2); } @Test public void testWhenNoJavacIsProvidedAJavacInMemoryStepIsAdded() throws Exception { BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone"); BuildRule rule = createJavaLibraryBuilder(libraryOneTarget) .addSrc(Paths.get("java/src/com/libone/Bar.java")) .build(ruleResolver); DefaultJavaLibrary buildRule = (DefaultJavaLibrary) rule; ImmutableList<Step> steps = buildRule.getBuildSteps( FakeBuildContext.withSourcePathResolver( new SourcePathResolver(new SourcePathRuleFinder(ruleResolver))), new FakeBuildableContext()); assertEquals(12, steps.size()); assertTrue(((JavacStep) steps.get(8)).getJavac() instanceof Jsr199Javac); } @Test public void testWhenJavacJarIsProvidedAJavacInMemoryStepIsAdded() throws Exception { BuildTarget libraryOneTarget = BuildTargetFactory.newInstance("//:libone"); BuildTarget javacTarget = BuildTargetFactory.newInstance("//langtools:javac"); TargetNode<?, ?> javacNode = PrebuiltJarBuilder.createBuilder(javacTarget) .setBinaryJar(Paths.get("java/src/com/libone/JavacJar.jar")) .build(); TargetNode<?, ?> ruleNode = createJavaLibraryBuilder(libraryOneTarget) .addSrc(Paths.get("java/src/com/libone/Bar.java")) .setCompiler(new DefaultBuildTargetSourcePath(javacTarget)) .build(); TargetGraph targetGraph = TargetGraphFactory.newInstance(javacNode, ruleNode); ruleResolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(ruleResolver)); BuildRule javac = ruleResolver.requireRule(javacTarget); BuildRule rule = ruleResolver.requireRule(libraryOneTarget); DefaultJavaLibrary buildable = (DefaultJavaLibrary) rule; ImmutableList<Step> steps = buildable.getBuildSteps( FakeBuildContext.withSourcePathResolver( new SourcePathResolver(new SourcePathRuleFinder(ruleResolver))), new FakeBuildableContext()); assertEquals(12, steps.size()); Javac javacStep = ((JavacStep) steps.get(8)).getJavac(); assertTrue(javacStep instanceof Jsr199Javac); JarBackedJavac jsrJavac = ((JarBackedJavac) javacStep); assertEquals( RichStream.from(jsrJavac.getCompilerClassPath()) .map(pathResolver::getRelativePath) .collect(MoreCollectors.toImmutableSet()), ImmutableSet.of(pathResolver.getRelativePath(javac.getSourcePathToOutput()))); } // Utilities private JavaLibrary getJavaLibrary(BuildRule rule) { return (JavaLibrary) rule; } private JavaLibraryBuilder createJavaLibraryBuilder(BuildTarget target) { return JavaLibraryBuilder.createBuilder(target, testJavaBuckConfig); } private JavaLibraryBuilder createJavaLibraryBuilder( BuildTarget target, ProjectFilesystem projectFilesystem) { return JavaLibraryBuilder.createBuilder(target, testJavaBuckConfig, projectFilesystem); } private void writeAbiJar( ProjectFilesystem filesystem, Path abiJarPath, String fileName, String fileContents) throws IOException { try (CustomJarOutputStream jar = ZipOutputStreams.newJarOutputStream(filesystem.newFileOutputStream(abiJarPath))) { jar.setEntryHashingEnabled(true); jar.writeEntry( fileName, new ByteArrayInputStream(fileContents.getBytes(StandardCharsets.UTF_8))); } } // test. private BuildContext createBuildContext(BuildRule javaLibrary, @Nullable String bootclasspath) { 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); // TODO(mbolin): Create a utility that populates a BuildContext.Builder with fakes. return BuildContext.builder() .setActionGraph(new ActionGraph(ImmutableList.of(javaLibrary))) .setSourcePathResolver(new SourcePathResolver(new SourcePathRuleFinder(ruleResolver))) .setJavaPackageFinder(EasyMock.createMock(JavaPackageFinder.class)) .setAndroidPlatformTargetSupplier(Suppliers.ofInstance(platformTarget)) .setEventBus(BuckEventBusFactory.newInstance()) .build(); } private abstract static class AnnotationProcessorTarget { private final String targetName; private AnnotationProcessorTarget(String targetName) { this.targetName = targetName; } public BuildTarget createTarget() { return BuildTargetFactory.newInstance(targetName); } public abstract BuildRule createRule(BuildTarget target) throws NoSuchBuildTargetException; } private AnnotationProcessorTarget validPrebuiltJar = new AnnotationProcessorTarget("//tools/java/src/com/facebook/library:prebuilt-processors") { @Override public BuildRule createRule(BuildTarget target) throws NoSuchBuildTargetException { return PrebuiltJarBuilder.createBuilder(target) .setBinaryJar(Paths.get("MyJar")) .build(ruleResolver); } }; private AnnotationProcessorTarget validJavaBinary = new AnnotationProcessorTarget("//tools/java/src/com/facebook/annotations:custom-processors") { @Override public BuildRule createRule(BuildTarget target) throws NoSuchBuildTargetException { return new JavaBinaryRuleBuilder(target) .setMainClass("com.facebook.Main") .build(ruleResolver); } }; private AnnotationProcessorTarget validJavaLibrary = new AnnotationProcessorTarget("//tools/java/src/com/facebook/somejava:library") { @Override public BuildRule createRule(BuildTarget target) throws NoSuchBuildTargetException { return JavaLibraryBuilder.createBuilder(target, testJavaBuckConfig) .addSrc(Paths.get("MyClass.java")) .setProguardConfig(new FakeSourcePath("MyProguardConfig")) .build(ruleResolver); } }; private AnnotationProcessorTarget validJavaLibraryAbi = new AnnotationProcessorTarget("//tools/java/src/com/facebook/somejava:library#class-abi") { @Override public BuildRule createRule(BuildTarget target) throws NoSuchBuildTargetException { return CalculateAbiFromClasses.of( target, new SourcePathRuleFinder(ruleResolver), new FakeBuildRuleParamsBuilder(target) .setProjectFilesystem(new FakeProjectFilesystem()) .build(), new FakeSourcePath("java/src/com/facebook/somejava/library/library-abi.jar")); } }; // Captures all the common code between the different annotation processing test scenarios. private class AnnotationProcessingScenario { private final AnnotationProcessingParams.Builder annotationProcessingParamsBuilder; public AnnotationProcessingScenario() throws IOException { annotationProcessingParamsBuilder = AnnotationProcessingParams.builder() .setLegacySafeAnnotationProcessors(Collections.emptySet()); } public AnnotationProcessingParams.Builder getAnnotationProcessingParamsBuilder() { return annotationProcessingParamsBuilder; } public void addAnnotationProcessorTarget(AnnotationProcessorTarget processor) throws NoSuchBuildTargetException { BuildTarget target = processor.createTarget(); BuildRule rule = processor.createRule(target); annotationProcessingParamsBuilder.addLegacyAnnotationProcessorDeps(rule); } public ImmutableList<String> buildAndGetCompileParameters() throws InterruptedException, IOException, NoSuchBuildTargetException { ProjectFilesystem projectFilesystem = new ProjectFilesystem(tmp.getRoot().toPath()); DefaultJavaLibrary javaLibrary = createJavaLibraryRule(projectFilesystem); BuildContext buildContext = createBuildContext(javaLibrary, /* bootclasspath */ null); List<Step> steps = javaLibrary.getBuildSteps(buildContext, new FakeBuildableContext()); JavacStep javacCommand = lastJavacCommand(steps); ExecutionContext executionContext = TestExecutionContext.newBuilder() .setConsole(new Console(Verbosity.SILENT, System.out, System.err, Ansi.withoutTty())) .setDebugEnabled(true) .build(); ImmutableList<String> options = javacCommand.getOptions( executionContext, /* buildClasspathEntries */ ImmutableSortedSet.of()); return options; } private DefaultJavaLibrary createJavaLibraryRule(ProjectFilesystem projectFilesystem) throws IOException, NoSuchBuildTargetException { 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(); JavacOptions options = JavacOptions.builder(DEFAULT_JAVAC_OPTIONS).setAnnotationProcessingParams(params).build(); BuildRuleParams buildRuleParams = new FakeBuildRuleParamsBuilder(buildTarget) .setProjectFilesystem(projectFilesystem) .build(); DefaultJavaLibrary javaLibrary = DefaultJavaLibrary.builder(buildRuleParams, ruleResolver, testJavaBuckConfig) .setJavacOptions(options) .setSrcs(ImmutableSortedSet.of(new FakeSourcePath(src))) .setResources(ImmutableSortedSet.of()) .setGeneratedSourceFolder(options.getGeneratedSourceFolderName()) .setProguardConfig(Optional.empty()) .setPostprocessClassesCommands(ImmutableList.of()) .setExportedDeps(ImmutableSortedSet.of()) .setProvidedDeps(ImmutableSortedSet.of()) .setTrackClassUsage(options.trackClassUsage()) .setResourcesRoot(Optional.empty()) .setManifestFile(Optional.empty()) .setMavenCoords(Optional.empty()) .setTests(ImmutableSortedSet.of()) .setClassesToRemoveFromJar(options.getClassesToRemoveFromJar()) .build(); ruleResolver.addToIndex(javaLibrary); return javaLibrary; } 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 ImmutableSet<Path> resolve( ImmutableSet<SourcePath> paths, SourcePathResolver resolver) { return paths.stream().map(resolver::getAbsolutePath).collect(MoreCollectors.toImmutableSet()); } }