diff options
author | Xavier Ducrohet <xav@android.com> | 2012-12-21 16:16:05 -0800 |
---|---|---|
committer | Xavier Ducrohet <xav@android.com> | 2013-01-02 13:21:52 -0800 |
commit | 0749a476551ad096b7e2436e3f1f758c10caad83 (patch) | |
tree | 2bdf681a77e4abbd360fc5453ac1a4c304db3c29 /builder | |
parent | 724415bae3481da6afa6a561fde3833ace4f9d6c (diff) | |
download | build-0749a476551ad096b7e2436e3f1f758c10caad83.tar.gz |
Add crunch support to the ResourceManager.
This relies on a newer version of aapt that is able to
do single file crunch.
All crunching is done through a thread controlled by an executor.
Change-Id: I578f970660f152031ed1a4f4074382fcf4d7b2b8
Diffstat (limited to 'builder')
7 files changed, 240 insertions, 62 deletions
diff --git a/builder/src/main/java/com/android/builder/AaptRunner.java b/builder/src/main/java/com/android/builder/AaptRunner.java new file mode 100644 index 0000000..ccd59a6 --- /dev/null +++ b/builder/src/main/java/com/android/builder/AaptRunner.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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.builder; + +import com.android.builder.internal.CommandLineRunner; + +import java.io.File; +import java.io.IOException; + +/** + * Instances of this class are able to run aapt command. + * + * To get an instance, use {@link com.android.builder.AndroidBuilder#getAaptRunner()} + */ +public class AaptRunner { + + private final String mAaptLocation; + private final CommandLineRunner mCommandLineRunner; + + AaptRunner(String aaptLocation, CommandLineRunner commandLineRunner) { + mAaptLocation = aaptLocation; + mCommandLineRunner = commandLineRunner; + } + + /** + * Runs the aapt crunch command on a single file + * @param from the file to crunch + * @param to the output file + * @throws IOException + * @throws InterruptedException + */ + public void crunchPng(File from, File to) throws IOException, InterruptedException { + String[] command = new String[] { + mAaptLocation, + "s", + "-i", + from.getAbsolutePath(), + "-o", + to.getAbsolutePath() + }; + + mCommandLineRunner.runCmdLine(command); + } +} diff --git a/builder/src/main/java/com/android/builder/AndroidBuilder.java b/builder/src/main/java/com/android/builder/AndroidBuilder.java index 8eea262..04f7e3c 100644 --- a/builder/src/main/java/com/android/builder/AndroidBuilder.java +++ b/builder/src/main/java/com/android/builder/AndroidBuilder.java @@ -72,7 +72,7 @@ import static com.google.common.base.Preconditions.checkState; * {@link #processResources(java.io.File, java.io.File, java.io.File, java.util.List, String, String, String, String, String, com.android.builder.VariantConfiguration.Type, boolean, AaptOptions)} * {@link #compileAidl(java.util.List, java.io.File, java.util.List)} * {@link #convertByteCode(Iterable, Iterable, String, DexOptions)} - * {@link #packageApk(String, String, java.util.List, String, String, boolean, boolean, String, String, String, String, String)} + * {@link #packageApk(String, String, java.util.List, String, String, boolean, java.io.File, String, String, String, String)} * * Java compilation is not handled but the builder provides the runtime classpath with * {@link #getRuntimeClasspath()}. @@ -171,6 +171,14 @@ public class AndroidBuilder { } /** + * Returns an {@link AaptRunner} able to run aapt commands. + * @return + */ + public AaptRunner getAaptRunner() { + return new AaptRunner(mTarget.getPath(IAndroidTarget.AAPT), mCmdLineRunner); + } + + /** * Generate the BuildConfig class for the project. * @param packageName the package in which to generate the class * @param debuggable whether the app is considered debuggable @@ -497,8 +505,7 @@ public class AndroidBuilder { command.add("-f"); - //TODO: reenable when we can crunch per-file in the ResourceMerger - // command.add("--no-crunch"); + command.add("--no-crunch"); // inputs command.add("-I"); @@ -575,8 +582,6 @@ public class AndroidBuilder { command.add(symbolOutputDir); } - mLogger.info("aapt command: %s", command.toString()); - mCmdLineRunner.runCmdLine(command); // now if the project has libraries, R needs to be created for each libraries, diff --git a/builder/src/main/java/com/android/builder/internal/CommandLineRunner.java b/builder/src/main/java/com/android/builder/internal/CommandLineRunner.java index 8155108..bcc3a6a 100644 --- a/builder/src/main/java/com/android/builder/internal/CommandLineRunner.java +++ b/builder/src/main/java/com/android/builder/internal/CommandLineRunner.java @@ -18,8 +18,6 @@ package com.android.builder.internal; import com.android.annotations.Nullable; import com.android.sdklib.util.GrabProcessOutput; -import com.android.sdklib.util.GrabProcessOutput.IProcessOutput; -import com.android.sdklib.util.GrabProcessOutput.Wait; import com.android.utils.ILogger; import java.io.IOException; @@ -29,6 +27,30 @@ public class CommandLineRunner { private final ILogger mLogger; + private class OutputGrabber implements GrabProcessOutput.IProcessOutput { + + private boolean mFoundError = false; + + @Override + public void out(@Nullable String line) { + if (line != null) { + mLogger.info(line); + } + } + + @Override + public void err(@Nullable String line) { + if (line != null) { + mLogger.error(null /*throwable*/, line); + mFoundError = true; + } + } + + private boolean foundError() { + return mFoundError; + } + } + public CommandLineRunner(ILogger logger) { mLogger = logger; } @@ -39,6 +61,8 @@ public class CommandLineRunner { } public void runCmdLine(String[] command) throws IOException, InterruptedException { + printCommand(command); + // launch the command line process Process process = Runtime.getRuntime().exec(command); @@ -54,28 +78,29 @@ public class CommandLineRunner { * @return the process return code. * @throws InterruptedException */ - private int grabProcessOutput( - final Process process) - throws InterruptedException { + private int grabProcessOutput(final Process process) throws InterruptedException { + + OutputGrabber grabber = new OutputGrabber(); - return GrabProcessOutput.grabProcessOutput( + int exitCode = GrabProcessOutput.grabProcessOutput( process, - Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output! - new IProcessOutput() { - - @Override - public void out(@Nullable String line) { - if (line != null) { - mLogger.info(line); - } - } - - @Override - public void err(@Nullable String line) { - if (line != null) { - mLogger.error(null /*throwable*/, line); - } - } - }); + GrabProcessOutput.Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output! + grabber); + + if (exitCode == 0 && grabber.foundError()) { + mLogger.error(null, "Process output to error stream but exitCode is " + exitCode); + exitCode = -42; + } + + return exitCode; + } + + private void printCommand(String[] command) { + StringBuilder sb = new StringBuilder("command: "); + for (String arg : command) { + sb.append(arg).append(' '); + } + + mLogger.info(sb.toString()); } } diff --git a/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java b/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java index 5d7de56..d8d355f 100644 --- a/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java +++ b/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java @@ -80,8 +80,6 @@ public class AidlProcessor implements SourceGenerator.Processor { command.add(filePath.getAbsolutePath()); - logger.info("aidl command: %s", command.toString()); - mRunner.runCmdLine(command); } diff --git a/builder/src/main/java/com/android/builder/internal/util/concurrent/WaitableExecutor.java b/builder/src/main/java/com/android/builder/internal/util/concurrent/WaitableExecutor.java new file mode 100644 index 0000000..319fbd8 --- /dev/null +++ b/builder/src/main/java/com/android/builder/internal/util/concurrent/WaitableExecutor.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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.builder.internal.util.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * A utility wrapper around a {@link CompletionService} using an ThreadPoolExecutor so that it + * is possible to wait on all the tasks. + * + * Tasks are submitted as {@link Callable} with {@link #execute(java.util.concurrent.Callable)}. + * + * After executing all tasks, it is possible to wait on them with {@link #waitForTasks()}. + */ +public class WaitableExecutor { + + private final CompletionService mCompletionService; + private int mCount = 0; + + public WaitableExecutor() { + mCompletionService = new ExecutorCompletionService<Object>(Executors.newCachedThreadPool()); + } + + /** + * Submits a Callable for execution. + * + * @param runnable the callable to run. + */ + public void execute(Callable runnable) { + mCompletionService.submit(runnable); + mCount++; + } + + /** + * Waits for all tasks to be executed. If a tasks through an exception, it will be thrown here. + * @throws InterruptedException + * @throws ExecutionException + */ + public void waitForTasks() throws InterruptedException, ExecutionException { + for (int i = 0 ; i < mCount ; i++) { + Future result = mCompletionService.take(); + Object r = result.get(); + } + } +} diff --git a/builder/src/main/java/com/android/builder/resources/ResourceMerger.java b/builder/src/main/java/com/android/builder/resources/ResourceMerger.java index cc1fc7b..a351d86 100755 --- a/builder/src/main/java/com/android/builder/resources/ResourceMerger.java +++ b/builder/src/main/java/com/android/builder/resources/ResourceMerger.java @@ -18,7 +18,10 @@ package com.android.builder.resources; import com.android.SdkConstants; import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.annotations.VisibleForTesting; +import com.android.builder.AaptRunner; +import com.android.builder.internal.util.concurrent.WaitableExecutor; import com.android.ide.common.xml.XmlPrettyPrinter; import com.android.resources.ResourceFolderType; import com.android.utils.Pair; @@ -42,15 +45,14 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.lang.ArrayStoreException; -import java.lang.Override; -import java.lang.String; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; /** * Merges {@link ResourceSet}s and writes a resource folder that can be fed to aapt. @@ -145,11 +147,17 @@ public class ResourceMerger implements ResourceMap { * The output is an Android style resource folder than can be fed to aapt. * * @param rootFolder the folder to write the resources in. + * @param aaptRunner an aapt runner. * @throws IOException * @throws DuplicateResourceException + * @throws ExecutionException + * @throws InterruptedException */ - public void writeResourceFolder(File rootFolder) - throws IOException, DuplicateResourceException { + public void writeResourceFolder(@NonNull File rootFolder, @Nullable AaptRunner aaptRunner) + throws IOException, DuplicateResourceException, ExecutionException, + InterruptedException { + WaitableExecutor executor = new WaitableExecutor(); + // get all the resource keys. Set<String> resourceKeys = Sets.newHashSet(); @@ -232,7 +240,7 @@ public class ResourceMerger implements ResourceMap { } else if (previouslyWritten == null || previouslyWritten == toWrite) { // easy one: new or updated res - writeResource(rootFolder, valuesResMap, toWrite); + writeResource(rootFolder, valuesResMap, toWrite, executor, aaptRunner); } else { // replacement of a resource by another. @@ -240,7 +248,7 @@ public class ResourceMerger implements ResourceMap { toWrite.setTouched(); // write the new value - writeResource(rootFolder, valuesResMap, toWrite); + writeResource(rootFolder, valuesResMap, toWrite, executor, aaptRunner); ResourceFile.FileType previousType = previouslyWritten.getSource().getType(); ResourceFile.FileType newType = toWrite.getSource().getType(); @@ -330,6 +338,8 @@ public class ResourceMerger implements ResourceMap { removeOutFile(rootFolder, folderName, FN_VALUES_XML); } + + executor.waitForTasks(); } /** @@ -372,10 +382,15 @@ public class ResourceMerger implements ResourceMap { * @param valuesResMap a map of existing values-type resources where the key is the qualifiers * of the values folder. * @param resource the resource to add. + * @param executor an executor + * @param aaptRunner an aapt runner. * @throws IOException */ - private void writeResource(File rootFolder, ListMultimap<String, Resource> valuesResMap, - Resource resource) throws IOException { + private void writeResource(@NonNull final File rootFolder, + @NonNull ListMultimap<String, Resource> valuesResMap, + @NonNull final Resource resource, + @NonNull WaitableExecutor executor, + @Nullable final AaptRunner aaptRunner) throws IOException { ResourceFile.FileType type = resource.getSource().getType(); if (type == ResourceFile.FileType.MULTI) { @@ -394,21 +409,34 @@ public class ResourceMerger implements ResourceMap { // This is a single value file. // Only write it if the state is TOUCHED. if (resource.isTouched()) { - ResourceFile resourceFile = resource.getSource(); - File file = resourceFile.getFile(); - - String filename = file.getName(); - String folderName = resource.getType().getName(); - String qualifiers = resourceFile.getQualifiers(); - if (qualifiers != null && qualifiers.length() > 0) { - folderName = folderName + SdkConstants.RES_QUALIFIER_SEP + qualifiers; - } - - File typeFolder = new File(rootFolder, folderName); - createDir(typeFolder); - - File outFile = new File(typeFolder, filename); - Files.copy(file, outFile); + executor.execute(new Callable() { + @Override + public Object call() throws Exception { + ResourceFile resourceFile = resource.getSource(); + File file = resourceFile.getFile(); + + String filename = file.getName(); + String folderName = resource.getType().getName(); + String qualifiers = resourceFile.getQualifiers(); + if (qualifiers != null && qualifiers.length() > 0) { + folderName = folderName + SdkConstants.RES_QUALIFIER_SEP + qualifiers; + } + + File typeFolder = new File(rootFolder, folderName); + createDir(typeFolder); + + File outFile = new File(typeFolder, filename); + + if (aaptRunner != null && filename.endsWith(".9.png")) { + // run aapt in single crunch mode on the original file to write the + // destination file. + aaptRunner.crunchPng(file, outFile); + } else { + Files.copy(file, outFile); + } + return null; + } + }); } } } @@ -603,7 +631,7 @@ public class ResourceMerger implements ResourceMap { return null; } - private static void createDir(File folder) throws IOException { + private synchronized void createDir(File folder) throws IOException { if (!folder.isDirectory() && !folder.mkdirs()) { throw new IOException("Failed to create directory: " + folder); } diff --git a/builder/src/test/java/com/android/builder/resources/ResourceMergerTest.java b/builder/src/test/java/com/android/builder/resources/ResourceMergerTest.java index f3f2623..f056413 100755 --- a/builder/src/test/java/com/android/builder/resources/ResourceMergerTest.java +++ b/builder/src/test/java/com/android/builder/resources/ResourceMergerTest.java @@ -32,10 +32,10 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import java.lang.String; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -268,7 +268,7 @@ public class ResourceMergerTest extends BaseTestCase { File resFolder = getFolderCopy(new File(root, "resOut")); // write the content of the resource merger. - resourceMerger.writeResourceFolder(resFolder); + resourceMerger.writeResourceFolder(resFolder, null /*aaptRunner*/); // Check the content. checkImageColor(new File(resFolder, "drawable" + File.separator + "touched.png"), @@ -371,7 +371,7 @@ public class ResourceMergerTest extends BaseTestCase { File resFolder = getFolderCopy(new File(root, "resOut")); // write the content of the resource merger. - resourceMerger.writeResourceFolder(resFolder); + resourceMerger.writeResourceFolder(resFolder, null /*aaptRunner*/); // Check the content. // values/values.xml @@ -445,7 +445,7 @@ public class ResourceMergerTest extends BaseTestCase { File resFolder = getFolderCopy(new File(root, "resOut")); // write the content of the resource merger. - resourceMerger.writeResourceFolder(resFolder); + resourceMerger.writeResourceFolder(resFolder, null /*aaptRunner*/); // Check the content. // values/values.xml @@ -537,7 +537,7 @@ public class ResourceMergerTest extends BaseTestCase { File resFolder = getFolderCopy(new File(root, "resOut")); // write the content of the resource merger. - resourceMerger.writeResourceFolder(resFolder); + resourceMerger.writeResourceFolder(resFolder, null /*aaptRunner*/); // deleted layout/file_replaced_by_alias.xml assertFalse(new File(resFolder, "layout" + File.separator + "file_replaced_by_alias.xml") @@ -680,12 +680,13 @@ public class ResourceMergerTest extends BaseTestCase { return sResourceMerger; } - private static File getWrittenResources() throws DuplicateResourceException, IOException { + private static File getWrittenResources() throws DuplicateResourceException, IOException, + ExecutionException, InterruptedException { ResourceMerger resourceMerger = getResourceMerger(); File folder = Files.createTempDir(); - resourceMerger.writeResourceFolder(folder); + resourceMerger.writeResourceFolder(folder, null /*aaptRunner*/); return folder; } |