/* * Copyright 2013-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.android; import static java.nio.charset.StandardCharsets.UTF_8; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import com.facebook.buck.dalvik.EstimateDexWeightStep; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.java.DefaultJavaLibrary; import com.facebook.buck.jvm.java.FakeJavaLibrary; import com.facebook.buck.jvm.java.JavaLibrary; import com.facebook.buck.jvm.java.JavaLibraryBuilder; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildOutputInitializer; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; 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.FakeOnDiskBuildInfo; import com.facebook.buck.rules.InitializableFromDisk; import com.facebook.buck.rules.OnDiskBuildInfo; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.TestExecutionContext; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.MoreAsserts; import com.facebook.buck.util.ObjectMappers; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import org.easymock.EasyMock; import org.junit.Test; public class DexProducedFromJavaLibraryThatContainsClassFilesTest { @Test public void testGetBuildStepsWhenThereAreClassesToDex() throws IOException, InterruptedException { ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem(); BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); FakeJavaLibrary javaLibraryRule = new FakeJavaLibrary( BuildTargetFactory.newInstance(filesystem.getRootPath(), "//foo:bar"), pathResolver, filesystem, ImmutableSortedSet.of()) { @Override public ImmutableSortedMap<String, HashCode> getClassNamesToHashes() { return ImmutableSortedMap.of("com/example/Foo", HashCode.fromString("cafebabe")); } }; resolver.addToIndex(javaLibraryRule); Path jarOutput = BuildTargets.getGenPath(filesystem, javaLibraryRule.getBuildTarget(), "%s.jar"); javaLibraryRule.setOutputFile(jarOutput.toString()); BuildContext context = FakeBuildContext.withSourcePathResolver(pathResolver); FakeBuildableContext buildableContext = new FakeBuildableContext(); Path dexOutput = BuildTargets.getGenPath( filesystem, javaLibraryRule.getBuildTarget().withFlavors(AndroidBinaryGraphEnhancer.DEX_FLAVOR), "%s.dex.jar"); createFiles(filesystem, dexOutput.toString(), jarOutput.toString()); BuildTarget buildTarget = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//foo:bar#dex"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(buildTarget).setProjectFilesystem(filesystem).build(); DexProducedFromJavaLibrary preDex = new DexProducedFromJavaLibrary(params, javaLibraryRule); List<Step> steps = preDex.getBuildSteps(context, buildableContext); AndroidPlatformTarget androidPlatformTarget = createMock(AndroidPlatformTarget.class); expect(androidPlatformTarget.getDxExecutable()).andStubReturn(Paths.get("/usr/bin/dx")); EasyMock.replay(androidPlatformTarget); ExecutionContext executionContext = TestExecutionContext.newBuilder() .setAndroidPlatformTargetSupplier(Suppliers.ofInstance(androidPlatformTarget)) .build(); String expectedDxCommand = String.format( "%s --dex --no-optimize --force-jumbo --output %s %s", Paths.get("/usr/bin/dx"), filesystem.resolve(dexOutput), filesystem.resolve(jarOutput)); MoreAsserts.assertSteps( "Generate bar.dex.jar.", ImmutableList.of( String.format("rm -f %s", filesystem.resolve(dexOutput)), String.format("mkdir -p %s", filesystem.resolve(dexOutput).getParent()), "estimate_dex_weight", "(cd " + filesystem.getRootPath() + " && " + expectedDxCommand + ")", String.format("zip-scrub %s", filesystem.resolve(dexOutput)), "record_dx_success"), steps, executionContext); ((EstimateDexWeightStep) steps.get(2)).setWeightEstimateForTesting(250); Step recordArtifactAndMetadataStep = steps.get(5); int exitCode = recordArtifactAndMetadataStep.execute(executionContext).getExitCode(); assertEquals(0, exitCode); assertEquals( "The generated .dex.jar file should be in the set of recorded artifacts.", ImmutableSet.of(BuildTargets.getGenPath(filesystem, buildTarget, "%s.dex.jar")), buildableContext.getRecordedArtifacts()); buildableContext.assertContainsMetadataMapping( DexProducedFromJavaLibrary.WEIGHT_ESTIMATE, "250"); } private void createFiles(ProjectFilesystem filesystem, String... paths) throws IOException { Path root = filesystem.getRootPath(); for (String path : paths) { Path resolved = root.resolve(path); Files.createDirectories(resolved.getParent()); Files.write(resolved, "".getBytes(UTF_8)); } } @Test public void testGetBuildStepsWhenThereAreNoClassesToDex() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); DefaultJavaLibrary javaLibrary = JavaLibraryBuilder.createBuilder("//foo:bar").build(resolver); javaLibrary .getBuildOutputInitializer() .setBuildOutput(new JavaLibrary.Data(ImmutableSortedMap.of())); BuildContext context = FakeBuildContext.NOOP_CONTEXT; FakeBuildableContext buildableContext = new FakeBuildableContext(); ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); BuildTarget buildTarget = BuildTargetFactory.newInstance("//foo:bar#dex"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(buildTarget).setProjectFilesystem(projectFilesystem).build(); DexProducedFromJavaLibrary preDex = new DexProducedFromJavaLibrary(params, javaLibrary); List<Step> steps = preDex.getBuildSteps(context, buildableContext); Path dexOutput = BuildTargets.getGenPath(projectFilesystem, buildTarget, "%s.dex.jar"); ExecutionContext executionContext = TestExecutionContext.newBuilder().build(); MoreAsserts.assertSteps( "Do not generate a .dex.jar file.", ImmutableList.of( String.format("rm -f %s", projectFilesystem.resolve(dexOutput)), String.format("mkdir -p %s", projectFilesystem.resolve(dexOutput.getParent())), "record_empty_dx"), steps, executionContext); Step recordArtifactAndMetadataStep = steps.get(2); assertThat(recordArtifactAndMetadataStep.getShortName(), startsWith("record_")); int exitCode = recordArtifactAndMetadataStep.execute(executionContext).getExitCode(); assertEquals(0, exitCode); } @Test public void testObserverMethods() throws Exception { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); DefaultJavaLibrary accumulateClassNames = JavaLibraryBuilder.createBuilder("//foo:bar").build(resolver); accumulateClassNames .getBuildOutputInitializer() .setBuildOutput( new JavaLibrary.Data( ImmutableSortedMap.of("com/example/Foo", HashCode.fromString("cafebabe")))); BuildTarget buildTarget = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(buildTarget).build(); DexProducedFromJavaLibrary preDexWithClasses = new DexProducedFromJavaLibrary(params, accumulateClassNames); assertNull(preDexWithClasses.getSourcePathToOutput()); assertEquals( BuildTargets.getGenPath(params.getProjectFilesystem(), buildTarget, "%s.dex.jar"), preDexWithClasses.getPathToDex()); } private static <T> void initialize( InitializableFromDisk<T> initializableFromDisk, OnDiskBuildInfo onDiskBuildInfo) throws IOException { BuildOutputInitializer<T> buildOutputInitializer = initializableFromDisk.getBuildOutputInitializer(); buildOutputInitializer.setBuildOutput( initializableFromDisk.initializeFromDisk(onDiskBuildInfo)); } @Test public void getOutputDoesNotAccessWrappedJavaLibrary() throws Exception { BuildRuleResolver ruleResolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); JavaLibrary javaLibrary = JavaLibraryBuilder.createBuilder(BuildTargetFactory.newInstance("//:lib")) .build(ruleResolver); BuildRuleParams params = new FakeBuildRuleParamsBuilder(BuildTargetFactory.newInstance("//:target")).build(); DexProducedFromJavaLibrary dexProducedFromJavaLibrary = new DexProducedFromJavaLibrary(params, javaLibrary); FakeOnDiskBuildInfo onDiskBuildInfo = new FakeOnDiskBuildInfo() .putMetadata(DexProducedFromJavaLibrary.WEIGHT_ESTIMATE, "0") .putMetadata( DexProducedFromJavaLibrary.CLASSNAMES_TO_HASHES, ObjectMappers.WRITER.writeValueAsString(ImmutableMap.<String, String>of())); initialize(dexProducedFromJavaLibrary, onDiskBuildInfo); assertFalse(dexProducedFromJavaLibrary.hasOutput()); } @Test public void testComputeAbiKey() { ImmutableSortedMap<String, HashCode> classNamesAndHashes = ImmutableSortedMap.of( "com/example/Foo", HashCode.fromString("e4fccb7520b7795e632651323c63217c9f59f72a"), "com/example/Bar", HashCode.fromString("087b7707a5f8e0a2adf5652e3cd2072d89a197dc"), "com/example/Baz", HashCode.fromString("62b1c2510840c0de55c13f66065a98a719be0f19")); String observedSha1 = DexProducedFromJavaLibrary.computeAbiKey(classNamesAndHashes).getHash(); String expectedSha1 = Hashing.sha1() .newHasher() .putUnencodedChars("com/example/Bar") .putByte((byte) 0) .putUnencodedChars("087b7707a5f8e0a2adf5652e3cd2072d89a197dc") .putByte((byte) 0) .putUnencodedChars("com/example/Baz") .putByte((byte) 0) .putUnencodedChars("62b1c2510840c0de55c13f66065a98a719be0f19") .putByte((byte) 0) .putUnencodedChars("com/example/Foo") .putByte((byte) 0) .putUnencodedChars("e4fccb7520b7795e632651323c63217c9f59f72a") .putByte((byte) 0) .hash() .toString(); assertEquals(expectedSha1, observedSha1); } }