diff options
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/dexer/DexBuilder.java')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/dexer/DexBuilder.java | 255 |
1 files changed, 0 insertions, 255 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/dexer/DexBuilder.java b/src/tools/android/java/com/google/devtools/build/android/dexer/DexBuilder.java deleted file mode 100644 index 20ef957009..0000000000 --- a/src/tools/android/java/com/google/devtools/build/android/dexer/DexBuilder.java +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2016 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.dexer; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.nio.charset.StandardCharsets.ISO_8859_1; -import static java.util.concurrent.Executors.newFixedThreadPool; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.Weigher; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.devtools.build.android.Converters.ExistingPathConverter; -import com.google.devtools.build.android.Converters.PathConverter; -import com.google.devtools.build.android.dexer.Dexing.DexingKey; -import com.google.devtools.build.android.dexer.Dexing.DexingOptions; -import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest; -import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse; -import com.google.devtools.common.options.Option; -import com.google.devtools.common.options.OptionsBase; -import com.google.devtools.common.options.OptionsParser; -import com.google.devtools.common.options.OptionsParsingException; - -import com.android.dx.command.DxConsole; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; - -import javax.annotation.Nullable; - -/** - * 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>. - */ -class DexBuilder { - - private static final long ONE_MEG = 1_000_000L; - - /** - * Commandline options. - */ - public static class Options extends OptionsBase { - @Option(name = "input_jar", - defaultValue = "null", - category = "input", - converter = ExistingPathConverter.class, - abbrev = 'i', - help = "Input file to read classes and jars from.") - public Path inputJar; - - @Option(name = "output_zip", - defaultValue = "null", - category = "output", - converter = PathConverter.class, - abbrev = 'o', - help = "Output file to write.") - public Path outputZip; - - @Option(name = "max_threads", - defaultValue = "8", - category = "misc", - help = "How many threads (besides the main thread) to use at most.") - public int maxThreads; - - @Option(name = "persistent_worker", - defaultValue = "false", - category = "hidden", - help = "Run as a Bazel persistent worker.") - public boolean persistentWorker; - } - - public static void main(String[] args) throws Exception { - if (args.length == 1 && args[0].startsWith("@")) { - args = Files.readAllLines(Paths.get(args[0].substring(1)), ISO_8859_1).toArray(new String[0]); - } - - OptionsParser optionsParser = - OptionsParser.newOptionsParser(Options.class, DexingOptions.class); - optionsParser.parseAndExitUponError(args); - Options options = optionsParser.getOptions(Options.class); - if (options.persistentWorker) { - runPersistentWorker(); - } else { - buildDexArchive(options, optionsParser.getOptions(DexingOptions.class)); - } - } - - @VisibleForTesting - static void buildDexArchive(Options options, DexingOptions dexingOptions) - throws Exception { - checkArgument(options.maxThreads > 0, - "--max_threads must be strictly positive, was: %s", options.maxThreads); - try (ZipFile in = new ZipFile(options.inputJar.toFile())) { - // Heuristic: use at most 1 thread per 1000 files in the input Jar - int threads = Math.min(options.maxThreads, in.size() / 1000 + 1); - ExecutorService executor = newFixedThreadPool(threads); - try (ZipOutputStream out = createZipOutputStream(options.outputZip)) { - produceDexArchive(in, out, executor, threads <= 1, dexingOptions, null); - } finally { - executor.shutdown(); - } - } - // Use input's timestamp for output file so the output file is stable. - Files.setLastModifiedTime(options.outputZip, Files.getLastModifiedTime(options.inputJar)); - } - - /** - * Implements a persistent worker process for use with Bazel (see {@code WorkerSpawnStrategy}). - */ - private static void runPersistentWorker() throws IOException { - ExecutorService executor = newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - Cache<DexingKey, byte[]> dexCache = CacheBuilder.newBuilder() - // Use at most 200 MB for cache and leave at least 25 MB of heap space alone. For reference: - // .class & class.dex files are around 1-5 KB, so this fits ~30K-35K class-dex pairs. - .maximumWeight(Math.min(Runtime.getRuntime().maxMemory() - 25 * ONE_MEG, 200 * ONE_MEG)) - .weigher(new Weigher<DexingKey, byte[]>() { - @Override - public int weigh(DexingKey key, byte[] value) { - return key.classfileContent().length + value.length; - } - }) - .build(); - try { - while (true) { - WorkRequest request = WorkRequest.parseDelimitedFrom(System.in); - if (request == null) { - return; - } - - // Redirect dx's output so we can return it in response - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos, /*autoFlush*/ true); - DxConsole.out = DxConsole.err = ps; - // Make sure that we exit nonzero in case uncaught errors occur during processRequest. - int exitCode = 1; - try { - processRequest(executor, dexCache, request.getArgumentsList()); - exitCode = 0; // success! - } catch (Exception e) { - // Deliberate catch-all so we can capture a stack trace. - // TODO(bazel-team): Consider canceling any outstanding futures created for this request - e.printStackTrace(ps); - } catch (Error e) { - e.printStackTrace(); - e.printStackTrace(ps); // try capturing the error, may fail if out of memory - throw e; // rethrow to kill the worker - } finally { - // Try sending a response no matter what - String output; - try { - output = baos.toString(); - } catch (Throwable t) { // most likely out of memory, so log with minimal memory needs - t.printStackTrace(); - output = "check worker log for exceptions"; - } - WorkResponse.newBuilder() - .setOutput(output) - .setExitCode(exitCode) - .build() - .writeDelimitedTo(System.out); - System.out.flush(); - } - } - } finally { - executor.shutdown(); - } - } - - private static void processRequest( - ExecutorService executor, Cache<DexingKey, byte[]> dexCache, List<String> args) - throws OptionsParsingException, IOException, InterruptedException, ExecutionException { - OptionsParser optionsParser = - OptionsParser.newOptionsParser(Options.class, DexingOptions.class); - optionsParser.setAllowResidue(false); - optionsParser.parse(args); - Options options = optionsParser.getOptions(Options.class); - try (ZipFile in = new ZipFile(options.inputJar.toFile()); - ZipOutputStream out = createZipOutputStream(options.outputZip)) { - produceDexArchive( - in, - out, - executor, - /*convertOnReaderThread*/ false, - optionsParser.getOptions(DexingOptions.class), - dexCache); - } - // Use input's timestamp for output file so the output file is stable. - Files.setLastModifiedTime(options.outputZip, Files.getLastModifiedTime(options.inputJar)); - } - - private static ZipOutputStream createZipOutputStream(Path path) throws IOException { - return new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(path))); - } - - private static void produceDexArchive( - ZipFile in, - ZipOutputStream out, - ExecutorService executor, - boolean convertOnReaderThread, - DexingOptions dexingOptions, - @Nullable Cache<DexingKey, byte[]> dexCache) - throws InterruptedException, ExecutionException, IOException { - // If we only have one thread in executor, we give a "direct" executor to the stuffer, which - // will convert .class files to .dex inline on the same thread that reads the input jar. - // This is an optimization that makes sure we can start writing the output file below while - // the stuffer is still working its way through the input. - DexConversionEnqueuer enqueuer = new DexConversionEnqueuer(in, - convertOnReaderThread ? MoreExecutors.newDirectExecutorService() : executor, - new DexConverter(new Dexing(dexingOptions)), - dexCache); - Future<?> enqueuerTask = executor.submit(enqueuer); - while (true) { - // Wait for next future in the queue *and* for that future to finish. To guarantee - // deterministic output we just write out the files in the order they appear, which is - // the same order as in the input zip. - ZipEntryContent file = enqueuer.getFiles().take().get(); - if (file == null) { - // "done" marker indicating no more files coming. - // Make sure enqueuer terminates normally (any wait should be minimal). This in - // particular surfaces any exceptions thrown in the enqueuer. - enqueuerTask.get(); - break; - } - out.putNextEntry(file.getEntry()); - out.write(file.getContent()); - out.closeEntry(); - } - } - - private DexBuilder() { - } -} |