aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar ajmichael <ajmichael@google.com>2017-10-31 16:05:33 -0400
committerGravatar John Cater <jcater@google.com>2017-11-01 09:58:53 -0400
commit2ad8c6978f786795b501dd4e6fa6b94cd910a485 (patch)
tree7f90fcca94a123cfd9e9bf864f00f7c56a581b02 /src
parentf411e2ef94e7268286a810741fff2ba324fe72c3 (diff)
Open source ZipFilterAction for use in Android testing.
https://github.com/bazelbuild/bazel/issues/903 RELNOTES: None PiperOrigin-RevId: 174079202
Diffstat (limited to 'src')
-rw-r--r--src/test/java/com/google/devtools/build/android/BUILD13
-rw-r--r--src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java296
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/BUILD10
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java211
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java71
5 files changed, 601 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/android/BUILD b/src/test/java/com/google/devtools/build/android/BUILD
index 04b2194719..cb5f346c9b 100644
--- a/src/test/java/com/google/devtools/build/android/BUILD
+++ b/src/test/java/com/google/devtools/build/android/BUILD
@@ -262,6 +262,19 @@ java_test(
],
)
+java_test(
+ name = "ZipFilterActionTest",
+ size = "small",
+ srcs = ["ZipFilterActionTest.java"],
+ deps = [
+ "//src/java_tools/singlejar/java/com/google/devtools/build/singlejar:libSingleJar",
+ "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
java_library(
name = "test_utils",
testonly = 1,
diff --git a/src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java b/src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java
new file mode 100644
index 0000000000..a59f2cb58d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java
@@ -0,0 +1,296 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.singlejar.ZipEntryFilter.CustomMergeStrategy;
+import com.google.devtools.build.singlejar.ZipEntryFilter.StrategyCallback;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link ZipFilterAction}. */
+@RunWith(JUnit4.class)
+public class ZipFilterActionTest {
+
+ private static final class Entry {
+ private final String name;
+ private final String contents;
+
+ public Entry(String name, String contents) {
+ this.name = name;
+ this.contents = contents;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getContents() {
+ return contents;
+ }
+ }
+
+ private enum FilterOperation {
+ SKIP,
+ RENAME,
+ CUSTOM_MERGE,
+ COPY
+ }
+
+ private static final class TestingStrategyCallback implements StrategyCallback {
+ private FilterOperation operation;
+
+ public void assertOp(FilterOperation operation) {
+ assertThat(this.operation).isEqualTo(operation);
+ }
+
+ @Override
+ public void skip() throws IOException {
+ operation = FilterOperation.SKIP;
+ }
+
+ @Override
+ public void rename(String filename, Date date) throws IOException {
+ operation = FilterOperation.RENAME;
+ }
+
+ @Override
+ public void customMerge(Date date, CustomMergeStrategy strategy) throws IOException {
+ operation = FilterOperation.CUSTOM_MERGE;
+ }
+
+ @Override
+ public void copy(Date date) throws IOException {
+ operation = FilterOperation.COPY;
+ }
+ }
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+ @Rule public TemporaryFolder tmp = new TemporaryFolder();
+
+ private int fileCount;
+ private TestingStrategyCallback callback;
+
+ private Path createZip(String... filenames) throws IOException {
+ Entry[] entries = new Entry[filenames.length];
+ int i = 0;
+ for (String filename : filenames) {
+ entries[i++] = new Entry(filename, "" + fileCount++);
+ }
+ return createZip(entries);
+ }
+
+ private Path createZip(Entry... entries) throws IOException {
+ File zip = tmp.newFile();
+ try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zip))) {
+ for (Entry entry : entries) {
+ ZipEntry e = new ZipEntry(entry.getName());
+ zout.putNextEntry(e);
+ zout.write(entry.getContents().getBytes(UTF_8));
+ zout.closeEntry();
+ }
+ }
+ return zip.toPath();
+ }
+
+ @Before public void setup() {
+ callback = new TestingStrategyCallback();
+ }
+
+ @Test public void testCreateFilter() throws IOException {
+ ImmutableSet<Path> filters = ImmutableSet.of(
+ createZip("foo.class", "bar.java"),
+ createZip("foo.java", "bar.java", "baz.class"));
+ ImmutableSet<String> types = ImmutableSet.of(".class");
+ Multimap<String, Long> filterFiles = ZipFilterAction.getEntriesToOmit(filters, types);
+ assertThat(filterFiles.keySet()).containsExactly("foo.class", "baz.class");
+ assertThat(filterFiles).valuesForKey("foo.class").hasSize(1);
+ assertThat(filterFiles).valuesForKey("baz.class").hasSize(1);
+ }
+
+ @Test public void testCreateFilter_NoZips() throws IOException {
+ ImmutableSet<Path> filters = ImmutableSet.of();
+ ImmutableSet<String> types = ImmutableSet.of(".class");
+ Multimap<String, Long> filterFiles = ZipFilterAction.getEntriesToOmit(filters, types);
+ assertThat(filterFiles).isEmpty();
+ }
+
+ @Test public void testCreateFilter_NoTypes() throws IOException {
+ ImmutableSet<Path> filters = ImmutableSet.of(
+ createZip("foo.class", "bar.java"),
+ createZip("foo.java", "bar.java", "baz.class"));
+ ImmutableSet<String> types = ImmutableSet.of();
+ Multimap<String, Long> filterFiles = ZipFilterAction.getEntriesToOmit(filters, types);
+ assertThat(filterFiles.keySet())
+ .containsExactly("foo.class", "bar.java", "foo.java", "baz.class");
+ }
+
+ @Test public void testCreateFilter_MultipleTypes() throws IOException {
+ ImmutableSet<Path> filters = ImmutableSet.of(
+ createZip("foo.class", "bar.java"),
+ createZip("foo.java", "bar.java", "baz.class"));
+ ImmutableSet<String> types = ImmutableSet.of(".class", "bar.java");
+ Multimap<String, Long> filterFiles = ZipFilterAction.getEntriesToOmit(filters, types);
+ assertThat(filterFiles.keySet()).containsExactly("foo.class", "baz.class", "bar.java");
+ assertThat(filterFiles).valuesForKey("foo.class").hasSize(1);
+ assertThat(filterFiles).valuesForKey("bar.java").hasSize(2);
+ }
+
+ @Test public void testZipEntryFilter() throws Exception {
+ ZipFilterEntryFilter filter = new ZipFilterEntryFilter(".*R.class.*",
+ ImmutableSetMultimap.of("foo.class", 1L, "baz.class", 2L),
+ ImmutableMap.of("foo.class", 1L, "bar.class", 2L, "baz.class", 3L, "res/R.class", 4L),
+ false);
+ filter.accept("foo.class", callback);
+ callback.assertOp(FilterOperation.SKIP);
+ filter.accept("bar.class", callback);
+ callback.assertOp(FilterOperation.COPY);
+ filter.accept("baz.class", callback);
+ callback.assertOp(FilterOperation.COPY);
+ filter.accept("res/R.class", callback);
+ callback.assertOp(FilterOperation.SKIP);
+ }
+
+ @Test public void testZipEntryFilter_ErrorOnMismatch() throws Exception {
+ ZipFilterEntryFilter filter = new ZipFilterEntryFilter(".*R.class.*",
+ ImmutableSetMultimap.of("foo.class", 1L, "baz.class", 2L),
+ ImmutableMap.of("foo.class", 1L, "bar.class", 2L, "baz.class", 3L, "res/R.class", 4L),
+ true);
+ filter.accept("foo.class", callback);
+ callback.assertOp(FilterOperation.SKIP);
+ filter.accept("bar.class", callback);
+ callback.assertOp(FilterOperation.COPY);
+ filter.accept("res/R.class", callback);
+ callback.assertOp(FilterOperation.SKIP);
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("name matches but the hash does not.");
+ filter.accept("baz.class", callback);
+ }
+
+ @Test public void testFlags() throws Exception {
+ File input = tmp.newFile("input");
+ File output = tmp.newFile("output");
+ output.delete();
+ File filter1 = tmp.newFile("filter1");
+ File filter2 = tmp.newFile("filter2");
+
+ ImmutableList<String> args = ImmutableList.of(
+ "--inputZip", input.getPath(),
+ "--outputZip", output.getPath(),
+ "--filterZips", Joiner.on(",").join(filter1.getPath(), filter2.getPath(),
+ filter1.getPath()),
+ "--filterTypes", Joiner.on(",").join(".class", ".class", ".java"),
+ "--explicitFilters", Joiner.on(",").join("R\\.class", "R\\$.*\\.class"),
+ "--outputMode", "DONT_CARE",
+ "--noerrorOnHashMismatch");
+ thrown.expect(ZipException.class);
+ thrown.expectMessage("Zip file 'filter1' is malformed");
+ ZipFilterAction.main(args.toArray(new String[0]));
+ }
+
+ @Test public void testFullIntegration() throws IOException {
+ Path input = createZip(new Entry("foo.java", "foo"), new Entry("bar.class", "bar"),
+ new Entry("baz.class", "baz"), new Entry("1.class", "1"), new Entry("2.class", "2"),
+ new Entry("R.class", "r"), new Entry("Read.class", "read"));
+ File output = tmp.newFile();
+ output.delete();
+ Path filter1 = createZip(new Entry("1.class", "1"), new Entry("b.class", "b"));
+ Path filter2 = createZip(new Entry("1.class", "2"), new Entry("2.class", "1"),
+ new Entry("foo.java", "foo"), new Entry("bar.class", "bar"));
+ ImmutableList<String> args = ImmutableList.of(
+ "--inputZip", input.toFile().getPath(),
+ "--outputZip", output.getPath(),
+ "--filterZips", Joiner.on(",").join(filter1.toFile().getPath(), filter2.toFile().getPath(),
+ filter1.toFile().getPath()),
+ "--filterTypes", ".class",
+ "--explicitFilters", Joiner.on(",").join("R\\.class", "R\\$.*\\.class"),
+ "--outputMode", "DONT_CARE");
+ ZipFilterAction.main(args.toArray(new String[0]));
+ List<String> filteredEntries = new ArrayList<>();
+ try (ZipFile zip = new ZipFile(output)) {
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ filteredEntries.add(entries.nextElement().getName());
+ }
+ }
+ assertThat(filteredEntries).containsExactly("foo.java", "baz.class", "2.class", "Read.class");
+ }
+
+ @Test public void testFullIntegrationErrorsOnHash() throws IOException {
+ Path input = createZip("foo.java", "bar.class", "baz.class");
+ File output = tmp.newFile();
+ output.delete();
+ Path filter = createZip("foo.java", "bar.class");
+ ImmutableList<String> args = ImmutableList.of(
+ "--inputZip", input.toFile().getPath(),
+ "--outputZip", output.getPath(),
+ "--filterZips", filter.toFile().getPath(),
+ "--filterTypes", ".class",
+ "--outputMode", "DONT_CARE",
+ "--errorOnHashMismatch");
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("name matches but the hash does not");
+ ZipFilterAction.main(args.toArray(new String[0]));
+ }
+
+ @Test public void testFullIntegrationErrorsOnHash_WithExplicitOverride()
+ throws IOException {
+ Path input = createZip("foo.java", "bar.class", "baz.class");
+ File output = tmp.newFile();
+ output.delete();
+ Path filter = createZip("foo.java", "bar.class");
+ ImmutableList<String> args = ImmutableList.of(
+ "--inputZip", input.toFile().getPath(),
+ "--outputZip", output.getPath(),
+ "--filterZips", filter.toFile().getPath(),
+ "--filterTypes", ".class",
+ "--explicitFilters", "bar\\.class",
+ "--outputMode", "DONT_CARE",
+ "--errorOnHashMismatch");
+ ZipFilterAction.main(args.toArray(new String[0]));
+ List<String> filteredEntries = new ArrayList<>();
+ try (ZipFile zip = new ZipFile(output)) {
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ filteredEntries.add(entries.nextElement().getName());
+ }
+ }
+ assertThat(filteredEntries).containsExactly("foo.java", "baz.class");
+ }
+}
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 916252726e..6bf42f53e3 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD
@@ -38,6 +38,13 @@ java_binary(
],
)
+java_binary(
+ name = "ZipFilterAction",
+ main_class = "com.google.devtools.build.android.ZipFilterAction",
+ visibility = ["//visibility:private"],
+ runtime_deps = [":android_builder_lib"],
+)
+
java_library(
name = "android_builder_lib",
srcs = glob([
@@ -46,6 +53,8 @@ java_library(
"aapt2/*.java",
]),
deps = [
+ "//src/java_tools/singlejar/java/com/google/devtools/build/singlejar:libSingleJar",
+ "//src/java_tools/singlejar/java/com/google/devtools/build/zip",
"//src/main/java/com/google/devtools/common/options",
"//src/tools/android/java/com/google/devtools/build/android/junctions",
"//src/tools/android/java/com/google/devtools/build/android/proto:serialize_format_java_pb",
@@ -56,6 +65,7 @@ java_library(
"//third_party:jsr305",
"//third_party/java/android_databinding:exec",
"//third_party/java/aosp_gradle_core",
+ "//third_party/java/jcommander",
"//third_party/protobuf:protobuf_java",
],
)
diff --git a/src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java b/src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java
new file mode 100644
index 0000000000..f7225eb6d4
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java
@@ -0,0 +1,211 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.Parameters;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.singlejar.ZipCombiner;
+import com.google.devtools.build.singlejar.ZipCombiner.OutputMode;
+import com.google.devtools.build.singlejar.ZipEntryFilter;
+import com.google.devtools.build.zip.ZipFileEntry;
+import com.google.devtools.build.zip.ZipReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+/**
+ * Action to filter entries out of a Zip file.
+ *
+ * <p>The entries to remove are determined from the filterZips and filterTypes. All entries from the
+ * filter Zip files that have an extension listed in filterTypes will be removed. If no filterZips
+ * are specified, no entries will be removed. Specifying no filterTypes is treated as if an
+ * extension of '.*' was specified.
+ *
+ * <p>Assuming each Zip as a set of entries, the result is:
+ * <pre> outputZip = inputZip - union[x intersect filterTypes for x in filterZips]</pre>
+ *
+ * <p><pre>
+ * Example Usage:
+ * java/com/google/build/android/ZipFilterAction\
+ * --inputZip path/to/inputZip
+ * --outputZip path/to/outputZip
+ * --filterZips [path/to/filterZip[,path/to/filterZip]...]
+ * --filterTypes [fileExtension[,fileExtension]...]
+ * --explicitFilters [fileRegex[,fileRegex]...]
+ * --outputMode [DONT_CARE|FORCE_DEFLATE|FORCE_STORED]
+ * --errorOnHashMismatch
+ * </pre>
+ */
+public class ZipFilterAction {
+
+ private static final Logger logger = Logger.getLogger(ZipFilterAction.class.getName());
+
+ @Parameters(optionPrefixes = "--")
+ static class Options {
+ @Parameter(
+ names = "--inputZip",
+ description = "Path of input zip.",
+ converter = PathFlagConverter.class,
+ validateValueWith = PathExistsValidator.class
+ )
+ Path inputZip;
+
+ @Parameter(
+ names = "--outputZip",
+ description = "Path to write output zip.",
+ converter = PathFlagConverter.class
+ )
+ Path outputZip;
+
+ @Parameter(
+ names = "--filterZips",
+ description = "Filter zips.",
+ converter = PathFlagConverter.class,
+ validateValueWith = AllPathsExistValidator.class
+ )
+ List<Path> filterZips = ImmutableList.of();
+
+ @Parameter(names = "--filterTypes", description = "Filter file types.")
+ List<String> filterTypes = ImmutableList.of();
+
+ @Parameter(names = "--explicitFilters", description = "Explicitly specified filters.")
+ List<String> explicitFilters = ImmutableList.of();
+
+ @Parameter(names = "--outputMode", description = "Output zip compression mode.")
+ OutputMode outputMode = OutputMode.DONT_CARE;
+
+ @Parameter(
+ names = "--errorOnHashMismatch",
+ description = "Error on entry filter with hash mismatch."
+ )
+ boolean errorOnHashMismatch = false;
+
+ // This is a hack to support existing users of --noerrorOnHashMismatch. JCommander does not
+ // support setting boolean flags with "--no", so instead we set the default to false and just
+ // ignore anyone who passes --noerrorOnHashMismatch.
+ @Parameter(names = "--noerrorOnHashMismatch")
+ boolean ignored = false;
+ }
+
+ /** Converts string flags to paths. Public because JCommander invokes this by reflection. */
+ public static class PathFlagConverter implements IStringConverter<Path> {
+
+ @Override
+ public Path convert(String text) {
+ return FileSystems.getDefault().getPath(text);
+ }
+ }
+
+ /** Validates that a path exists. Public because JCommander invokes this by reflection. */
+ public static class PathExistsValidator implements IValueValidator<Path> {
+
+ @Override
+ public void validate(String s, Path path) {
+ if (!Files.exists(path)) {
+ throw new ParameterException(String.format("%s is not a valid path.", path.toString()));
+ }
+ }
+ }
+
+ /** Validates that a set of paths exist. Public because JCommander invokes this by reflection. */
+ public static class AllPathsExistValidator implements IValueValidator<List<Path>> {
+
+ @Override
+ public void validate(String s, List<Path> paths) {
+ for (Path path : paths) {
+ if (!Files.exists(path)) {
+ throw new ParameterException(String.format("%s is not a valid path.", path.toString()));
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static Multimap<String, Long> getEntriesToOmit(
+ Collection<Path> filterZips, Collection<String> filterTypes) throws IOException {
+ // Escape filter types to prevent regex abuse
+ Set<String> escapedFilterTypes = new HashSet<>();
+ for (String filterType : filterTypes) {
+ escapedFilterTypes.add(Pattern.quote(filterType));
+ }
+ // Match any string that ends with any of the filter file types
+ String filterRegex = String.format(".*(%s)$", Joiner.on("|").join(escapedFilterTypes));
+
+ ImmutableSetMultimap.Builder<String, Long> entriesToOmit = ImmutableSetMultimap.builder();
+ for (Path filterZip : filterZips) {
+ try (ZipReader zip = new ZipReader(filterZip.toFile())) {
+ for (ZipFileEntry entry : zip.entries()) {
+ if (filterTypes.isEmpty() || entry.getName().matches(filterRegex)) {
+ entriesToOmit.put(entry.getName(), entry.getCrc());
+ }
+ }
+ }
+ }
+ return entriesToOmit.build();
+ }
+
+ public static void main(String[] args) throws IOException {
+ Options options = new Options();
+ new JCommander(options).parse(args);
+ logger.fine(
+ String.format(
+ "Creating filter from entries of type %s, in zip files %s.",
+ options.filterTypes, options.filterZips));
+
+ final Stopwatch timer = Stopwatch.createStarted();
+ final Multimap<String, Long> entriesToOmit =
+ getEntriesToOmit(options.filterZips, options.filterTypes);
+ final String explicitFilter =
+ options.explicitFilters.isEmpty()
+ ? ""
+ : String.format(".*(%s).*", Joiner.on("|").join(options.explicitFilters));
+ logger.fine(String.format("Filter created in %dms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ ImmutableMap.Builder<String, Long> inputEntries = ImmutableMap.builder();
+ try (ZipReader input = new ZipReader(options.inputZip.toFile())) {
+ for (ZipFileEntry entry : input.entries()) {
+ inputEntries.put(entry.getName(), entry.getCrc());
+ }
+ }
+ ZipEntryFilter entryFilter =
+ new ZipFilterEntryFilter(
+ explicitFilter, entriesToOmit, inputEntries.build(), options.errorOnHashMismatch);
+
+ try (OutputStream out = Files.newOutputStream(options.outputZip);
+ ZipCombiner combiner = new ZipCombiner(options.outputMode, entryFilter, out)) {
+ combiner.addZip(options.inputZip.toFile());
+ }
+ logger.fine(String.format("Filtering completed in %dms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java b/src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java
new file mode 100644
index 0000000000..f6e556b626
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java
@@ -0,0 +1,71 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.singlejar.ZipEntryFilter;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A {@link ZipEntryFilter} for use with {@link ZipFilterAction}. Filters out entries that match the
+ * provided regular expression or are in the list to omit.
+ */
+class ZipFilterEntryFilter implements ZipEntryFilter {
+
+ private final String explicitFilter;
+ private final Multimap<String, Long> entriesToOmit;
+ private final Map<String, Long> inputEntries;
+ private final boolean errorOnHashMismatch;
+
+ /**
+ * Creates a new filter.
+ *
+ * @param explicitFilter a regular expression to match against entry filenames
+ * @param entriesToOmit a map of filename and CRC-32 of entries to omit
+ * @param inputEntries a map of filename and CRC-32 of entries in the input Zip file
+ * @param errorOnHashMistmatch whether to error or warn when there is a CRC-32 mismatch
+ */
+ public ZipFilterEntryFilter(String explicitFilter, Multimap<String, Long> entriesToOmit,
+ Map<String, Long> inputEntries, boolean errorOnHashMistmatch) {
+ this.explicitFilter = explicitFilter;
+ this.entriesToOmit = entriesToOmit;
+ this.inputEntries = inputEntries;
+ this.errorOnHashMismatch = errorOnHashMistmatch;
+ }
+
+ @Override
+ public void accept(String filename, StrategyCallback callback) throws IOException {
+ if (filename.matches(explicitFilter)) {
+ callback.skip();
+ } else if (entriesToOmit.containsKey(filename)) {
+ Long entryCrc = inputEntries.get(filename);
+ if (entriesToOmit.containsEntry(filename, entryCrc)) {
+ callback.skip();
+ } else {
+ if (errorOnHashMismatch) {
+ throw new IllegalStateException(String.format("Requested to filter entries of name "
+ + "'%s'; name matches but the hash does not. Aborting", filename));
+ } else {
+ System.out.printf("\u001b[35mWARNING:\u001b[0m Requested to filter entries of name "
+ + "'%s'; name matches but the hash does not. Copying anyway.\n", filename);
+ callback.copy(null);
+ }
+ }
+ } else {
+ callback.copy(null);
+ }
+ }
+}