| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.compatdexbuilder; |
| |
| import com.android.tools.r8.ByteDataView; |
| import com.android.tools.r8.CompatDxHelper; |
| 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.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.google.common.io.ByteStreams; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.StandardCharsets; |
| 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.Future; |
| import java.util.zip.CRC32; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| import java.util.zip.ZipOutputStream; |
| |
| public class CompatDexBuilder { |
| |
| private static class DexConsumer extends DexIndexedConsumer.ForwardingConsumer { |
| |
| byte[] bytes; |
| |
| public DexConsumer() { |
| super(null); |
| } |
| |
| @Override |
| public synchronized void accept( |
| int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) { |
| super.accept(fileIndex, data, descriptors, handler); |
| assert bytes == null; |
| bytes = data.copyByteData(); |
| } |
| |
| byte[] getBytes() { |
| return bytes; |
| } |
| } |
| |
| private String input = null; |
| private String output = null; |
| private int numberOfThreads = 8; |
| private boolean noLocals = false; |
| |
| public static void main(String[] args) |
| throws IOException, InterruptedException, ExecutionException { |
| new CompatDexBuilder().run(args); |
| } |
| |
| 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; |
| 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 = ThreadUtils.getExecutorService(numberOfThreads); |
| try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(Paths.get(output)))) { |
| |
| List<ZipEntry> toDex = new ArrayList<>(); |
| |
| try (ZipFile zipFile = new ZipFile(input, StandardCharsets.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)) { |
| addEntry(entry.getName(), stream, out); |
| } |
| } else { |
| toDex.add(entry); |
| } |
| } |
| |
| List<Future<DexConsumer>> futures = new ArrayList<>(toDex.size()); |
| for (int i = 0; i < toDex.size(); i++) { |
| ZipEntry classEntry = toDex.get(i); |
| 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(); |
| addEntry(entry.getName() + ".dex", consumer.getBytes(), 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(); |
| CompatDxHelper.ignoreDexInArchive(builder); |
| builder |
| .setProgramConsumer(consumer) |
| .setMode(noLocals ? CompilationMode.RELEASE : CompilationMode.DEBUG) |
| .setMinApiLevel(AndroidApiLevel.H_MR2.getLevel()) |
| .setDisableDesugaring(true); |
| 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; |
| } |
| |
| private static void addEntry(String name, InputStream stream, ZipOutputStream out) |
| throws IOException { |
| addEntry(name, ByteStreams.toByteArray(stream), out); |
| } |
| |
| private static void addEntry(String name, byte[] bytes, ZipOutputStream out) throws IOException { |
| ZipEntry zipEntry = new ZipEntry(name); |
| CRC32 crc32 = new CRC32(); |
| crc32.update(bytes); |
| zipEntry.setSize(bytes.length); |
| zipEntry.setMethod(ZipEntry.STORED); |
| zipEntry.setCrc(crc32.getValue()); |
| zipEntry.setTime(0); |
| out.putNextEntry(zipEntry); |
| out.write(bytes); |
| out.closeEntry(); |
| } |
| } |