aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java188
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainerConverter.java77
-rw-r--r--src/test/shell/bazel/android/BUILD1
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java4
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java53
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java18
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java250
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java23
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java80
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/BUILD8
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/BUILD.tools8
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Converters.java77
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java80
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java135
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java6
-rw-r--r--tools/android/BUILD5
21 files changed, 898 insertions, 131 deletions
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
new file mode 100644
index 0000000000..8a5310331b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
@@ -0,0 +1,188 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.rules.android;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+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.ActionConstructionContext;
+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.rules.android.AndroidResourcesProvider.ResourceContainer;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
+import com.google.devtools.build.lib.rules.android.ResourceContainerConverter.Builder.SeparatorType;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder for creating $android_resource_merger action. The action merges resources and generates
+ * the merged R classes for an android_library to hand off to java compilation of the library
+ * sources. It also generates a merged resources zip file to pass on to the
+ * $android_resource_validator action. For android_binary, see {@link
+ * AndroidResourcesProcessorBuilder}.
+ */
+class AndroidResourceMergingActionBuilder {
+
+ private static final ResourceContainerConverter.ToArtifacts RESOURCE_CONTAINER_TO_ARTIFACTS =
+ ResourceContainerConverter.builder()
+ .includeResourceRoots()
+ .includeSymbolsBin()
+ .toArtifactConverter();
+ private static final ResourceContainerConverter.ToArg RESOURCE_CONTAINER_TO_ARG =
+ ResourceContainerConverter.builder()
+ .includeResourceRoots()
+ .includeLabel()
+ .includeSymbolsBin()
+ .withSeparator(SeparatorType.SEMICOLON_AMPERSAND)
+ .toArgConverter();
+
+ private final RuleContext ruleContext;
+ private final AndroidSdkProvider sdk;
+
+ // Inputs
+ private ResourceContainer primary;
+ private ResourceDependencies dependencies;
+
+ // Outputs
+ private Artifact mergedResourcesOut;
+ private Artifact classJarOut;
+ private Artifact manifestOut;
+
+ // Flags
+ private String customJavaPackage;
+
+ /** @param ruleContext The RuleContext that was used to create the SpawnAction.Builder. */
+ public AndroidResourceMergingActionBuilder(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ this.sdk = AndroidSdkProvider.fromRuleContext(ruleContext);
+ }
+
+ /**
+ * The primary resource for merging. This resource will overwrite any resource or data value in
+ * the transitive closure.
+ */
+ public AndroidResourceMergingActionBuilder withPrimary(ResourceContainer primary) {
+ this.primary = primary;
+ return this;
+ }
+
+ public AndroidResourceMergingActionBuilder withDependencies(ResourceDependencies resourceDeps) {
+ this.dependencies = resourceDeps;
+ return this;
+ }
+
+ public AndroidResourceMergingActionBuilder setMergedResourcesOut(Artifact mergedResourcesOut) {
+ this.mergedResourcesOut = mergedResourcesOut;
+ return this;
+ }
+
+ public AndroidResourceMergingActionBuilder setClassJarOut(Artifact classJarOut) {
+ this.classJarOut = classJarOut;
+ return this;
+ }
+
+ public AndroidResourceMergingActionBuilder setManifestOut(Artifact manifestOut) {
+ this.manifestOut = manifestOut;
+ return this;
+ }
+
+ public AndroidResourceMergingActionBuilder setJavaPackage(String customJavaPackage) {
+ this.customJavaPackage = customJavaPackage;
+ return this;
+ }
+
+ public ResourceContainer build(ActionConstructionContext context) {
+ CustomCommandLine.Builder builder = new CustomCommandLine.Builder();
+
+ // Use a FluentIterable to avoid flattening the NestedSets
+ NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder();
+ inputs.addAll(
+ ruleContext
+ .getExecutablePrerequisite("$android_resource_merger", Mode.HOST)
+ .getRunfilesSupport()
+ .getRunfilesArtifactsWithoutMiddlemen());
+
+ builder.addExecPath("--androidJar", sdk.getAndroidJar());
+ inputs.add(sdk.getAndroidJar());
+
+ Preconditions.checkNotNull(primary);
+ builder.add("--primaryData").add(RESOURCE_CONTAINER_TO_ARG.apply(primary));
+ inputs.addTransitive(RESOURCE_CONTAINER_TO_ARTIFACTS.apply(primary));
+
+ Preconditions.checkNotNull(primary.getManifest());
+ builder.addExecPath("--primaryManifest", primary.getManifest());
+ inputs.add(primary.getManifest());
+
+ 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 (mergedResourcesOut != null) {
+ builder.addExecPath("--resourcesOutput", mergedResourcesOut);
+ outs.add(mergedResourcesOut);
+ }
+
+ // For now, do manifest processing to remove placeholders that aren't handled by the legacy
+ // manifest merger. Remove this once enough users migrate over to the new manifest merger.
+ if (manifestOut != null) {
+ builder.addExecPath("--manifestOutput", manifestOut);
+ outs.add(manifestOut);
+ }
+
+ if (!Strings.isNullOrEmpty(customJavaPackage)) {
+ // Sets an alternative java package for the generated R.java
+ // this allows android rules to generate resources outside of the java{,tests} tree.
+ builder.add("--packageForR").add(customJavaPackage);
+ }
+
+ SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
+ // Create the spawn action.
+ ruleContext.registerAction(
+ spawnActionBuilder
+ .addTransitiveInputs(inputs.build())
+ .addOutputs(ImmutableList.copyOf(outs))
+ .setCommandLine(builder.build())
+ .setExecutable(
+ ruleContext.getExecutablePrerequisite("$android_resource_merger", Mode.HOST))
+ .setProgressMessage("Merging Android resources for " + ruleContext.getLabel())
+ .setMnemonic("AndroidResourceMerger")
+ .build(context));
+
+ // Return the full set of processed transitive dependencies.
+ // TODO(jvoung): pass the classJar out -- once that is a field of ResourceContainer.
+ return new ResourceContainer(
+ primary.getLabel(),
+ primary.getJavaPackage(),
+ primary.getRenameManifestPackage(),
+ primary.getConstantsInlined(),
+ primary.getApk(),
+ manifestOut != null ? manifestOut : primary.getManifest(),
+ primary.getJavaSourceJar(),
+ primary.getArtifacts(ResourceType.ASSETS),
+ primary.getArtifacts(ResourceType.RESOURCES),
+ primary.getRoots(ResourceType.ASSETS),
+ primary.getRoots(ResourceType.RESOURCES),
+ primary.isManifestExported(),
+ primary.getRTxt(),
+ primary.getSymbolsTxt());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
index 3c2b915920..789c3dcacf 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
+import com.google.devtools.build.lib.rules.android.ResourceContainerConverter.Builder.SeparatorType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -50,6 +51,7 @@ public class AndroidResourcesProcessorBuilder {
ResourceContainerConverter.builder()
.includeResourceRoots()
.includeManifest()
+ .withSeparator(SeparatorType.COLON_COMMA)
.toArgConverter();
private static final ResourceContainerConverter.ToArg RESOURCE_DEP_TO_ARG =
@@ -58,6 +60,7 @@ public class AndroidResourcesProcessorBuilder {
.includeManifest()
.includeRTxt()
.includeSymbolsBin()
+ .withSeparator(SeparatorType.COLON_COMMA)
.toArgConverter();
private ResourceContainer primary;
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 dc1312662e..6e14804023 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
@@ -169,6 +169,7 @@ public final class AndroidRuleClasses {
public static final String DEFAULT_MANIFEST_MERGER = "//tools/android:manifest_merger";
public static final String DEFAULT_RCLASS_GENERATOR = "//tools/android:rclass_generator";
public static final String DEFAULT_RESOURCES_PROCESSOR = "//tools/android:resources_processor";
+ public static final String DEFAULT_RESOURCE_MERGER = "//tools/android:resource_merger";
public static final String DEFAULT_RESOURCE_PARSER = "//tools/android:resource_parser";
public static final String DEFAULT_RESOURCE_SHRINKER = "//tools/android:resource_shrinker";
public static final String DEFAULT_SDK = "//tools/android:sdk";
@@ -408,6 +409,8 @@ public final class AndroidRuleClasses {
env.getToolsLabel(DEFAULT_RCLASS_GENERATOR)))
.add(attr("$android_resources_processor", LABEL).cfg(HOST).exec().value(
env.getToolsLabel(DEFAULT_RESOURCES_PROCESSOR)))
+ .add(attr("$android_resource_merger", LABEL).cfg(HOST).exec().value(
+ env.getToolsLabel(DEFAULT_RESOURCE_MERGER)))
.add(attr("$android_resource_parser", LABEL).cfg(HOST).exec().value(
env.getToolsLabel(DEFAULT_RESOURCE_PARSER)))
.add(attr("$android_resource_shrinker", LABEL).cfg(HOST).exec().value(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainerConverter.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainerConverter.java
index b271e90861..d86ada91c5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainerConverter.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainerConverter.java
@@ -17,6 +17,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -42,24 +43,44 @@ public class ResourceContainerConverter {
return new Builder();
}
- interface ToArg extends Function<ResourceContainer, String> {}
+ interface ToArg extends Function<ResourceContainer, String> {
- interface ToArtifacts extends Function<ResourceContainer, NestedSet<Artifact>> {}
+ String listSeparator();
+ }
+
+ interface ToArtifacts extends Function<ResourceContainer, NestedSet<Artifact>> {
+
+ }
static class Builder {
private boolean includeResourceRoots;
+ private boolean includeLabel;
private boolean includeManifest;
private boolean includeRTxt;
private boolean includeSymbolsBin;
+ private SeparatorType separatorType;
+ private Joiner argJoiner;
+ private Function<String, String> escaper = Functions.identity();
+
+ enum SeparatorType {
+ COLON_COMMA,
+ SEMICOLON_AMPERSAND
+ }
- Builder() {}
+ Builder() {
+ }
Builder includeResourceRoots() {
includeResourceRoots = true;
return this;
}
+ Builder includeLabel() {
+ includeLabel = true;
+ return this;
+ }
+
Builder includeManifest() {
includeManifest = true;
return this;
@@ -75,9 +96,35 @@ public class ResourceContainerConverter {
return this;
}
- private static final Joiner ARG_JOINER = Joiner.on(":");
+ Builder withSeparator(SeparatorType type) {
+ separatorType = type;
+ return this;
+ }
ToArg toArgConverter() {
+ switch (separatorType) {
+ case COLON_COMMA:
+ argJoiner = Joiner.on(":");
+ // We currently use ":" to separate components of an argument and "," to separate
+ // arguments in a list of arguments. Those characters require escaping if used in a label
+ // (part of the set of allowed characters in a label).
+ if (includeLabel) {
+ escaper = new Function<String, String>() {
+ @Override
+ public String apply(String input) {
+ return input.replace(":", "\\:").replace(",", "\\,");
+ }
+ };
+ }
+ break;
+ case SEMICOLON_AMPERSAND:
+ argJoiner = Joiner.on(";");
+ break;
+ default:
+ Preconditions.checkState(false, "Unknown separator type " + separatorType);
+ break;
+ }
+
return new ToArg() {
@Override
public String apply(ResourceContainer container) {
@@ -86,6 +133,9 @@ public class ResourceContainerConverter {
cmdPieces.add(convertRoots(container, ResourceType.RESOURCES));
cmdPieces.add(convertRoots(container, ResourceType.ASSETS));
}
+ if (includeLabel) {
+ cmdPieces.add(escaper.apply(container.getLabel().toString()));
+ }
if (includeManifest) {
cmdPieces.add(container.getManifest().getExecPathString());
}
@@ -99,7 +149,20 @@ public class ResourceContainerConverter {
? ""
: container.getSymbolsTxt().getExecPathString());
}
- return ARG_JOINER.join(cmdPieces.build());
+ return argJoiner.join(cmdPieces.build());
+ }
+
+ @Override
+ public String listSeparator() {
+ switch (separatorType) {
+ case COLON_COMMA:
+ return ",";
+ case SEMICOLON_AMPERSAND:
+ return "&";
+ default:
+ Preconditions.checkState(false, "Unknown separator type " + separatorType);
+ return null;
+ }
}
};
}
@@ -161,7 +224,7 @@ public class ResourceContainerConverter {
if (!dependencies.getTransitiveResources().isEmpty()) {
cmdBuilder.addJoinStrings(
"--data",
- ",",
+ toArg.listSeparator(),
Iterables.unmodifiableIterable(
Iterables.transform(dependencies.getTransitiveResources(), toArg)));
}
@@ -170,7 +233,7 @@ public class ResourceContainerConverter {
if (!dependencies.getDirectResources().isEmpty()) {
cmdBuilder.addJoinStrings(
"--directData",
- ",",
+ toArg.listSeparator(),
Iterables.unmodifiableIterable(
Iterables.transform(dependencies.getDirectResources(), toArg)));
}
diff --git a/src/test/shell/bazel/android/BUILD b/src/test/shell/bazel/android/BUILD
index ac44c592d9..3861a9e1f5 100644
--- a/src/test/shell/bazel/android/BUILD
+++ b/src/test/shell/bazel/android/BUILD
@@ -17,6 +17,7 @@ sh_test(
"//external:android_ndk_for_testing",
"//external:android_sdk_for_testing",
"//src/tools/android/java/com/google/devtools/build/android:AarGeneratorAction_deploy.jar",
+ "//src/tools/android/java/com/google/devtools/build/android:AndroidResourceMergingAction_deploy.jar",
"//src/tools/android/java/com/google/devtools/build/android:AndroidResourceParsingAction_deploy.jar",
"//src/tools/android/java/com/google/devtools/build/android:AndroidResourceProcessingAction_deploy.jar",
"//src/tools/android/java/com/google/devtools/build/android:RClassGeneratorAction_deploy.jar",
diff --git a/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java b/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java
index dcf0c56ab1..e1175115bc 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java
@@ -82,8 +82,8 @@ public class AarGeneratorAction {
category = "input",
help = "Additional Data dependencies. These values will be used if not defined in "
+ "the primary resources. The expected format is "
- + "resources[#resources]:assets[#assets]:manifest:r.txt"
- + "[,resources[#resources]:assets[#assets]:manifest:r.txt]")
+ + DependencyAndroidData.EXPECTED_FORMAT
+ + "[,...]")
public List<DependencyAndroidData> dependencyData;
@Option(name = "manifest",
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 f330b95f9e..d682144ef7 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
@@ -52,12 +52,12 @@ public class AndroidDataMerger {
private final AndroidDataSerializer serializer;
- private final DependencyAndroidData dependency;
+ private final SerializedAndroidData dependency;
private final Builder targetBuilder;
private ParseDependencyDataTask(
- AndroidDataSerializer serializer, DependencyAndroidData dependency, Builder targetBuilder) {
+ AndroidDataSerializer serializer, SerializedAndroidData dependency, Builder targetBuilder) {
this.serializer = serializer;
this.dependency = dependency;
this.targetBuilder = targetBuilder;
@@ -72,11 +72,10 @@ public class AndroidDataMerger {
if (!e.isLegacy()) {
throw new MergingException(e);
}
- //TODO(corysmith): List the offending target here.
logger.fine(
String.format(
"\u001B[31mDEPRECATION:\u001B[0m Legacy resources used for %s",
- dependency.getManifest()));
+ dependency.getLabel()));
// Legacy android resources -- treat them as direct dependencies.
dependency.walk(ParsedAndroidDataBuildingPathWalker.create(parsedDataBuilder));
}
@@ -187,15 +186,17 @@ public class AndroidDataMerger {
}
/**
- * Merges a list of {@link DependencyAndroidData} with a {@link UnvalidatedAndroidData}.
+ * Loads a list of dependency {@link SerializedAndroidData} and merge with the primary {@link
+ * ParsedAndroidData}.
*
* @see AndroidDataMerger#merge(ParsedAndroidData, ParsedAndroidData, UnvalidatedAndroidData,
- * boolean) for details.
+ * boolean) for details.
*/
- UnwrittenMergedAndroidData merge(
- List<DependencyAndroidData> transitive,
- List<DependencyAndroidData> direct,
- UnvalidatedAndroidData primary,
+ UnwrittenMergedAndroidData loadAndMerge(
+ List<? extends SerializedAndroidData> transitive,
+ List<? extends SerializedAndroidData> direct,
+ ParsedAndroidData primary,
+ Path primaryManifest,
boolean allowPrimaryOverrideAll)
throws MergingException {
Stopwatch timer = Stopwatch.createStarted();
@@ -204,12 +205,12 @@ public class AndroidDataMerger {
final ParsedAndroidData.Builder transitiveBuilder = ParsedAndroidData.Builder.newBuilder();
final AndroidDataSerializer serializer = AndroidDataSerializer.create();
final List<ListenableFuture<Boolean>> tasks = new ArrayList<>();
- for (final DependencyAndroidData dependency : direct) {
+ for (final SerializedAndroidData dependency : direct) {
tasks.add(
executorService.submit(
new ParseDependencyDataTask(serializer, dependency, directBuilder)));
}
- for (final DependencyAndroidData dependency : transitive) {
+ for (final SerializedAndroidData dependency : transitive) {
tasks.add(
executorService.submit(
new ParseDependencyDataTask(serializer, dependency, transitiveBuilder)));
@@ -222,8 +223,12 @@ public class AndroidDataMerger {
logger.fine(
String.format("Merged dependencies read in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
timer.reset().start();
- return merge(
- transitiveBuilder.build(), directBuilder.build(), primary, allowPrimaryOverrideAll);
+ return doMerge(
+ transitiveBuilder.build(),
+ directBuilder.build(),
+ primary,
+ primaryManifest,
+ allowPrimaryOverrideAll);
} finally {
logger.fine(String.format("Resources merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
}
@@ -288,7 +293,7 @@ public class AndroidDataMerger {
* @return An UnwrittenMergedAndroidData, containing DataResource objects that can be written to
* disk for aapt processing or serialized for future merge passes.
* @throws MergingException if there are merge conflicts or issues with parsing resources from
- * Primary.
+ * primaryData.
*/
UnwrittenMergedAndroidData merge(
ParsedAndroidData transitive,
@@ -296,18 +301,30 @@ public class AndroidDataMerger {
UnvalidatedAndroidData primaryData,
boolean allowPrimaryOverrideAll)
throws MergingException {
-
try {
// Extract the primary resources.
ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primaryData);
+ return doMerge(
+ transitive, direct, parsedPrimary, primaryData.getManifest(), allowPrimaryOverrideAll);
+ } catch (IOException e) {
+ throw new MergingException(e);
+ }
+ }
+ private UnwrittenMergedAndroidData doMerge(
+ ParsedAndroidData transitive,
+ ParsedAndroidData direct,
+ ParsedAndroidData parsedPrimary,
+ Path primaryManifest,
+ boolean allowPrimaryOverrideAll)
+ throws MergingException {
+ try {
// Create the builders for the final parsed data.
final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder();
final ParsedAndroidData.Builder transitiveBuilder = ParsedAndroidData.Builder.newBuilder();
final KeyValueConsumers transitiveConsumers = transitiveBuilder.consumers();
final KeyValueConsumers primaryConsumers = primaryBuilder.consumers();
-
final Set<MergeConflict> conflicts = new HashSet<>();
conflicts.addAll(parsedPrimary.conflicts());
for (MergeConflict conflict : Iterables.concat(direct.conflicts(), transitive.conflicts())) {
@@ -420,7 +437,7 @@ public class AndroidDataMerger {
}
return UnwrittenMergedAndroidData.of(
- primaryData.getManifest(),
+ primaryManifest,
primaryBuilder.build(),
transitiveBuilder.build());
} catch (IOException e) {
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 4c3118e61b..9140cb7ec4 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
@@ -62,6 +62,8 @@ public class AndroidResourceClassWriter implements Flushable {
private final AndroidFrameworkAttrIdProvider androidIdProvider;
private final Path outputBasePath;
private final String packageName;
+ private boolean includeClassFile = true;
+ private boolean includeJavaFile = true;
private final Map<ResourceType, Set<String>> innerClasses = new EnumMap<>(ResourceType.class);
private final Map<String, Map<String, Boolean>> styleableAttrs = new HashMap<>();
@@ -79,6 +81,14 @@ public class AndroidResourceClassWriter implements Flushable {
this.packageName = packageName;
}
+ public void setIncludeClassFile(boolean include) {
+ this.includeClassFile = include;
+ }
+
+ public void setIncludeJavaFile(boolean include) {
+ this.includeJavaFile = include;
+ }
+
public void writeSimpleResource(ResourceType type, String name) {
Set<String> fields = innerClasses.get(type);
if (fields == null) {
@@ -140,8 +150,12 @@ public class AndroidResourceClassWriter implements Flushable {
throw new IOException(e);
}
- writeAsJava(initializers);
- writeAsClass(initializers);
+ if (includeClassFile) {
+ writeAsClass(initializers);
+ }
+ if (includeJavaFile) {
+ writeAsJava(initializers);
+ }
}
/**
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
new file mode 100644
index 0000000000..3d70fd07cc
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java
@@ -0,0 +1,250 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.core.VariantConfiguration.Type;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.PngCruncher;
+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.io.Files;
+import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions;
+import com.google.devtools.build.android.Converters.ExistingPathConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.Converters.SerializedAndroidDataConverter;
+import com.google.devtools.build.android.Converters.SerializedAndroidDataListConverter;
+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.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Provides an entry point for the resource merging action. After merging, this action generates the
+ * R.class files required to compile the rest of the java sources.
+ *
+ * <p>This action only generates the class jar. The R source jar is generated by AAPT at a later
+ * time and off of the critical path, by {@link AndroidResourceValidatorAction}. That way, the
+ * source will contain javadocs derived from comments in the .xml files. Ideally users wouldn't use
+ * the javadoc, but instead generate documentation directly from the source .xml files.
+ */
+public class AndroidResourceMergingAction {
+
+ private static final StdLogger stdLogger = new StdLogger(StdLogger.Level.WARNING);
+
+ private static final Logger logger =
+ Logger.getLogger(AndroidResourceMergingAction.class.getName());
+
+ /** Flag specifications for this action. */
+ public static final class Options extends OptionsBase {
+
+ @Option(
+ name = "primaryData",
+ defaultValue = "null",
+ converter = SerializedAndroidDataConverter.class,
+ category = "input",
+ help =
+ "The directory containing the primary resource directory. The contents will override"
+ + " the contents of any other resource directories during merging."
+ + " The expected format is " + SerializedAndroidData.EXPECTED_FORMAT
+ )
+ public SerializedAndroidData primaryData;
+
+ @Option(
+ name = "primaryManifest",
+ defaultValue = "null",
+ converter = ExistingPathConverter.class,
+ category = "input",
+ help = "Path to primary resource's manifest file."
+ )
+ public Path primaryManifest;
+
+ @Option(
+ name = "data",
+ defaultValue = "",
+ converter = SerializedAndroidDataListConverter.class,
+ category = "input",
+ help =
+ "Transitive Data dependencies. These values will be used if not defined in the "
+ + "primary resources. The expected format is "
+ + SerializedAndroidData.EXPECTED_FORMAT + "[&...]"
+ )
+ public List<SerializedAndroidData> transitiveData;
+
+ @Option(
+ name = "directData",
+ defaultValue = "",
+ converter = SerializedAndroidDataListConverter.class,
+ category = "input",
+ help =
+ "Direct Data dependencies. These values will be used if not defined in the "
+ + "primary resources. The expected format is "
+ + SerializedAndroidData.EXPECTED_FORMAT + "[&...]"
+ )
+ public List<SerializedAndroidData> directData;
+
+ @Option(
+ name = "resourcesOutput",
+ defaultValue = "null",
+ converter = PathConverter.class,
+ category = "output",
+ help = "Path to the write merged resources archive."
+ )
+ public Path resourcesOutput;
+
+ @Option(
+ name = "classJarOutput",
+ defaultValue = "null",
+ converter = PathConverter.class,
+ category = "output",
+ help = "Path for the generated java class jar."
+ )
+ public Path classJarOutput;
+
+ @Option(
+ name = "manifestOutput",
+ defaultValue = "null",
+ converter = PathConverter.class,
+ category = "output",
+ help = "Path for the output processed AndroidManifest.xml."
+ )
+ public Path manifestOutput;
+
+ @Option(
+ name = "packageForR",
+ defaultValue = "null",
+ category = "config",
+ help = "Custom java package to generate the R symbols files."
+ )
+ public String packageForR;
+ }
+
+ public static void main(String[] args) throws Exception {
+ final Stopwatch timer = Stopwatch.createStarted();
+ OptionsParser optionsParser =
+ OptionsParser.newOptionsParser(Options.class, AaptConfigOptions.class);
+ optionsParser.parseAndExitUponError(args);
+ AaptConfigOptions aaptConfigOptions = optionsParser.getOptions(AaptConfigOptions.class);
+ Options options = optionsParser.getOptions(Options.class);
+
+ final AndroidResourceProcessor resourceProcessor = new AndroidResourceProcessor(stdLogger);
+
+ Preconditions.checkNotNull(options.primaryData);
+ Preconditions.checkNotNull(options.primaryManifest);
+ Preconditions.checkNotNull(options.classJarOutput);
+
+ 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)));
+
+ VariantConfiguration.Type packageType = Type.LIBRARY;
+ String packageName = options.packageForR;
+ AndroidResourceClassWriter resourceClassWriter =
+ new AndroidResourceClassWriter(
+ new AndroidFrameworkAttrIdJar(aaptConfigOptions.androidJar),
+ generatedSources,
+ packageName);
+ resourceClassWriter.setIncludeClassFile(true);
+ resourceClassWriter.setIncludeJavaFile(false);
+
+ final MergedAndroidData mergedData =
+ resourceProcessor.mergeData(
+ options.primaryData,
+ options.primaryManifest,
+ options.directData,
+ options.transitiveData,
+ mergedResources,
+ mergedAssets,
+ new StubPngCruncher(),
+ packageType,
+ 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 =
+ resourceProcessor.processManifest(
+ packageType,
+ options.packageForR,
+ null, /* applicationId */
+ -1, /* versionCode */
+ null, /* versionName */
+ mergedData,
+ processedManifest);
+ resourceProcessor.copyManifestToOutput(processedData, options.manifestOutput);
+ }
+
+ resourceProcessor.createClassJar(generatedSources, options.classJarOutput);
+
+ logger.fine(
+ String.format("Create classJar finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ if (options.resourcesOutput != null) {
+ // 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).
+ resourceProcessor.createResourcesZip(
+ mergedData.getResourceDir(),
+ mergedData.getAssetDir(),
+ options.resourcesOutput,
+ true /* compress */);
+ logger.fine(
+ String.format(
+ "Create resources.zip finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+ } 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;
+ } finally {
+ resourceProcessor.shutdown();
+ }
+ 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(File from, File to)
+ throws InterruptedException, LoggedErrorException, IOException {
+ Files.touch(to);
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java
index e48440db48..65ff00aadf 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java
@@ -45,7 +45,7 @@ public class AndroidResourceParsingAction {
converter = UnvalidatedAndroidDirectoriesConverter.class,
category = "input",
help = "The resource and asset directories to parse and summarize in a symbols file."
- + " The expected format is resources[#resources]:assets[#assets]")
+ + " The expected format is " + UnvalidatedAndroidDirectories.EXPECTED_FORMAT)
public UnvalidatedAndroidDirectories primaryData;
@Option(name = "output",
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
index 7983256ec6..cc78940086 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
@@ -56,11 +56,11 @@ import java.util.logging.Logger;
* --adb path/to/sdk/adb\
* --zipAlign path/to/sdk/zipAlign\
* --androidJar path/to/sdk/androidJar\
- * --manifest path/to/manifest\
- * --primaryData path/to/resources:path/to/assets:path/to/manifest:path/to/R.txt
- * --data p/t/res1:p/t/assets1:p/t/1/AndroidManifest.xml:p/t/1/R.txt,\
- * p/t/res2:p/t/assets2:p/t/2/AndroidManifest.xml:p/t/2/R.txt
- * --packagePath path/to/write/archive.ap_
+ * --manifestOutput path/to/manifest\
+ * --primaryData path/to/resources:path/to/assets:path/to/manifest\
+ * --data p/t/res1:p/t/assets1:p/t/1/AndroidManifest.xml:p/t/1/R.txt:symbols,\
+ * p/t/res2:p/t/assets2:p/t/2/AndroidManifest.xml:p/t/2/R.txt:symbols\
+ * --packagePath path/to/write/archive.ap_\
* --srcJarOutput path/to/write/archive.srcjar
* </pre>
*/
@@ -80,7 +80,7 @@ public class AndroidResourceProcessingAction {
category = "input",
help = "The directory containing the primary resource directory. The contents will override"
+ " the contents of any other resource directories during merging. The expected format"
- + " is resources[|resources]:assets[|assets]:manifest")
+ + " is " + UnvalidatedAndroidData.EXPECTED_FORMAT)
public UnvalidatedAndroidData primaryData;
@Option(name = "data",
@@ -89,8 +89,8 @@ public class AndroidResourceProcessingAction {
category = "input",
help = "Transitive Data dependencies. These values will be used if not defined in the "
+ "primary resources. The expected format is "
- + "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin"
- + "[,resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin]")
+ + DependencyAndroidData.EXPECTED_FORMAT
+ + "[,...]")
public List<DependencyAndroidData> transitiveData;
@Option(name = "directData",
@@ -99,8 +99,8 @@ public class AndroidResourceProcessingAction {
category = "input",
help = "Direct Data dependencies. These values will be used if not defined in the "
+ "primary resources. The expected format is "
- + "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin"
- + "[,resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin]")
+ + DependencyAndroidData.EXPECTED_FORMAT
+ + "[,...]")
public List<DependencyAndroidData> directData;
@Option(name = "rOutput",
@@ -333,7 +333,8 @@ public class AndroidResourceProcessingAction {
resourceProcessor.createResourcesZip(
processedData.getResourceDir(),
processedData.getAssetDir(),
- options.resourcesOutput);
+ options.resourcesOutput,
+ false /* compress */);
}
logger.fine(
String.format("Packaging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
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 d5194a098d..6e2f340623 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
@@ -384,17 +384,22 @@ public class AndroidResourceProcessor {
* @param resourcesRoot The root containing android resources to be written.
* @param assetsRoot The root containing android assets to be written.
* @param output The path to write the zip file
+ * @param compress Whether or not to compress the content
* @throws IOException
*/
- public void createResourcesZip(Path resourcesRoot, Path assetsRoot, Path output)
+ public void createResourcesZip(Path resourcesRoot, Path assetsRoot, Path output, boolean compress)
throws IOException {
try (ZipOutputStream zout = new ZipOutputStream(
new BufferedOutputStream(Files.newOutputStream(output)))) {
if (Files.exists(resourcesRoot)) {
- Files.walkFileTree(resourcesRoot, new ZipBuilderVisitor(zout, resourcesRoot, "res"));
+ ZipBuilderVisitor visitor = new ZipBuilderVisitor(zout, resourcesRoot, "res");
+ visitor.setCompress(compress);
+ Files.walkFileTree(resourcesRoot, visitor);
}
if (Files.exists(assetsRoot)) {
- Files.walkFileTree(assetsRoot, new ZipBuilderVisitor(zout, assetsRoot, "assets"));
+ ZipBuilderVisitor visitor = new ZipBuilderVisitor(zout, assetsRoot, "assets");
+ visitor.setCompress(compress);
+ Files.walkFileTree(assetsRoot, visitor);
}
}
}
@@ -1005,27 +1010,80 @@ public class AndroidResourceProcessor {
return output;
}
-
+
/**
- * Merges all secondary resources with the primary resources.
+ * Merges all secondary resources with the primary resources, given that the primary resources
+ * have not yet been parsed and serialized.
*/
public MergedAndroidData mergeData(
final UnvalidatedAndroidData primary,
- final List<DependencyAndroidData> direct,
- final List<DependencyAndroidData> transitive,
+ final List<? extends SerializedAndroidData> direct,
+ final List<? extends SerializedAndroidData> transitive,
final Path resourcesOut,
final Path assetsOut,
@Nullable final PngCruncher cruncher,
final VariantConfiguration.Type type,
@Nullable final Path symbolsOut)
throws MergingException {
+ try {
+ final ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primary);
+ return mergeData(parsedPrimary, primary.getManifest(), direct, transitive,
+ resourcesOut, assetsOut, cruncher, type, symbolsOut, null /* rclassWriter */);
+ } catch (IOException e) {
+ throw new MergingException(e);
+ }
+ }
+
+ /**
+ * Merges all secondary resources with the primary resources, given that the primary resources
+ * have been separately parsed and serialized.
+ */
+ public MergedAndroidData mergeData(
+ final SerializedAndroidData primary,
+ final Path primaryManifest,
+ final List<? extends SerializedAndroidData> direct,
+ final List<? extends SerializedAndroidData> transitive,
+ final Path resourcesOut,
+ final Path assetsOut,
+ @Nullable final PngCruncher cruncher,
+ final VariantConfiguration.Type type,
+ @Nullable AndroidResourceClassWriter rclassWriter)
+ throws MergingException {
+ final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder();
+ final AndroidDataSerializer serializer = AndroidDataSerializer.create();
+ primary.deserialize(serializer, primaryBuilder.consumers());
+ ParsedAndroidData primaryData = primaryBuilder.build();
+ return mergeData(primaryData, primaryManifest, direct, transitive,
+ resourcesOut, assetsOut, cruncher, type, null /* symbolsOut */, rclassWriter);
+ }
+
+ /**
+ * Merges all secondary resources with the primary resources.
+ */
+ private MergedAndroidData mergeData(
+ final ParsedAndroidData primary,
+ final Path primaryManifest,
+ final List<? extends SerializedAndroidData> direct,
+ final List<? extends SerializedAndroidData> transitive,
+ final Path resourcesOut,
+ final Path assetsOut,
+ @Nullable final PngCruncher cruncher,
+ final VariantConfiguration.Type type,
+ @Nullable final Path symbolsOut,
+ @Nullable AndroidResourceClassWriter rclassWriter)
+ throws MergingException {
Stopwatch timer = Stopwatch.createStarted();
final ListeningExecutorService executorService =
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15));
try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
AndroidDataMerger merger = AndroidDataMerger.createWithPathDeduplictor(executorService);
UnwrittenMergedAndroidData merged =
- merger.merge(transitive, direct, primary, type != VariantConfiguration.Type.LIBRARY);
+ merger.loadAndMerge(
+ transitive,
+ direct,
+ primary,
+ primaryManifest,
+ type != VariantConfiguration.Type.LIBRARY);
logger.fine(String.format("merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
timer.reset().start();
if (symbolsOut != null) {
@@ -1037,6 +1095,12 @@ public class AndroidResourceProcessor {
"serialize merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
timer.reset().start();
}
+ if (rclassWriter != null) {
+ merged.writeResourceClass(rclassWriter);
+ logger.fine(
+ String.format("write classes finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ timer.reset().start();
+ }
AndroidDataWriter writer =
AndroidDataWriter.createWith(
resourcesOut.getParent(), resourcesOut, assetsOut, cruncher, executorService);
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD
index f4b5689623..6dc6b4b2ce 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD
@@ -26,6 +26,14 @@ java_binary(
)
java_binary(
+ name = "AndroidResourceMergingAction",
+ main_class = "com.google.devtools.build.android.AndroidResourceMergingAction",
+ runtime_deps = [
+ ":android_builder_lib",
+ ],
+)
+
+java_binary(
name = "AndroidResourceParsingAction",
main_class = "com.google.devtools.build.android.AndroidResourceParsingAction",
runtime_deps = [
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
index 612936fd71..3a08124f58 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
@@ -14,6 +14,14 @@ java_binary(
)
java_binary(
+ name = "AndroidResourceMergingAction",
+ main_class = "com.google.devtools.build.android.AndroidResourceMergingAction",
+ runtime_deps = [
+ ":classes",
+ ],
+)
+
+java_binary(
name = "AndroidResourceParsingAction",
main_class = "com.google.devtools.build.android.AndroidResourceParsingAction",
runtime_deps = [
diff --git a/src/tools/android/java/com/google/devtools/build/android/Converters.java b/src/tools/android/java/com/google/devtools/build/android/Converters.java
index 7afc60b229..455fbccdba 100644
--- a/src/tools/android/java/com/google/devtools/build/android/Converters.java
+++ b/src/tools/android/java/com/google/devtools/build/android/Converters.java
@@ -67,7 +67,7 @@ public final class Converters {
@Override
public String getTypeDescription() {
- return "unvalidated android data in the format " + UnvalidatedAndroidData.expectedFormat();
+ return "unvalidated android data in the format " + UnvalidatedAndroidData.EXPECTED_FORMAT;
}
}
@@ -90,7 +90,7 @@ public final class Converters {
@Override
public String getTypeDescription() {
return "unvalidated android directories in the format "
- + UnvalidatedAndroidDirectories.expectedFormat();
+ + UnvalidatedAndroidDirectories.EXPECTED_FORMAT;
}
}
@@ -104,7 +104,7 @@ public final class Converters {
@Override
public List<DependencyAndroidData> convert(String input) throws OptionsParsingException {
if (input.isEmpty()) {
- return ImmutableList.<DependencyAndroidData>of();
+ return ImmutableList.of();
}
try {
ImmutableList.Builder<DependencyAndroidData> builder = ImmutableList.builder();
@@ -121,8 +121,58 @@ public final class Converters {
@Override
public String getTypeDescription() {
return "a list of dependency android data in the format "
- + "resources[#resources]:assets[#assets]:manifest:r.txt"
- + "[,resources[#resources]:assets[#assets]:manifest:r.txt]";
+ + DependencyAndroidData.EXPECTED_FORMAT + "[,...]";
+ }
+ }
+
+ /**
+ * Converter for a {@link SerializedAndroidData}.
+ */
+ public static class SerializedAndroidDataConverter implements Converter<SerializedAndroidData> {
+
+ @Override
+ public SerializedAndroidData convert(String input) throws OptionsParsingException {
+ try {
+ return SerializedAndroidData.valueOf(input);
+ } catch (IllegalArgumentException e) {
+ throw new OptionsParsingException(
+ String.format("invalid SerializedAndroidData: %s", e.getMessage()), e);
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "preparsed android data in the format " + SerializedAndroidData.EXPECTED_FORMAT;
+ }
+ }
+
+ /**
+ * Converter for a list of {@link SerializedAndroidData}.
+ */
+ public static class SerializedAndroidDataListConverter
+ implements Converter<List<SerializedAndroidData>> {
+
+ @Override
+ public List<SerializedAndroidData> convert(String input) throws OptionsParsingException {
+ if (input.isEmpty()) {
+ return ImmutableList.of();
+ }
+ try {
+ ImmutableList.Builder<SerializedAndroidData> builder = ImmutableList.builder();
+ for (String entry : input.split("&")) {
+ builder.add(SerializedAndroidData.valueOf(entry));
+ }
+ return builder.build();
+ } catch (IllegalArgumentException e) {
+ throw new OptionsParsingException(
+ String.format("invalid SerializedAndroidData: %s", e.getMessage()), e);
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a list of preparsed android data in the format "
+ + SerializedAndroidData.EXPECTED_FORMAT + "[&...]";
}
}
@@ -281,6 +331,15 @@ public final class Converters {
}
}
+ // Commas that are not escaped by a backslash.
+ private static final String UNESCAPED_COMMA_REGEX = "(?<!\\\\)\\,";
+ // Colons that are not escaped by a backslash.
+ private static final String UNESCAPED_COLON_REGEX = "(?<!\\\\)\\:";
+
+ private static String unescapeInput(String input) {
+ return input.replace("\\:", ":").replace("\\,", ",");
+ }
+
/**
* A converter for dictionary arguments of the format key:value[,key:value]*. The keys and values
* may contain colons and commas as long as they are escaped with a backslash.
@@ -301,8 +360,8 @@ public final class Converters {
}
Map<K, V> map = new LinkedHashMap<>();
// Only split on comma and colon that are not escaped with a backslash
- for (String entry : input.split("(?<!\\\\)\\,")) {
- String[] entryFields = entry.split("(?<!\\\\)\\:", -1);
+ for (String entry : input.split(UNESCAPED_COMMA_REGEX)) {
+ String[] entryFields = entry.split(UNESCAPED_COLON_REGEX, -1);
if (entryFields.length < 2) {
throw new OptionsParsingException(String.format(
"Dictionary entry [%s] does not contain both a key and a value.",
@@ -313,7 +372,7 @@ public final class Converters {
entry));
}
// Unescape any comma or colon that is not a key or value separator.
- String keyString = entryFields[0].replace("\\:", ":").replace("\\,", ",");
+ String keyString = unescapeInput(entryFields[0]);
K key = keyConverter.convert(keyString);
if (map.containsKey(key)) {
throw new OptionsParsingException(String.format(
@@ -321,7 +380,7 @@ public final class Converters {
keyString));
}
// Unescape any comma or colon that is not a key or value separator.
- String valueString = entryFields[1].replace("\\:", ":").replace("\\,", ",");
+ String valueString = unescapeInput(entryFields[1]);
V value = valueConverter.convert(valueString);
map.put(key, value);
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
index bd77054d36..4f9dc1458d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
@@ -15,13 +15,10 @@ package com.google.devtools.build.android;
import com.android.builder.dependency.SymbolFileProvider;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.io.File;
-import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -37,8 +34,11 @@ import java.util.regex.Pattern;
* invocation) AndroidData can have multiple roots for resources and assets.
* </p>
*/
-class DependencyAndroidData {
- static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+:.+(:.*)?");
+class DependencyAndroidData extends SerializedAndroidData {
+ private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+:.+(:.*)?");
+
+ public static final String EXPECTED_FORMAT =
+ "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin";
public static DependencyAndroidData valueOf(String text) {
return valueOf(text, FileSystems.getDefault());
@@ -48,13 +48,11 @@ class DependencyAndroidData {
static DependencyAndroidData valueOf(String text, FileSystem fileSystem) {
if (!VALID_REGEX.matcher(text).find()) {
throw new IllegalArgumentException(
- text
- + " is not in the format 'resources[#resources]:assets[#assets]:manifest:"
- + "r.txt:symbols.txt'");
+ text + " is not in the format '" + EXPECTED_FORMAT + "'");
}
- String[] parts = text.split("\\:");
- // TODO(bazel-team): Handle the local-r.txt file.
- // The local R is optional -- if it is missing, we'll use the full R.txt
+ String[] parts = text.split(":");
+ // TODO(bazel-team): Handle the symbols.bin file.
+ // The local symbols.bin is optional -- if it is missing, we'll use the full R.txt
return new DependencyAndroidData(
splitPaths(parts[0], fileSystem),
parts[1].length() == 0 ? ImmutableList.<Path>of() : splitPaths(parts[1], fileSystem),
@@ -63,42 +61,19 @@ class DependencyAndroidData {
parts.length == 5 ? fileSystem.getPath(parts[4]) : null);
}
- private static ImmutableList<Path> splitPaths(String pathsString, FileSystem fileSystem) {
- if (pathsString.trim().isEmpty()) {
- return ImmutableList.<Path>of();
- }
- ImmutableList.Builder<Path> paths = new ImmutableList.Builder<>();
- for (String pathString : pathsString.split("#")) {
- Preconditions.checkArgument(!pathString.trim().isEmpty());
- paths.add(exists(fileSystem.getPath(pathString)));
- }
- return paths.build();
- }
-
- private static Path exists(Path path) {
- if (!Files.exists(path)) {
- throw new IllegalArgumentException(path + " does not exist");
- }
- return path;
- }
-
- private final Path rTxt;
private final Path manifest;
- private final ImmutableList<Path> assetDirs;
- private final ImmutableList<Path> resourceDirs;
- private final Path symbolsTxt;
+ private final Path rTxt;
public DependencyAndroidData(
ImmutableList<Path> resourceDirs,
ImmutableList<Path> assetDirs,
Path manifest,
Path rTxt,
- Path symbolsTxt) {
- this.resourceDirs = resourceDirs;
- this.assetDirs = assetDirs;
+ Path symbols) {
+ // Use the manifest as a label for now.
+ super(resourceDirs, assetDirs, manifest.toString(), symbols);
this.manifest = manifest;
this.rTxt = rTxt;
- this.symbolsTxt = symbolsTxt;
}
public SymbolFileProvider asSymbolFileProvider() {
@@ -115,19 +90,15 @@ class DependencyAndroidData {
};
}
- public Path getManifest() {
- return manifest;
- }
-
@Override
public String toString() {
return String.format(
- "AndroidData(%s, %s, %s, %s, %s)", resourceDirs, assetDirs, manifest, rTxt, symbolsTxt);
+ "AndroidData(%s, %s, %s, %s, %s)", resourceDirs, assetDirs, manifest, rTxt, symbols);
}
@Override
public int hashCode() {
- return Objects.hash(resourceDirs, assetDirs, manifest, rTxt, symbolsTxt);
+ return Objects.hash(resourceDirs, assetDirs, manifest, rTxt, symbols);
}
@Override
@@ -145,27 +116,8 @@ class DependencyAndroidData {
return Objects.equals(other.resourceDirs, resourceDirs)
&& Objects.equals(other.assetDirs, assetDirs)
&& Objects.equals(other.rTxt, rTxt)
- && Objects.equals(other.symbolsTxt, symbolsTxt)
+ && Objects.equals(other.symbols, symbols)
&& Objects.equals(other.manifest, manifest);
}
- public void walk(final AndroidDataPathWalker pathWalker) throws IOException {
- for (Path path : resourceDirs) {
- pathWalker.walkResources(path);
- }
- for (Path path : assetDirs) {
- pathWalker.walkAssets(path);
- }
- }
-
- public void deserialize(
- AndroidDataSerializer serializer,
- KeyValueConsumers consumers)
- throws DeserializationException {
- // Missing symbolsTxt means the resources where provided via android_resources rules.
- if (symbolsTxt == null) {
- throw new DeserializationException(true);
- }
- serializer.read(symbolsTxt, consumers);
- }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java b/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java
index 4d0498533f..a4b5f6ba2d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java
@@ -238,7 +238,7 @@ public class ResourceShrinkerAction {
null /* dataBindingInfoOut */);
if (options.shrunkResources != null) {
resourceProcessor.createResourcesZip(shrunkResources, resourceFiles.resolve("assets"),
- options.shrunkResources);
+ options.shrunkResources, false /* compress */);
}
logger.fine(String.format("Packing resources finished at %sms",
timer.elapsed(TimeUnit.MILLISECONDS)));
diff --git a/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java
new file mode 100644
index 0000000000..08f32b9594
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java
@@ -0,0 +1,135 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Android resource and assets that have been parsed ahead of time, and summarized by an {@link
+ * AndroidDataSerializer}. This class drives reloading the data.
+ */
+public class SerializedAndroidData {
+
+ private static final Pattern VALID_REGEX = Pattern.compile(".*;.*;.+;.*");
+
+ public static final String EXPECTED_FORMAT =
+ "resources[#resources];assets[#assets];label;symbols.bin";
+
+ public static SerializedAndroidData valueOf(String text) {
+ return valueOf(text, FileSystems.getDefault());
+ }
+
+ static SerializedAndroidData valueOf(String text, FileSystem fileSystem) {
+ if (!VALID_REGEX.matcher(text).find()) {
+ throw new IllegalArgumentException(text + " is not in the format '" + EXPECTED_FORMAT + "'");
+ }
+ String[] parts = text.split(";");
+ return new SerializedAndroidData(
+ splitPaths(parts[0], fileSystem),
+ splitPaths(parts[1], fileSystem),
+ parts[2],
+ parts.length > 3 ? fileSystem.getPath(parts[3]) : null);
+ }
+
+ protected static ImmutableList<Path> splitPaths(String pathsString, FileSystem fileSystem) {
+ if (pathsString.trim().isEmpty()) {
+ return ImmutableList.of();
+ }
+ ImmutableList.Builder<Path> paths = new ImmutableList.Builder<>();
+ for (String pathString : pathsString.split("#")) {
+ Preconditions.checkArgument(!pathString.trim().isEmpty());
+ paths.add(exists(fileSystem.getPath(pathString)));
+ }
+ return paths.build();
+ }
+
+ protected static Path exists(Path path) {
+ if (!Files.exists(path)) {
+ throw new IllegalArgumentException(path + " does not exist");
+ }
+ return path;
+ }
+
+ protected final ImmutableList<Path> assetDirs;
+ protected final ImmutableList<Path> resourceDirs;
+ protected final String label;
+ protected final Path symbols;
+
+ public SerializedAndroidData(
+ ImmutableList<Path> resourceDirs, ImmutableList<Path> assetDirs, String label, Path symbols) {
+ this.resourceDirs = resourceDirs;
+ this.assetDirs = assetDirs;
+ this.label = label;
+ this.symbols = symbols;
+ }
+
+ public void walk(final AndroidDataPathWalker pathWalker) throws IOException {
+ for (Path path : resourceDirs) {
+ pathWalker.walkResources(path);
+ }
+ for (Path path : assetDirs) {
+ pathWalker.walkAssets(path);
+ }
+ }
+
+ public void deserialize(AndroidDataSerializer serializer, KeyValueConsumers consumers)
+ throws DeserializationException {
+ // Missing symbols means the resources where provided via android_resources rules.
+ if (symbols == null) {
+ throw new DeserializationException(true);
+ }
+ serializer.read(symbols, consumers);
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "SerializedAndroidData(%s, %s, %s, %s)", resourceDirs, assetDirs, label, symbols);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(resourceDirs, assetDirs, label, symbols);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof SerializedAndroidData)) {
+ return false;
+ }
+ SerializedAndroidData other = (SerializedAndroidData) obj;
+ return Objects.equals(other.resourceDirs, resourceDirs)
+ && Objects.equals(other.assetDirs, assetDirs)
+ && Objects.equals(other.symbols, symbols)
+ && Objects.equals(other.label, label);
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java
index beeeba7e37..20717d6005 100644
--- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java
@@ -31,9 +31,7 @@ import java.util.regex.Pattern;
class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories {
private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+");
- static String expectedFormat() {
- return "resources[#resources]:assets[#assets]:manifest";
- }
+ public static final String EXPECTED_FORMAT = "resources[#resources]:assets[#assets]:manifest";
public static UnvalidatedAndroidData valueOf(String text) {
return valueOf(text, FileSystems.getDefault());
@@ -43,7 +41,7 @@ class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories {
static UnvalidatedAndroidData valueOf(String text, FileSystem fileSystem) {
if (!VALID_REGEX.matcher(text).find()) {
throw new IllegalArgumentException(
- text + " is not in the format '" + expectedFormat() + "'");
+ text + " is not in the format '" + EXPECTED_FORMAT + "'");
}
String[] parts = text.split(":");
return new UnvalidatedAndroidData(
diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java
index f87da7fbf0..5723f6ec61 100644
--- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java
+++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java
@@ -30,9 +30,7 @@ public class UnvalidatedAndroidDirectories {
private static final Pattern VALID_REGEX = Pattern.compile(".*:.*");
- static String expectedFormat() {
- return "resources[#resources]:assets[#assets]";
- }
+ public static final String EXPECTED_FORMAT = "resources[#resources]:assets[#assets]";
public static UnvalidatedAndroidDirectories valueOf(String text) {
return valueOf(text, FileSystems.getDefault());
@@ -42,7 +40,7 @@ public class UnvalidatedAndroidDirectories {
static UnvalidatedAndroidDirectories valueOf(String text, FileSystem fileSystem) {
if (!VALID_REGEX.matcher(text).find()) {
throw new IllegalArgumentException(
- text + " is not in the format '" + expectedFormat() + "'");
+ text + " is not in the format '" + EXPECTED_FORMAT + "'");
}
String[] parts = text.split(":");
return new UnvalidatedAndroidDirectories(
diff --git a/tools/android/BUILD b/tools/android/BUILD
index c32b795552..9eb2023587 100644
--- a/tools/android/BUILD
+++ b/tools/android/BUILD
@@ -36,6 +36,11 @@ alias(
)
alias(
+ name = "resource_merger",
+ actual = "//src/tools/android/java/com/google/devtools/build/android:AndroidResourceMergingAction",
+)
+
+alias(
name = "resource_parser",
actual = "//src/tools/android/java/com/google/devtools/build/android:AndroidResourceParsingAction",
)