Move R8 bazel support classes from R8 into bazel
RELNOTES: None.
PiperOrigin-RevId: 302887671
diff --git a/src/test/java/com/google/devtools/build/android/BUILD b/src/test/java/com/google/devtools/build/android/BUILD
index ecec8be..f703692 100644
--- a/src/test/java/com/google/devtools/build/android/BUILD
+++ b/src/test/java/com/google/devtools/build/android/BUILD
@@ -7,6 +7,7 @@
"//src/test/java/com/google/devtools/build/android/desugar:srcs",
"//src/test/java/com/google/devtools/build/android/dexer:srcs",
"//src/test/java/com/google/devtools/build/android/junctions:srcs",
+ "//src/test/java/com/google/devtools/build/android/r8:srcs",
"//src/test/java/com/google/devtools/build/android/resources:srcs",
"//src/test/java/com/google/devtools/build/android/testing/manifestmerge:srcs",
"//src/test/java/com/google/devtools/build/android/xml:srcs",
diff --git a/src/test/java/com/google/devtools/build/android/r8/AllTests.java b/src/test/java/com/google/devtools/build/android/r8/AllTests.java
new file mode 100644
index 0000000..b433326
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/AllTests.java
@@ -0,0 +1,21 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import com.google.devtools.build.lib.testutil.ClasspathSuite;
+import org.junit.runner.RunWith;
+
+/** Test suite for dexer tests. */
+@RunWith(ClasspathSuite.class)
+public class AllTests {}
diff --git a/src/test/java/com/google/devtools/build/android/r8/BUILD b/src/test/java/com/google/devtools/build/android/r8/BUILD
new file mode 100644
index 0000000..df1a879
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/BUILD
@@ -0,0 +1,109 @@
+load("@rules_java//java:defs.bzl", "java_library", "java_test")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(["**"]),
+ visibility = ["//src/test/java/com/google/devtools/build/android:__pkg__"],
+)
+
+# Description:
+# Tests for the bazel D8 bridge code.
+java_library(
+ name = "tests",
+ srcs = [
+ "AllTests.java",
+ ] + select({
+ "//external:has_androidsdk": glob(
+ ["*Test.java"],
+ exclude = [
+ "NoAndroidSdkStubTest.java",
+ "AllTests.java",
+ ],
+ ),
+ "//conditions:default": ["NoAndroidSdkStubTest.java"],
+ }),
+ deps = [
+ "//src/test/java/com/google/devtools/build/lib/testutil:TestSuite",
+ "//src/tools/android/java/com/google/devtools/build/android/r8",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "@bazel_tools//tools/java/runfiles",
+ ] + select({
+ "//external:has_androidsdk": [
+ "//external:android/dx_jar_import",
+ "//external:android/d8_jar_import",
+ ],
+ "//conditions:default": [],
+ }),
+)
+
+java_test(
+ name = "AllTests",
+ size = "small",
+ data = [
+ ":arithmetic",
+ ":barray",
+ ":dexmergersample",
+ ":naming001",
+ ":twosimpleclasses",
+ ],
+ jvm_flags = [
+ "-DCompatDexBuilderTests.twosimpleclasses=$(location :twosimpleclasses)",
+ "-DCompatDexBuilderTests.naming001=$(location :naming001)",
+ "-DCompatDxTests.arithmetic=$(location :arithmetic)",
+ "-DCompatDxTests.barray=$(location :barray)",
+ "-DDexFileMergerTest.dexmergersample=$(location :dexmergersample)",
+ ],
+ runtime_deps = [
+ ":tests",
+ ],
+)
+
+java_library(
+ name = "twosimpleclasses",
+ srcs = glob(["testdata/twosimpleclasses/*.java"]),
+)
+
+java_library(
+ name = "naming001",
+ srcs = glob(["testdata/naming001/*.java"]),
+)
+
+java_library(
+ name = "arithmetic",
+ srcs = glob(["testdata/arithmetic/*.java"]),
+)
+
+java_library(
+ name = "barray",
+ srcs = glob(["testdata/barray/*.java"]),
+)
+
+java_library(
+ name = "dexmergersample",
+ srcs = glob(["testdata/dexmergersample/*.java"]),
+)
+
+test_suite(
+ name = "windows_tests",
+ tags = [
+ "-no_windows",
+ "-slow",
+ ],
+ visibility = ["//visibility:private"],
+)
+
+test_suite(
+ name = "all_windows_tests",
+ tests = [
+ ":windows_tests",
+ ],
+ visibility = ["//src/test/java/com/google/devtools/build/android:__pkg__"],
+)
diff --git a/src/test/java/com/google/devtools/build/android/r8/CompatDexBuilderTest.java b/src/test/java/com/google/devtools/build/android/r8/CompatDexBuilderTest.java
new file mode 100644
index 0000000..24e3655
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/CompatDexBuilderTest.java
@@ -0,0 +1,106 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipFile;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for CompatDexBuilder. */
+@RunWith(JUnit4.class)
+public class CompatDexBuilderTest {
+ @Rule public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void compileManyClasses() throws IOException, InterruptedException, ExecutionException {
+ // Random set of classes from the R8 example test directory naming001.
+ final String inputJar = System.getProperty("CompatDexBuilderTests.naming001");
+ final List<String> classNames =
+ ImmutableList.of(
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "Reflect2$A",
+ "Reflect2$B",
+ "Reflect2",
+ "Reflect");
+
+ // Run CompatDexBuilder on naming001.jar
+ Path outputZip = temp.getRoot().toPath().resolve("out.zip");
+ CompatDexBuilder.main(
+ new String[] {"--input_jar", inputJar, "--output_zip", outputZip.toString()});
+ assertThat(Files.exists(outputZip)).isTrue();
+
+ // Verify if all the classes have their corresponding ".class.dex" files in the zip.
+ Set<String> expectedNames = new HashSet<>();
+ for (String className : classNames) {
+ expectedNames.add(
+ "com/google/devtools/build/android/r8/testdata/naming001/" + className + ".class.dex");
+ }
+ try (ZipFile zipFile = new ZipFile(outputZip.toFile(), UTF_8)) {
+ zipFile.stream()
+ .forEach(
+ ze -> {
+ expectedNames.remove(ze.getName());
+ });
+ }
+ assertThat(expectedNames).isEmpty();
+ }
+
+ @Test
+ public void compileTwoClassesAndRun() throws Exception {
+ // Run CompatDexBuilder on dexMergeSample.jar
+ final String inputJar = System.getProperty("CompatDexBuilderTests.twosimpleclasses");
+ Path outputZip = temp.getRoot().toPath().resolve("out.zip");
+ CompatDexBuilder.main(
+ new String[] {"--input_jar", inputJar, "--output_zip", outputZip.toString()});
+
+ // Merge zip content into a single dex file.
+ Path d8OutDir = temp.newFolder().toPath();
+ D8.run(
+ D8Command.builder()
+ .setOutput(d8OutDir, OutputMode.DexIndexed)
+ .addProgramFiles(outputZip)
+ .build());
+
+ // TODO(sgjesse): Validate by running methods of Class1 and Class2.
+ // https://r8.googlesource.com/r8/+/5ee92486c896b918efb62e69bff5dfa79f30e7c2/src/test/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderTests.java#95
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/CompatDxTest.java b/src/test/java/com/google/devtools/build/android/r8/CompatDxTest.java
new file mode 100644
index 0000000..45ffc5c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/CompatDxTest.java
@@ -0,0 +1,233 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for CompatDx. */
+@RunWith(JUnit4.class)
+public class CompatDxTest {
+ private static final String EXAMPLE_JAR_FILE_1 = System.getProperty("CompatDxTests.arithmetic");
+ private static final String EXAMPLE_JAR_FILE_2 = System.getProperty("CompatDxTests.barray");
+
+ private static final String NO_LOCALS = "--no-locals";
+ private static final String NO_POSITIONS = "--positions=none";
+ private static final String MULTIDEX = "--multi-dex";
+ private static final String NUM_THREADS_5 = "--num-threads=5";
+
+ @Rule public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void noFilesTest() throws IOException {
+ runDexer("--no-files");
+ }
+
+ @Test
+ public void noOutputTest() throws IOException {
+ runDexerWithoutOutput(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void singleJarInputFile() throws IOException {
+ runDexer(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void multipleJarInputFiles() throws IOException {
+ runDexer(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1, EXAMPLE_JAR_FILE_2);
+ }
+
+ @Test
+ public void outputZipFile() throws IOException {
+ runDexerWithOutput("foo.dex.zip", NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void useMultipleThreads() throws IOException {
+ runDexer(NUM_THREADS_5, NO_POSITIONS, NO_LOCALS, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void withPositions() throws IOException {
+ runDexer(NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void withLocals() throws IOException {
+ runDexer(NO_POSITIONS, MULTIDEX, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void withoutMultidex() throws IOException {
+ runDexer(NO_POSITIONS, NO_LOCALS, EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void writeToNamedDexFile() throws IOException {
+ runDexerWithOutput("named-output.dex", EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void keepClassesSingleDexTest() throws IOException {
+ runDexerWithOutput("out.zip", "--keep-classes", EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void keepClassesMultiDexTest() throws IOException {
+ runDexerWithOutput("out.zip", "--keep-classes", "--multi-dex", EXAMPLE_JAR_FILE_1);
+ }
+
+ @Test
+ public void ignoreDexInArchiveTest() throws IOException {
+ // Create a JAR with both a .class and a .dex file (the .dex file is just empty).
+ Path jarWithClassesAndDex = temp.newFile("test.jar").toPath();
+ Files.copy(
+ Paths.get(EXAMPLE_JAR_FILE_1), jarWithClassesAndDex, StandardCopyOption.REPLACE_EXISTING);
+ jarWithClassesAndDex.toFile().setWritable(true);
+ URI uri = URI.create("jar:" + jarWithClassesAndDex.toUri());
+ try (FileSystem fileSystem =
+ FileSystems.newFileSystem(uri, ImmutableMap.of("create", "true"))) {
+ Path dexFile = fileSystem.getPath("classes.dex");
+ Files.newOutputStream(dexFile, StandardOpenOption.CREATE).close();
+ }
+
+ // Only test this with CompatDx, as dx does not like the empty .dex file.
+ List<String> d8Args =
+ ImmutableList.of("--output=" + temp.newFolder("out"), jarWithClassesAndDex.toString());
+ CompatDx.main(d8Args.toArray(new String[0]));
+ }
+
+ private void runDexer(String... args) throws IOException {
+ runDexerWithOutput("", args);
+ }
+
+ private void runDexerWithoutOutput(String... args) throws IOException {
+ runDexerWithOutput(null, args);
+ }
+
+ private Path getOutputD8() {
+ return temp.getRoot().toPath().resolve("d8-out");
+ }
+
+ private Path getOutputDX() {
+ return temp.getRoot().toPath().resolve("dx-out");
+ }
+
+ private void runDexerWithOutput(String out, String... args) throws IOException {
+ Path d8Out = null;
+ Path dxOut = null;
+ if (out != null) {
+ Path baseD8 = getOutputD8();
+ Path baseDX = getOutputDX();
+ Files.createDirectory(baseD8);
+ Files.createDirectory(baseDX);
+ d8Out = baseD8.resolve(out);
+ dxOut = baseDX.resolve(out);
+ assertThat(dxOut.toString()).isNotEqualTo(d8Out.toString());
+ }
+
+ List<String> d8Args = new ArrayList<>();
+ d8Args.add("--dex");
+ if (d8Out != null) {
+ d8Args.add("--output=" + d8Out);
+ }
+ Collections.addAll(d8Args, args);
+ System.out.println("running: d8 " + Joiner.on(" ").join(d8Args));
+ CompatDx.main(d8Args.toArray(new String[0]));
+
+ List<String> dxArgs = new ArrayList<>();
+ dxArgs.add("--dex");
+ if (dxOut != null) {
+ dxArgs.add("--output=" + dxOut);
+ }
+ Collections.addAll(dxArgs, args);
+ System.out.println("running: dx " + Joiner.on(" ").join(dxArgs));
+ com.android.dx.command.Main.main(dxArgs.toArray(new String[0]));
+
+ if (out == null) {
+ // Can't check output if explicitly not writing any.
+ return;
+ }
+
+ List<Path> d8Files;
+ try (Stream<Path> d8FilesStream =
+ Files.list(Files.isDirectory(d8Out) ? d8Out : d8Out.getParent())) {
+ d8Files = d8FilesStream.sorted().collect(toList());
+ }
+ List<Path> dxFiles;
+ try (Stream<Path> dxFilesStream =
+ Files.list(Files.isDirectory(dxOut) ? dxOut : dxOut.getParent())) {
+ dxFiles = dxFilesStream.sorted().collect(toList());
+ }
+ assertWithMessage("Out file names differ")
+ .that(
+ Joiner.on(System.lineSeparator())
+ .join(d8Files.stream().map(Path::getFileName).iterator()))
+ .isEqualTo(
+ Joiner.on(System.lineSeparator())
+ .join(dxFiles.stream().map(Path::getFileName).iterator()));
+ for (int i = 0; i < d8Files.size(); i++) {
+ if (FileUtils.isArchive(d8Files.get(i))) {
+ compareArchiveFiles(d8Files.get(i), dxFiles.get(i));
+ }
+ }
+ }
+
+ private static void compareArchiveFiles(Path d8File, Path dxFile) throws IOException {
+ ZipFile d8Zip = new ZipFile(d8File.toFile(), UTF_8);
+ ZipFile dxZip = new ZipFile(dxFile.toFile(), UTF_8);
+ // TODO(zerny): This should test resource containment too once supported.
+ Set<String> d8Content = d8Zip.stream().map(ZipEntry::getName).collect(toSet());
+ Set<String> dxContent =
+ dxZip.stream()
+ .map(ZipEntry::getName)
+ .filter(
+ name ->
+ name.endsWith(FileUtils.DEX_EXTENSION)
+ || name.endsWith(FileUtils.CLASS_EXTENSION))
+ .collect(toSet());
+ assertWithMessage("Expected dx and d8 output to contain same DEX anf class file entries")
+ .that(d8Content)
+ .containsExactlyElementsIn(dxContent);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/DexFileMergerTest.java b/src/test/java/com/google/devtools/build/android/r8/DexFileMergerTest.java
new file mode 100644
index 0000000..bd220bd
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/DexFileMergerTest.java
@@ -0,0 +1,177 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexFileMergerHelper;
+import com.android.tools.r8.ExtractMarker;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.errors.CompilationError;
+import com.google.common.io.ByteStreams;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+// import com.android.tools.r8.maindexlist.MainDexListTests;
+
+/** Test for DexFileMerger. */
+@RunWith(JUnit4.class)
+public class DexFileMergerTest {
+
+ @Rule public TemporaryFolder temp = new TemporaryFolder();
+
+ private static final String CLASSES_JAR = System.getProperty("DexFileMergerTest.dexmergersample");
+ private String class1Class;
+ private String class2Class;
+
+ @Before
+ public void setUp() throws Exception {
+ File jarUnzipFolder = temp.newFolder();
+ unzip(Paths.get(CLASSES_JAR).toString(), jarUnzipFolder, entry -> true);
+ class1Class =
+ jarUnzipFolder
+ + "/com/google/devtools/build/android/r8/testdata/dexmergersample/Class1.class";
+ class2Class =
+ jarUnzipFolder
+ + "/com/google/devtools/build/android/r8/testdata/dexmergersample/Class2.class";
+ }
+
+ private Path compileTwoClasses(OutputMode outputMode, boolean addMarker)
+ throws CompilationFailedException, IOException {
+ // Compile Class1 and Class2.
+ Path output = temp.newFolder().toPath().resolve("compiled.zip");
+ D8Command command =
+ D8Command.builder()
+ .setOutput(output, outputMode)
+ .addProgramFiles(Paths.get(class2Class))
+ .addProgramFiles(Paths.get(class1Class))
+ .build();
+
+ DexFileMergerHelper.runD8ForTesting(command, !addMarker);
+
+ return output;
+ }
+
+ private Path mergeWithDexMerger(Path input) throws Exception {
+ Path output = temp.newFolder().toPath().resolve("merged-with-dexmerger.zip");
+ DexFileMerger.main(new String[] {"--input", input.toString(), "--output", output.toString()});
+ return output;
+ }
+
+ private Path mergeWithD8(Path input) throws Exception {
+ Path output = temp.newFolder().toPath().resolve("merged-with-d8.zip");
+ D8.main(new String[] {input.toString(), "--output", output.toString()});
+ return output;
+ }
+
+ @Test
+ public void markerPreserved() throws Exception {
+ Path input = compileTwoClasses(OutputMode.DexIndexed, true);
+ assertThat(ExtractMarker.extractMarkerFromDexFile(input)).hasSize(1);
+ assertThat(ExtractMarker.extractMarkerFromDexFile(mergeWithDexMerger(input))).hasSize(1);
+ assertThat(ExtractMarker.extractMarkerFromDexFile(mergeWithD8(input))).hasSize(1);
+ }
+
+ @Test
+ public void markerNotAdded() throws Exception {
+ Path input = compileTwoClasses(OutputMode.DexIndexed, false);
+ assertThat(ExtractMarker.extractMarkerFromDexFile(input)).isEmpty();
+ assertThat(ExtractMarker.extractMarkerFromDexFile(mergeWithDexMerger(input))).isEmpty();
+ assertThat(ExtractMarker.extractMarkerFromDexFile(mergeWithD8(input))).isEmpty();
+ }
+
+ @Test
+ public void mergeTwoFiles() throws CompilationFailedException, IOException {
+ Path mergerInputZip = compileTwoClasses(OutputMode.DexFilePerClassFile, false);
+
+ Path mergerOutputZip = temp.getRoot().toPath().resolve("merger-out.zip");
+ DexFileMerger.main(
+ new String[] {
+ "--input", mergerInputZip.toString(), "--output", mergerOutputZip.toString()
+ });
+
+ // TODO(sgjesse): Validate by running methods of Class1 and Class2.
+ // https://r8.googlesource.com/r8/+/5ee92486c896b918efb62e69bff5dfa79f30e7c2/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java#103
+ }
+
+ // TODO(sgjesse): Port tests for merge overflow.
+ // https://r8.googlesource.com/r8/+/5ee92486c896b918efb62e69bff5dfa79f30e7c2/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java#131
+
+ // Copied from R8 class com.android.tools.r8.utils.ZipUtils.
+ private interface OnEntryHandler {
+ void onEntry(ZipEntry entry, InputStream input) throws IOException;
+ }
+
+ // Copied from R8 class com.android.tools.r8.utils.ZipUtils.
+ private static void iter(String zipFileStr, OnEntryHandler handler) throws IOException {
+ try (ZipFile zipFile = new ZipFile(zipFileStr, UTF_8)) {
+ zipFile.stream()
+ .forEach(
+ entry -> {
+ try (InputStream entryStream = zipFile.getInputStream(entry)) {
+ handler.onEntry(entry, entryStream);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ });
+ }
+ }
+
+ // Copied from R8 class com.android.tools.r8.utils.ZipUtils.
+ private static List<File> unzip(String zipFile, File outDirectory, Predicate<ZipEntry> filter)
+ throws IOException {
+ final Path outDirectoryPath = outDirectory.toPath();
+ final List<File> outFiles = new ArrayList<>();
+ iter(
+ zipFile,
+ (entry, input) -> {
+ String name = entry.getName();
+ if (!entry.isDirectory() && filter.test(entry)) {
+ if (name.contains("..")) {
+ // Protect against malicious archives.
+ throw new CompilationError("Invalid entry name \"" + name + "\"");
+ }
+ Path outPath = outDirectoryPath.resolve(name);
+ File outFile = outPath.toFile();
+ outFile.getParentFile().mkdirs();
+ System.out.println(outFile);
+ try (OutputStream output = new FileOutputStream(outFile)) {
+ ByteStreams.copy(input, output);
+ }
+ outFiles.add(outFile);
+ }
+ });
+ return outFiles;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/NoAndroidSdkStubTest.java b/src/test/java/com/google/devtools/build/android/r8/NoAndroidSdkStubTest.java
new file mode 100644
index 0000000..80f033e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/NoAndroidSdkStubTest.java
@@ -0,0 +1,29 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Stub test to run if {@code android_sdk_repository} is not in the {@code WORKSPACE} file. */
+@RunWith(JUnit4.class)
+public class NoAndroidSdkStubTest {
+ @Test
+ public void printWarningMessageTest() {
+ System.out.println(
+ "Android tests are being skipped because no android_sdk_repository rule is set up in the "
+ + "WORKSPACE file.");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/arithmetic/Arithmetic.java b/src/test/java/com/google/devtools/build/android/r8/testdata/arithmetic/Arithmetic.java
new file mode 100644
index 0000000..64df6ba
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/arithmetic/Arithmetic.java
@@ -0,0 +1,175 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.arithmetic;
+
+import java.util.Arrays;
+
+/** Test class */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class Arithmetic {
+ static void addInts(int[] ints) {
+ int result = 0;
+ for (int i : ints) {
+ result += i;
+ }
+ System.out.println("addInts: " + Arrays.toString(ints) + " = " + result);
+ }
+
+ static void addDoubles(double[] doubles) {
+ double result = 0;
+ for (double d : doubles) {
+ result += d;
+ }
+ System.out.println("addDoubles: " + Arrays.toString(doubles) + " = " + result);
+ }
+
+ static void addLongs(long[] longs) {
+ long result = 0;
+ for (long l : longs) {
+ result += l;
+ }
+ System.out.println("addLongs: " + Arrays.toString(longs) + " = " + result);
+ }
+
+ @SuppressWarnings("IdentityBinaryExpression")
+ static void binaryOps() {
+ int i = 0;
+ System.out.println("i values:");
+ i = i + 1;
+ System.out.println(i);
+ i = 1 + i;
+ System.out.println(i);
+ i = i * 4;
+ System.out.println(i);
+ i = i * i;
+ System.out.println(i);
+ i = 4 * i;
+ System.out.println(i);
+ i = i / 4;
+ System.out.println(i);
+ i = i / i;
+ System.out.println(i);
+ i = i % i;
+ System.out.println(i);
+
+ long l = 0;
+ System.out.println("l values:");
+ l = l + 1;
+ System.out.println(l);
+ l = 1 + l;
+ System.out.println(l);
+ l = l * 4;
+ System.out.println(l);
+ l = l * l;
+ System.out.println(l);
+ l = 4 * l;
+ System.out.println(l);
+ l = l / 4;
+ System.out.println(l);
+ l = l / l;
+ System.out.println(l);
+ l = l % l;
+ System.out.println(l);
+
+ double d = 0.0;
+ System.out.println("d values: ");
+ d = d + 1.0;
+ System.out.println(d);
+ d = 1.0 + d;
+ System.out.println(d);
+ d = d * 4.0;
+ System.out.println(d);
+ d = d * d;
+ System.out.println(d);
+ d = 4.0 * d;
+ System.out.println(d);
+ d = d / 4.0;
+ System.out.println(d);
+ d = d / d;
+ System.out.println(d);
+ d = d % d;
+ System.out.println(d);
+
+ float f = 0.0f;
+ System.out.println("f values: ");
+ f = f + 1.0f;
+ System.out.println(f);
+ f = 1.0f + f;
+ System.out.println(f);
+ f = f * 4.0f;
+ System.out.println(f);
+ f = f * f;
+ System.out.println(f);
+ f = 4.0f * f;
+ System.out.println(f);
+ f = f / 4.0f;
+ System.out.println(f);
+ f = f / f;
+ System.out.println(f);
+ f = f % f;
+ System.out.println(f);
+ }
+
+ public static void moreOps() {
+ int a = 42;
+ int b = -a;
+ int shiftLeftA = a << 5;
+ int shiftRightA = a >> 5;
+ int uShiftRightA = -a >>> 5;
+ System.out.println(a + b + shiftLeftA + shiftRightA + uShiftRightA);
+ float c = 42.42f;
+ float d = -c;
+ System.out.println(c + d);
+ double e = 43.43;
+ double f = -e;
+ System.out.println(e + f);
+ long g = 5000000000L;
+ long h = -g;
+ long shiftLeftG = g << 8;
+ long shiftRightG = g >> 8;
+ long uShiftRightG = -g >>> 8;
+ System.out.println(g + h + shiftLeftG + shiftRightG + uShiftRightG);
+ }
+
+ public static void bitwiseInts(int x, int y) {
+ System.out.println(x & y);
+ System.out.println(x | y);
+ System.out.println(x ^ y);
+ System.out.println(~x);
+ }
+
+ public static void bitwiseLongs(long x, long y) {
+ System.out.println(x & y);
+ System.out.println(x | y);
+ System.out.println(x ^ y);
+ System.out.println(~x);
+ }
+
+ public static void main(String[] args) {
+ addInts(new int[] {});
+ addInts(new int[] {1});
+ addInts(new int[] {0, 1, 2, 3});
+ addDoubles(new double[] {0.0});
+ addDoubles(new double[] {0.0, 1.0, 2.0});
+ addDoubles(new double[] {0.0, 1.0, 2.0, 3.0});
+ long l = 0x0000000100000000L;
+ addLongs(new long[] {});
+ addLongs(new long[] {l});
+ addLongs(new long[] {l, l + 1, l + 2});
+ binaryOps();
+ moreOps();
+ bitwiseInts(12345, 54321);
+ bitwiseLongs(54321, 12345);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/barray/BArray.java b/src/test/java/com/google/devtools/build/android/r8/testdata/barray/BArray.java
new file mode 100644
index 0000000..f912e28
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/barray/BArray.java
@@ -0,0 +1,76 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.barray;
+
+/** Test class */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class BArray {
+
+ public static void main(String[] args) {
+ System.out.println("null boolean: " + readNullBooleanArray());
+ System.out.println("null byte: " + readNullByteArray());
+ System.out.println("boolean: " + readBooleanArray(writeBooleanArray(args)));
+ System.out.println("byte: " + readByteArray(writeByteArray(args)));
+ }
+
+ public static boolean readNullBooleanArray() {
+ boolean[] boolArray = null;
+ try {
+ return boolArray[0] || boolArray[1];
+ } catch (Throwable e) {
+ return true;
+ }
+ }
+
+ public static byte readNullByteArray() {
+ byte[] byteArray = null;
+ try {
+ return byteArray[0];
+ } catch (Throwable e) {
+ return 42;
+ }
+ }
+
+ public static boolean[] writeBooleanArray(String[] args) {
+ boolean[] array = new boolean[args.length];
+ for (int i = 0; i < args.length; i++) {
+ array[i] = args[i].length() == 42;
+ }
+ return array;
+ }
+
+ public static byte[] writeByteArray(String[] args) {
+ byte[] array = new byte[args.length];
+ for (int i = 0; i < args.length; i++) {
+ array[i] = (byte) args[i].length();
+ }
+ return array;
+ }
+
+ public static boolean readBooleanArray(boolean[] boolArray) {
+ try {
+ return boolArray[0] || boolArray[1];
+ } catch (Throwable e) {
+ return true;
+ }
+ }
+
+ public static byte readByteArray(byte[] byteArray) {
+ try {
+ return byteArray[0];
+ } catch (Throwable e) {
+ return 42;
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/dexmergersample/Class1.java b/src/test/java/com/google/devtools/build/android/r8/testdata/dexmergersample/Class1.java
new file mode 100644
index 0000000..9f7f3d3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/dexmergersample/Class1.java
@@ -0,0 +1,22 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.dexmergersample;
+
+/** Test class */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class Class1 {
+ public static void main(String[] args) {
+ System.out.println("Class1");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/dexmergersample/Class2.java b/src/test/java/com/google/devtools/build/android/r8/testdata/dexmergersample/Class2.java
new file mode 100644
index 0000000..61f15e3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/dexmergersample/Class2.java
@@ -0,0 +1,22 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.dexmergersample;
+
+/** Test class */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class Class2 {
+ public static void main(String[] args) {
+ System.out.println("Class2");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/A.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/A.java
new file mode 100644
index 0000000..757b8a9
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/A.java
@@ -0,0 +1,37 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class A {
+
+ A() {}
+
+ A(int i) {}
+
+ static {
+ C.m();
+ }
+
+ void m() {}
+
+ @SuppressWarnings("unused")
+ private void privateFunc() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/B.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/B.java
new file mode 100644
index 0000000..311d35f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/B.java
@@ -0,0 +1,23 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class B {}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/C.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/C.java
new file mode 100644
index 0000000..416465e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/C.java
@@ -0,0 +1,27 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class C {
+ static void f() {}
+
+ static void m() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/D.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/D.java
new file mode 100644
index 0000000..6d1abe5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/D.java
@@ -0,0 +1,37 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class D {
+ public void keep() {
+ System.out.println();
+ }
+
+ public static void main(String[] args) {
+ D d = new E();
+ d.keep();
+ }
+
+ public static void main2(String[] args) {
+ D d = new D();
+ d.keep();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/E.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/E.java
new file mode 100644
index 0000000..67431de
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/E.java
@@ -0,0 +1,26 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class E extends D {
+ @Override
+ public void keep() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/F.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/F.java
new file mode 100644
index 0000000..efb87f9
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/F.java
@@ -0,0 +1,26 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class F extends E {
+ @Override
+ public void keep() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/G.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/G.java
new file mode 100644
index 0000000..cf5956c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/G.java
@@ -0,0 +1,31 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class G implements H {
+ @Override
+ public void m() {}
+
+ public static void main(String[] args) {
+ H i = new G();
+ i.m();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/H.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/H.java
new file mode 100644
index 0000000..dff350b
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/H.java
@@ -0,0 +1,23 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public interface H extends I {}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/I.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/I.java
new file mode 100644
index 0000000..065ed70
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/I.java
@@ -0,0 +1,25 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public interface I {
+ void m();
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/J.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/J.java
new file mode 100644
index 0000000..232bc6d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/J.java
@@ -0,0 +1,47 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class J {
+ int[][][] m5(int a, boolean b, long c) {
+ return null;
+ }
+
+ int[][][] m3() {
+ return null;
+ }
+
+ int[][] m2() {
+ return null;
+ }
+
+ int[] m1() {
+ return null;
+ }
+
+ int m0() {
+ return 0;
+ }
+
+ int[][][] m() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/K.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/K.java
new file mode 100644
index 0000000..e8e4a3e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/K.java
@@ -0,0 +1,45 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings({"PrivateConstructorForUtilityClass", "unused"})
+public class K {
+ private int i;
+ private int h;
+ private final int i2 = 7;
+ private static int j;
+ private static final int I3 = 7;
+
+ private static final Object o = "TAG";
+ private static final String TAG = "TAG";
+ private final String tag2 = "TAG";
+
+ static {
+ j = 6;
+ }
+
+ {
+ i = 6;
+ }
+
+ void keep() {
+ h = 7;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/L.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/L.java
new file mode 100644
index 0000000..d959e02
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/L.java
@@ -0,0 +1,29 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class L {
+ private static final String TAG = "TAG";
+
+ void onReceive() {
+ System.out.println(TAG);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/Reflect.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/Reflect.java
new file mode 100644
index 0000000..141c58c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/Reflect.java
@@ -0,0 +1,68 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings({"PrivateConstructorForUtilityClass", "ClassCanBeStatic"})
+public class Reflect {
+ void keep() throws ClassNotFoundException {
+ Class.forName("naming001.Reflect2");
+ Class.forName("ClassThatDoesNotExists");
+ }
+
+ void keep2() throws NoSuchFieldException, SecurityException {
+ Reflect2.class.getField("fieldPublic");
+ Reflect2.class.getField("fieldPrivate");
+ }
+
+ void keep3() throws NoSuchFieldException, SecurityException {
+ Reflect2.class.getDeclaredField("fieldPublic");
+ Reflect2.class.getDeclaredField("fieldPrivate");
+ }
+
+ void keep4() throws SecurityException, NoSuchMethodException {
+ Reflect2.class.getMethod("m", new Class<?>[] {Reflect2.A.class});
+ Reflect2.class.getMethod("m", new Class<?>[] {Reflect2.B.class});
+ Reflect2.class.getMethod("methodThatDoesNotExist", new Class<?>[] {Reflect2.A.class});
+ }
+
+ void keep5() throws SecurityException, NoSuchMethodException {
+ Reflect2.class.getDeclaredMethod("m", new Class<?>[] {Reflect2.A.class});
+ Reflect2.class.getDeclaredMethod("m", new Class<?>[] {Reflect2.B.class});
+ }
+
+ void keep6() throws SecurityException {
+ AtomicIntegerFieldUpdater.newUpdater(Reflect2.class, "fieldPublic");
+ }
+
+ void keep7() throws SecurityException {
+ AtomicLongFieldUpdater.newUpdater(Reflect2.class, "fieldLong");
+ AtomicLongFieldUpdater.newUpdater(Reflect2.class, "fieldLong2");
+ }
+
+ void keep8() throws SecurityException {
+ AtomicReferenceFieldUpdater.newUpdater(Reflect2.class, Reflect2.A.class, "a");
+ AtomicReferenceFieldUpdater.newUpdater(Reflect2.class, Reflect2.A.class, "b");
+ AtomicReferenceFieldUpdater.newUpdater(Reflect2.class, Object.class, "c");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/Reflect2.java b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/Reflect2.java
new file mode 100644
index 0000000..4537ab4
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/naming001/Reflect2.java
@@ -0,0 +1,51 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.naming001;
+
+/**
+ * Test class TODO(sgjesse): Update this class to be more targeted to testing CompatDexBuilder. The
+ * content of this class is pretty random. It was just the class that happened to be used for
+ * testing in the R8 repo. The class was actually there for testing some R8 renaming, and
+ * CompatDexBuilderTest just happened to piggy-bag on this class.
+ */
+@SuppressWarnings({"PrivateConstructorForUtilityClass", "ClassCanBeStatic", "unused"})
+public class Reflect2 {
+ public volatile int fieldPublic;
+
+ private volatile int fieldPrivate;
+
+ public volatile long fieldLong;
+
+ private volatile long fieldLong2;
+
+ volatile long fieldLong3;
+
+ protected volatile long fieldLong4;
+
+ public volatile A a;
+
+ public volatile B b;
+
+ private volatile Object c;
+
+ private void calledMethod() {}
+
+ public void m(A a) {}
+
+ private void privateMethod(B b) {}
+
+ class A {}
+
+ class B {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/twosimpleclasses/Class1.java b/src/test/java/com/google/devtools/build/android/r8/testdata/twosimpleclasses/Class1.java
new file mode 100644
index 0000000..7976ba7
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/twosimpleclasses/Class1.java
@@ -0,0 +1,22 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.twosimpleclasses;
+
+/** Test class */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class Class1 {
+ public static void main(String[] args) {
+ System.out.println("Class1");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/testdata/twosimpleclasses/Class2.java b/src/test/java/com/google/devtools/build/android/r8/testdata/twosimpleclasses/Class2.java
new file mode 100644
index 0000000..a7e8100
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/testdata/twosimpleclasses/Class2.java
@@ -0,0 +1,22 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.testdata.twosimpleclasses;
+
+/** Test class */
+@SuppressWarnings("PrivateConstructorForUtilityClass")
+public class Class2 {
+ public static void main(String[] args) {
+ System.out.println("Class2");
+ }
+}
diff --git a/src/tools/android/java/com/android/tools/r8/BUILD b/src/tools/android/java/com/android/tools/r8/BUILD
new file mode 100644
index 0000000..ceb1f4f
--- /dev/null
+++ b/src/tools/android/java/com/android/tools/r8/BUILD
@@ -0,0 +1,35 @@
+# Description:
+# Gateway classes to access package private parts of classes in com.android.tools.r8.
+
+load("@rules_java//java:defs.bzl", "java_library")
+
+licenses(["notice"])
+
+package(default_visibility = ["//javatests/com/google/devtools/build/android:__subpackages__"])
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = [
+ "//src/java/com/google/devtools/build/android/r8:__pkg__",
+ "//src/tools/android/java/com/google/devtools/build/android:__pkg__",
+ ],
+)
+
+java_library(
+ name = "r8_support",
+ srcs = select({
+ "//external:has_androidsdk": glob(
+ ["*.java"],
+ ),
+ "//conditions:default": [],
+ }),
+ visibility = [
+ "//src/test/java/com/google/devtools/build/android/r8:__pkg__",
+ "//src/tools/android/java/com/google/devtools/build/android/r8:__pkg__",
+ ],
+ deps = select({
+ "//external:has_androidsdk": ["//external:android/d8_jar_import"],
+ "//conditions:default": [],
+ }),
+)
diff --git a/src/tools/android/java/com/android/tools/r8/CompatDxSupport.java b/src/tools/android/java/com/android/tools/r8/CompatDxSupport.java
new file mode 100644
index 0000000..31141b3
--- /dev/null
+++ b/src/tools/android/java/com/android/tools/r8/CompatDxSupport.java
@@ -0,0 +1,72 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Class for accessing package private members of R8, which is used in the Bazel integration, but
+ * are not in the public API.
+ */
+public class CompatDxSupport {
+ public static void run(D8Command command, boolean minimalMainDex)
+ throws CompilationFailedException {
+ AndroidApp app = command.getInputApp();
+ InternalOptions options = command.getInternalOptions();
+ // DX allows --multi-dex without specifying a main dex list for legacy devices.
+ // That is broken, but for CompatDX we do the same to not break existing builds
+ // that are trying to transition.
+ try {
+ // Use reflection for:
+ // <code>options.enableMainDexListCheck = false;</code>
+ // as bazel might link to an old r8.jar which does not have this field.
+ Field enableMainDexListCheck = options.getClass().getField("enableMainDexListCheck");
+ try {
+ enableMainDexListCheck.setBoolean(options, false);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ } catch (NoSuchFieldException e) {
+ // Ignore if bazel is linking to an old r8.jar.
+ }
+
+ // DX has a minimal main dex flag. In compat mode only do minimal main dex
+ // if the flag is actually set.
+ options.minimalMainDex = minimalMainDex;
+
+ D8.runForTesting(app, options);
+ }
+
+ public static void enableDesugarBackportStatics(D8Command.Builder builder) {
+ // Use reflection for:
+ // <code>builder.enableDesugarBackportStatics();</code>
+ // as bazel might link to an old r8.jar which does not have this field.
+ try {
+ Method enableDesugarBackportStatics =
+ builder.getClass().getMethod("enableDesugarBackportStatics");
+ try {
+ enableDesugarBackportStatics.invoke(builder);
+ } catch (ReflectiveOperationException e) {
+ throw new AssertionError(e);
+ }
+ } catch (NoSuchMethodException e) {
+ // Ignore if bazel is linking to an old r8.jar.
+ }
+ }
+
+ private CompatDxSupport() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD
index 4cccae8..56861bc 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD
@@ -15,6 +15,7 @@
"//src/tools/android/java/com/google/devtools/build/android/incrementaldeployment:embedded_tools",
"//src/tools/android/java/com/google/devtools/build/android/junctions:embedded_tools",
"//src/tools/android/java/com/google/devtools/build/android/proto:srcs",
+ "//src/tools/android/java/com/google/devtools/build/android/r8:embedded_tools",
"//src/tools/android/java/com/google/devtools/build/android/ziputils:embedded_tools",
],
)
@@ -28,6 +29,7 @@
"//src/tools/android/java/com/google/devtools/build/android/desugar/scan",
"//src/tools/android/java/com/google/devtools/build/android/dexer:dexerdeps",
"//src/tools/android/java/com/google/devtools/build/android/idlclass:idlclass_lib",
+ "//src/tools/android/java/com/google/devtools/build/android/r8:dexerdeps",
"//src/tools/android/java/com/google/devtools/build/android/ziputils:ziputils_lib",
],
)
@@ -101,12 +103,14 @@
filegroup(
name = "srcs",
srcs = glob(["**"]) + [
+ "//src/tools/android/java/com/android/tools/r8:srcs",
"//src/tools/android/java/com/google/devtools/build/android/desugar:srcs",
"//src/tools/android/java/com/google/devtools/build/android/dexer:srcs",
"//src/tools/android/java/com/google/devtools/build/android/idlclass:srcs",
"//src/tools/android/java/com/google/devtools/build/android/incrementaldeployment:srcs",
"//src/tools/android/java/com/google/devtools/build/android/junctions:srcs",
"//src/tools/android/java/com/google/devtools/build/android/proto:srcs",
+ "//src/tools/android/java/com/google/devtools/build/android/r8:srcs",
"//src/tools/android/java/com/google/devtools/build/android/resources:srcs",
"//src/tools/android/java/com/google/devtools/build/android/ziputils:srcs",
],
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/BUILD b/src/tools/android/java/com/google/devtools/build/android/r8/BUILD
new file mode 100644
index 0000000..8335a71
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/BUILD
@@ -0,0 +1,56 @@
+# Description:
+# Collection of dex utilities used in the bazel android actions.
+
+load("@rules_java//java:defs.bzl", "java_library")
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = [
+ "//src/test/java/com/google/devtools/build/android/r8:__pkg__",
+ "//src/tools/android/java/com/google/devtools/build/android:__pkg__",
+ ],
+)
+
+filegroup(
+ name = "embedded_tools",
+ srcs = glob(
+ ["*.java"],
+ exclude = ["NoAndroidSdkStub.java"],
+ ) + ["BUILD.tools"],
+ visibility = ["//visibility:public"],
+)
+
+java_library(
+ name = "r8",
+ srcs = select({
+ "//external:has_androidsdk": glob(
+ ["*.java"],
+ exclude = ["NoAndroidSdkStub.java"],
+ ),
+ "//conditions:default": ["NoAndroidSdkStub.java"],
+ }),
+ visibility = ["//src/test/java/com/google/devtools/build/android/r8:__pkg__"],
+ deps = [
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/tools/android/java/com/android/tools/r8:r8_support",
+ "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
+ "//third_party:guava",
+ ] + select({
+ "//external:has_androidsdk": ["//external:android/d8_jar_import"],
+ "//conditions:default": [],
+ }),
+)
+
+# The DexFileMerger and DexBuilder are built in BUILD.tools which is built in
+# a developers workspace, not the Bazel workspace. So we must bundle the
+# dependencies of those binaries into the embedded tools.
+java_library(
+ name = "dexerdeps",
+ visibility = [
+ "//src/tools/android/java/com/google/devtools/build/android:__pkg__",
+ ],
+ runtime_deps = [
+ # None right now. The D8/R8 jar has no dependencies.
+ ],
+)
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/BUILD.tools b/src/tools/android/java/com/google/devtools/build/android/r8/BUILD.tools
new file mode 100644
index 0000000..766ae70
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/BUILD.tools
@@ -0,0 +1,23 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+ name = "r8",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//external:android/d8_jar_import",
+ ],
+)
+
+java_binary(
+ name = "DexBuilder",
+ main_class = "com.google.devtools.build.android.r8.DexBuilder",
+ visibility = ["//tools/android:__subpackages__"],
+ runtime_deps = [":r8"],
+)
+
+java_binary(
+ name = "DexFileMerger",
+ main_class = "com.google.devtools.build.android.r8.DexFileMerger",
+ visibility = ["//tools/android:__subpackages__"],
+ runtime_deps = [":r8"],
+)
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/CompatDexBuilder.java b/src/tools/android/java/com/google/devtools/build/android/r8/CompatDexBuilder.java
new file mode 100644
index 0000000..172e193
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/CompatDexBuilder.java
@@ -0,0 +1,202 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import static com.google.common.base.Verify.verify;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.CompatDxSupport;
+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.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Tool used by Bazel that converts a Jar file of .class files into a .zip file of .dex files, one
+ * per .class file, which we call a <i>dex archive</i>.
+ *
+ * <p>D8 version of DexBuilder.
+ */
+public class CompatDexBuilder {
+
+ private static class DexConsumer implements DexIndexedConsumer {
+
+ byte[] bytes;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ verify(bytes == null, "Should not have been populated until now");
+ bytes = data.copyByteData();
+ }
+
+ byte[] getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Do nothing.
+ }
+ }
+
+ private String input;
+ private String output;
+ private int numberOfThreads = 8;
+ private boolean noLocals;
+ private boolean backportStatics;
+
+ public static void main(String[] args)
+ throws IOException, InterruptedException, ExecutionException {
+ new CompatDexBuilder().run(args);
+ }
+
+ @SuppressWarnings("JdkObsolete")
+ private void run(String[] args) throws IOException, InterruptedException, ExecutionException {
+ List<String> flags = new ArrayList<>();
+
+ for (String arg : args) {
+ if (arg.startsWith("@")) {
+ flags.addAll(Files.readAllLines(Paths.get(arg.substring(1))));
+ } else {
+ flags.add(arg);
+ }
+ }
+
+ for (int i = 0; i < flags.size(); i++) {
+ String flag = flags.get(i);
+ if (flag.startsWith("--positions=")) {
+ String positionsValue = flag.substring("--positions=".length());
+ if (positionsValue.startsWith("throwing") || positionsValue.startsWith("important")) {
+ noLocals = true;
+ }
+ continue;
+ }
+ if (flag.startsWith("--num-threads=")) {
+ numberOfThreads = Integer.parseInt(flag.substring("--num-threads=".length()));
+ continue;
+ }
+ switch (flag) {
+ case "--input_jar":
+ input = flags.get(++i);
+ break;
+ case "--output_zip":
+ output = flags.get(++i);
+ break;
+ case "--verify-dex-file":
+ case "--no-verify-dex-file":
+ case "--show_flags":
+ case "--no-optimize":
+ case "--nooptimize":
+ case "--help":
+ // Ignore
+ break;
+ case "--nolocals":
+ noLocals = true;
+ break;
+ case "--desugar-backport-statics":
+ backportStatics = true;
+ break;
+ default:
+ System.err.println("Unsupported option: " + flag);
+ System.exit(1);
+ }
+ }
+
+ if (input == null) {
+ System.err.println("No input jar specified");
+ System.exit(1);
+ }
+
+ if (output == null) {
+ System.err.println("No output jar specified");
+ System.exit(1);
+ }
+
+ ExecutorService executor = Executors.newWorkStealingPool(numberOfThreads);
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(Paths.get(output)))) {
+
+ List<ZipEntry> toDex = new ArrayList<>();
+
+ try (ZipFile zipFile = new ZipFile(input, UTF_8)) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!entry.getName().endsWith(".class")) {
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ ZipUtils.addEntry(entry.getName(), stream, out);
+ }
+ } else {
+ toDex.add(entry);
+ }
+ }
+
+ List<Future<DexConsumer>> futures = new ArrayList<>(toDex.size());
+ for (ZipEntry classEntry : toDex) {
+ futures.add(executor.submit(() -> dexEntry(zipFile, classEntry, executor)));
+ }
+ for (int i = 0; i < futures.size(); i++) {
+ ZipEntry entry = toDex.get(i);
+ DexConsumer consumer = futures.get(i).get();
+ ZipUtils.addEntry(entry.getName() + ".dex", consumer.getBytes(), ZipEntry.STORED, out);
+ }
+ }
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ private DexConsumer dexEntry(ZipFile zipFile, ZipEntry classEntry, ExecutorService executor)
+ throws IOException, CompilationFailedException {
+ DexConsumer consumer = new DexConsumer();
+ D8Command.Builder builder = D8Command.builder();
+ builder
+ .setProgramConsumer(consumer)
+ .setMode(noLocals ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+ .setMinApiLevel(13) // H_MR2.
+ .setDisableDesugaring(true);
+ if (backportStatics) {
+ CompatDxSupport.enableDesugarBackportStatics(builder);
+ }
+ try (InputStream stream = zipFile.getInputStream(classEntry)) {
+ builder.addClassProgramData(
+ ByteStreams.toByteArray(stream),
+ new ArchiveEntryOrigin(
+ classEntry.getName(), new PathOrigin(Paths.get(zipFile.getName()))));
+ }
+ D8.run(builder.build(), executor);
+ return consumer;
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/CompatDx.java b/src/tools/android/java/com/google/devtools/build/android/r8/CompatDx.java
new file mode 100644
index 0000000..39e4bb0
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/CompatDx.java
@@ -0,0 +1,881 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import static com.google.common.base.Verify.verify;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.CompatDxSupport;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.android.r8.CompatDx.DxCompatOptions.DxUsageMessage;
+import com.google.devtools.build.android.r8.CompatDx.DxCompatOptions.PositionInfo;
+import com.google.devtools.common.options.Converters.StringConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Dx compatibility interface for d8.
+ *
+ * <p>This should become a mostly drop-in replacement for uses of the DX dexer (eg, dx --dex ...).
+ */
+public class CompatDx {
+
+ private static final String USAGE_HEADER = "Usage: compatdx [options] <input files>";
+
+ /** Commandline options. */
+ public static class Options extends OptionsBase {
+ @Option(
+ name = "dex",
+ defaultValue = "true",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Generate dex output.")
+ public boolean dex;
+
+ @Option(
+ name = "debug",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Print debug information.")
+ public boolean debug;
+
+ @Option(
+ name = "verbose",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Print verbose information.")
+ public boolean verbose;
+
+ @Option(
+ name = "positions",
+ defaultValue = "lines",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "What source-position information to keep. One of: none, lines, important.")
+ public String positions;
+
+ @Option(
+ name = "no-locals",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Don't keep local variable information.")
+ public boolean noLocals;
+
+ @Option(
+ name = "statistics",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Print statistics information.")
+ public boolean statistics;
+
+ @Option(
+ name = "no-optimize",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Don't optimize.")
+ public boolean noOptimize;
+
+ @Option(
+ name = "optimize-list",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "File listing methods to optimize.")
+ public String optimizeList;
+
+ @Option(
+ name = "no-optimize-list",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "File listing methods not to optimize.")
+ public String noOptimizeList;
+
+ @Option(
+ name = "no-strict",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Disable strict file/class name checks.")
+ public boolean noStrict;
+
+ @Option(
+ name = "keep-classes",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Keep input class files in in output jar.")
+ public boolean keepClasses;
+
+ @Option(
+ name = "output",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "Output file or directory.")
+ public String output;
+
+ @Option(
+ name = "dump-to",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "File to dump information to.")
+ public String dumpTo;
+
+ @Option(
+ name = "dump-width",
+ defaultValue = "8",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Max width for columns in dump output.")
+ public int dumpWidth;
+
+ @Option(
+ name = "dump-method",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "Method to dump information for.")
+ public String methodToDump;
+
+ @Option(
+ name = "dump",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Dump information.")
+ public boolean dump;
+
+ @Option(
+ name = "verbose-dump",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Dump verbose information.")
+ public boolean verboseDump;
+
+ @Option(
+ name = "no-files",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Don't fail if given no files.")
+ public boolean noFiles;
+
+ @Option(
+ name = "core-library",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Construct a core library.")
+ public boolean coreLibrary;
+
+ @Option(
+ name = "num-threads",
+ defaultValue = "1",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Number of threads to run with.")
+ public int numThreads;
+
+ @Option(
+ name = "incremental",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Merge result with the output if it exists.")
+ public boolean incremental;
+
+ @Option(
+ name = "force-jumbo",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Force use of string-jumbo instructions.")
+ public boolean forceJumbo;
+
+ @Option(
+ name = "no-warning",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Suppress warnings.")
+ public boolean noWarning;
+
+ @Option(
+ name = "set-max-idx-number",
+ defaultValue = "0",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Undocumented: Set maximal index number to use in a dex file.")
+ public int maxIndexNumber;
+
+ @Option(
+ name = "minimal-main-dex",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Produce smallest possible main dex.")
+ public boolean minimalMainDex;
+
+ @Option(
+ name = "main-dex-list",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ converter = StringConverter.class,
+ help = "File listing classes that must be in the main dex file.")
+ public String mainDexList;
+
+ @Option(
+ name = "multi-dex",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Allow generation of multi-dex.")
+ public boolean multiDex;
+
+ @Option(
+ name = "min-sdk-version",
+ defaultValue = "1",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Minimum Android API level compatibility.")
+ public int minApiLevel;
+
+ @Option(
+ name = "desugar-backport-statics",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Backport additional Java 8 APIs.")
+ public boolean backportStatics;
+
+ @Option(
+ name = "input-list",
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = StringConverter.class,
+ help = "File listing input files.")
+ public String inputList;
+
+ @Option(
+ name = "version",
+ defaultValue = "false", // dx's default
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ allowMultiple = false,
+ help = "Print the version of this tool.")
+ public boolean version;
+ }
+
+ /** Compatibility options parsing for the DX --dex sub-command. */
+ public static class DxCompatOptions {
+
+ // Final values after parsing.
+ // Note: These are ordered by their occurrence in "dx --help"
+ public final boolean help;
+ public final boolean version;
+ public final boolean debug;
+ public final boolean verbose;
+ public final PositionInfo positions;
+ public final boolean noLocals;
+ public final boolean noOptimize;
+ public final boolean statistics;
+ public final String optimizeList;
+ public final String noOptimizeList;
+ public final boolean noStrict;
+ public final boolean keepClasses;
+ public final String output;
+ public final String dumpTo;
+ public final int dumpWidth;
+ public final String methodToDump;
+ public final boolean verboseDump;
+ public final boolean dump;
+ public final boolean noFiles;
+ public final boolean coreLibrary;
+ public final int numThreads;
+ public final boolean incremental;
+ public final boolean forceJumbo;
+ public final boolean noWarning;
+ public final boolean multiDex;
+ public final String mainDexList;
+ public final boolean minimalMainDex;
+ public final int minApiLevel;
+ public final boolean backportStatics;
+ public final String inputList;
+ public final List<String> inputs;
+ // Undocumented option
+ public final int maxIndexNumber;
+
+ /**
+ * Values for dx --positions flag. Corresponding to "none", "important", "lines", "throwing".
+ */
+ public enum PositionInfo {
+ NONE,
+ IMPORTANT,
+ LINES,
+ THROWING
+ }
+
+ /** Exception thrown on invalid dx compat usage. */
+ public static class DxUsageMessage extends Exception {
+
+ public final String message;
+
+ DxUsageMessage(String message) {
+ this.message = message;
+ }
+
+ void printHelpOn(PrintStream sink) throws IOException {
+ sink.println(message);
+ }
+ }
+
+ private DxCompatOptions(Options options, List<String> remaining) {
+ help = false;
+ version = options.version;
+ debug = options.debug;
+ verbose = options.debug;
+ switch (options.positions) {
+ case "none":
+ positions = PositionInfo.NONE;
+ break;
+ case "lines":
+ positions = PositionInfo.LINES;
+ break;
+ case "throwing":
+ positions = PositionInfo.THROWING;
+ break;
+ case "important":
+ positions = PositionInfo.IMPORTANT;
+ break;
+ default:
+ throw new AssertionError("Unreachable");
+ }
+ noLocals = options.noLocals;
+ noOptimize = options.noOptimize;
+ statistics = options.statistics;
+ optimizeList = options.optimizeList;
+ noOptimizeList = options.noOptimizeList;
+ noStrict = options.noStrict;
+ keepClasses = options.keepClasses;
+ output = options.output;
+ dumpTo = options.dumpTo;
+ dumpWidth = options.dumpWidth;
+ methodToDump = options.methodToDump;
+ dump = options.dump;
+ verboseDump = options.verboseDump;
+ noFiles = options.noFiles;
+ coreLibrary = options.coreLibrary;
+ numThreads = options.numThreads;
+ incremental = options.incremental;
+ forceJumbo = options.forceJumbo;
+ noWarning = options.noWarning;
+ multiDex = options.multiDex;
+ mainDexList = options.mainDexList;
+ minimalMainDex = options.minimalMainDex;
+ minApiLevel = options.minApiLevel;
+ backportStatics = options.backportStatics;
+ inputList = options.inputList;
+ inputs = remaining;
+ maxIndexNumber = options.maxIndexNumber;
+ }
+
+ public static DxCompatOptions parse(String[] args) {
+ OptionsParser optionsParser = OptionsParser.builder().optionsClasses(Options.class).build();
+ optionsParser.parseAndExitUponError(args);
+ Options options = optionsParser.getOptions(Options.class);
+ return new DxCompatOptions(options, optionsParser.getResidue());
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ try {
+ run(args);
+ } catch (DxUsageMessage e) {
+ System.err.println(USAGE_HEADER);
+ e.printHelpOn(System.err);
+ System.exit(1);
+ } catch (CompilationFailedException e) {
+ throw new AssertionError("Failure", e);
+ }
+ }
+
+ private static void run(String[] args)
+ throws DxUsageMessage, IOException, CompilationFailedException {
+ DxCompatOptions dexArgs = DxCompatOptions.parse(args);
+ if (dexArgs.version) {
+ System.out.println("CompatDx " + Version.getVersionString());
+ return;
+ }
+ CompilationMode mode = CompilationMode.RELEASE;
+ Path output = null;
+ List<Path> inputs = new ArrayList<>();
+ boolean singleDexFile = !dexArgs.multiDex;
+ Path mainDexList = null;
+ int numberOfThreads = 1;
+
+ for (String path : dexArgs.inputs) {
+ processPath(Paths.get(path), inputs);
+ }
+ if (inputs.isEmpty()) {
+ if (dexArgs.noFiles) {
+ return;
+ }
+ throw new DxUsageMessage("No input files specified");
+ }
+
+ if (dexArgs.dump && dexArgs.verbose) {
+ System.out.println("Warning: dump is not supported");
+ }
+
+ if (dexArgs.verboseDump) {
+ throw new Unimplemented("verbose dump file not yet supported");
+ }
+
+ if (dexArgs.methodToDump != null) {
+ throw new Unimplemented("method-dump not yet supported");
+ }
+
+ if (dexArgs.output != null) {
+ output = Paths.get(dexArgs.output);
+ if (FileUtils.isDexFile(output)) {
+ if (!singleDexFile) {
+ throw new DxUsageMessage("Cannot output to a single dex-file when running with multidex");
+ }
+ } else if (!FileUtils.isArchive(output)
+ && (!output.toFile().exists() || !output.toFile().isDirectory())) {
+ throw new DxUsageMessage(
+ "Unsupported output file or output directory does not exist. "
+ + "Output must be a directory or a file of type dex, apk, jar or zip.");
+ }
+ }
+
+ if (dexArgs.dumpTo != null && dexArgs.verbose) {
+ System.out.println("dump-to file not yet supported");
+ }
+
+ if (dexArgs.positions == PositionInfo.NONE && dexArgs.verbose) {
+ System.out.println("Warning: no support for positions none.");
+ }
+
+ if (dexArgs.positions == PositionInfo.LINES && !dexArgs.noLocals) {
+ mode = CompilationMode.DEBUG;
+ }
+
+ if (dexArgs.incremental) {
+ throw new Unimplemented("incremental merge not supported yet");
+ }
+
+ if (dexArgs.forceJumbo && dexArgs.verbose) {
+ System.out.println(
+ "Warning: no support for forcing jumbo-strings.\n"
+ + "Strings will only use jumbo-string indexing if necessary.\n"
+ + "Make sure that any dex merger subsequently used "
+ + "supports correct handling of jumbo-strings (eg, D8/R8 does).");
+ }
+
+ if (dexArgs.noOptimize && dexArgs.verbose) {
+ System.out.println("Warning: no support for not optimizing");
+ }
+
+ if (dexArgs.optimizeList != null) {
+ throw new Unimplemented("no support for optimize-method list");
+ }
+
+ if (dexArgs.noOptimizeList != null) {
+ throw new Unimplemented("no support for dont-optimize-method list");
+ }
+
+ if (dexArgs.statistics && dexArgs.verbose) {
+ System.out.println("Warning: no support for printing statistics");
+ }
+
+ if (dexArgs.numThreads > 1) {
+ numberOfThreads = dexArgs.numThreads;
+ }
+
+ if (dexArgs.mainDexList != null) {
+ mainDexList = Paths.get(dexArgs.mainDexList);
+ }
+
+ if (dexArgs.noStrict) {
+ if (dexArgs.verbose) {
+ System.out.println("Warning: conservative main-dex list not yet supported");
+ }
+ } else {
+ if (dexArgs.verbose) {
+ System.out.println("Warning: strict name checking not yet supported");
+ }
+ }
+
+ if (dexArgs.minimalMainDex && dexArgs.verbose) {
+ System.out.println("Warning: minimal main-dex support is not yet supported");
+ }
+
+ if (dexArgs.maxIndexNumber != 0 && dexArgs.verbose) {
+ System.out.println("Warning: internal maximum-index setting is not supported");
+ }
+
+ if (numberOfThreads < 1) {
+ throw new DxUsageMessage("Invalid numThreads value of " + numberOfThreads);
+ }
+ ExecutorService executor = Executors.newWorkStealingPool(numberOfThreads);
+
+ try {
+ D8Command.Builder builder = D8Command.builder();
+ inputs.forEach(
+ input ->
+ builder.addProgramResourceProvider(ArchiveResourceProvider.fromArchive(input, true)));
+
+ builder
+ // .addProgramFiles(inputs)
+ .setProgramConsumer(createConsumer(inputs, output, singleDexFile, dexArgs.keepClasses))
+ .setMode(mode)
+ .setDisableDesugaring(true) // DX does not desugar.
+ .setMinApiLevel(dexArgs.minApiLevel);
+ if (mainDexList != null) {
+ builder.addMainDexListFiles(mainDexList);
+ }
+ if (dexArgs.backportStatics) {
+ CompatDxSupport.enableDesugarBackportStatics(builder);
+ }
+ CompatDxSupport.run(builder.build(), dexArgs.minimalMainDex);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ private static ProgramConsumer createConsumer(
+ List<Path> inputs, Path output, boolean singleDexFile, boolean keepClasses)
+ throws DxUsageMessage {
+ if (output == null) {
+ return DexIndexedConsumer.emptyConsumer();
+ }
+ if (singleDexFile) {
+ return new SingleDexFileConsumer(
+ FileUtils.isDexFile(output)
+ ? new NamedDexFileConsumer(output)
+ : createDexConsumer(output, inputs, keepClasses));
+ }
+ return createDexConsumer(output, inputs, keepClasses);
+ }
+
+ private static DexIndexedConsumer createDexConsumer(
+ Path output, List<Path> inputs, boolean keepClasses) throws DxUsageMessage {
+ if (keepClasses) {
+ if (!FileUtils.isArchive(output)) {
+ throw new DxCompatOptions.DxUsageMessage(
+ "Output must be an archive when --keep-classes is set.");
+ }
+ return new ArchiveConsumer(output, inputs);
+ }
+ return FileUtils.isArchive(output)
+ ? new ArchiveConsumer(output)
+ : new DexIndexedConsumer.DirectoryConsumer(output);
+ }
+
+ private static class SingleDexFileConsumer extends DexIndexedConsumer.ForwardingConsumer {
+
+ private byte[] bytes = null;
+
+ public SingleDexFileConsumer(DexIndexedConsumer consumer) {
+ super(consumer);
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ if (fileIndex > 0) {
+ throw new CompilationError(
+ "Compilation result could not fit into a single dex file. "
+ + "Reduce the input-program size or run with --multi-dex enabled");
+ }
+ verify(bytes == null, "Should not have been populated until now");
+ // Store a copy of the bytes as we may not assume the backing is valid after accept returns.
+ bytes = data.copyByteData();
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (bytes != null) {
+ super.accept(0, ByteDataView.of(bytes), null, handler);
+ }
+ super.finished(handler);
+ }
+ }
+
+ private static class NamedDexFileConsumer extends DexIndexedConsumer.ForwardingConsumer {
+
+ private final Path output;
+
+ public NamedDexFileConsumer(Path output) {
+ super(null);
+ this.output = output;
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ StandardOpenOption[] options = {
+ StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING
+ };
+ try (OutputStream stream = new BufferedOutputStream(Files.newOutputStream(output, options))) {
+ stream.write(data.getBuffer(), data.getOffset(), data.getLength());
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, new PathOrigin(output)));
+ }
+ }
+ }
+
+ /**
+ * Consumer for writing the generated classes.dex files to an archive. Supports writing the input
+ * class files to the same archive as well.
+ */
+ private static class ArchiveConsumer implements DexIndexedConsumer {
+ private final Path path;
+ private final List<Path> inputs;
+ private final Origin origin;
+ private ZipOutputStream stream;
+ private int nextClassesDexIndex;
+ private final Map<Integer, ClassesDexFileData> pendingClassesDexFiles = new HashMap<>();
+
+ /** Content of a classes.dex file */
+ private static class ClassesDexFileData {
+ private final int index;
+ private final ByteDataView content;
+
+ private ClassesDexFileData(int index, ByteDataView content) {
+ this.index = index;
+ this.content = content;
+ }
+ }
+
+ ArchiveConsumer(Path path) {
+ this(path, ImmutableList.of());
+ }
+
+ ArchiveConsumer(Path path, List<Path> inputs) {
+ this.path = path;
+ this.inputs = inputs;
+ this.origin = new PathOrigin(path);
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ ensureOpenArchive(handler);
+ addIndexedClassesDexFile(fileIndex, data, handler);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ verify(pendingClassesDexFiles.isEmpty(), "All DEX files should have been written");
+ if (stream == null) {
+ return;
+ }
+ try {
+ writeInputClassesToArchive(handler);
+ stream.close();
+ stream = null;
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+
+ private synchronized void addIndexedClassesDexFile(
+ int fileIndex, ByteDataView data, DiagnosticsHandler handler) {
+ // Always add the classes.dex files in <code>fileIndex</code> order to have stable output.
+ // Store the ones which arrive out-of-order and write as soon as possible.
+ pendingClassesDexFiles.put(
+ fileIndex, new ClassesDexFileData(fileIndex, ByteDataView.of(data.copyByteData())));
+ while (pendingClassesDexFiles.containsKey(nextClassesDexIndex)) {
+ ClassesDexFileData classesDexFileData = pendingClassesDexFiles.get(nextClassesDexIndex);
+ writeClassesDexFile(classesDexFileData, handler);
+ pendingClassesDexFiles.remove(nextClassesDexIndex);
+ nextClassesDexIndex++;
+ }
+ }
+
+ /** Get or open the zip output stream. */
+ private synchronized void ensureOpenArchive(DiagnosticsHandler handler) {
+ if (stream != null) {
+ return;
+ }
+ try {
+ stream =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+
+ private void writeClassesDexFile(
+ ClassesDexFileData classesDexFileData, DiagnosticsHandler handler) {
+ try {
+ ZipUtils.writeToZipStream(
+ getDexFileName(classesDexFileData.index),
+ classesDexFileData.content,
+ ZipEntry.DEFLATED,
+ stream);
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+
+ protected String getDexFileName(int fileIndex) {
+ return "classes" + (fileIndex == 0 ? "" : (fileIndex + 1)) + FileUtils.DEX_EXTENSION;
+ }
+
+ private void writeClassFile(String name, ByteDataView content, DiagnosticsHandler handler) {
+ try {
+ ZipUtils.writeToZipStream(name, content, ZipEntry.DEFLATED, stream);
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+
+ @SuppressWarnings("JdkObsolete") // Uses Enumeration by design.
+ private void writeInputClassesToArchive(DiagnosticsHandler handler) throws IOException {
+ // For each input archive file, add all class files within.
+ for (Path input : inputs) {
+ if (FileUtils.isArchive(input)) {
+ try (ZipFile zipFile = new ZipFile(input.toFile(), UTF_8)) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (FileUtils.isClassFile(entry.getName())) {
+ try (InputStream entryStream = zipFile.getInputStream(entry)) {
+ byte[] bytes = ByteStreams.toByteArray(entryStream);
+ writeClassFile(entry.getName(), ByteDataView.of(bytes), handler);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void processPath(Path path, List<Path> files) throws IOException {
+ if (!Files.exists(path)) {
+ throw new CompilationError("File does not exist: " + path);
+ }
+ if (Files.isDirectory(path)) {
+ processDirectory(path, files);
+ return;
+ }
+ if (FileUtils.isZipFile(path) || FileUtils.isJarFile(path) || FileUtils.isClassFile(path)) {
+ files.add(path);
+ return;
+ }
+ if (FileUtils.isApkFile(path)) {
+ throw new Unimplemented("apk files not yet supported: " + path);
+ }
+ }
+
+ private static void processDirectory(Path directory, List<Path> files) throws IOException {
+ verify(Files.exists(directory), "Directory must exist");
+
+ try (Stream<Path> pathStream = Files.list(directory)) {
+ for (Path file : pathStream.collect(toList())) {
+ processPath(file, files);
+ }
+ }
+ }
+
+ private CompatDx() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/DexFileMerger.java b/src/tools/android/java/com/google/devtools/build/android/r8/DexFileMerger.java
new file mode 100644
index 0000000..d0ef675
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/DexFileMerger.java
@@ -0,0 +1,444 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import com.android.tools.r8.ByteDataView;
+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.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.android.Converters.ExistingPathConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.common.options.EnumConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Tool used by Bazel as a replacement for Android's {@code dx} tool that assembles a single or, if
+ * allowed and necessary, multiple {@code .dex} files from a given archive of {@code .dex} and
+ * {@code .class} files. The tool merges the {@code .dex} files it encounters into a single file and
+ * additionally encodes any {@code .class} files it encounters. If multidex is allowed then the tool
+ * will generate multiple files subject to the {@code .dex} file format's limits on the number of
+ * methods and fields.
+ *
+ * <p>D8 version of DexFileMerger.
+ */
+public class DexFileMerger {
+ /** File name prefix of a {@code .dex} file automatically loaded in an archive. */
+ private static final String DEX_PREFIX = "classes";
+
+ private static final String DEFAULT_OUTPUT_ARCHIVE_FILENAME = "classes.dex.jar";
+
+ private static final boolean PRINT_ARGS = false;
+
+ /** Strategies for outputting multiple {@code .dex} files supported by {@link DexFileMerger}. */
+ public enum MultidexStrategy {
+ /** Create exactly one .dex file. The operation will fail if .dex limits are exceeded. */
+ OFF,
+ /** Create exactly one <prefixN>.dex file with N taken from the (single) input archive. */
+ GIVEN_SHARD,
+ /**
+ * Assemble .dex files similar to {@link com.android.dx.command.dexer.Main dx}, with all but one
+ * file as large as possible.
+ */
+ MINIMAL,
+ /**
+ * Allow some leeway and sometimes use additional .dex files to speed up processing. This option
+ * exists to give flexibility but it often (or always) may be identical to {@link #MINIMAL}.
+ */
+ BEST_EFFORT;
+
+ public boolean isMultidexAllowed() {
+ switch (this) {
+ case OFF:
+ case GIVEN_SHARD:
+ return false;
+ case MINIMAL:
+ case BEST_EFFORT:
+ return true;
+ }
+ throw new AssertionError("Unknown: " + this);
+ }
+ }
+
+ /** Option converter for {@link MultidexStrategy}. */
+ public static class MultidexStrategyConverter extends EnumConverter<MultidexStrategy> {
+ public MultidexStrategyConverter() {
+ super(MultidexStrategy.class, "multidex strategy");
+ }
+ }
+
+ /** Commandline options. */
+ public static class Options extends OptionsBase {
+ @Option(
+ name = "input",
+ allowMultiple = true,
+ defaultValue = "",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = ExistingPathConverter.class,
+ abbrev = 'i',
+ help =
+ "Input archives with .dex files to merge. Inputs are processed in given order, so"
+ + " classes from later inputs will be added after earlier inputs. Duplicate"
+ + " classes are dropped.")
+ public List<Path> inputArchives;
+
+ @Option(
+ name = "output",
+ defaultValue = DEFAULT_OUTPUT_ARCHIVE_FILENAME,
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = PathConverter.class,
+ abbrev = 'o',
+ help = "Output archive to write.")
+ public Path outputArchive;
+
+ @Option(
+ name = "multidex",
+ defaultValue = "off",
+ category = "multidex",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = MultidexStrategyConverter.class,
+ help = "Allow more than one .dex file in the output.")
+ public MultidexStrategy multidexMode;
+
+ @Option(
+ name = "main-dex-list",
+ defaultValue = "null",
+ category = "multidex",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = ExistingPathConverter.class,
+ help = "List of classes to be placed into \"main\" classes.dex file.")
+ public Path mainDexListFile;
+
+ @Option(
+ name = "minimal-main-dex",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "If true, *only* classes listed in --main_dex_list file are placed into \"main\" "
+ + "classes.dex file.")
+ public boolean minimalMainDex;
+
+ @Option(
+ name = "verbose",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "If true, print information about the merged files and resulting files to stdout.")
+ public boolean verbose;
+
+ @Option(
+ name = "max-bytes-wasted-per-file",
+ defaultValue = "0",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Limit on conservatively allocated but unused bytes per dex file, which can enable "
+ + "faster merging.")
+ public int wasteThresholdPerDex;
+
+ // Undocumented dx option for testing multidex logic
+ @Option(
+ name = "set-max-idx-number",
+ defaultValue = "0",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Limit on fields and methods in a single dex file.")
+ public int maxNumberOfIdxPerDex;
+
+ @Option(
+ name = "forceJumbo",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Typically not needed flag intended to imitate dx's --forceJumbo.")
+ public boolean forceJumbo;
+
+ @Option(
+ name = "dex_prefix",
+ defaultValue = DEX_PREFIX,
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Dex file output prefix.")
+ public String dexPrefix;
+ }
+
+ private static Options parseArguments(String[] args) throws IOException {
+ OptionsParser optionsParser =
+ OptionsParser.builder()
+ .optionsClasses(Options.class)
+ .argsPreProcessor(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()))
+ .build();
+ optionsParser.parseAndExitUponError(args);
+
+ return optionsParser.getOptions(Options.class);
+ }
+
+ /**
+ * Implements a DexIndexedConsumer writing into a ZipStream with support for custom dex file name
+ * prefix, reindexing a single dex output file to a nonzero index and reporting if any data has
+ * been written.
+ */
+ private static class ArchiveConsumer implements DexIndexedConsumer {
+ private final Path path;
+ private final String prefix;
+ private final Integer singleFixedFileIndex;
+ private final Origin origin;
+ private ZipOutputStream stream = null;
+
+ private int highestIndexWritten = -1;
+ private final Map<Integer, Runnable> writers = new TreeMap<>();
+ private boolean hasWrittenSomething = false;
+
+ /** If singleFixedFileIndex is not null then we expect only one output dex file */
+ private ArchiveConsumer(Path path, String prefix, Integer singleFixedFileIndex) {
+ this.path = path;
+ this.prefix = prefix;
+ this.singleFixedFileIndex = singleFixedFileIndex;
+ this.origin = new PathOrigin(path);
+ }
+
+ private boolean hasWrittenSomething() {
+ return hasWrittenSomething;
+ }
+
+ private String getDexFileName(int fileIndex) {
+ if (singleFixedFileIndex != null) {
+ fileIndex = singleFixedFileIndex;
+ }
+ return prefix + (fileIndex == 0 ? "" : (fileIndex + 1)) + FileUtils.DEX_EXTENSION;
+ }
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ if (singleFixedFileIndex != null && fileIndex != 0) {
+ handler.error(new StringDiagnostic("Result does not fit into a single dex file."));
+ return;
+ }
+ // Make a copy of the actual bytes as they will possibly be accessed later by the runner.
+ final byte[] bytes = data.copyByteData();
+ writers.put(fileIndex, () -> writeEntry(fileIndex, bytes, handler));
+
+ while (writers.containsKey(highestIndexWritten + 1)) {
+ ++highestIndexWritten;
+ writers.get(highestIndexWritten).run();
+ writers.remove(highestIndexWritten);
+ }
+ }
+
+ /** Get or open the zip output stream. */
+ private synchronized ZipOutputStream getStream(DiagnosticsHandler handler) {
+ if (stream == null) {
+ try {
+ stream =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+ return stream;
+ }
+
+ private void writeEntry(int fileIndex, byte[] data, DiagnosticsHandler handler) {
+ try {
+ ZipUtils.writeToZipStream(
+ getDexFileName(fileIndex),
+ ByteDataView.of(data),
+ ZipEntry.DEFLATED,
+ getStream(handler));
+ hasWrittenSomething = true;
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!writers.isEmpty()) {
+ handler.error(
+ new StringDiagnostic(
+ "Failed to write zip, for a multidex output some of the classes.dex files were"
+ + " not produced."));
+ }
+ try {
+ if (stream != null) {
+ stream.close();
+ stream = null;
+ }
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e, origin));
+ }
+ }
+ }
+
+ private static int parseFileIndexFromShardFilename(String inputArchive) {
+ Pattern namingPattern = Pattern.compile("([0-9]+)\\..*");
+ String name = new File(inputArchive).getName();
+ Matcher matcher = namingPattern.matcher(name);
+ if (!matcher.matches()) {
+ throw new IllegalStateException(
+ String.format(
+ "Expect input named <N>.xxx.zip for --multidex=given_shard but got %s.", name));
+ }
+ int shard = Integer.parseInt(matcher.group(1));
+ if (shard <= 0) {
+ throw new IllegalStateException(
+ String.format("Expect positive N in input named <N>.xxx.zip but got %d.", shard));
+ }
+ return shard;
+ }
+
+ public static void run(String[] args) throws CompilationFailedException, IOException {
+ Options options = parseArguments(args);
+
+ if (options.inputArchives.isEmpty()) {
+ throw new IllegalStateException("Need at least one --input");
+ }
+
+ if (options.mainDexListFile != null && options.inputArchives.size() != 1) {
+ throw new IllegalStateException(
+ "--main-dex-list only supported with exactly one --input, use DexFileSplitter for more");
+ }
+
+ if (!options.multidexMode.isMultidexAllowed()) {
+ if (options.mainDexListFile != null) {
+ throw new IllegalStateException(
+ "--main-dex-list is only supported with multidex enabled, but mode is: "
+ + options.multidexMode);
+ }
+ if (options.minimalMainDex) {
+ throw new IllegalStateException(
+ "--minimal-main-dex is only supported with multidex enabled, but mode is: "
+ + options.multidexMode);
+ }
+ }
+
+ D8Command.Builder builder = D8Command.builder();
+
+ Map<String, Integer> inputOrdering =
+ Maps.newHashMapWithExpectedSize(options.inputArchives.size());
+ int sequenceNumber = 0;
+ for (Path s : options.inputArchives) {
+ builder.addProgramFiles(s);
+ inputOrdering.put(s.toString(), sequenceNumber++);
+ }
+
+ // Determine enabling multidexing and file indexing.
+ Integer singleFixedFileIndex = null;
+ switch (options.multidexMode) {
+ case OFF:
+ singleFixedFileIndex = 0;
+ break;
+ case GIVEN_SHARD:
+ if (options.inputArchives.size() != 1) {
+ throw new IllegalStateException("'--multidex=given_shard' requires exactly one --input.");
+ }
+ singleFixedFileIndex =
+ parseFileIndexFromShardFilename(options.inputArchives.get(0).toString()) - 1;
+ break;
+ case MINIMAL:
+ case BEST_EFFORT:
+ // Nothing to do.
+ break;
+ }
+
+ if (options.mainDexListFile != null) {
+ builder.addMainDexListFiles(options.mainDexListFile);
+ }
+
+ ArchiveConsumer consumer =
+ new ArchiveConsumer(options.outputArchive, options.dexPrefix, singleFixedFileIndex);
+ builder.setProgramConsumer(consumer);
+
+ // Try to run through com.android.tools.r8.DexFileMergerHelper.run. If not found, which
+ // can happen when bazel use a d8.jar from a Platform SDK, fall back to plain D8 execution.
+ try {
+ Class<?> dexFileMergerHelper = Class.forName("com.android.tools.r8.DexFileMergerHelper");
+ try {
+ Method run =
+ dexFileMergerHelper.getDeclaredMethod("run", D8Command.class, Boolean.class, Map.class);
+ // DexFileMergerHelper.run(builder.build(), options.minimalMainDex, inputOrdering);
+ run.invoke(null, builder.build(), options.minimalMainDex, inputOrdering);
+ } catch (NoSuchMethodException e) {
+ D8.run(builder.build());
+ } catch (ReflectiveOperationException e) {
+ throw new AssertionError("Unable to invoke run in DexFileMergerHelper", e);
+ }
+ } catch (ClassNotFoundException e) {
+ D8.run(builder.build());
+ }
+
+ // If input was empty we still need to write out an empty zip.
+ if (!consumer.hasWrittenSomething()) {
+ File f = options.outputArchive.toFile();
+ try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f))) {}
+ }
+ }
+
+ public static void main(String[] args) throws CompilationFailedException {
+ try {
+ if (PRINT_ARGS) {
+ printArgs(args);
+ }
+ run(args);
+ } catch (CompilationFailedException | IOException e) {
+ System.err.println("Merge failed: " + e.getMessage());
+ throw new CompilationFailedException("Merge failed: " + e.getMessage());
+ }
+ }
+
+ private static void printArgs(String[] args) {
+ System.err.print("r8.DexFileMerger");
+ for (String s : args) {
+ System.err.printf(" %s", s);
+ }
+ System.err.println();
+ }
+
+ private DexFileMerger() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java b/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java
new file mode 100644
index 0000000..e54cfe0
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java
@@ -0,0 +1,94 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import com.google.common.base.Ascii;
+import java.nio.file.Path;
+
+class FileUtils {
+ public static final String AAR_EXTENSION = ".aar";
+ public static final String APK_EXTENSION = ".apk";
+ public static final String CLASS_EXTENSION = ".class";
+ public static final String DEX_EXTENSION = ".dex";
+ public static final String JAR_EXTENSION = ".jar";
+ public static final String ZIP_EXTENSION = ".zip";
+ public static final String MODULE_INFO_CLASS = "module-info.class";
+ public static final String META_INF = "meta-inf";
+
+ private static boolean hasExtension(String name, String extension) {
+ return Ascii.toLowerCase(name).endsWith(extension);
+ }
+
+ private static boolean hasExtension(Path path, String extension) {
+ return hasExtension(path.getFileName().toString(), extension);
+ }
+
+ static boolean isDexFile(Path path) {
+ return hasExtension(path, DEX_EXTENSION);
+ }
+
+ static boolean isClassFile(String name) {
+ name = Ascii.toLowerCase(name);
+ // Android does not support Java 9 module, thus skip module-info.
+ if (name.equals(MODULE_INFO_CLASS)) {
+ return false;
+ }
+ if (name.startsWith(META_INF) || name.startsWith("/" + META_INF)) {
+ return false;
+ }
+ return name.endsWith(CLASS_EXTENSION);
+ }
+
+ static boolean isClassFile(Path path) {
+ return isClassFile(path.getFileName().toString());
+ }
+
+ static boolean isJarFile(String name) {
+ return hasExtension(name, JAR_EXTENSION);
+ }
+
+ static boolean isJarFile(Path path) {
+ return hasExtension(path, JAR_EXTENSION);
+ }
+
+ static boolean isZipFile(String name) {
+ return hasExtension(name, ZIP_EXTENSION);
+ }
+
+ static boolean isZipFile(Path path) {
+ return hasExtension(path, ZIP_EXTENSION);
+ }
+
+ static boolean isApkFile(String name) {
+ return hasExtension(name, APK_EXTENSION);
+ }
+
+ static boolean isApkFile(Path path) {
+ return hasExtension(path, APK_EXTENSION);
+ }
+
+ static boolean isAarFile(String name) {
+ return hasExtension(name, AAR_EXTENSION);
+ }
+
+ static boolean isAarFile(Path path) {
+ return hasExtension(path, AAR_EXTENSION);
+ }
+
+ static boolean isArchive(Path path) {
+ return isApkFile(path) || isJarFile(path) || isZipFile(path) || isAarFile(path);
+ }
+
+ private FileUtils() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/NoAndroidSdkStub.java b/src/tools/android/java/com/google/devtools/build/android/r8/NoAndroidSdkStub.java
new file mode 100644
index 0000000..6fc6dae
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/NoAndroidSdkStub.java
@@ -0,0 +1,15 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+class NoAndroidSdkStub {}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/ZipUtils.java b/src/tools/android/java/com/google/devtools/build/android/r8/ZipUtils.java
new file mode 100644
index 0000000..aa25e3e
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/ZipUtils.java
@@ -0,0 +1,63 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import com.android.tools.r8.ByteDataView;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+class ZipUtils {
+ static void addEntry(String name, InputStream stream, ZipOutputStream zip) throws IOException {
+ ZipUtils.addEntry(name, ByteStreams.toByteArray(stream), ZipEntry.STORED, zip);
+ }
+
+ static void addEntry(String name, byte[] bytes, int compressionMethod, ZipOutputStream zip)
+ throws IOException {
+ CRC32 crc = new CRC32();
+ crc.update(bytes);
+ ZipEntry entry = createEntry(name, bytes.length, crc.getValue(), compressionMethod);
+ zip.putNextEntry(entry);
+ zip.write(bytes);
+ zip.closeEntry();
+ }
+
+ static void writeToZipStream(
+ String name, ByteDataView content, int compressionMethod, ZipOutputStream zip)
+ throws IOException {
+ byte[] buffer = content.getBuffer();
+ int offset = content.getOffset();
+ int length = content.getLength();
+ CRC32 crc = new CRC32();
+ crc.update(buffer, offset, length);
+ ZipEntry entry = createEntry(name, length, crc.getValue(), compressionMethod);
+ zip.putNextEntry(entry);
+ zip.write(buffer, offset, length);
+ zip.closeEntry();
+ }
+
+ private static ZipEntry createEntry(String name, int length, long crc, int compressionMethod) {
+ ZipEntry entry = new ZipEntry(name);
+ entry.setMethod(compressionMethod);
+ entry.setSize(length);
+ entry.setCrc(crc);
+ entry.setTime(0);
+ return entry;
+ }
+
+ private ZipUtils() {}
+}