blob: 0ebe0474ebe3afd13f678269ca891e160b4f98c5 [file] [log] [blame]
// Copyright (c) 2019, 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.ToolHelper.CLASSPATH_SEPARATOR;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Charsets;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
// The type arguments R8Command, Builder is not relevant for running external R8.
public class ExternalR8TestBuilder
extends TestShrinkerBuilder<
R8Command,
Builder,
ExternalR8TestCompileResult,
ExternalR8TestRunResult,
ExternalR8TestBuilder> {
// The r8.jar to run.
private Path r8jar = ToolHelper.R8_JAR;
// Ordered list of program jar entries.
private final List<Path> programJars = new ArrayList<>();
// Ordered list of library jar entries.
private final List<Path> libJars = new ArrayList<>();
// Proguard configuration file lines.
private final List<String> config = new ArrayList<>();
// Additional Proguard configuration files.
private List<Path> proguardConfigFiles = new ArrayList<>();
// External JDK to use to run R8
private final TestRuntime runtime;
private boolean addR8ExternalDeps = false;
private List<String> jvmFlags = new ArrayList<>();
private ExternalR8TestBuilder(
TestState state, Builder builder, Backend backend, TestRuntime runtime) {
super(state, builder, backend);
assert runtime != null;
this.runtime = runtime;
}
public static ExternalR8TestBuilder create(
TestState state, Backend backend, TestRuntime runtime) {
return new ExternalR8TestBuilder(state, R8Command.builder(), backend, runtime);
}
@Override
ExternalR8TestBuilder self() {
return this;
}
public ExternalR8TestBuilder addJvmFlag(String flag) {
jvmFlags.add(flag);
return self();
}
@Override
ExternalR8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException {
try {
Path outputFolder = getState().getNewTempFolder();
Path outputJar = outputFolder.resolve("output.jar");
Path proguardMapFile = outputFolder.resolve("output.jar.map");
String classPath =
addR8ExternalDeps
? r8jar.toAbsolutePath().toString()
+ CLASSPATH_SEPARATOR
+ ToolHelper.DEPS_NOT_RELOCATED
: r8jar.toAbsolutePath().toString();
List<String> command = new ArrayList<>();
if (runtime.isDex()) {
throw new Unimplemented();
}
Collections.addAll(command, runtime.asCf().getJavaExecutable().toString());
command.addAll(jvmFlags);
Collections.addAll(
command,
"-ea",
"-cp",
classPath,
R8.class.getTypeName(),
"--output",
outputJar.toAbsolutePath().toString(),
"--pg-map-output",
proguardMapFile.toString(),
backend == Backend.CF ? "--classfile" : "--dex",
builder.getMode() == CompilationMode.DEBUG ? "--debug" : "--release");
if (!config.isEmpty()) {
Path proguardConfigFile = outputFolder.resolve("proguard-config.txt");
FileUtils.writeTextFile(proguardConfigFile, config);
command.add("--pg-conf");
command.add(proguardConfigFile.toAbsolutePath().toString());
}
for (Path proguardConfigFile : proguardConfigFiles) {
command.add("--pg-conf");
command.add(proguardConfigFile.toAbsolutePath().toString());
}
if (libJars.isEmpty()) {
command.add("--lib");
command.add(TestBase.runtimeJar(backend).toAbsolutePath().toString());
} else {
for (Path libJar : libJars) {
command.add("--lib");
command.add(libJar.toAbsolutePath().toString());
}
}
command.addAll(programJars.stream().map(Path::toString).collect(Collectors.toList()));
ProcessBuilder processBuilder = new ProcessBuilder(command);
ProcessResult processResult = ToolHelper.runProcess(processBuilder);
assertEquals(processResult.stderr, 0, processResult.exitCode);
String proguardMap =
proguardMapFile.toFile().exists()
? FileUtils.readTextFile(proguardMapFile, Charsets.UTF_8)
: "";
return new ExternalR8TestCompileResult(
getState(), outputJar, processResult, proguardMap, getOutputMode());
} catch (IOException e) {
throw new CompilationFailedException(e);
}
}
@Override
public ExternalR8TestBuilder addApplyMapping(String proguardMap) {
throw new Unimplemented("No support for adding mapfile content yet");
}
@Override
public ExternalR8TestBuilder addDataEntryResources(DataEntryResource... resources) {
throw new Unimplemented("No support for adding data entry resources");
}
@Override
public ExternalR8TestBuilder addProgramClasses(Collection<Class<?>> classes) {
// Adding a collection of classes will build a jar of exactly those classes so that no other
// classes are made available via a too broad classpath directory.
try {
Path programJar = getState().getNewTempFolder().resolve("input.jar");
ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(programJar);
for (Class<?> clazz : classes) {
String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
inputConsumer.accept(ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), descriptor, null);
}
inputConsumer.finished(null);
programJars.add(programJar);
return self();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public ExternalR8TestBuilder addProgramFiles(Collection<Path> files) {
for (Path file : files) {
if (FileUtils.isJarFile(file)) {
programJars.add(file);
} else {
throw new Unimplemented("No support for adding paths directly");
}
}
return self();
}
@Override
public ExternalR8TestBuilder addProgramClassFileData(Collection<byte[]> classes) {
throw new Unimplemented("No support for adding classfile data directly");
}
@Override
public ExternalR8TestBuilder addProgramDexFileData(Collection<byte[]> data) {
throw new Unimplemented("No support for adding dex file data directly");
}
@Override
public ExternalR8TestBuilder addLibraryFiles(Collection<Path> files) {
libJars.addAll(files);
return self();
}
@Override
public ExternalR8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
throw new Unimplemented("No support for adding classpath data directly");
}
@Override
public ExternalR8TestBuilder addClasspathFiles(Collection<Path> files) {
throw new Unimplemented("No support for adding classpath data directly");
}
@Override
public ExternalR8TestBuilder addKeepRuleFiles(List<Path> proguardConfigFiles) {
this.proguardConfigFiles.addAll(proguardConfigFiles);
return self();
}
@Override
public ExternalR8TestBuilder addKeepRules(Collection<String> rules) {
config.addAll(rules);
return self();
}
public ExternalR8TestBuilder useR8WithRelocatedDeps() {
return useProvidedR8(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR);
}
public ExternalR8TestBuilder useProvidedR8(Path r8jar) {
this.r8jar = r8jar;
return self();
}
public ExternalR8TestBuilder addR8ExternalDepsToClasspath() {
this.addR8ExternalDeps = true;
return self();
}
}