| // 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.benchmarks.BenchmarkResults; | |
| import com.android.tools.r8.utils.AndroidApp; | |
| import com.android.tools.r8.utils.DescriptorUtils; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import com.android.tools.r8.utils.internal.FileUtils; | |
| import com.android.tools.r8.utils.internal.ListUtils; | |
| import com.android.tools.r8.utils.internal.exceptions.Unimplemented; | |
| import com.google.common.base.Charsets; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| 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, | |
| ExternalR8TestMultiRunResult, | |
| ExternalR8TestBuilder> { | |
| // The r8.jar to run. | |
| private List<Path> r8Classpath = ToolHelper.getBuildPropR8RuntimePath(); | |
| // Ordered list of program jar entries. | |
| private final List<Path> programJars = new ArrayList<>(); | |
| // Ordered list of classpath jar entries. | |
| private final List<Path> classpathJars = 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; | |
| // Enable/disable compiling with assertions | |
| private boolean enableAssertions = true; | |
| // Allow test proguard options | |
| private boolean allowTestProguardOptions = false; | |
| private String dumpInputToFile = null; | |
| private String dumpInputToDirectory = null; | |
| 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 List<String> getConfig() { | |
| return Collections.unmodifiableList(config); | |
| } | |
| public ExternalR8TestBuilder addJvmFlag(String flag) { | |
| jvmFlags.add(flag); | |
| return self(); | |
| } | |
| public ExternalR8TestBuilder enableAssertions(boolean enable) { | |
| enableAssertions = enable; | |
| return self(); | |
| } | |
| public ExternalR8TestBuilder allowTestProguardOptions(boolean allow) { | |
| allowTestProguardOptions = allow; | |
| return self(); | |
| } | |
| public ExternalR8TestBuilder dumpInputToFile(String arg) { | |
| dumpInputToFile = arg; | |
| return self(); | |
| } | |
| public ExternalR8TestBuilder dumpInputToDirectory(String arg) { | |
| dumpInputToDirectory = arg; | |
| return self(); | |
| } | |
| @Override | |
| ExternalR8TestCompileResult internalCompile( | |
| Builder builder, | |
| Consumer<InternalOptions> optionsConsumer, | |
| Supplier<AndroidApp> app, | |
| BenchmarkResults benchmarkResults) | |
| throws CompilationFailedException { | |
| assert benchmarkResults == null; | |
| assert !libraryDesugaringTestConfiguration.isEnabled(); | |
| try { | |
| Path outputFolder = getState().getNewTempFolder(); | |
| Path outputJar = outputFolder.resolve("output.jar"); | |
| Path proguardMapFile = outputFolder.resolve("output.jar.map"); | |
| List<Path> classpathList = new ArrayList<>(r8Classpath); | |
| if (addR8ExternalDeps) { | |
| classpathList.addAll(ToolHelper.getDeps()); | |
| } | |
| String classPath = | |
| String.join( | |
| CLASSPATH_SEPARATOR, | |
| ListUtils.map(classpathList, p -> p.toAbsolutePath().toString())); | |
| List<String> command = new ArrayList<>(); | |
| if (runtime.isDex()) { | |
| throw new Unimplemented(); | |
| } | |
| Collections.addAll(command, runtime.asCf().getJavaExecutable().toString()); | |
| command.addAll(jvmFlags); | |
| if (enableAssertions) { | |
| command.add("-ea"); | |
| } | |
| if (allowTestProguardOptions) { | |
| command.add("-Dcom.android.tools.r8.allowTestProguardOptions=true"); | |
| } | |
| Collections.addAll( | |
| command, | |
| "-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()); | |
| } | |
| } | |
| if (!classpathJars.isEmpty()) { | |
| for (Path classpathJar : classpathJars) { | |
| command.add("--classpath"); | |
| command.add(classpathJar.toAbsolutePath().toString()); | |
| } | |
| } | |
| if (dumpInputToFile != null) { | |
| command.add("--dumpinputtofile"); | |
| command.add(dumpInputToFile); | |
| } | |
| if (dumpInputToDirectory != null) { | |
| command.add("--dumpinputtodirectory"); | |
| command.add(dumpInputToDirectory); | |
| } | |
| command.addAll(programJars.stream().map(Path::toString).collect(Collectors.toList())); | |
| ProcessBuilder processBuilder = new ProcessBuilder(command); | |
| ProcessResult processResult = ToolHelper.runProcess(processBuilder, getStdoutForTesting()); | |
| assertEquals( | |
| "STDOUT\n:" + processResult.stdout + "\nSTDERR:\n" + processResult.stderr, | |
| 0, | |
| processResult.exitCode); | |
| String proguardMap = | |
| proguardMapFile.toFile().exists() | |
| ? FileUtils.readTextFile(proguardMapFile, Charsets.UTF_8) | |
| : ""; | |
| return new ExternalR8TestCompileResult( | |
| getState(), outputJar, processResult, proguardMap, getMinApiLevel(), getOutputMode()); | |
| } catch (IOException e) { | |
| throw InternalCompilationFailedExceptionUtils.createForTesting(e); | |
| } | |
| } | |
| @Override | |
| public ExternalR8TestBuilder addApplyMapping(String proguardMap) { | |
| throw new Unimplemented("No support for adding mapfile content yet"); | |
| } | |
| @Override | |
| public ExternalR8TestBuilder addDataResources(List<? extends DataResource> 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 { | |
| try { | |
| addProgramClassFileData(Files.readAllBytes(file)); | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| } | |
| return self(); | |
| } | |
| @Override | |
| public ExternalR8TestBuilder addProgramClassFileData(Collection<byte[]> classes) { | |
| try { | |
| Path out = getState().getNewTempFolder().resolve("out.jar"); | |
| TestBase.writeClassFileDataToJar(out, classes); | |
| programJars.add(out); | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| return self(); | |
| } | |
| @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) { | |
| classpathJars.addAll(files); | |
| return self(); | |
| } | |
| @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.getR8WithRelocatedDeps()); | |
| } | |
| public ExternalR8TestBuilder useProvidedR8(Path r8jar) { | |
| this.r8Classpath = Collections.singletonList(r8jar); | |
| return self(); | |
| } | |
| public ExternalR8TestBuilder addR8ExternalDepsToClasspath() { | |
| this.addR8ExternalDeps = true; | |
| return self(); | |
| } | |
| } |