aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-08-29 17:06:53 +0000
committerGravatar Klaus Aehlig <aehlig@google.com>2016-08-30 08:32:45 +0000
commit5963ae7407f32cf25ebbe7e046f5bc5d0240aae3 (patch)
tree99a600b92832aaa12d52ee46b05ed1045ff60261 /src/tools/android/java/com/google/devtools/build
parent012b06b0a89ae76e623f8844da3a787841da757c (diff)
Add a lightweight resource merge action.
Part 2 of the 3 new proposed android_library res processing actions. The primary and deps are all assumed to be parsed+summarized in a protobuf. Represent that with a new class (similar to DependencyAndroidData but w/out R.txt). Avoid having "manifest" artifacts as deps input, and instead use "label", since that is only used in a warning. DepAD still uses the manifest for #asSymbolFileProvider, so we keep it there. Move loading the primary out of the merge function so that we can share the merge function with this style of primary data, and the existing style of of primary data (UnvalidatedAndroidData). This produces an R class.jar and a zip file to pass along to a future validation action. Images are stubbed out since they are irrelevant to the validation action. -- MOS_MIGRATED_REVID=131604421
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build')
-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
15 files changed, 628 insertions, 124 deletions
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(