aboutsummaryrefslogtreecommitdiff
path: root/builder
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2012-12-21 16:16:05 -0800
committerXavier Ducrohet <xav@android.com>2013-01-02 13:21:52 -0800
commit0749a476551ad096b7e2436e3f1f758c10caad83 (patch)
tree2bdf681a77e4abbd360fc5453ac1a4c304db3c29 /builder
parent724415bae3481da6afa6a561fde3833ace4f9d6c (diff)
downloadbuild-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')
-rw-r--r--builder/src/main/java/com/android/builder/AaptRunner.java58
-rw-r--r--builder/src/main/java/com/android/builder/AndroidBuilder.java15
-rw-r--r--builder/src/main/java/com/android/builder/internal/CommandLineRunner.java71
-rw-r--r--builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java2
-rw-r--r--builder/src/main/java/com/android/builder/internal/util/concurrent/WaitableExecutor.java63
-rwxr-xr-xbuilder/src/main/java/com/android/builder/resources/ResourceMerger.java78
-rwxr-xr-xbuilder/src/test/java/com/android/builder/resources/ResourceMergerTest.java15
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;
}