Merge "Add service loader test"
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index e235e67..8731d2a 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.utils.OutputBuilder;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.IOException;
@@ -181,6 +182,12 @@
public static void writeResources(Path archive, List<ProgramResource> resources)
throws IOException, ResourceException {
+ writeResources(archive, resources, ImmutableList.of());
+ }
+
+ public static void writeResources(
+ Path archive, List<ProgramResource> resources, List<DataEntryResource> dataResources)
+ throws IOException, ResourceException {
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (Closer closer = Closer.create()) {
@@ -191,6 +198,11 @@
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
}
+ for (DataEntryResource dataResource : dataResources) {
+ String entryName = dataResource.getName();
+ byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+ ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 28d1628..369367e 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResource;
import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DirectoryClassFileProvider;
@@ -213,6 +214,29 @@
}
}
+ public List<DataEntryResource> getDataEntryResourcesForTesting() throws ResourceException {
+ List<DataEntryResource> out = new ArrayList<>();
+ for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) {
+ DataResourceProvider dataResourceProvider = programResourceProvider.getDataResourceProvider();
+ if (dataResourceProvider != null) {
+ dataResourceProvider.accept(
+ new Visitor() {
+
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ // Ignore.
+ }
+
+ @Override
+ public void visit(DataEntryResource file) {
+ out.add(file);
+ }
+ });
+ }
+ }
+ return out;
+ }
+
/** Get program resource providers. */
public List<ProgramResourceProvider> getProgramResourceProviders() {
return programResourceProviders;
@@ -320,14 +344,12 @@
}
}
- /**
- * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
- */
+ /** Write the dex program resources to @code{archive}. */
public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
try {
if (outputMode == OutputMode.DexIndexed) {
- List<ProgramResource> resources = getDexProgramResourcesForTesting();
- DexIndexedConsumer.ArchiveConsumer.writeResources(archive, resources);
+ DexIndexedConsumer.ArchiveConsumer.writeResources(
+ archive, getDexProgramResourcesForTesting(), getDataEntryResourcesForTesting());
} else if (outputMode == OutputMode.DexFilePerClassFile) {
List<ProgramResource> resources = getDexProgramResourcesForTesting();
DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index 4371edd..c7078ec 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -6,6 +6,9 @@
import com.android.tools.r8.BaseCompilerCommand;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
@@ -105,6 +108,29 @@
}
}
+ @Override
+ public DataResourceConsumer getDataResourceConsumer() {
+ assert consumer.getDataResourceConsumer() == null;
+ return new DataResourceConsumer() {
+
+ @Override
+ public void accept(
+ DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+ // Ignore.
+ }
+
+ @Override
+ public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ builder.addDataResource(file);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Ignore.
+ }
+ };
+ }
+
synchronized void addDexFile(int fileIndex, byte[] data, Set<String> descriptors) {
files.put(fileIndex, new DescriptorsWithContents(descriptors, data));
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 244639e..c91c288 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -141,7 +141,7 @@
return builder.toString();
}
- public static String lines(String... lines) {
+ public static String lines(List<String> lines) {
StringBuilder builder = new StringBuilder();
for (String line : lines) {
builder.append(line).append(LINE_SEPARATOR);
@@ -149,6 +149,10 @@
return builder.toString();
}
+ public static String lines(String... lines) {
+ return lines(Arrays.asList(lines));
+ }
+
public static String withNativeLineSeparator(String s) {
s = s.replace("\r\n", "\n");
if (LINE_SEPARATOR.equals("\r\n")) {
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 9dfb30d..245a35e 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -131,6 +131,11 @@
}
@Override
+ public ExternalR8TestBuilder addDataEntryResources(DataEntryResource... resources) {
+ throw new Unimplemented("No support for adding data entry resources");
+ }
+
+ @Override
public ExternalR8TestBuilder addProgramClasses(Collection<Class<?>> classes) {
// Adding a collection of classes will build a jar of exactly those classes so that no other
// classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 0fd0354..3b4647f 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -181,6 +181,11 @@
}
@Override
+ public ProguardTestBuilder addDataEntryResources(DataEntryResource... resources) {
+ throw new Unimplemented("No support for adding data entry resources");
+ }
+
+ @Override
public ProguardTestBuilder addProgramClasses(Collection<Class<?>> classes) {
// Adding a collection of classes will build a jar of exactly those classes so that no other
// classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index daf5568..e8555d7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -97,6 +97,11 @@
}
@Override
+ public R8TestBuilder addDataEntryResources(DataEntryResource... resources) {
+ return addDataResources(Arrays.asList(resources));
+ }
+
+ @Override
public R8TestBuilder addKeepRuleFiles(List<Path> files) {
builder.addProguardConfigurationFiles(files);
return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 9d8e03c..a17ab09 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -46,6 +46,8 @@
return minification(false);
}
+ public abstract T addDataEntryResources(DataEntryResource... resources);
+
public abstract T addKeepRuleFiles(List<Path> files);
public T addKeepRuleFiles(Path... files) throws IOException {
@@ -62,6 +64,10 @@
return addKeepRules("-keep class ** { *; }");
}
+ public T addKeepAllInterfacesRule() {
+ return addKeepRules("-keep interface ** { *; }");
+ }
+
public T addKeepClassRules(Class<?>... classes) {
for (Class<?> clazz : classes) {
addKeepRules("-keep class " + clazz.getTypeName());
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
new file mode 100644
index 0000000..e005a2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Lists;
+import java.util.List;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderTest extends TestBase {
+
+ private final boolean includeWorldGreeter;
+
+ @Parameters(name = "Include WorldGreeter: {0}")
+ public static Boolean[] data() {
+ return BooleanUtils.values();
+ }
+
+ public ServiceLoaderTest(boolean includeWorldGreeter) {
+ this.includeWorldGreeter = includeWorldGreeter;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = includeWorldGreeter ? "Hello world!" : "Hello";
+
+ List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+ if (includeWorldGreeter) {
+ serviceImplementations.add(WorldGreeter.class.getTypeName());
+ }
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(ServiceLoaderTest.class)
+ .addKeepMainRule(TestClass.class)
+ // TODO(b/124181030): Test should work without the following keep-all-rules.
+ .addKeepAllClassesRule()
+ .addKeepAllInterfacesRule()
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(serviceImplementations).getBytes(),
+ "META-INF/services/" + Greeter.class.getTypeName(),
+ Origin.unknown()))
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ // TODO(b/124181030): Verify that Greeter is merged into HelloGreeter when `includeWorldGreeter`
+ // is false.
+
+ // TODO(b/124181030): Verify that META-INF/services/...WorldGreeter is removed when
+ // `includeWorldGreeter` is false.
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (Greeter greeter : ServiceLoader.load(Greeter.class)) {
+ System.out.print(greeter.greeting());
+ }
+ }
+ }
+
+ public interface Greeter {
+
+ String greeting();
+ }
+
+ public static class HelloGreeter implements Greeter {
+
+ @Override
+ public String greeting() {
+ return "Hello";
+ }
+ }
+
+ public static class WorldGreeter implements Greeter {
+
+ @Override
+ public String greeting() {
+ return " world!";
+ }
+ }
+}