diff options
Diffstat (limited to 'src')
13 files changed, 488 insertions, 240 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java index 7c340694cb..516db565c0 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java @@ -99,17 +99,18 @@ public class AarImport implements RuleConfiguredTargetFactory { Artifact resourcesZip = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP); - ResourceApk resourceApk = androidManifest.packWithDataAndResources( - ruleContext, - new LocalResourceContainer.Builder(ruleContext) - .withResources(ImmutableList.of(resourcesProvider)) - .build(), - ResourceDependencies.fromRuleDeps(ruleContext, JavaCommon.isNeverLink(ruleContext)), - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_SYMBOLS), - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), - resourcesZip, - /* alwaysExportManifest = */ true); + ResourceApk resourceApk = + androidManifest.packWithDataAndResources( + ruleContext, + new LocalResourceContainer.Builder(ruleContext) + .withResources(ImmutableList.of(resourcesProvider)) + .build(), + ResourceDependencies.fromRuleDeps(ruleContext, JavaCommon.isNeverLink(ruleContext)), + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LOCAL_SYMBOLS), + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), + resourcesZip, + /* alwaysExportManifest = */ true); // There isn't really any use case for building an aar_import target on its own, so the files to // build could be empty. The resources zip and merged jars are added here as a sanity check for diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java index bd57dffab5..f573132b03 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java @@ -96,7 +96,7 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { true, /* isLibrary */ ResourceDependencies.fromRuleDeps(ruleContext, JavaCommon.isNeverLink(ruleContext)), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_SYMBOLS), + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_MERGED_SYMBOLS), ResourceConfigurationFilter.empty(ruleContext), ImmutableList.<String>of(), /* uncompressedExtensions */ false, /* crunchPng */ @@ -270,3 +270,4 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { return builder; } } + diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java index 3d7886d368..dc84ee5dc5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java @@ -144,10 +144,11 @@ public class AndroidResourceMergingActionBuilder { ResourceContainerConverter.convertDependencies( dependencies, builder, inputs, RESOURCE_CONTAINER_TO_ARG, RESOURCE_CONTAINER_TO_ARTIFACTS); - Preconditions.checkNotNull(classJarOut); List<Artifact> outs = new ArrayList<>(); - builder.addExecPath("--classJarOutput", classJarOut); - outs.add(classJarOut); + if (classJarOut != null) { + builder.addExecPath("--classJarOutput", classJarOut); + outs.add(classJarOut); + } if (mergedResourcesOut != null) { builder.addExecPath("--resourcesOutput", mergedResourcesOut); @@ -187,8 +188,11 @@ public class AndroidResourceMergingActionBuilder { .build(context)); // Return the full set of processed transitive dependencies. - ResourceContainer.Builder result = primary.toBuilder() - .setJavaClassJar(classJarOut); + ResourceContainer.Builder result = primary.toBuilder(); + if (classJarOut != null) { + // ensure the classJar is propgated if it exists. Otherwise, AndroidCommon tries to make it. + result.setJavaClassJar(classJarOut); + } if (manifestOut != null) { result.setManifest(manifestOut); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java index 63a25ef0a5..7b345a16af 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java @@ -116,8 +116,8 @@ public final class AndroidRuleClasses { fromTemplates("%{name}_symbols/R.txt"); public static final SafeImplicitOutputsFunction ANDROID_LOCAL_SYMBOLS = fromTemplates("%{name}_symbols/local.bin"); - public static final SafeImplicitOutputsFunction ANDROID_SYMBOLS = - fromTemplates("%{name}_symbols/symbols.bin"); + public static final SafeImplicitOutputsFunction ANDROID_MERGED_SYMBOLS = + fromTemplates("%{name}_symbols/merged.bin"); public static final ImplicitOutputsFunction ANDROID_PROCESSED_MANIFEST = fromTemplates("%{name}_processed_manifest/AndroidManifest.xml"); public static final SafeImplicitOutputsFunction MOBILE_INSTALL_STUB_APPLICATION_MANIFEST = diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java index f5fabfc623..32e78d86e4 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java @@ -465,38 +465,43 @@ public final class ApplicationManifest { if (isLibrary && AndroidCommon.getAndroidConfig(ruleContext).useParallelResourceProcessing()) { // android_library should only build the APK one way (!incremental). Preconditions.checkArgument(!incremental); - Artifact rJavaClassJar = ruleContext.getImplicitOutputArtifact( - AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR); - - if (resourceContainer.getSymbols() != null) { - new AndroidResourceParsingActionBuilder(ruleContext) - .withPrimary(resourceContainer) - .setParse(data) - .setOutput(resourceContainer.getSymbols()) - .build(ruleContext); - } + Artifact rJavaClassJar = + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR); - AndroidResourceMergingActionBuilder resourcesMergerBuilder = - new AndroidResourceMergingActionBuilder(ruleContext) - .setJavaPackage(resourceContainer.getJavaPackage()) + ResourceContainer parsed = + new AndroidResourceParsingActionBuilder(ruleContext) + .setParse(data) .withPrimary(resourceContainer) + .setOutput(resourceContainer.getSymbols()) + .build(ruleContext); + + ResourceContainer generated = + new LibraryRGeneratorActionBuilder() + .setJavaPackage(resourceContainer.getJavaPackage()) + .withPrimary(parsed) + .withDependencies(resourceDeps) + .setClassJarOut(rJavaClassJar) + .build(ruleContext); + + ResourceContainer merged = + new AndroidResourceMergingActionBuilder(ruleContext) + .setJavaPackage(generated.getJavaPackage()) + .withPrimary(generated) .withDependencies(resourceDeps) .setMergedResourcesOut(mergedResources) .setManifestOut(manifestOut) - .setClassJarOut(rJavaClassJar) - .setDataBindingInfoZip(dataBindingInfoZip); - ResourceContainer merged = resourcesMergerBuilder.build(ruleContext); + .setDataBindingInfoZip(dataBindingInfoZip) + .build(ruleContext); - AndroidResourceValidatorActionBuilder validatorBuilder = + processed = new AndroidResourceValidatorActionBuilder(ruleContext) .setJavaPackage(merged.getJavaPackage()) - .setDebug( - ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT) + .setDebug(ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT) .setMergedResources(mergedResources) .withPrimary(merged) .setSourceJarOut(merged.getJavaSourceJar()) - .setRTxtOut(merged.getRTxt()); - processed = validatorBuilder.build(ruleContext); + .setRTxtOut(merged.getRTxt()) + .build(ruleContext); } else { AndroidResourcesProcessorBuilder builder = new AndroidResourcesProcessorBuilder(ruleContext) @@ -644,14 +649,16 @@ public final class ApplicationManifest { aaptActionHelper.createGenerateApkAction(resourceApk, resourceContainer.getRenameManifestPackage(), additionalAaptOpts.build(), densities); - ResourceContainer updatedResources = resourceContainer.toBuilder() - .setLabel(ruleContext.getLabel()) - .setApk(resourceApk) - .setManifest(getManifest()) - .setJavaSourceJar(javaSourcesJar) - .setJavaClassJar(null) - .setSymbols(null) - .build(); + ResourceContainer updatedResources = + resourceContainer + .toBuilder() + .setLabel(ruleContext.getLabel()) + .setApk(resourceApk) + .setManifest(getManifest()) + .setJavaSourceJar(javaSourcesJar) + .setJavaClassJar(null) + .setSymbols(null) + .build(); aaptActionHelper.createGenerateProguardAction(proguardCfg, mainDexProguardCfg); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java new file mode 100644 index 0000000000..361a70a803 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java @@ -0,0 +1,117 @@ +// 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.lib.rules.android; + +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; + +/** Builder for the action that generates the R class for libraries. */ +public class LibraryRGeneratorActionBuilder { + static final Function<ResourceContainer, Artifact> TO_SYMBOL_ARTIFACT = + new Function<ResourceContainer, Artifact>() { + @Override + public Artifact apply(ResourceContainer input) { + return input.getSymbols(); + } + }; + static final Function<ResourceContainer, String> TO_SYMBOL_PATH = + new Function<ResourceContainer, String>() { + @Override + public String apply(ResourceContainer container) { + return container.getSymbols().getExecPathString(); + } + }; + + private String javaPackage; + private Iterable<ResourceContainer> deps = ImmutableList.<ResourceContainer>of(); + private ResourceContainer resourceContainer; + private Artifact rJavaClassJar; + + public LibraryRGeneratorActionBuilder setJavaPackage(String javaPackage) { + this.javaPackage = javaPackage; + return this; + } + + public LibraryRGeneratorActionBuilder withPrimary(ResourceContainer resourceContainer) { + this.resourceContainer = resourceContainer; + return this; + } + + public LibraryRGeneratorActionBuilder withDependencies(ResourceDependencies resourceDeps) { + this.deps = resourceDeps.getResources(); + return this; + } + + public LibraryRGeneratorActionBuilder setClassJarOut(Artifact rJavaClassJar) { + this.rJavaClassJar = rJavaClassJar; + return this; + } + + public ResourceContainer build(RuleContext ruleContext) { + AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); + + CustomCommandLine.Builder builder = new CustomCommandLine.Builder(); + NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder(); + FilesToRunProvider executable = + ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST); + inputs.addAll(executable.getRunfilesSupport().getRunfilesArtifactsWithoutMiddlemen()); + + builder.add("--tool").add("GENERATE_LIBRARY_R").add("--"); + + if (!Strings.isNullOrEmpty(javaPackage)) { + builder.add("--packageForR").add(javaPackage); + } + + FluentIterable<ResourceContainer> symbolProviders = + FluentIterable.from(deps).append(resourceContainer); + + builder.addJoinStrings( + "--symbols", + ruleContext.getConfiguration().getHostPathSeparator(), + symbolProviders.transform(TO_SYMBOL_PATH)); + inputs.addTransitive( + NestedSetBuilder.wrap( + Order.NAIVE_LINK_ORDER, symbolProviders.transform(TO_SYMBOL_ARTIFACT))); + + builder.addExecPath("--classJarOutput", rJavaClassJar); + + builder.addExecPath("--androidJar", sdk.getAndroidJar()); + inputs.add(sdk.getAndroidJar()); + + // Create the spawn action. + SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder(); + ruleContext.registerAction( + spawnActionBuilder + .addTransitiveInputs(inputs.build()) + .addOutputs(ImmutableList.<Artifact>of(rJavaClassJar)) + .useParameterFile(ParameterFileType.UNQUOTED) + .setCommandLine(builder.build()) + .setExecutable(executable) + .setProgressMessage("Generating Library R Classes: " + ruleContext.getLabel()) + .setMnemonic("LibraryRClassGenerator") + .build(ruleContext)); + return resourceContainer.toBuilder().setJavaClassJar(rJavaClassJar).build(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLibraryTest.java index 52cd935e2e..8bba37d7d1 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLibraryTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLibraryTest.java @@ -718,8 +718,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { "<resources><string name = 'hello'>Hello Android!</string></resources>"); ConfiguredTarget resource = getConfiguredTarget("//c/b/m/a:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("c/b/m/a/b_/res"), args); } @@ -734,8 +736,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { "<resources><string name = 'hello'>Hello Android!</string></resources>"); ConfiguredTarget resource = getConfiguredTarget("//java/android:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/android/res"), args); } @@ -752,8 +756,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { "<resources><string name = 'hello'>Hello Android!</string></resources>"); ConfiguredTarget resource = getConfiguredTarget("//java/android:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/android/res"), args); } @@ -768,8 +774,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { "exports_files(['res/values/strings.xml'])"); ConfiguredTarget resource = getConfiguredTarget("//java/android:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/other/res"), args); assertNoEvents(); } @@ -787,8 +795,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { ")"); ConfiguredTarget resource = getConfiguredTarget("//java/android:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/other/res"), args); assertNoEvents(); } @@ -807,8 +817,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { "exports_files(['res/values/strings.xml'])"); ConfiguredTarget resource = getConfiguredTarget("//java/android:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/other/res"), args); assertNoEvents(); } @@ -829,8 +841,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { ")"); ConfiguredTarget resource = getConfiguredTarget("//java/android:r"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/other/res"), args); assertNoEvents(); } @@ -1072,22 +1086,27 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { target.getProvider(AndroidResourcesProvider.class).getDirectAndroidResources()); SpawnAction resourceParserAction = - (SpawnAction) - actionsTestUtil() - .getActionForArtifactEndingWith(artifacts, - "/" + resources.getSymbols().getFilename()); + getGeneratingSpawnAction( + getImplicitOutputArtifact(target, AndroidRuleClasses.ANDROID_MERGED_SYMBOLS)); + + SpawnAction mergeAction = + getGeneratingSpawnAction( + getImplicitOutputArtifact(target, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)); + SpawnAction resourceClassJarAction = (SpawnAction) actionsTestUtil() - .getActionForArtifactEndingWith(artifacts, - "/" + resources.getJavaClassJar().getFilename()); + .getActionForArtifactEndingWith( + artifacts, "/" + resources.getJavaClassJar().getFilename()); + SpawnAction resourceSrcJarAction = (SpawnAction) actionsTestUtil() - .getActionForArtifactEndingWith(artifacts, - "/" + resources.getJavaSourceJar().getFilename()); + .getActionForArtifactEndingWith( + artifacts, "/" + resources.getJavaSourceJar().getFilename()); assertThat(resourceParserAction.getMnemonic()).isEqualTo("AndroidResourceParser"); - assertThat(resourceClassJarAction.getMnemonic()).isEqualTo("AndroidResourceMerger"); + assertThat(mergeAction.getMnemonic()).isEqualTo("AndroidResourceMerger"); + assertThat(resourceClassJarAction.getMnemonic()).isEqualTo("LibraryRClassGenerator"); assertThat(resourceSrcJarAction.getMnemonic()).isEqualTo("AndroidResourceValidator"); // Validator also generates an R.txt. assertThat(resourceSrcJarAction.getOutputs()).contains(resources.getRTxt()); @@ -1357,8 +1376,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { " resource_files = ['d2-res/values/strings.xml'],", " )"); ConfiguredTarget resource = getConfiguredTarget("//java/android/resources/d1:d1"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs(resource, ImmutableList.of("java/android/resources/d1/d1-res"), args); Truth.assertThat(getDirectDependentResourceDirs(resource, args)) .contains("java/android/resources/d2/d2-res"); @@ -1395,8 +1416,10 @@ public class AndroidLibraryTest extends AndroidBuildViewTestCase { " )"); ConfiguredTarget resource = getConfiguredTarget("//java/android/resources/d1:d1"); - List<String> args = ((SpawnAction) getGeneratingAction(getResourceArtifact(resource))) - .getArguments(); + List<String> args = + getGeneratingSpawnAction( + getImplicitOutputArtifact(resource, AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .getArguments(); assertPrimaryResourceDirs( resource, ImmutableList.of("java/android/resources/d1/d1-res"), args); Truth.assertThat(getDirectDependentResourceDirs(resource, args)) diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java index 9ea843d498..7d2dded2e7 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java @@ -13,26 +13,65 @@ // limitations under the License. package com.google.devtools.build.android; +import com.android.ide.common.res2.MergingException; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; +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.ParsedAndroidData.Builder; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.proto.SerializeFormat.Header; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.ArrayList; import java.util.Collection; +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.Logger; /** Deserializes {@link DataKey}, {@link DataValue} entries from a binary file. */ public class AndroidDataDeserializer { + /** Task to deserialize resources from a path. */ + static final class Deserialize implements Callable<Boolean> { + + private final Path symbolPath; + + private final Builder finalDataBuilder; + private final AndroidDataDeserializer deserializer; + + private Deserialize( + AndroidDataDeserializer deserializer, Path symbolPath, Builder finalDataBuilder) { + this.deserializer = deserializer; + this.symbolPath = symbolPath; + this.finalDataBuilder = finalDataBuilder; + } + + @Override + public Boolean call() throws Exception { + final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder(); + deserializer.read(symbolPath, parsedDataBuilder.consumers()); + // The builder isn't threadsafe, so synchronize the copyTo call. + synchronized (finalDataBuilder) { + // All the resources are sorted before writing, so they can be aggregated in + // whatever order here. + parsedDataBuilder.copyTo(finalDataBuilder); + } + return Boolean.TRUE; + } + } + private static final Logger logger = Logger.getLogger(AndroidDataDeserializer.class.getName()); private final ImmutableSet<String> filteredResources; @@ -137,5 +176,37 @@ public class AndroidDataDeserializer { } } } + + /** Deserializes a list of serialized resource paths to a {@link ParsedAndroidData}. */ + public static ParsedAndroidData deserializeSymbolsToData(List<Path> symbolPaths) + throws IOException, MergingException { + AndroidDataDeserializer deserializer = create(); + final ListeningExecutorService executorService = + MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15)); + final Builder deserializedDataBuilder = ParsedAndroidData.Builder.newBuilder(); + try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) { + List<ListenableFuture<Boolean>> deserializing = new ArrayList<>(); + for (final Path symbolPath : symbolPaths) { + deserializing.add( + executorService.submit( + new AndroidDataDeserializer.Deserialize( + deserializer, symbolPath, deserializedDataBuilder))); + } + FailedFutureAggregator<MergingException> aggregator = + FailedFutureAggregator.createForMergingExceptionWithMessage( + "Failure(s) during dependency parsing"); + aggregator.aggregateAndMaybeThrow(deserializing); + } + return deserializedDataBuilder.build(); + } + + public static ParsedAndroidData deserializeSingleAndroidData(SerializedAndroidData data) + throws MergingException { + final ParsedAndroidData.Builder builder = ParsedAndroidData.Builder.newBuilder(); + final AndroidDataDeserializer deserializer = create(); + data.deserialize(deserializer, builder.consumers()); + return builder.build(); + } } + diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java index 7b5f14d501..433620d144 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java @@ -28,6 +28,7 @@ import com.google.common.collect.Ordering; 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.AndroidResourceMergingAction.Options; import com.google.devtools.build.android.xml.Namespaces; import java.io.BufferedWriter; import java.io.File; @@ -103,7 +104,7 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { private static final char[] START_RESOURCES_TAG = "<resources".toCharArray(); public static final char[] END_RESOURCES = "</resources>".toCharArray(); private static final char[] LINE_END = "\n".toCharArray(); - private static final PngCruncher NOOP_CRUNCHER = + static final PngCruncher NOOP_CRUNCHER = new PngCruncher() { @Override public int start() { @@ -111,8 +112,7 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { } @Override - public void end(int key) throws InterruptedException { - } + public void end(int key) throws InterruptedException {} @Override public void crunchPng(int key, @NonNull File source, @NonNull File destination) @@ -126,6 +126,38 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { } }; + /** + * The merged {@link Options#resourcesOutput} is only used for validation and not for running + * (unlike the final APK), so the image files do not need to be the true image files. We only need + * the filenames to be the same. + * + * <p>Thus, we only create empty files for PNGs (convenient with a custom PngCruncher object). + * This does miss out on other image files like .webp. + */ + static final PngCruncher STUB_CRUNCHER = + new PngCruncher() { + + @Override + public void crunchPng(int key, File from, File to) throws PngException { + try { + to.createNewFile(); + if (!to.setLastModified(System.currentTimeMillis())) { + throw new PngException("Could not set milliseconds"); + } + } catch (IOException e) { + throw new PngException(e); + } + } + + @Override + public int start() { + return 0; + } + + @Override + public void end(int key) {} + }; + private final Path destination; private final Map<String, ResourceValuesDefinitions> valueTags = new HashMap<>(); @@ -166,6 +198,26 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { NOOP_CRUNCHER, MoreExecutors.newDirectExecutorService()); } + + /** + * Creates a new writer for processing android libraries. + * + * <p>This writer has stub png cruncher that touches empty files for png resources. + * + * @param manifestDirectory The base directory for the AndroidManifest. + * @param resourceDirectory The directory to copy resources into. + * @param assetsDirectory The directory to copy assets into. + * @param executorService An execution service for multi-threaded writing. + * @return A new {@link AndroidDataWriter}. + */ + public static AndroidDataWriter createForLibrary( + Path manifestDirectory, + Path resourceDirectory, + Path assetsDirectory, + ListeningExecutorService executorService) { + return createWith( + manifestDirectory, resourceDirectory, assetsDirectory, STUB_CRUNCHER, executorService); + } /** * Creates a new writer. 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 6e7d6290a0..c549730dae 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 @@ -28,12 +28,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -/** Collects all the functionationality for an action to merge resources. */ +/** Collects all the functionality for an action to merge resources. */ +// TODO(bazel-team): Turn into an instance object, in order to use an external ExecutorService. public class AndroidResourceMerger { static final Logger logger = Logger.getLogger(AndroidResourceProcessor.class.getName()); /** Merges all secondary resources with the primary resources. */ - static MergedAndroidData mergeData( + public static MergedAndroidData mergeData( final ParsedAndroidData primary, final Path primaryManifest, final List<? extends SerializedAndroidData> direct, @@ -53,9 +54,14 @@ public class AndroidResourceMerger { AndroidDataMerger merger = AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer); UnwrittenMergedAndroidData merged = - merger.loadAndMerge( - transitive, direct, primary, primaryManifest, type != VariantType.LIBRARY); - logger.fine(String.format("merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + mergeData( + executorService, + transitive, + direct, + primary, + primaryManifest, + type != VariantType.LIBRARY, + deserializer); timer.reset().start(); if (symbolsOut != null) { AndroidDataSerializer serializer = AndroidDataSerializer.create(); @@ -84,6 +90,26 @@ public class AndroidResourceMerger { } } + public static UnwrittenMergedAndroidData mergeData( + ListeningExecutorService executorService, + List<? extends SerializedAndroidData> transitive, + List<? extends SerializedAndroidData> direct, + ParsedAndroidData primary, + Path primaryManifest, + boolean allowPrimaryOverrideAll, + AndroidDataDeserializer deserializer) + throws MergingException { + Stopwatch timer = Stopwatch.createStarted(); + try { + AndroidDataMerger merger = + AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer); + return merger.loadAndMerge( + transitive, direct, primary, primaryManifest, allowPrimaryOverrideAll); + } finally { + logger.fine(String.format("merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + } + } + /** * Merges all secondary resources with the primary resources, given that the primary resources * have not yet been parsed and serialized. @@ -153,3 +179,4 @@ public class AndroidResourceMerger { } } + diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java index f5f49f6894..6db47d1d36 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java @@ -14,14 +14,13 @@ package com.google.devtools.build.android; import com.android.builder.core.VariantType; -import com.android.ide.common.internal.PngCruncher; -import com.android.ide.common.internal.PngException; import com.android.ide.common.res2.MergingException; import com.android.utils.StdLogger; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.base.Strings; -import com.google.common.io.Files; +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.ExistingPathConverter; import com.google.devtools.build.android.Converters.PathConverter; @@ -30,11 +29,13 @@ import com.google.devtools.build.android.Converters.SerializedAndroidDataListCon import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; -import java.io.File; +import java.io.Closeable; import java.io.IOException; import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -166,117 +167,115 @@ public class AndroidResourceMergingAction { Preconditions.checkNotNull(options.primaryData); Preconditions.checkNotNull(options.primaryManifest); - Preconditions.checkNotNull(options.classJarOutput); + + final ListeningExecutorService executorService = + MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15)); try (ScopedTemporaryDirectory scopedTmp = new ScopedTemporaryDirectory("android_resource_merge_tmp")) { - Path tmp = scopedTmp.getPath(); - Path mergedAssets = tmp.resolve("merged_assets"); - Path mergedResources = tmp.resolve("merged_resources"); - Path generatedSources = tmp.resolve("generated_resources"); - Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml"); - - logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); - - VariantType packageType = VariantType.LIBRARY; - AndroidResourceClassWriter resourceClassWriter = - AndroidResourceClassWriter.createWith(aaptConfigOptions.androidJar, - generatedSources, - Strings.nullToEmpty(options.packageForR)); - resourceClassWriter.setIncludeClassFile(true); - resourceClassWriter.setIncludeJavaFile(false); - - final MergedAndroidData mergedData = - AndroidResourceMerger.mergeData( - options.primaryData, - options.primaryManifest, - options.directData, - options.transitiveData, - mergedResources, - mergedAssets, - new StubPngCruncher(), - packageType, - options.symbolsBinOut, - resourceClassWriter); - - logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); - // Until enough users with manifest placeholders migrate to the new manifest merger, - // we need to replace ${applicationId} and ${packageName} with options.packageForR to make - // the manifests compatible with the old manifest merger. - if (options.manifestOutput != null) { - MergedAndroidData processedData = + try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) { + Path tmp = scopedTmp.getPath(); + Path mergedAssets = tmp.resolve("merged_assets"); + Path mergedResources = tmp.resolve("merged_resources"); + Path generatedSources = tmp.resolve("generated_resources"); + Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml"); + + logger.fine(String.format("Setup finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + timer.reset().start(); + final UnwrittenMergedAndroidData unwrittenMergedData = + AndroidResourceMerger.mergeData( + executorService, + options.transitiveData, + options.directData, + AndroidDataDeserializer.deserializeSingleAndroidData(options.primaryData), + options.primaryManifest, + false /* allow binary overrides */, + AndroidDataDeserializer.create()); + + logger.fine(String.format("merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + timer.reset().start(); + + if (options.symbolsBinOut != null) { + AndroidDataSerializer serializer = AndroidDataSerializer.create(); + unwrittenMergedData.serializeTo(serializer); + serializer.flushTo(options.symbolsBinOut); + } + + if (options.classJarOutput != null) { + AndroidResourceClassWriter resourceClassWriter = + AndroidResourceClassWriter.createWith( + aaptConfigOptions.androidJar, + generatedSources, + Strings.nullToEmpty(options.packageForR)); + resourceClassWriter.setIncludeClassFile(true); + resourceClassWriter.setIncludeJavaFile(false); + unwrittenMergedData.writeResourceClass(resourceClassWriter); + logger.fine( + String.format( + "Write classes finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + timer.reset().start(); + AndroidResourceOutputs.createClassJar(generatedSources, options.classJarOutput); + logger.fine( + String.format( + "Create classJar finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + timer.reset().start(); + } + + if (options.resourcesOutput != null) { + AndroidDataWriter mergedDataWriter = + AndroidDataWriter.createForLibrary( + tmp, mergedResources, mergedAssets, executorService); + MergedAndroidData mergedData = unwrittenMergedData.write(mergedDataWriter); + + Path resourcesDir = + AndroidResourceProcessor.processDataBindings( + mergedData.getResourceDir(), + options.dataBindingInfoOut, + VariantType.LIBRARY, + options.packageForR, + options.primaryManifest); + + // For now, try compressing the library resources that we pass to the validator. + // This takes extra CPU resources to pack and unpack (~2x), but can reduce the + // zip size (~4x). + try { + Files.createDirectories(options.resourcesOutput.getParent()); + AndroidResourceOutputs.createResourcesZip( + resourcesDir, + mergedData.getAssetDir(), + options.resourcesOutput, + true /* compress */); + logger.fine( + String.format( + "Resource Zip finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + timer.reset().start(); + } catch (IOException e) { + throw new RuntimeException(e); + } + // Until enough users with manifest placeholders migrate to the new manifest merger, + // we need to replace ${applicationId} and ${packageName} with options.packageForR to make + // the manifests compatible with the old manifest merger. + if (options.manifestOutput != null) { AndroidManifestProcessor.with(stdLogger) .processManifest( - packageType, + VariantType.LIBRARY, options.packageForR, null, /* applicationId */ -1, /* versionCode */ null, /* versionName */ mergedData, processedManifest); - AndroidResourceOutputs.copyManifestToOutput(processedData, options.manifestOutput); - } - - AndroidResourceOutputs.createClassJar(generatedSources, options.classJarOutput); - - logger.fine( - String.format("Create classJar finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); - - if (options.resourcesOutput != null) { - Path resourcesDir = - AndroidResourceProcessor.processDataBindings( - mergedData.getResourceDir(), - options.dataBindingInfoOut, - packageType, - options.packageForR, - options.primaryManifest); - - // For now, try compressing the library resources that we pass to the validator. This takes - // extra CPU resources to pack and unpack (~2x), but can reduce the zip size (~4x). - AndroidResourceOutputs.createResourcesZip( - resourcesDir, mergedData.getAssetDir(), options.resourcesOutput, true /* compress */); - logger.fine( - String.format( - "Create resources.zip finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + AndroidResourceOutputs.copyManifestToOutput(mergedData, options.manifestOutput); + } + } + } catch (MergingException e) { + logger.log(Level.SEVERE, "Error during merging resources", e); + throw e; + } catch (Exception e) { + logger.log(Level.SEVERE, "Unexpected", e); + throw e; } - } catch (MergingException e) { - logger.log(Level.SEVERE, "Error during merging resources", e); - throw e; - } catch (Exception e) { - logger.log(Level.SEVERE, "Unexpected", e); - throw e; } - logger.fine(String.format("Resources merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); - } - - /** - * The merged {@link Options#resourcesOutput} is only used for validation and not for running - * (unlike the final APK), so the image files do not need to be the true image files. We only need - * the filenames to be the same. - * - * <p>Thus, we only create empty files for PNGs (convenient with a custom PngCruncher object). - * This does miss out on other image files like .webp. - */ - private static final class StubPngCruncher implements PngCruncher { - - @Override - public void crunchPng(int key, File from, File to) throws PngException { - try { - Files.touch(to); - } catch (IOException e) { - throw new PngException(e); - } - } - - @Override - public int start() { - return 0; - } - - @Override - public void end(int key) { - } - } } diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java index 836c3855cb..1c9982aecc 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java @@ -28,7 +28,6 @@ import com.android.builder.model.AaptOptions; import com.android.ide.common.internal.CommandLineRunner; import com.android.ide.common.internal.ExecutorSingleton; import com.android.ide.common.internal.LoggedErrorException; -import com.android.ide.common.res2.MergingException; import com.android.io.FileWrapper; import com.android.io.StreamException; import com.android.repository.Revision; @@ -46,7 +45,6 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.devtools.build.android.Converters.ExistingPathConverter; import com.google.devtools.build.android.Converters.RevisionConverter; -import com.google.devtools.build.android.ParsedAndroidData.Builder; import com.google.devtools.build.android.SplitConfigurationFilter.UnrecognizedSplitsException; import com.google.devtools.build.android.resources.RClassGenerator; import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter; @@ -734,55 +732,4 @@ public class AndroidResourceProcessor { } return Files.createDirectories(out); } - - /** Deserializes a list of serialized resource paths to a {@link ParsedAndroidData}. */ - public ParsedAndroidData deserializeSymbolsToData(List<Path> symbolPaths) - throws IOException, MergingException { - AndroidDataDeserializer deserializer = AndroidDataDeserializer.create(); - final ListeningExecutorService executorService = - MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15)); - final Builder deserializedDataBuilder = ParsedAndroidData.Builder.newBuilder(); - try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) { - List<ListenableFuture<Boolean>> deserializing = new ArrayList<>(); - for (final Path symbolPath : symbolPaths) { - deserializing.add( - executorService.submit( - new Deserialize(deserializer, symbolPath, deserializedDataBuilder))); - } - FailedFutureAggregator<MergingException> aggregator = - FailedFutureAggregator.createForMergingExceptionWithMessage( - "Failure(s) during dependency parsing"); - aggregator.aggregateAndMaybeThrow(deserializing); - } - return deserializedDataBuilder.build(); - } - - /** Task to deserialize resources from a path. */ - private static final class Deserialize implements Callable<Boolean> { - - private final Path symbolPath; - - private final Builder finalDataBuilder; - private final AndroidDataDeserializer deserializer; - - private Deserialize( - AndroidDataDeserializer deserializer, Path symbolPath, Builder finalDataBuilder) { - this.deserializer = deserializer; - this.symbolPath = symbolPath; - this.finalDataBuilder = finalDataBuilder; - } - - @Override - public Boolean call() throws Exception { - final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder(); - deserializer.read(symbolPath, parsedDataBuilder.consumers()); - // The builder isn't threadsafe, so synchronize the copyTo call. - synchronized (finalDataBuilder) { - // All the resources are sorted before writing, so they can be aggregated in - // whatever order here. - parsedDataBuilder.copyTo(finalDataBuilder); - } - return Boolean.TRUE; - } - } } diff --git a/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java b/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java index 51c771946b..1cb816c01e 100644 --- a/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java @@ -93,11 +93,10 @@ public class LibraryRClassGeneratorAction { Strings.nullToEmpty(options.packageForR)); resourceClassWriter.setIncludeClassFile(true); resourceClassWriter.setIncludeJavaFile(false); - final AndroidResourceProcessor resourceProcessor = new AndroidResourceProcessor(stdLogger); logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); final ParsedAndroidData data = - resourceProcessor.deserializeSymbolsToData(options.symbols); + AndroidDataDeserializer.deserializeSymbolsToData(options.symbols); logger.fine( String.format("Deserialization finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); |