blob: 4fd3276b66b19ae8352fca5206e7b3e578438a6d [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalResource;
import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.OutputMode;
import com.beust.jcommander.internal.Lists;
import com.google.common.io.Closer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.UnaryOperator;
import org.junit.Assert;
import org.junit.Test;
public class D8IncrementalRunExamplesAndroidOTest
extends RunExamplesAndroidOTest<D8Command.Builder> {
class D8IncrementalTestRunner extends TestRunner {
D8IncrementalTestRunner(String testName, String packageName, String mainClass) {
super(testName, packageName, mainClass);
}
@Override
TestRunner withMinApiLevel(int minApiLevel) {
return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
}
@Override
void build(Path testJarFile, Path out) throws Throwable {
Map<String, Resource> files = compileClassesTogether(testJarFile, null);
mergeClassFiles(Lists.newArrayList(files.values()), out);
}
// Dex classes separately.
SortedMap<String, Resource> compileClassesSeparately(Path testJarFile) throws Throwable {
TreeMap<String, Resource> fileToResource = new TreeMap<>();
List<String> classFiles = collectClassFiles(testJarFile);
for (String classFile : classFiles) {
AndroidApp app = compileClassFiles(
testJarFile.toString(), Collections.singletonList(classFile), null, OutputMode.Indexed);
assert app.getDexProgramResources().size() == 1;
fileToResource.put(
makeRelative(testJarFile, Paths.get(classFile)).toString(),
app.getDexProgramResources().get(0));
}
return fileToResource;
}
// Dex classes in one D8 invocation.
SortedMap<String, Resource> compileClassesTogether(
Path testJarFile, Path output) throws Throwable {
TreeMap<String, Resource> fileToResource = new TreeMap<>();
List<String> classFiles = collectClassFiles(testJarFile);
AndroidApp app = compileClassFiles(
testJarFile.toString(), classFiles, output, OutputMode.FilePerClass);
for (InternalResource resource : app.getDexProgramResources()) {
String classDescriptor = resource.getSingleClassDescriptorOrNull();
Assert.assertNotNull("Add resources are expected to have a descriptor", classDescriptor);
classDescriptor = classDescriptor.substring(1, classDescriptor.length() - 1);
fileToResource.put(classDescriptor + ".class", resource);
}
return fileToResource;
}
private Path makeRelative(Path testJarFile, Path classFile) {
classFile = classFile.toAbsolutePath();
Path regularParent =
testJarFile.getParent().resolve(Paths.get("classes")).toAbsolutePath();
Path legacyParent = regularParent.resolve(Paths.get("..",
regularParent.getFileName().toString() + "Legacy", "classes")).toAbsolutePath();
if (classFile.startsWith(regularParent)) {
return regularParent.relativize(classFile);
}
Assert.assertTrue(classFile.startsWith(legacyParent));
return legacyParent.relativize(classFile);
}
private List<String> collectClassFiles(Path testJarFile) {
List<String> result = new ArrayList<>();
// Collect Java 8 classes.
Path parent = testJarFile.getParent();
File packageDir = parent.resolve(Paths.get("classes", packageName)).toFile();
collectClassFiles(packageDir, result);
// Collect legacy classes.
Path legacyPath = Paths.get("..",
parent.getFileName().toString() + "Legacy", "classes", packageName);
packageDir = parent.resolve(legacyPath).toFile();
collectClassFiles(packageDir, result);
Collections.sort(result);
return result;
}
private void collectClassFiles(File dir, List<String> result) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
collectClassFiles(file, result);
} else {
result.add(file.getAbsolutePath());
}
}
}
}
AndroidApp compileClassFiles(String classpath,
List<String> inputFiles, Path output, OutputMode outputMode) throws Throwable {
D8Command.Builder builder = D8Command.builder();
builder = builder.addClasspathFiles(Paths.get(classpath));
for (String inputFile : inputFiles) {
builder = builder.addProgramFiles(Paths.get(inputFile));
}
for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
builder = builder.setOutputMode(outputMode);
builder = builder.addLibraryFiles(
Paths.get(ToolHelper.getAndroidJar(builder.getMinApiLevel())));
if (output != null) {
builder = builder.setOutputPath(output);
}
D8Command command = builder.build();
try {
return ToolHelper.runD8(command, this::combinedOptionConsumer);
} catch (Unimplemented | CompilationError | InternalCompilerError re) {
throw re;
} catch (RuntimeException re) {
throw re.getCause() == null ? re : re.getCause();
}
}
Resource mergeClassFiles(List<Resource> dexFiles, Path out) throws Throwable {
D8Command.Builder builder = D8Command.builder();
for (Resource dexFile : dexFiles) {
builder.addDexProgramData(readFromResource(dexFile));
}
for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
if (out != null) {
builder = builder.setOutputPath(out);
}
D8Command command = builder.build();
try {
AndroidApp app = ToolHelper.runD8(command, this::combinedOptionConsumer);
assert app.getDexProgramResources().size() == 1;
return app.getDexProgramResources().get(0);
} catch (Unimplemented | CompilationError | InternalCompilerError re) {
throw re;
} catch (RuntimeException re) {
throw re.getCause() == null ? re : re.getCause();
}
}
}
@Test
public void dexPerClassFileNoDesugaring() throws Throwable {
String testName = "dexPerClassFileNoDesugaring";
String testPackage = "incremental";
String mainClass = "IncrementallyCompiled";
Path inputJarFile = Paths.get(EXAMPLE_DIR, testPackage + JAR_EXTENSION);
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
Map<String, Resource> compiledSeparately = test.compileClassesSeparately(inputJarFile);
Map<String, Resource> compiledTogether = test.compileClassesTogether(inputJarFile, null);
Assert.assertEquals(compiledSeparately.size(), compiledTogether.size());
for (Map.Entry<String, Resource> entry : compiledSeparately.entrySet()) {
Resource otherResource = compiledTogether.get(entry.getKey());
Assert.assertNotNull(otherResource);
Assert.assertArrayEquals(readFromResource(entry.getValue()), readFromResource(otherResource));
}
Resource mergedFromCompiledSeparately =
test.mergeClassFiles(Lists.newArrayList(compiledSeparately.values()), null);
Resource mergedFromCompiledTogether =
test.mergeClassFiles(Lists.newArrayList(compiledTogether.values()), null);
Assert.assertArrayEquals(
readFromResource(mergedFromCompiledSeparately),
readFromResource(mergedFromCompiledTogether));
}
@Test
public void dexPerClassFileWithDesugaring() throws Throwable {
String testName = "dexPerClassFileWithDesugaring";
String testPackage = "lambdadesugaringnplus";
String mainClass = "LambdasWithStaticAndDefaultMethods";
Path inputJarFile = Paths.get(EXAMPLE_DIR, testPackage + JAR_EXTENSION);
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
Resource mergedFromCompiledSeparately =
test.mergeClassFiles(Lists.newArrayList(
test.compileClassesSeparately(inputJarFile).values()), null);
Resource mergedFromCompiledTogether =
test.mergeClassFiles(Lists.newArrayList(
test.compileClassesTogether(inputJarFile, null).values()), null);
Assert.assertArrayEquals(
readFromResource(mergedFromCompiledSeparately),
readFromResource(mergedFromCompiledTogether));
}
@Test
public void dexPerClassFileOutputFiles() throws Throwable {
String testName = "dexPerClassFileNoDesugaring";
String testPackage = "incremental";
String mainClass = "IncrementallyCompiled";
Path out = temp.getRoot().toPath();
Path inputJarFile = Paths.get(EXAMPLE_DIR, testPackage + JAR_EXTENSION);
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
test.compileClassesTogether(inputJarFile, out);
String[] dexFiles = out.toFile().list();
assert dexFiles != null;
Arrays.sort(dexFiles);
String[] expectedFileNames = {
"incremental.IncrementallyCompiled$A$AB.dex",
"incremental.IncrementallyCompiled$A.dex",
"incremental.IncrementallyCompiled$B$BA.dex",
"incremental.IncrementallyCompiled$B.dex",
"incremental.IncrementallyCompiled$C.dex",
"incremental.IncrementallyCompiled.dex"
};
Assert.assertArrayEquals(expectedFileNames, dexFiles);
}
@Override
D8IncrementalTestRunner test(String testName, String packageName, String mainClass) {
return new D8IncrementalTestRunner(testName, packageName, mainClass);
}
static byte[] readFromResource(Resource resource) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[16384];
try (Closer closer = Closer.create()) {
InputStream stream = resource.getStream(closer);
int read;
while ((read = stream.read(buffer, 0, buffer.length)) != -1) {
output.write(buffer, 0, read);
}
}
return output.toByteArray();
}
}