Move old binary tests to the new API test set up.
The main-dex APIs are split out into their own tests, the rest is close to a
blind copy. The test inputs are changed to avoid using legacy example artifacts.
Bug: b/181858113
Change-Id: I4db1ce2f42e8cc5637284124da8ecbf98bf82c88
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
deleted file mode 100644
index 775a308..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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.apiusagesample;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DirectoryClassFileProvider;
-import com.android.tools.r8.ProgramResource;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class CachingArchiveClassFileProvider extends ArchiveClassFileProvider {
-
- private ConcurrentHashMap<String, ProgramResource> resources = new ConcurrentHashMap<>();
-
- private CachingArchiveClassFileProvider(Path archive) throws IOException {
- super(archive);
- }
-
- @Override
- public ProgramResource getProgramResource(String descriptor) {
- return resources.computeIfAbsent(descriptor, super::getProgramResource);
- }
-
- public static ClassFileResourceProvider getProvider(Path entry)
- throws IOException {
- if (Files.isRegularFile(entry)) {
- return new CachingArchiveClassFileProvider(entry);
- } else if (Files.isDirectory(entry)) {
- return DirectoryClassFileProvider.fromDirectory(entry);
- } else {
- throw new FileNotFoundException(entry.toString());
- }
- }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
deleted file mode 100644
index 80a2845..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ /dev/null
@@ -1,628 +0,0 @@
-// 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.apiusagesample;
-
-import com.android.tools.r8.ArchiveProgramResourceProvider;
-import com.android.tools.r8.AssertionsConfiguration;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DesugarGraphConsumer;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class D8ApiUsageSample {
-
- private static final Origin origin =
- new Origin(Origin.root()) {
- @Override
- public String part() {
- return "D8ApiUsageSample";
- }
- };
-
- private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
-
- /**
- * Example invocation:
- *
- * <pre>
- * java -jar d8-api-uses.jar \
- * --output path/to/output/dir \
- * --min-api minApiLevel \
- * --lib path/to/library.jar \
- * --classpath path/to/classpath.jar \
- * path/to/input{1,2,3}.{jar,class}
- * </pre>
- */
- public static void main(String[] args) {
- // Parse arguments with the commandline parser to make use of its API.
- D8Command.Builder cmd = D8Command.parse(args, origin);
- CompilationMode mode = cmd.getMode();
- Path temp = cmd.getOutputPath();
- int minApiLevel = cmd.getMinApiLevel();
- // The Builder API does not provide access to the concrete paths
- // (everything is put into providers) so manually parse them here.
- List<Path> libraries = new ArrayList<>(1);
- List<Path> classpath = new ArrayList<>(args.length);
- List<Path> mainDexList = new ArrayList<>(1);
- List<Path> mainDexRules = new ArrayList<>(1);
- List<Path> inputs = new ArrayList<>(args.length);
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("--lib")) {
- libraries.add(Paths.get(args[++i]));
- } else if (args[i].equals("--classpath")) {
- classpath.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-list")) {
- mainDexList.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-rules")) {
- mainDexRules.add(Paths.get(args[++i]));
- } else if (isArchive(args[i]) || isClassFile(args[i])) {
- inputs.add(Paths.get(args[i]));
- }
- }
- if (!Files.exists(temp) || !Files.isDirectory(temp)) {
- throw new RuntimeException("Must supply a temp/output directory");
- }
- if (inputs.isEmpty()) {
- throw new RuntimeException("Must supply program inputs");
- }
- if (classpath.isEmpty()) {
- throw new RuntimeException("Must supply classpath inputs");
- }
- if (libraries.isEmpty()) {
- throw new RuntimeException("Must supply library inputs");
- }
- if (mainDexList.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-list inputs");
- }
- if (mainDexRules.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-rules inputs");
- }
-
- useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
- useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
- useProgramData(minApiLevel, libraries, classpath, inputs);
- useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
- useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
- useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
- useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
- useMainDexRulesFiles(minApiLevel, libraries, classpath, inputs, mainDexRules);
- useMainDexRules(minApiLevel, libraries, classpath, inputs, mainDexRules);
- useAssertionConfig(minApiLevel, libraries, classpath, inputs);
- useVArgVariants(minApiLevel, libraries, classpath, inputs, mainDexList);
- incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
- }
-
- // Check API support for compiling Java class-files from the file system.
- private static void useProgramFileList(
- CompilationMode mode,
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMode(mode)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for compiling Java class-files from byte content.
- private static void useProgramData(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath);
- for (ClassFileContent classfile : readClassFiles(inputs)) {
- builder.addClassProgramData(classfile.data, classfile.origin);
- }
- for (Path input : inputs) {
- if (isDexFile(input)) {
- builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
- }
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- // Check API support for compiling Java class-files from a program provider abstraction.
- private static void useProgramResourceProvider(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath);
- for (Path input : inputs) {
- if (isArchive(input)) {
- builder.addProgramResourceProvider(
- ArchiveProgramResourceProvider.fromArchive(
- input, ArchiveProgramResourceProvider::includeClassFileEntries));
- } else if (isClassFile(input)) {
- builder.addProgramResourceProvider(
- new ProgramResourceProvider() {
- @Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
- }
- });
- } else if (isDexFile(input)) {
- builder.addProgramResourceProvider(
- new ProgramResourceProvider() {
- @Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
- }
- });
- }
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useLibraryAndClasspathProvider(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addProgramFiles(inputs);
- for (Path library : libraries) {
- builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(library));
- }
- for (Path path : classpath) {
- builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(path));
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexListFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexClasses(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- List<String> mainDexClasses = new ArrayList<>(1);
- for (Path path : mainDexList) {
- for (String line : Files.readAllLines(path)) {
- String entry = line.trim();
- if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
- continue;
- }
- mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
- }
- }
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addMainDexClasses(mainDexClasses)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexRulesFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexRules) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addMainDexRulesFiles(mainDexRules)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexRules(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexRulesFiles) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs);
- for (Path mainDexRulesFile : mainDexRulesFiles) {
- builder.addMainDexRules(
- Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useAssertionConfig(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
- .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for all the varg variants.
- private static void useVArgVariants(
- int minApiLevel,
- List<Path> libraries,
- List<Path> classpath,
- List<Path> inputs,
- List<Path> mainDexList) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries.get(0))
- .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
- .addClasspathFiles(classpath.get(0))
- .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new))
- .addProgramFiles(inputs.get(0))
- .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
- .addMainDexListFiles(mainDexList.get(0))
- .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void incrementalCompileAndMerge(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- // Compile and merge via index intermediates.
- mergeIntermediates(
- minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
- // Compile and merge via per-classfile intermediates.
- mergeIntermediates(
- minApiLevel, compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
- }
-
- private static Collection<byte[]> compileToIndexedIntermediates(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setIntermediate(true)
- .setProgramConsumer(consumer)
- .addClasspathFiles(classpath)
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .setDisableDesugaring(false)
- .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- return consumer.bytes;
- }
-
- private static Collection<byte[]> compileToPerClassFileIntermediates(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(consumer)
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- return consumer.bytes;
- }
-
- private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .setDisableDesugaring(true);
- for (byte[] intermediate : intermediates) {
- builder.addDexProgramData(intermediate, Origin.unknown());
- }
- try {
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected merging error", e);
- }
- }
-
- // Helpers for tests.
- // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
- // rely on it.
-
- private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
- List<ClassFileContent> classfiles = new ArrayList<>();
- for (Path file : files) {
- if (isArchive(file)) {
- Origin zipOrigin = new PathOrigin(file);
- ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
- ZipEntry entry;
- while (null != (entry = zip.getNextEntry())) {
- String name = entry.getName();
- if (isClassFile(name)) {
- Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
- classfiles.add(new ClassFileContent(origin, readBytes(zip)));
- }
- }
- } else if (isClassFile(file)) {
- classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
- }
- }
- return classfiles;
- }
-
- private static byte[] readBytes(InputStream stream) throws IOException {
- try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
- byte[] buffer = new byte[0xffff];
- for (int length; (length = stream.read(buffer)) != -1; ) {
- bytes.write(buffer, 0, length);
- }
- return bytes.toByteArray();
- }
- }
-
- private static boolean isClassFile(Path file) {
- return isClassFile(file.toString());
- }
-
- private static boolean isClassFile(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".class");
- }
-
- private static boolean isDexFile(Path file) {
- return isDexFile(file.toString());
- }
-
- private static boolean isDexFile(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".dex");
- }
-
- private static boolean isArchive(Path file) {
- return isArchive(file.toString());
- }
-
- private static boolean isArchive(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".zip") || file.endsWith(".jar");
- }
-
- private static class ClassFileContent {
- final Origin origin;
- final byte[] data;
-
- public ClassFileContent(Origin origin, byte[] data) {
- this.origin = origin;
- this.data = data;
- }
- }
-
- private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
-
- List<byte[]> bytes = new ArrayList<>();
-
- @Override
- public synchronized void accept(
- int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
- bytes.add(data);
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {}
- }
-
- private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
-
- List<byte[]> bytes = new ArrayList<>();
-
- @Override
- public synchronized void accept(
- String primaryClassDescriptor,
- byte[] data,
- Set<String> descriptors,
- DiagnosticsHandler handler) {
- bytes.add(data);
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {}
- }
-
- private static class EnsureOutputConsumer implements DexIndexedConsumer {
- boolean hasOutput = false;
-
- @Override
- public synchronized void accept(
- int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
- hasOutput = true;
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- if (!hasOutput) {
- handler.error(new StringDiagnostic("Expected to produce output but had none"));
- }
- }
- }
-
- private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
-
- @Override
- public void accept(Origin dependent, Origin dependency) {
- }
-
- public void finished() {
-
- }
- }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
deleted file mode 100644
index d523a53..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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.apiusagesample;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-class D8DiagnosticsHandler implements DiagnosticsHandler {
-
- public D8DiagnosticsHandler() {
- }
-
- public static Origin getOrigin(Path root, Path entry) {
- if (Files.isRegularFile(root)) {
- return new ArchiveEntryOrigin(entry.toString(), new PathOrigin(root));
- } else {
- return new PathOrigin(root.resolve(entry.toString()));
- }
- }
-
- @Override
- public void error(Diagnostic error) {
- convertToMessage(error);
- }
-
- @Override
- public void warning(Diagnostic warning) {
- if (warning instanceof InterfaceDesugarMissingTypeDiagnostic) {
- desugarInterfaceMethodInfo((InterfaceDesugarMissingTypeDiagnostic) warning);
- } else {
- convertToMessage(warning);
- }
- }
-
- @Override
- public void info(Diagnostic info) {
- convertToMessage(info);
- }
-
- void desugarInterfaceMethodInfo(InterfaceDesugarMissingTypeDiagnostic info) {
- System.out.println("desugar is missing: " + info.getMissingType().toString());
- System.out.println(" used from: " + info.getContextType().toString());
- convertToMessage(info);
- }
-
- protected void convertToMessage(Diagnostic diagnostic) {
- String textMessage = diagnostic.getDiagnosticMessage();
-
- Origin origin = diagnostic.getOrigin();
- Position positionInOrigin = diagnostic.getPosition();
- String position;
- if (origin instanceof PathOrigin) {
- Path originFile = ((PathOrigin) origin).getPath();
- if (positionInOrigin instanceof TextRange) {
- TextRange textRange = (TextRange) positionInOrigin;
- position = originFile + ": "
- + textRange.getStart().getLine() + "," + textRange.getStart().getColumn()
- + " - " + textRange.getEnd().getLine() + "," + textRange.getEnd().getColumn();
- } else if (positionInOrigin instanceof TextPosition) {
- TextPosition textPosition = (TextPosition) positionInOrigin;
- position = originFile + ": "
- + textPosition.getLine() + "," + textPosition.getColumn();
- } else {
- position = originFile.toString();
- }
- } else if (origin.parent() instanceof PathOrigin) {
- Path originFile = ((PathOrigin) origin.parent()).getPath();
- position = originFile.toString();
- } else {
- position = "UNKNOWN";
- if (origin != Origin.unknown()) {
- textMessage = origin.toString() + ": " + textMessage;
- }
- }
-
- System.out.println(position + ": " + textMessage);
- }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
deleted file mode 100644
index 521ccba..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ /dev/null
@@ -1,612 +0,0 @@
-// Copyright (c) 2018, 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.apiusagesample;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.ArchiveProgramResourceProvider;
-import com.android.tools.r8.AssertionsConfiguration;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.Version;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class R8ApiUsageSample {
-
- private static final Origin origin =
- new Origin(Origin.root()) {
- @Override
- public String part() {
- return "R8ApiUsageSample";
- }
- };
-
- private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
-
- /**
- * Example invocation:
- *
- * <pre>
- * java -jar r8-api-uses.jar \
- * --output path/to/output/dir \
- * --min-api minApiLevel \
- * --lib path/to/library.jar \
- * path/to/input{1,2,3}.{jar,class}
- * </pre>
- */
- public static void main(String[] args) {
- // Check version API
- checkVersionApi();
- // Parse arguments with the commandline parser to make use of its API.
- R8Command.Builder cmd = R8Command.parse(args, origin);
- CompilationMode mode = cmd.getMode();
- Path temp = cmd.getOutputPath();
- int minApiLevel = cmd.getMinApiLevel();
- // The Builder API does not provide access to the concrete paths
- // (everything is put into providers) so manually parse them here.
- List<Path> libraries = new ArrayList<>(1);
- List<Path> mainDexList = new ArrayList<>(1);
- List<Path> mainDexRules = new ArrayList<>(1);
- List<Path> pgConf = new ArrayList<>(1);
- List<Path> inputs = new ArrayList<>(args.length);
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("--lib")) {
- libraries.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-list")) {
- mainDexList.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-rules")) {
- mainDexRules.add(Paths.get(args[++i]));
- } else if (args[i].equals("--pg-conf")) {
- pgConf.add(Paths.get(args[++i]));
- } else if (isArchive(args[i]) || isClassFile(args[i])) {
- inputs.add(Paths.get(args[i]));
- }
- }
- if (!Files.exists(temp) || !Files.isDirectory(temp)) {
- throw new RuntimeException("Must supply a temp/output directory");
- }
- if (inputs.isEmpty()) {
- throw new RuntimeException("Must supply program inputs");
- }
- if (libraries.isEmpty()) {
- throw new RuntimeException("Must supply library inputs");
- }
- if (mainDexList.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-list inputs");
- }
- if (mainDexRules.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-rules inputs");
- }
- if (pgConf.isEmpty()) {
- throw new RuntimeException("Must supply pg-conf inputs");
- }
-
- useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
- useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
- useProgramData(minApiLevel, libraries, inputs);
- useProgramResourceProvider(minApiLevel, libraries, inputs);
- useLibraryResourceProvider(minApiLevel, libraries, inputs);
- useMainDexListFiles(minApiLevel, libraries, inputs, mainDexList);
- useMainDexClasses(minApiLevel, libraries, inputs, mainDexList);
- useMainDexRulesFiles(minApiLevel, libraries, inputs, mainDexRules);
- useMainDexRules(minApiLevel, libraries, inputs, mainDexRules);
- useProguardConfigFiles(minApiLevel, libraries, inputs, mainDexList, pgConf);
- useProguardConfigLines(minApiLevel, libraries, inputs, mainDexList, pgConf);
- useAssertionConfig(minApiLevel, libraries, inputs);
- useVArgVariants(minApiLevel, libraries, inputs, mainDexList, mainDexRules, pgConf);
- useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
- }
-
- private static class InMemoryStringConsumer implements StringConsumer {
- public String value = null;
-
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- value = string;
- }
- }
-
- private static void useProguardConfigConsumers(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
- InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
- InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
- InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addProguardConfigurationFiles(pgConf)
- .setProguardUsageConsumer(usageConsumer)
- .setProguardSeedsConsumer(seedsConsumer)
- .setProguardConfigurationConsumer(configConsumer)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exception", e);
- }
- if (usageConsumer.value == null) {
- throw new RuntimeException("Expected usage info but had none");
- }
- if (seedsConsumer.value == null) {
- throw new RuntimeException("Expected seeds info but had none");
- }
- if (configConsumer.value == null) {
- throw new RuntimeException("Expected config info but had none");
- }
- }
-
- // Check API support for compiling Java class-files from the file system.
- private static void useProgramFileList(
- CompilationMode mode, int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMode(mode)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for compiling Java class-files from byte content.
- private static void useProgramData(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries);
- for (ClassFileContent classfile : readClassFiles(inputs)) {
- builder.addClassProgramData(classfile.data, classfile.origin);
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- // Check API support for compiling Java class-files from a program provider abstraction.
- private static void useProgramResourceProvider(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries);
- for (Path input : inputs) {
- if (isArchive(input)) {
- builder.addProgramResourceProvider(
- ArchiveProgramResourceProvider.fromArchive(
- input, ArchiveProgramResourceProvider::includeClassFileEntries));
- } else {
- builder.addProgramResourceProvider(
- new ProgramResourceProvider() {
- @Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
- }
- });
- }
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useLibraryResourceProvider(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addProgramFiles(inputs);
- for (Path library : libraries) {
- builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexListFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexClasses(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- List<String> mainDexClasses = new ArrayList<>(1);
- for (Path path : mainDexList) {
- for (String line : Files.readAllLines(path)) {
- String entry = line.trim();
- if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
- continue;
- }
- mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
- }
- }
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexClasses(mainDexClasses)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexRulesFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexRules) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexRulesFiles(mainDexRules)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexRules(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexRulesFiles) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs);
- for (Path mainDexRulesFile : mainDexRulesFiles) {
- builder.addMainDexRules(
- Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useProguardConfigFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList,
- List<Path> pgConf) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList)
- .addProguardConfigurationFiles(pgConf)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useProguardConfigLines(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList,
- List<Path> pgConf) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList);
- for (Path file : pgConf) {
- builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useAssertionConfig(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setDisableTreeShaking(true)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
- .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for all the varg variants.
- private static void useVArgVariants(
- int minApiLevel,
- List<Path> libraries,
- List<Path> inputs,
- List<Path> mainDexList,
- List<Path> mainDexRules,
- List<Path> pgConf) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries.get(0))
- .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
- .addProgramFiles(inputs.get(0))
- .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
- .addMainDexListFiles(mainDexList.get(0))
- .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
- .addMainDexRulesFiles(mainDexRules.get(0))
- .addMainDexRulesFiles(mainDexRules.stream().skip(1).toArray(Path[]::new))
- .addProguardConfigurationFiles(pgConf.get(0))
- .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Helpers for tests.
- // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
- // rely on it.
-
- private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
- List<ClassFileContent> classfiles = new ArrayList<>();
- for (Path file : files) {
- if (isArchive(file)) {
- Origin zipOrigin = new PathOrigin(file);
- ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
- ZipEntry entry;
- while (null != (entry = zip.getNextEntry())) {
- String name = entry.getName();
- if (isClassFile(name)) {
- Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
- classfiles.add(new ClassFileContent(origin, readBytes(zip)));
- }
- }
- } else if (isClassFile(file)) {
- classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
- }
- }
- return classfiles;
- }
-
- private static byte[] readBytes(InputStream stream) throws IOException {
- try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
- byte[] buffer = new byte[0xffff];
- for (int length; (length = stream.read(buffer)) != -1; ) {
- bytes.write(buffer, 0, length);
- }
- return bytes.toByteArray();
- }
- }
-
- private static boolean isClassFile(Path file) {
- return isClassFile(file.toString());
- }
-
- private static boolean isClassFile(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".class");
- }
-
- private static boolean isArchive(Path file) {
- return isArchive(file.toString());
- }
-
- private static boolean isArchive(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".zip") || file.endsWith(".jar");
- }
-
- private static class ClassFileContent {
- final Origin origin;
- final byte[] data;
-
- public ClassFileContent(Origin origin, byte[] data) {
- this.origin = origin;
- this.data = data;
- }
- }
-
- private static class EnsureOutputConsumer implements DexIndexedConsumer {
- boolean hasOutput = false;
-
- @Override
- public synchronized void accept(
- int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
- hasOutput = true;
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- if (!hasOutput) {
- handler.error(new StringDiagnostic("Expected to produce output but had none"));
- }
- }
- }
-
- private static void checkVersionApi() {
- String labelValue;
- int labelAccess;
- try {
- Field field = Version.class.getDeclaredField("LABEL");
- labelAccess = field.getModifiers();
- labelValue = (String) field.get(Version.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- if (!Modifier.isPublic(labelAccess)
- || !Modifier.isStatic(labelAccess)
- || !Modifier.isFinal(labelAccess)) {
- throw new RuntimeException("Expected public static final LABEL");
- }
- if (labelValue.isEmpty()) {
- throw new RuntimeException("Expected LABEL constant");
- }
- if (Version.LABEL.isEmpty()) {
- throw new RuntimeException("Expected LABEL constant");
- }
- if (Version.getVersionString() == null) {
- throw new RuntimeException("Expected getVersionString API");
- }
- if (Version.getMajorVersion() < -1) {
- throw new RuntimeException("Expected getMajorVersion API");
- }
- if (Version.getMinorVersion() < -1) {
- throw new RuntimeException("Expected getMinorVersion API");
- }
- if (Version.getPatchVersion() < -1) {
- throw new RuntimeException("Expected getPatchVersion API");
- }
- if (Version.getPreReleaseString() == null && false) {
- throw new RuntimeException("Expected getPreReleaseString API");
- }
- if (Version.isDevelopmentVersion() && false) {
- throw new RuntimeException("Expected isDevelopmentVersion API");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
deleted file mode 100644
index d660bed..0000000
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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 static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class D8ApiBinaryCompatibilityTests extends TestBase {
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
- }
-
- public D8ApiBinaryCompatibilityTests(TestParameters parameters) {
- parameters.assertNoneRuntime();
- }
-
- @Test
- public void testCompatibility() throws IOException {
- Path jar = ToolHelper.API_SAMPLE_JAR;
- String main = "com.android.tools.apiusagesample.D8ApiUsageSample";
- int minApiLevel = AndroidApiLevel.K.getLevel();
-
- Path lib1 =
- Paths.get(
- ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
- "desugaringwithmissingclasslib1" + JAR_EXTENSION);
- Path lib2 =
- Paths.get(
- ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
- "desugaringwithmissingclasslib2" + JAR_EXTENSION);
- Path inputDir =
- Paths.get(
- ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR, "classes", "desugaringwithmissingclasstest1");
- List<Path> input =
- ImmutableList.of(
- inputDir.resolve("ImplementMethodsWithDefault.class"), inputDir.resolve("Main.class"));
-
- Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
- FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
-
- Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
- FileUtils.writeTextFile(mainDexRules, "# empty file");
-
- // It is important to place the api usage sample jar after the current classpath because we want
- // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
- // be found.
- String classPath = System.getProperty("java.class.path") + File.pathSeparator + jar;
- List<String> command =
- ImmutableList.<String>builder()
- .addAll(
- ImmutableList.of(
- ToolHelper.getJavaExecutable(),
- "-cp",
- classPath,
- main,
- // Compiler arguments.
- "--output",
- temp.newFolder().getAbsolutePath(),
- "--min-api",
- Integer.toString(minApiLevel),
- "--main-dex-list",
- mainDexList.toString(),
- "--main-dex-rules",
- mainDexRules.toString(),
- "--lib",
- ToolHelper.getAndroidJar(AndroidApiLevel.getAndroidApiLevel(minApiLevel))
- .toString(),
- "--classpath",
- lib1.toString(),
- "--classpath",
- lib2.toString()))
- .addAll(input.stream().map(Path::toString).collect(Collectors.toList()))
- .build();
-
- ProcessBuilder builder = new ProcessBuilder(command);
- ProcessResult result = ToolHelper.runProcess(builder);
- assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
- Assert.assertEquals("", filterOutMainDexListWarnings(result.stdout));
- Assert.assertEquals("", result.stderr);
- }
-
- public static String filterOutMainDexListWarnings(String output) {
- StringBuilder builder = new StringBuilder();
- for (String line : output.split("\n")) {
- if (!line.contains("Unsupported usage of main-dex list")) {
- builder.append(line).append("\n");
- }
- }
- return builder.toString();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
deleted file mode 100644
index 67753da..0000000
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018, 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 org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class R8ApiBinaryCompatibilityTests extends TestBase {
-
- static final Path JAR = ToolHelper.API_SAMPLE_JAR;
- static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
- static final AndroidApiLevel MIN_API = AndroidApiLevel.K;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
- }
-
- public R8ApiBinaryCompatibilityTests(TestParameters parameters) {
- parameters.assertNoneRuntime();
- }
-
- @Test
- public void testCompatibility() throws IOException {
- List<Path> inputs =
- ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
-
- String keepMain = "-keep public class arithmetic.Arithmetic {\n"
- + " public static void main(java.lang.String[]);\n"
- + "}";
-
- Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
- FileUtils.writeTextFile(pgConf, keepMain);
-
- Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
- FileUtils.writeTextFile(mainDexRules, keepMain);
-
- Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
- FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
-
- List<String> command =
- ImmutableList.<String>builder()
- .addAll(
- ImmutableList.of(
- ToolHelper.getJavaExecutable(),
- "-cp",
- JAR.toString() + File.pathSeparator + System.getProperty("java.class.path"),
- MAIN,
- // Compiler arguments.
- "--output",
- temp.newFolder().toString(),
- "--min-api",
- Integer.toString(MIN_API.getLevel()),
- "--pg-conf",
- pgConf.toString(),
- "--main-dex-rules",
- mainDexRules.toString(),
- "--main-dex-list",
- mainDexList.toString(),
- "--lib",
- ToolHelper.getAndroidJar(MIN_API).toString()))
- .addAll(inputs.stream().map(Path::toString).collect(Collectors.toList()))
- .build();
-
- ProcessBuilder builder = new ProcessBuilder(command);
- ProcessResult result = ToolHelper.runProcess(builder);
- assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
- assertEquals("", D8ApiBinaryCompatibilityTests.filterOutMainDexListWarnings(result.stdout));
- assertEquals("", result.stderr);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index c50b238..5d349fc 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -20,11 +20,16 @@
import com.android.tools.r8.compilerapi.globalsyntheticsgenerator.GlobalSyntheticsGeneratorTest;
import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
import com.android.tools.r8.compilerapi.inputmap.InputMapTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexClassesTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexListTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexRulesTest;
import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
import com.android.tools.r8.compilerapi.mockdata.MockClass;
import com.android.tools.r8.compilerapi.mockdata.MockClassWithAssertion;
import com.android.tools.r8.compilerapi.mockdata.PostStartupMockClass;
import com.android.tools.r8.compilerapi.partitionmap.PartitionMapCommandTest;
+import com.android.tools.r8.compilerapi.sampleapi.D8ApiUsageSampleTest;
+import com.android.tools.r8.compilerapi.sampleapi.R8ApiUsageSampleTest;
import com.android.tools.r8.compilerapi.sourcefile.CustomSourceFileTest;
import com.android.tools.r8.compilerapi.startupprofile.StartupProfileApiTest;
import com.android.tools.r8.compilerapi.syntheticscontexts.SyntheticContextsConsumerTest;
@@ -47,6 +52,8 @@
private static final List<Class<? extends CompilerApiTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
ImmutableList.of(
ApiTestingSetUpTest.ApiTest.class,
+ D8ApiUsageSampleTest.ApiTest.class,
+ R8ApiUsageSampleTest.ApiTest.class,
CustomMapIdTest.ApiTest.class,
CustomSourceFileTest.ApiTest.class,
AssertionConfigurationTest.ApiTest.class,
@@ -66,7 +73,10 @@
PartitionMapCommandTest.ApiTest.class,
CancelCompilationCheckerTest.ApiTest.class,
GlobalSyntheticsGeneratorTest.ApiTest.class,
- InputMapTest.ApiTest.class);
+ InputMapTest.ApiTest.class,
+ MainDexListTest.ApiTest.class,
+ MainDexClassesTest.ApiTest.class,
+ MainDexRulesTest.ApiTest.class);
private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
ImmutableList.of();
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java
new file mode 100644
index 0000000..c24ab90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2024, 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.compilerapi.maindex;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class MainDexClassesTest extends CompilerApiTestRunner {
+
+ public MainDexClassesTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ static class InputClass {}
+
+ interface Runner {
+ void run(ApiTest test, String[] mainDexClasses, DiagnosticsHandler handler) throws Exception;
+ }
+
+ private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ String[] mainDexClasses = new String[] {InputClass.class.getName()};
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ runner.run(test, mainDexClasses, handler);
+ return handler;
+ }
+
+ private Path getDexInput() throws Exception {
+ return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+ }
+
+ private static Path getCfInput() {
+ return ToolHelper.getClassFileForTestClass(InputClass.class);
+ }
+
+ @Test
+ public void testD8DexInputs() throws Exception {
+ runTest((test, mainDexClasses, handler) -> test.runD8(getDexInput(), mainDexClasses, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testD8CfInputs() throws Exception {
+ runTest((test, mainDexClasses, handler) -> test.runD8(getCfInput(), mainDexClasses, handler))
+ .assertOnlyWarnings()
+ .assertAllWarningsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest((test, mainDexClasses, handler) -> test.runR8(getCfInput(), mainDexClasses, handler))
+ .assertOnlyWarnings()
+ .assertAllWarningsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(Path programInput, String[] mainDexClassesInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ D8Command.Builder builder =
+ handler == null ? D8Command.builder() : D8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexClassesInput != null) {
+ builder.addMainDexClasses(mainDexClassesInput);
+ builder.addMainDexClasses(Arrays.asList(mainDexClassesInput));
+ }
+ D8.run(builder.build());
+ }
+
+ public void runR8(Path programInput, String[] mainDexClassesInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ R8Command.Builder builder =
+ handler == null ? R8Command.builder() : R8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ builder.addMainDexClasses(mainDexClassesInput);
+ builder.addMainDexClasses(Arrays.asList(mainDexClassesInput));
+ R8.run(builder.build());
+ }
+
+ @Test
+ public void testD8() throws CompilationFailedException {
+ runD8(null, new String[0], null);
+ }
+
+ @Test
+ public void testR8() throws CompilationFailedException {
+ runR8(null, new String[0], null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java
new file mode 100644
index 0000000..7b2264d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2024, 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.compilerapi.maindex;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import java.nio.file.Path;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MainDexListTest extends CompilerApiTestRunner {
+
+ public MainDexListTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ static class InputClass {}
+
+ interface Runner {
+ void run(ApiTest test, Path mainDexList, DiagnosticsHandler handler) throws Exception;
+ }
+
+ private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ Path mainDexListInput =
+ FileUtils.writeTextFile(
+ temp.newFile().toPath(), InputClass.class.getName().replace('.', '/') + ".class");
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ runner.run(test, mainDexListInput, handler);
+ return handler;
+ }
+
+ private Path getDexInput() throws Exception {
+ return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+ }
+
+ private static Path getCfInput() {
+ return ToolHelper.getClassFileForTestClass(InputClass.class);
+ }
+
+ @Test
+ public void testD8DexInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getDexInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testD8CfInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getCfInput(), mainDexList, handler))
+ .assertOnlyWarnings()
+ .assertAllWarningsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runR8(getCfInput(), mainDexList, handler))
+ .assertOnlyWarnings()
+ .assertAllWarningsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(Path programInput, Path mainDexListInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ D8Command.Builder builder =
+ handler == null ? D8Command.builder() : D8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexListInput != null) {
+ builder.addMainDexListFiles(mainDexListInput);
+ builder.addMainDexListFiles(Collections.singletonList(mainDexListInput));
+ }
+ D8.run(builder.build());
+ }
+
+ public void runR8(Path programInput, Path mainDexListInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ R8Command.Builder builder =
+ handler == null ? R8Command.builder() : R8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexListInput != null) {
+ builder.addMainDexListFiles(mainDexListInput);
+ builder.addMainDexListFiles(Collections.singletonList(mainDexListInput));
+ }
+ R8.run(builder.build());
+ }
+
+ @Test
+ public void testD8() throws CompilationFailedException {
+ runD8(null, null, null);
+ }
+
+ @Test
+ public void testR8() throws CompilationFailedException {
+ runR8(null, null, null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java
new file mode 100644
index 0000000..5701058
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2024, 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.compilerapi.maindex;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class MainDexRulesTest extends CompilerApiTestRunner {
+
+ public MainDexRulesTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ static class InputClass {}
+
+ interface Runner {
+ void run(ApiTest test, Path mainDexList, DiagnosticsHandler handler) throws Exception;
+ }
+
+ private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ Path mainDexRulesFile = FileUtils.writeTextFile(temp.newFile().toPath(), "# empty file");
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ runner.run(test, mainDexRulesFile, handler);
+ return handler;
+ }
+
+ private Path getDexInput() throws Exception {
+ return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+ }
+
+ private static Path getCfInput() {
+ return ToolHelper.getClassFileForTestClass(InputClass.class);
+ }
+
+ @Test
+ public void testD8DexInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getDexInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testD8CfInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getCfInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runR8(getCfInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(Path programInput, Path mainDexRulesFile, DiagnosticsHandler handler)
+ throws Exception {
+ D8Command.Builder builder =
+ handler == null ? D8Command.builder() : D8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexRulesFile != null) {
+ List<String> rules = Files.readAllLines(mainDexRulesFile);
+ builder.addMainDexRules(rules, new PathOrigin(mainDexRulesFile));
+ builder.addMainDexRulesFiles(mainDexRulesFile);
+ builder.addMainDexRulesFiles(Collections.singletonList(mainDexRulesFile));
+ }
+ D8.run(builder.build());
+ }
+
+ public void runR8(Path programInput, Path mainDexRulesFile, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ R8Command.Builder builder =
+ handler == null ? R8Command.builder() : R8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexRulesFile != null) {
+ List<String> rules;
+ try {
+ rules = Files.readAllLines(mainDexRulesFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ builder.addMainDexRules(rules, new PathOrigin(mainDexRulesFile));
+ builder.addMainDexRulesFiles(mainDexRulesFile);
+ builder.addMainDexRulesFiles(Collections.singletonList(mainDexRulesFile));
+ }
+ R8.run(builder.build());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ runD8(null, null, null);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runR8(null, null, null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java
new file mode 100644
index 0000000..7a71205
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java
@@ -0,0 +1,541 @@
+// Copyright (c) 2024, 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.compilerapi.sampleapi;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * This API test is a copy over from the old API sample test set up.
+ *
+ * <p>NOTE: Don't use this test as the basis for new API tests. Instead, use one of the simpler and
+ * more feature directed tests found in the sibling packages.
+ */
+public class D8ApiUsageSampleTest extends CompilerApiTestRunner {
+
+ public D8ApiUsageSampleTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void test() throws IOException {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ test.run(temp);
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ private static final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "D8ApiUsageSample";
+ }
+ };
+
+ private static final DiagnosticsHandler handler = new DiagnosticsHandler() {};
+
+ @Test
+ public void test() throws IOException {
+ run(temp);
+ }
+
+ public void run(TemporaryFolder temp) throws IOException {
+ runFromArgs(
+ new String[] {
+ "--output",
+ temp.newFolder().getAbsolutePath(),
+ "--min-api",
+ "19",
+ "--lib",
+ getAndroidJar().toString(),
+ "--classpath",
+ getAndroidJar().toString(),
+ getPathForClass(getMockClass()).toString()
+ });
+ }
+
+ public void runFromArgs(String[] args) {
+ // Parse arguments with the commandline parser to make use of its API.
+ D8Command.Builder cmd = D8Command.parse(args, origin);
+ CompilationMode mode = cmd.getMode();
+ Path temp = cmd.getOutputPath();
+ int minApiLevel = cmd.getMinApiLevel();
+ // The Builder API does not provide access to the concrete paths
+ // (everything is put into providers) so manually parse them here.
+ List<Path> libraries = new ArrayList<>(1);
+ List<Path> classpath = new ArrayList<>(args.length);
+ List<Path> inputs = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--lib")) {
+ libraries.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--classpath")) {
+ classpath.add(Paths.get(args[++i]));
+ } else if (isArchive(args[i]) || isClassFile(args[i])) {
+ inputs.add(Paths.get(args[i]));
+ }
+ }
+ if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+ throw new RuntimeException("Must supply a temp/output directory");
+ }
+ if (inputs.isEmpty()) {
+ throw new RuntimeException("Must supply program inputs");
+ }
+ if (libraries.isEmpty()) {
+ throw new RuntimeException("Must supply library inputs");
+ }
+ useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
+ useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
+ useProgramData(minApiLevel, libraries, classpath, inputs);
+ useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
+ useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
+ useAssertionConfig(minApiLevel, libraries, classpath, inputs);
+ useVArgVariants(minApiLevel, libraries, classpath, inputs);
+ incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
+ }
+
+ // Check API support for compiling Java class-files from the file system.
+ private static void useProgramFileList(
+ CompilationMode mode,
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMode(mode)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from byte content.
+ private static void useProgramData(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath);
+ for (ClassFileContent classfile : readClassFiles(inputs)) {
+ builder.addClassProgramData(classfile.data, classfile.origin);
+ }
+ for (Path input : inputs) {
+ if (isDexFile(input)) {
+ builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
+ }
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from a program provider abstraction.
+ private static void useProgramResourceProvider(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath);
+ for (Path input : inputs) {
+ if (isArchive(input)) {
+ builder.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(
+ input, ArchiveProgramResourceProvider::includeClassFileEntries));
+ } else if (isClassFile(input)) {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+ }
+ });
+ } else if (isDexFile(input)) {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
+ }
+ });
+ }
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useLibraryAndClasspathProvider(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addProgramFiles(inputs);
+ for (Path library : libraries) {
+ builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+ }
+ for (Path path : classpath) {
+ builder.addClasspathResourceProvider(new ArchiveClassFileProvider(path));
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useAssertionConfig(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ String pkg = "com.android.tools.r8.compilerapi.sampleapi";
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopePackage(pkg).setPassthrough().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + ".D8ApiUsageSampleTest")
+ .setCompileTimeEnable()
+ .build())
+ .addAssertionsConfiguration(
+ b -> b.setScopeClass(pkg + ".D8ApiUsageSampleTest").setPassthrough().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + ".D8ApiUsageSampleTest")
+ .setCompileTimeDisable()
+ .build())
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for all the varg variants.
+ private static void useVArgVariants(
+ int minApiLevel, List<Path> libraries, List<Path> classpath, List<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries.get(0))
+ .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+ .addProgramFiles(inputs.get(0))
+ .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new));
+ if (!classpath.isEmpty()) {
+ builder
+ .addClasspathFiles(classpath.get(0))
+ .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new));
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void incrementalCompileAndMerge(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ // Compile and merge via index intermediates.
+ mergeIntermediates(
+ minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
+ // Compile and merge via per-classfile intermediates.
+ mergeIntermediates(
+ minApiLevel,
+ compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
+ }
+
+ private static Collection<byte[]> compileToIndexedIntermediates(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setIntermediate(true)
+ .setProgramConsumer(consumer)
+ .addClasspathFiles(classpath)
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .setDisableDesugaring(false)
+ .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ return consumer.bytes;
+ }
+
+ private static Collection<byte[]> compileToPerClassFileIntermediates(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(consumer)
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ return consumer.bytes;
+ }
+
+ private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .setDisableDesugaring(true);
+ for (byte[] intermediate : intermediates) {
+ builder.addDexProgramData(intermediate, Origin.unknown());
+ }
+ try {
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected merging error", e);
+ }
+ }
+
+ // Helpers for tests.
+ // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+ // rely on it.
+
+ private static List<ClassFileContent> readClassFiles(Collection<Path> files)
+ throws IOException {
+ List<ClassFileContent> classfiles = new ArrayList<>();
+ for (Path file : files) {
+ if (isArchive(file)) {
+ Origin zipOrigin = new PathOrigin(file);
+ ZipInputStream zip =
+ new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
+ ZipEntry entry;
+ while (null != (entry = zip.getNextEntry())) {
+ String name = entry.getName();
+ if (isClassFile(name)) {
+ Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
+ classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+ }
+ }
+ } else if (isClassFile(file)) {
+ classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+ }
+ }
+ return classfiles;
+ }
+
+ private static byte[] readBytes(InputStream stream) throws IOException {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[0xffff];
+ for (int length; (length = stream.read(buffer)) != -1; ) {
+ bytes.write(buffer, 0, length);
+ }
+ return bytes.toByteArray();
+ }
+ }
+
+ private static String toLowerCase(String str) {
+ return str.toLowerCase(Locale.ROOT);
+ }
+
+ private static boolean isClassFile(Path file) {
+ return isClassFile(file.toString());
+ }
+
+ private static boolean isClassFile(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".class");
+ }
+
+ private static boolean isDexFile(Path file) {
+ return isDexFile(file.toString());
+ }
+
+ private static boolean isDexFile(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".dex");
+ }
+
+ private static boolean isArchive(Path file) {
+ return isArchive(file.toString());
+ }
+
+ private static boolean isArchive(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".zip") || file.endsWith(".jar");
+ }
+
+ private static class ClassFileContent {
+ final Origin origin;
+ final byte[] data;
+
+ public ClassFileContent(Origin origin, byte[] data) {
+ this.origin = origin;
+ this.data = data;
+ }
+ }
+
+ private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
+
+ List<byte[]> bytes = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ bytes.add(data);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
+
+ private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
+
+ List<byte[]> bytes = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ bytes.add(data);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
+
+ private static class EnsureOutputConsumer implements DexIndexedConsumer {
+ boolean hasOutput = false;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!hasOutput) {
+ handler.error(new StringDiagnostic("Expected to produce output but had none"));
+ }
+ }
+ }
+
+ private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
+
+ @Override
+ public void accept(Origin dependent, Origin dependency) {}
+
+ public void finished() {}
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java
new file mode 100644
index 0000000..b3258e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java
@@ -0,0 +1,522 @@
+// Copyright (c) 2024, 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.compilerapi.sampleapi;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * This API test is a copy over from the old API sample test set up.
+ *
+ * <p>NOTE: Don't use this test as the basis for new API tests. Instead, use one of the simpler and
+ * more feature directed tests found in the sibling packages.
+ */
+public class R8ApiUsageSampleTest extends CompilerApiTestRunner {
+
+ public R8ApiUsageSampleTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void test() throws IOException {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ test.run(temp);
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ private static final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "R8ApiUsageSample";
+ }
+ };
+
+ private static final DiagnosticsHandler handler = new DiagnosticsHandler() {};
+
+ @Test
+ public void test() throws IOException {
+ run(temp);
+ }
+
+ public void run(TemporaryFolder temp) throws IOException {
+ Path pgConf = temp.getRoot().toPath().resolve("rules.conf");
+ Files.write(pgConf, getKeepMainRules(getMockClass()));
+ runFromArgs(
+ new String[] {
+ "--output",
+ temp.newFolder().getAbsolutePath(),
+ "--min-api",
+ "19",
+ "--pg-conf",
+ pgConf.toString(),
+ "--lib",
+ getAndroidJar().toString(),
+ getPathForClass(getMockClass()).toString()
+ });
+ }
+
+ public void runFromArgs(String[] args) {
+ // Check version API
+ checkVersionApi();
+ // Parse arguments with the commandline parser to make use of its API.
+ R8Command.Builder cmd = R8Command.parse(args, origin);
+ CompilationMode mode = cmd.getMode();
+ Path temp = cmd.getOutputPath();
+ int minApiLevel = cmd.getMinApiLevel();
+ // The Builder API does not provide access to the concrete paths
+ // (everything is put into providers) so manually parse them here.
+ List<Path> libraries = new ArrayList<>(1);
+ List<Path> pgConf = new ArrayList<>(1);
+ List<Path> inputs = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--lib")) {
+ libraries.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--pg-conf")) {
+ pgConf.add(Paths.get(args[++i]));
+ } else if (isArchive(args[i]) || isClassFile(args[i])) {
+ inputs.add(Paths.get(args[i]));
+ }
+ }
+ if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+ throw new RuntimeException("Must supply a temp/output directory");
+ }
+ if (inputs.isEmpty()) {
+ throw new RuntimeException("Must supply program inputs");
+ }
+ if (libraries.isEmpty()) {
+ throw new RuntimeException("Must supply library inputs");
+ }
+ if (pgConf.isEmpty()) {
+ throw new RuntimeException("Must supply pg-conf inputs");
+ }
+
+ useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
+ useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
+ useProgramData(minApiLevel, libraries, inputs);
+ useProgramResourceProvider(minApiLevel, libraries, inputs);
+ useLibraryResourceProvider(minApiLevel, libraries, inputs);
+ useProguardConfigFiles(minApiLevel, libraries, inputs, pgConf);
+ useProguardConfigLines(minApiLevel, libraries, inputs, pgConf);
+ useAssertionConfig(minApiLevel, libraries, inputs);
+ useVArgVariants(minApiLevel, libraries, inputs, pgConf);
+ useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
+ }
+
+ private static class InMemoryStringConsumer implements StringConsumer {
+
+ public String value = null;
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ value = string;
+ }
+ }
+
+ private static void useProguardConfigConsumers(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+ InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
+ InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
+ InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addProguardConfigurationFiles(pgConf)
+ .setProguardUsageConsumer(usageConsumer)
+ .setProguardSeedsConsumer(seedsConsumer)
+ .setProguardConfigurationConsumer(configConsumer)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exception", e);
+ }
+ if (usageConsumer.value == null) {
+ throw new RuntimeException("Expected usage info but had none");
+ }
+ if (seedsConsumer.value == null) {
+ throw new RuntimeException("Expected seeds info but had none");
+ }
+ if (configConsumer.value == null) {
+ throw new RuntimeException("Expected config info but had none");
+ }
+ }
+
+ // Check API support for compiling Java class-files from the file system.
+ private static void useProgramFileList(
+ CompilationMode mode,
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMode(mode)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from byte content.
+ private static void useProgramData(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries);
+ for (ClassFileContent classfile : readClassFiles(inputs)) {
+ builder.addClassProgramData(classfile.data, classfile.origin);
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from a program provider abstraction.
+ private static void useProgramResourceProvider(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries);
+ for (Path input : inputs) {
+ if (isArchive(input)) {
+ builder.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(
+ input, ArchiveProgramResourceProvider::includeClassFileEntries));
+ } else {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+ }
+ });
+ }
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useLibraryResourceProvider(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addProgramFiles(inputs);
+ for (Path library : libraries) {
+ builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useProguardConfigFiles(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addProguardConfigurationFiles(pgConf)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useProguardConfigLines(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs);
+ for (Path file : pgConf) {
+ builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useAssertionConfig(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ String pkg = "com.android.tools.r8.compilerapi.sampleapi";
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setDisableTreeShaking(true)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopePackage(pkg).setPassthrough().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + "R8ApiUsageSampleTest")
+ .setCompileTimeEnable()
+ .build())
+ .addAssertionsConfiguration(
+ b -> b.setScopeClass(pkg + "R8ApiUsageSampleTest").setPassthrough().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + "R8ApiUsageSampleTest")
+ .setCompileTimeDisable()
+ .build())
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for all the varg variants.
+ private static void useVArgVariants(
+ int minApiLevel, List<Path> libraries, List<Path> inputs, List<Path> pgConf) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries.get(0))
+ .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+ .addProgramFiles(inputs.get(0))
+ .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
+ .addProguardConfigurationFiles(pgConf.get(0))
+ .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Helpers for tests.
+ // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+ // rely on it.
+
+ private static List<ClassFileContent> readClassFiles(Collection<Path> files)
+ throws IOException {
+ List<ClassFileContent> classfiles = new ArrayList<>();
+ for (Path file : files) {
+ if (isArchive(file)) {
+ Origin zipOrigin = new PathOrigin(file);
+ ZipInputStream zip =
+ new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
+ ZipEntry entry;
+ while (null != (entry = zip.getNextEntry())) {
+ String name = entry.getName();
+ if (isClassFile(name)) {
+ Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
+ classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+ }
+ }
+ } else if (isClassFile(file)) {
+ classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+ }
+ }
+ return classfiles;
+ }
+
+ private static byte[] readBytes(InputStream stream) throws IOException {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[0xffff];
+ for (int length; (length = stream.read(buffer)) != -1; ) {
+ bytes.write(buffer, 0, length);
+ }
+ return bytes.toByteArray();
+ }
+ }
+
+ private static String toLowerCase(String str) {
+ return str.toLowerCase(Locale.ROOT);
+ }
+
+ private static boolean isClassFile(Path file) {
+ return isClassFile(file.toString());
+ }
+
+ private static boolean isClassFile(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".class");
+ }
+
+ private static boolean isArchive(Path file) {
+ return isArchive(file.toString());
+ }
+
+ private static boolean isArchive(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".zip") || file.endsWith(".jar");
+ }
+
+ private static class ClassFileContent {
+
+ final Origin origin;
+ final byte[] data;
+
+ public ClassFileContent(Origin origin, byte[] data) {
+ this.origin = origin;
+ this.data = data;
+ }
+ }
+
+ private static class EnsureOutputConsumer implements DexIndexedConsumer {
+
+ boolean hasOutput = false;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!hasOutput) {
+ handler.error(new StringDiagnostic("Expected to produce output but had none"));
+ }
+ }
+ }
+
+ private static void checkVersionApi() {
+ String labelValue;
+ int labelAccess;
+ try {
+ Field field = Version.class.getDeclaredField("LABEL");
+ labelAccess = field.getModifiers();
+ labelValue = (String) field.get(Version.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ if (!Modifier.isPublic(labelAccess)
+ || !Modifier.isStatic(labelAccess)
+ || !Modifier.isFinal(labelAccess)) {
+ throw new RuntimeException("Expected public static final LABEL");
+ }
+ if (labelValue.isEmpty()) {
+ throw new RuntimeException("Expected LABEL constant");
+ }
+ if (Version.LABEL.isEmpty()) {
+ throw new RuntimeException("Expected LABEL constant");
+ }
+ if (Version.getVersionString() == null) {
+ throw new RuntimeException("Expected getVersionString API");
+ }
+ if (Version.getMajorVersion() < -1) {
+ throw new RuntimeException("Expected getMajorVersion API");
+ }
+ if (Version.getMinorVersion() < -1) {
+ throw new RuntimeException("Expected getMinorVersion API");
+ }
+ if (Version.getPatchVersion() < -1) {
+ throw new RuntimeException("Expected getPatchVersion API");
+ }
+ if (Version.getPreReleaseString() == null && false) {
+ throw new RuntimeException("Expected getPreReleaseString API");
+ }
+ if (Version.isDevelopmentVersion() && false) {
+ throw new RuntimeException("Expected isDevelopmentVersion API");
+ }
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index d3481a6..1d1ede2 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -211,9 +211,6 @@
public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.6.jar";
public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.6.jar";
- public static final Path API_SAMPLE_JAR =
- Paths.get(getProjectRoot(), "tests", "r8_api_usage_sample.jar");
-
public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
public static final String CLASSPATH_SEPARATOR = File.pathSeparator;
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
deleted file mode 100644
index 61fd5f8..0000000
--- a/tests/r8_api_usage_sample.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 009f4e4..d5d2cf4 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-9de592304071fa794e07e9e8d955aa4ae62cdef1
\ No newline at end of file
+02211ea3af0536a2e074154e513c222f31e1514c
\ No newline at end of file