aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2017-05-23 17:19:14 +0200
committerGravatar Irina Iancu <elenairina@google.com>2017-05-23 17:41:32 +0200
commitf6c4d6d66118410b1139a84fe34ba8134661bfa2 (patch)
tree67ee24a40c88bea1a38807155806fb957a852b9a
parent81316798806b274d84c4eb1131bab8c90b0c7ba1 (diff)
Add a new action for generating reconciled R classes for Robolectric.
This includes some refactoring: * Move the symbol deserialization our of the merger and into the ParsedAndroidData (probably move again.) * Change the FailedFutureAggregator generics to work more callables RELNOTES: None PiperOrigin-RevId: 156863698
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java61
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/FailedFutureAggregator.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java176
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java67
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java12
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java6
8 files changed, 267 insertions, 66 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java
index b9a372d32c..44967168b9 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java
@@ -17,12 +17,9 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Iterables;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
-import com.google.devtools.build.android.ParsedAndroidData.Builder;
-import com.google.devtools.build.android.ParsedAndroidData.ParsedAndroidDataBuildingPathWalker;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
@@ -32,7 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -42,44 +38,6 @@ class AndroidDataMerger {
private static final Logger logger = Logger.getLogger(AndroidDataMerger.class.getCanonicalName());
- private final class ParseDependencyDataTask implements Callable<Boolean> {
-
- private final SerializedAndroidData dependency;
-
- private final Builder targetBuilder;
-
- private ParseDependencyDataTask(SerializedAndroidData dependency, Builder targetBuilder) {
- this.dependency = dependency;
- this.targetBuilder = targetBuilder;
- }
-
- @Override
- public Boolean call() throws Exception {
- final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder();
- try {
- dependency.deserialize(deserializer, parsedDataBuilder.consumers());
- } catch (DeserializationException e) {
- if (!e.isLegacy()) {
- throw MergingException.wrapException(e);
- }
- logger.fine(
- String.format(
- "\u001B[31mDEPRECATION:\u001B[0m Legacy resources used for %s",
- dependency.getLabel()));
- // Legacy android resources -- treat them as direct dependencies.
- dependency.walk(ParsedAndroidDataBuildingPathWalker.create(parsedDataBuilder));
- }
- // The builder isn't threadsafe, so synchronize the copyTo call.
- synchronized (targetBuilder) {
- // All the resources are sorted before writing, so they can be aggregated in
- // whatever order here.
- parsedDataBuilder.copyTo(targetBuilder);
- }
- // Had to return something?
- return Boolean.TRUE;
- }
- }
-
/** Interface for comparing paths. */
interface SourceChecker {
boolean checkEquality(DataSource one, DataSource two) throws IOException;
@@ -184,27 +142,12 @@ class AndroidDataMerger {
throws MergingException {
Stopwatch timer = Stopwatch.createStarted();
try {
- final ParsedAndroidData.Builder directBuilder = ParsedAndroidData.Builder.newBuilder();
- final ParsedAndroidData.Builder transitiveBuilder = ParsedAndroidData.Builder.newBuilder();
- final List<ListenableFuture<Boolean>> tasks = new ArrayList<>();
- for (final SerializedAndroidData dependency : direct) {
- tasks.add(executorService.submit(new ParseDependencyDataTask(dependency, directBuilder)));
- }
- for (final SerializedAndroidData dependency : transitive) {
- tasks.add(
- executorService.submit(new ParseDependencyDataTask(dependency, transitiveBuilder)));
- }
- // Wait for all the parsing to complete.
- FailedFutureAggregator<MergingException> aggregator =
- FailedFutureAggregator.createForMergingExceptionWithMessage(
- "Failure(s) during dependency parsing");
- aggregator.aggregateAndMaybeThrow(tasks);
logger.fine(
String.format("Merged dependencies read in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
timer.reset().start();
return doMerge(
- transitiveBuilder.build(),
- directBuilder.build(),
+ ParsedAndroidData.loadedFrom(transitive, executorService, deserializer),
+ ParsedAndroidData.loadedFrom(direct, executorService, deserializer),
primary,
primaryManifest,
allowPrimaryOverrideAll);
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
index b64f9774f6..ec7a3286c9 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
@@ -44,7 +44,7 @@ public class AndroidResourceClassWriter implements Flushable, AndroidResourceSym
public static AndroidResourceClassWriter of(
AndroidFrameworkAttrIdProvider androidIdProvider, Path outputBasePath, String packageName) {
return new AndroidResourceClassWriter(
- new PlaceholderIdFieldInitializerBuilder(androidIdProvider), outputBasePath, packageName);
+ PlaceholderIdFieldInitializerBuilder.from(androidIdProvider), outputBasePath, packageName);
}
private final Path outputBasePath;
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java
index 7101033c89..8fc6075382 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java
@@ -70,8 +70,6 @@ public class AndroidResourceMerger {
final ListeningExecutorService executorService =
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15));
try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
- AndroidDataMerger merger =
- AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer);
UnwrittenMergedAndroidData merged =
mergeData(
executorService,
diff --git a/src/tools/android/java/com/google/devtools/build/android/FailedFutureAggregator.java b/src/tools/android/java/com/google/devtools/build/android/FailedFutureAggregator.java
index da61920803..06f49036e2 100644
--- a/src/tools/android/java/com/google/devtools/build/android/FailedFutureAggregator.java
+++ b/src/tools/android/java/com/google/devtools/build/android/FailedFutureAggregator.java
@@ -56,11 +56,12 @@ class FailedFutureAggregator<T extends Throwable> {
this.exceptionFactory = exceptionFactory;
}
- /** Iterates throw a list of futures, throwing an Exception if any have failed. */
- public void aggregateAndMaybeThrow(List<ListenableFuture<Boolean>> tasks) throws T {
+ /** Iterates a list of futures, throwing an Exception if any have failed. */
+ public <V> void aggregateAndMaybeThrow(List<? extends ListenableFuture<? extends V>> tasks)
+ throws T {
// Retrieve all the exceptions and wrap them in an IOException.
T exception = null;
- for (ListenableFuture<Boolean> task : tasks) {
+ for (ListenableFuture<?> task : tasks) {
try {
task.get();
} catch (ExecutionException | InterruptedException e) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java b/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java
new file mode 100644
index 0000000000..e95b646d6d
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java
@@ -0,0 +1,176 @@
+// 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;
+
+import com.android.builder.dependency.SymbolFileProvider;
+import com.android.resources.ResourceType;
+import com.google.common.base.Optional;
+import com.google.common.base.Stopwatch;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions;
+import com.google.devtools.build.android.Converters.DependencyAndroidDataListConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.resources.RClassGenerator;
+import com.google.devtools.build.android.resources.ResourceSymbols;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import java.io.Closeable;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This action generates consistant ids R.class files for use in robolectric tests.
+ */
+public class GenerateRobolectricResourceSymbolsAction {
+
+ private static final Logger logger =
+ Logger.getLogger(GenerateRobolectricResourceSymbolsAction.class.getName());
+
+ private static final class WriteLibraryRClass implements Callable<Boolean> {
+ private final Entry<String, ListenableFuture<ResourceSymbols>> librarySymbolEntry;
+ private final RClassGenerator generator;
+
+ private WriteLibraryRClass(
+ Entry<String, ListenableFuture<ResourceSymbols>> librarySymbolEntry,
+ RClassGenerator generator) {
+ this.librarySymbolEntry = librarySymbolEntry;
+ this.generator = generator;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ generator.write(
+ librarySymbolEntry.getKey(), librarySymbolEntry.getValue().get().asInitializers());
+ return true;
+ }
+ }
+
+ /** Flag specifications for this action. */
+ public static final class Options extends OptionsBase {
+
+ @Option(
+ name = "data",
+ defaultValue = "",
+ converter = DependencyAndroidDataListConverter.class,
+ category = "input",
+ help =
+ "Data dependencies. The expected format is "
+ + DependencyAndroidData.EXPECTED_FORMAT
+ + "[&...]"
+ )
+ public List<DependencyAndroidData> data;
+
+ @Option(
+ name = "classJarOutput",
+ defaultValue = "null",
+ converter = PathConverter.class,
+ category = "output",
+ help = "Path for the generated java class jar."
+ )
+ public Path classJarOutput;
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ final Stopwatch timer = Stopwatch.createStarted();
+ OptionsParser optionsParser =
+ OptionsParser.newOptionsParser(Options.class, AaptConfigOptions.class);
+ optionsParser.enableParamsFileSupport(FileSystems.getDefault());
+ optionsParser.parseAndExitUponError(args);
+ AaptConfigOptions aaptConfigOptions = optionsParser.getOptions(AaptConfigOptions.class);
+ Options options = optionsParser.getOptions(Options.class);
+
+ try (ScopedTemporaryDirectory scopedTmp =
+ new ScopedTemporaryDirectory("robolectric_resources_tmp")) {
+ Path tmp = scopedTmp.getPath();
+ Path generatedSources = tmp.resolve("generated_resources");
+ // The reported availableProcessors may be higher than the actual resources
+ // (on a shared system). On the other hand, a lot of the work is I/O, so it's not completely
+ // CPU bound. As a compromise, divide by 2 the reported availableProcessors.
+ int numThreads = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
+ ListeningExecutorService executorService =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numThreads));
+ try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
+
+ logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ final PlaceholderIdFieldInitializerBuilder robolectricIds =
+ PlaceholderIdFieldInitializerBuilder.from(aaptConfigOptions.androidJar);
+ ParsedAndroidData.loadedFrom(
+ options.data, executorService, AndroidDataDeserializer.create())
+ .writeResourcesTo(
+ new AndroidResourceSymbolSink() {
+
+ @Override
+ public void acceptSimpleResource(ResourceType type, String name) {
+ robolectricIds.addSimpleResource(type, name);
+ }
+
+ @Override
+ public void acceptPublicResource(
+ ResourceType type, String name, Optional<Integer> value) {
+ robolectricIds.addPublicResource(type, name, value);
+ }
+
+ @Override
+ public void acceptStyleableResource(
+ FullyQualifiedName key, Map<FullyQualifiedName, Boolean> attrs) {
+ robolectricIds.addStyleableResource(key, attrs);
+ }
+ });
+
+ final RClassGenerator generator =
+ RClassGenerator.with(generatedSources, robolectricIds.build(), false);
+
+ List<SymbolFileProvider> libraries = new ArrayList<>();
+ for (DependencyAndroidData dataDep : options.data) {
+ SymbolFileProvider library = dataDep.asSymbolFileProvider();
+ libraries.add(library);
+ }
+ List<ListenableFuture<Boolean>> writeSymbolsTask = new ArrayList<>();
+ for (final Entry<String, ListenableFuture<ResourceSymbols>> librarySymbolEntry :
+ ResourceSymbols.loadFrom(libraries, executorService, null).entries()) {
+ writeSymbolsTask.add(
+ executorService.submit(new WriteLibraryRClass(librarySymbolEntry, generator)));
+ }
+ FailedFutureAggregator.forIOExceptionsWithMessage("Errors writing symbols.")
+ .aggregateAndMaybeThrow(writeSymbolsTask);
+ }
+
+ logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ AndroidResourceOutputs.createClassJar(generatedSources, options.classJarOutput);
+ System.out.println(options.classJarOutput);
+ logger.fine(
+ String.format("Create classJar finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Unexpected", e);
+ throw e;
+ }
+ logger.fine(String.format("Resources merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java
index 36eeabe9bd..cd3401e1db 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java
@@ -24,6 +24,8 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import com.google.devtools.build.android.xml.StyleableXmlResourceValue;
import java.io.IOException;
@@ -42,6 +44,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
@@ -394,6 +397,70 @@ public class ParsedAndroidData {
return pathWalker.createParsedAndroidData();
}
+ private static final class ParseDependencyDataTask implements Callable<Void> {
+
+ private final SerializedAndroidData dependency;
+
+ private final Builder targetBuilder;
+
+ private final AndroidDataDeserializer deserializer;
+
+ private ParseDependencyDataTask(
+ AndroidDataDeserializer deserializer,
+ SerializedAndroidData dependency,
+ Builder targetBuilder) {
+ this.deserializer = deserializer;
+ this.dependency = dependency;
+ this.targetBuilder = targetBuilder;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder();
+ try {
+ dependency.deserialize(deserializer, parsedDataBuilder.consumers());
+ } catch (DeserializationException e) {
+ if (!e.isLegacy()) {
+ throw MergingException.wrapException(e);
+ }
+ logger.fine(
+ String.format(
+ "\u001B[31mDEPRECATION:\u001B[0m Legacy resources used for %s",
+ dependency.getLabel()));
+ // Legacy android resources -- treat them as direct dependencies.
+ dependency.walk(ParsedAndroidDataBuildingPathWalker.create(parsedDataBuilder));
+ }
+ // The builder isn't threadsafe, so synchronize the copyTo call.
+ synchronized (targetBuilder) {
+ // All the resources are sorted before writing, so they can be aggregated in
+ // whatever order here.
+ parsedDataBuilder.copyTo(targetBuilder);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Deserializes data and merges them into a single {@link ParsedAndroidData}.
+ *
+ * @throws MergingException for deserialization errors.
+ */
+ public static ParsedAndroidData loadedFrom(
+ List<? extends SerializedAndroidData> data,
+ ListeningExecutorService executorService,
+ AndroidDataDeserializer deserializer) {
+ List<ListenableFuture<Void>> tasks = new ArrayList<>();
+ final Builder target = Builder.newBuilder();
+ for (SerializedAndroidData serialized : data) {
+ tasks.add(
+ executorService.submit(new ParseDependencyDataTask(deserializer, serialized, target)));
+ }
+ FailedFutureAggregator.createForMergingExceptionWithMessage(
+ "Failure(s) during dependency parsing")
+ .aggregateAndMaybeThrow(tasks);
+ return target.build();
+ }
+
private final ImmutableSet<MergeConflict> conflicts;
private final ImmutableMap<DataKey, DataResource> overwritingResources;
private final ImmutableMap<DataKey, DataResource> combiningResources;
diff --git a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java
index 189ea76d8e..0065f8fc15 100644
--- a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java
+++ b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java
@@ -26,6 +26,7 @@ import com.google.devtools.build.android.resources.FieldInitializer;
import com.google.devtools.build.android.resources.FieldInitializers;
import com.google.devtools.build.android.resources.IntArrayFieldInitializer;
import com.google.devtools.build.android.resources.IntFieldInitializer;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
@@ -158,6 +159,15 @@ class PlaceholderIdFieldInitializerBuilder {
return resourceName.replace('.', '_');
}
+ public static PlaceholderIdFieldInitializerBuilder from(
+ AndroidFrameworkAttrIdProvider androidIdProvider) {
+ return new PlaceholderIdFieldInitializerBuilder(androidIdProvider);
+ }
+
+ public static PlaceholderIdFieldInitializerBuilder from(Path androidJar) {
+ return from(new AndroidFrameworkAttrIdJar(androidJar));
+ }
+
private final AndroidFrameworkAttrIdProvider androidIdProvider;
private final Map<ResourceType, Set<String>> innerClasses = new EnumMap<>(ResourceType.class);
@@ -167,7 +177,7 @@ class PlaceholderIdFieldInitializerBuilder {
private final Map<String, Map<String, Boolean>> styleableAttrs = new HashMap<>();
- public PlaceholderIdFieldInitializerBuilder(AndroidFrameworkAttrIdProvider androidIdProvider) {
+ private PlaceholderIdFieldInitializerBuilder(AndroidFrameworkAttrIdProvider androidIdProvider) {
this.androidIdProvider = androidIdProvider;
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java
index d9a3204465..59f0aebdd4 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java
@@ -71,6 +71,12 @@ public class ResourceProcessorBusyBox {
LibraryRClassGeneratorAction.main(args);
}
},
+ GENERATE_ROBOLECTRIC_R() {
+ @Override
+ void call(String[] args) throws Exception {
+ GenerateRobolectricResourceSymbolsAction.main(args);
+ }
+ },
PARSE() {
@Override
void call(String[] args) throws Exception {