Renaming and elimination of services in META-INF/services/
Bug: 124181030
Change-Id: Iab363008bffe5c257c9f48fba16e307f67bd586f
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 364a794..a557881 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -150,7 +150,8 @@
return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
}
- public static void writeResources(Path archive, List<ProgramResource> resources)
+ public static void writeResources(
+ Path archive, List<ProgramResource> resources, List<DataEntryResource> dataResources)
throws IOException, ResourceException {
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -163,6 +164,11 @@
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
}
+ for (DataEntryResource dataResource : dataResources) {
+ String entryName = dataResource.getName();
+ byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+ ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 466a26f..2273c4d 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -204,6 +204,7 @@
new ApplicationWriter(
app,
+ null,
options,
marker == null ? null : ImmutableList.copyOf(markers),
null,
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index b6bb4e0..6974d46 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -95,6 +95,7 @@
ApplicationWriter writer =
new ApplicationWriter(
app,
+ null,
options,
markers,
null,
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 27c564a..6826f63 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -96,6 +96,7 @@
try {
new ApplicationWriter(
featureApp,
+ null,
options,
markers,
null,
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9c2a30c..4d79275 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
@@ -185,6 +186,7 @@
static void writeApplication(
ExecutorService executorService,
DexApplication application,
+ AppView<? extends AppInfo> appView,
String deadCode,
GraphLense graphLense,
NamingLens namingLens,
@@ -198,6 +200,7 @@
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(
application,
+ appView,
options,
marker,
deadCode,
@@ -209,6 +212,7 @@
} else {
new ApplicationWriter(
application,
+ appView,
options,
Collections.singletonList(marker),
deadCode,
@@ -486,6 +490,8 @@
new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
}
+ appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
+
timing.begin("Create IR");
Set<DexCallSite> desugaredCallSites;
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -668,6 +674,7 @@
writeApplication(
executorService,
application,
+ appView,
application.deadCode,
appView.graphLense(),
namingLens,
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 60167c5..2b661d0 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -178,7 +178,7 @@
InternalOptions options = new InternalOptions();
AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
ApplicationWriter writer =
- new ApplicationWriter(app, options, null, null, null, null, null, null);
+ new ApplicationWriter(app, null, options, null, null, null, null, null, null);
writer.write(executor);
compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 26398d8..7b2c688 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -60,6 +62,7 @@
public class ApplicationWriter {
public final DexApplication application;
+ public final AppView<? extends AppInfo> appView;
public final String deadCode;
public final GraphLense graphLense;
public final NamingLens namingLens;
@@ -129,6 +132,7 @@
public ApplicationWriter(
DexApplication application,
+ AppView<? extends AppInfo> appView,
InternalOptions options,
List<Marker> markers,
String deadCode,
@@ -138,6 +142,7 @@
ProguardMapSupplier proguardMapSupplier) {
this(
application,
+ appView,
options,
markers,
deadCode,
@@ -150,6 +155,7 @@
public ApplicationWriter(
DexApplication application,
+ AppView<? extends AppInfo> appView,
InternalOptions options,
List<Marker> markers,
String deadCode,
@@ -160,6 +166,7 @@
DexIndexedConsumer consumer) {
assert application != null;
this.application = application;
+ this.appView = appView;
assert options != null;
this.options = options;
this.markers = markers;
@@ -298,6 +305,7 @@
// Supply info to all additional resource consumers.
supplyAdditionalConsumers(
application,
+ appView,
graphLense,
namingLens,
options,
@@ -311,6 +319,7 @@
public static void supplyAdditionalConsumers(
DexApplication application,
+ AppView<? extends AppInfo> appView,
GraphLense graphLense,
NamingLens namingLens,
InternalOptions options,
@@ -349,7 +358,7 @@
.collect(Collectors.toList());
ResourceAdapter resourceAdapter =
- new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+ new ResourceAdapter(appView, application.dexItemFactory, graphLense, namingLens, options);
Set<String> generatedResourceNames = new HashSet<>();
for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
@@ -367,6 +376,10 @@
@Override
public void visit(DataEntryResource file) {
+ if (resourceAdapter.shouldBeDeleted(file)) {
+ return;
+ }
+
DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
if (generatedResourceNames.add(adapted.getName())) {
dataResourceConsumer.accept(adapted, options.reporter);
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index ae59576..630f25d 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -8,11 +8,15 @@
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardPathFilter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -27,16 +31,19 @@
public class ResourceAdapter {
+ private final AppView<? extends AppInfo> appView;
private final DexItemFactory dexItemFactory;
private final GraphLense graphLense;
private final NamingLens namingLense;
private final InternalOptions options;
public ResourceAdapter(
+ AppView<? extends AppInfo> appView,
DexItemFactory dexItemFactory,
GraphLense graphLense,
NamingLens namingLense,
InternalOptions options) {
+ this.appView = appView;
this.dexItemFactory = dexItemFactory;
this.graphLense = graphLense;
this.namingLense = namingLense;
@@ -88,8 +95,40 @@
return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin());
}
+ // Returns true for files in META-INF/services/ that are never used by the application.
+ public boolean shouldBeDeleted(DataEntryResource file) {
+ if (appView != null && appView.appInfo().hasLiveness()) {
+ AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+ if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) {
+ String serviceName = file.getName().substring(AppServices.SERVICE_DIRECTORY_NAME.length());
+ if (!DescriptorUtils.isValidJavaType(serviceName)) {
+ return false;
+ }
+
+ DexString serviceDescriptor =
+ dexItemFactory.lookupString(DescriptorUtils.javaTypeToDescriptor(serviceName));
+ if (serviceDescriptor == null) {
+ return false;
+ }
+
+ DexType serviceType = appView.dexItemFactory().lookupType(serviceDescriptor);
+ if (serviceType == null) {
+ return false;
+ }
+
+ DexType rewrittenServiceType = appView.graphLense().lookupType(serviceType);
+ assert appView.appServices().allServiceTypes().contains(rewrittenServiceType);
+ return !appInfo.instantiatedAppServices.contains(rewrittenServiceType);
+ }
+ }
+ return false;
+ }
+
private String adaptFileName(DataEntryResource file) {
- FileNameAdapter adapter = new FileNameAdapter(file.getName());
+ FileNameAdapter adapter =
+ file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)
+ ? new ServiceFileNameAdapter(file.getName())
+ : new DefaultFileNameAdapter(file.getName());
if (adapter.run()) {
return adapter.getResult();
}
@@ -307,7 +346,7 @@
private void outputRangeFromInput(int from, int toExclusive) {
if (from < toExclusive) {
- result.append(contents.substring(from, toExclusive));
+ result.append(contents, from, toExclusive);
}
}
@@ -362,8 +401,8 @@
}
}
- private abstract class FileNameAdapterBase extends StringAdapter {
- public FileNameAdapterBase(String filename) {
+ private abstract class FileNameAdapter extends StringAdapter {
+ public FileNameAdapter(String filename) {
super(filename);
}
@@ -391,8 +430,8 @@
}
}
- private class FileNameAdapter extends FileNameAdapterBase {
- public FileNameAdapter(String filename) {
+ private class DefaultFileNameAdapter extends FileNameAdapter {
+ public DefaultFileNameAdapter(String filename) {
super(filename);
}
@@ -402,7 +441,28 @@
}
}
- private class DirectoryNameAdapter extends FileNameAdapterBase {
+ private class ServiceFileNameAdapter extends FileNameAdapter {
+ public ServiceFileNameAdapter(String filename) {
+ super(filename);
+ }
+
+ @Override
+ public char getClassNameSeparator() {
+ return '.';
+ }
+
+ @Override
+ public boolean allowRenamingOfPrefixes() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamingCandidate(int from, int toExclusive) {
+ return from == AppServices.SERVICE_DIRECTORY_NAME.length() && eof(toExclusive);
+ }
+ }
+
+ private class DirectoryNameAdapter extends FileNameAdapter {
public DirectoryNameAdapter(String filename) {
super(filename);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 4c1881c..ec22d85 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams;
import java.io.IOException;
@@ -21,24 +22,32 @@
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
/** A description of the services and their implementations found in META-INF/services/. */
public class AppServices {
+ public static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
+
+ private final AppView<? extends AppInfo> appView;
+
// Mapping from service types to service implementation types.
private final Map<DexType, Set<DexType>> services;
- private AppServices(Map<DexType, Set<DexType>> services) {
+ private AppServices(AppView<? extends AppInfo> appView, Map<DexType, Set<DexType>> services) {
+ this.appView = appView;
this.services = services;
}
public Set<DexType> allServiceTypes() {
+ assert verifyRewrittenWithLens();
return services.keySet();
}
public Set<DexType> serviceImplementationsFor(DexType serviceType) {
+ assert verifyRewrittenWithLens();
assert services.containsKey(serviceType);
Set<DexType> serviceImplementationTypes = services.get(serviceType);
if (serviceImplementationTypes == null) {
@@ -51,14 +60,35 @@
return serviceImplementationTypes;
}
+ public AppServices rewrittenWithLens(GraphLense graphLens) {
+ ImmutableMap.Builder<DexType, Set<DexType>> rewrittenServices = ImmutableMap.builder();
+ for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+ DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
+ ImmutableSet.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableSet.builder();
+ for (DexType serviceImplementationType : entry.getValue()) {
+ rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+ }
+ rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+ }
+ return new AppServices(appView, rewrittenServices.build());
+ }
+
+ private boolean verifyRewrittenWithLens() {
+ for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+ assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
+ for (DexType type : entry.getValue()) {
+ assert type == appView.graphLense().lookupType(type);
+ }
+ }
+ return true;
+ }
+
public static Builder builder(AppView<? extends AppInfo> appView) {
return new Builder(appView);
}
public static class Builder {
- private static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
-
private final AppView<? extends AppInfo> appView;
private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
@@ -76,7 +106,7 @@
readServices(dataResourceProvider);
}
}
- return new AppServices(services);
+ return new AppServices(appView, services);
}
private void readServices(DataResourceProvider dataResourceProvider) {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 8d55a59..6a9e056 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
@@ -42,7 +44,6 @@
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
-import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.ExecutorService;
@@ -67,6 +68,7 @@
public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
private final DexApplication application;
+ private final AppView<? extends AppInfo> appView;
private final GraphLense graphLense;
private final NamingLens namingLens;
private final InternalOptions options;
@@ -78,6 +80,7 @@
public CfApplicationWriter(
DexApplication application,
+ AppView<? extends AppInfo> appView,
InternalOptions options,
Marker marker,
String deadCode,
@@ -86,6 +89,7 @@
String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
this.application = application;
+ this.appView = appView;
this.graphLense = graphLense;
this.namingLens = namingLens;
this.options = options;
@@ -96,7 +100,7 @@
this.proguardSeedsData = proguardSeedsData;
}
- public void write(ClassFileConsumer consumer, ExecutorService executor) throws IOException {
+ public void write(ClassFileConsumer consumer, ExecutorService executor) {
application.timing.begin("CfApplicationWriter.write");
try {
writeApplication(consumer, executor);
@@ -105,8 +109,7 @@
}
}
- private void writeApplication(ClassFileConsumer consumer, ExecutorService executor)
- throws IOException {
+ private void writeApplication(ClassFileConsumer consumer, ExecutorService executor) {
ProguardMapSupplier.ProguardMapAndId proguardMapAndId = null;
if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
proguardMapAndId = proguardMapSupplier.getProguardMapAndId();
@@ -124,6 +127,7 @@
}
ApplicationWriter.supplyAdditionalConsumers(
application,
+ appView,
graphLense,
namingLens,
options,
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f167226..b32134a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -221,6 +221,12 @@
new SetWithReason<>(this::registerField);
/**
+ * Set of service types (from META-INF/services/) that may have been instantiated reflectively via
+ * ServiceLoader.load() or ServiceLoader.loadInstalled().
+ */
+ private final Set<DexType> instantiatedAppServices = Sets.newIdentityHashSet();
+
+ /**
* Set of interface types for which a lambda expression can be reached. These never have a single
* interface implementation.
*/
@@ -1845,6 +1851,8 @@
}
private void handleServiceInstantiation(DexType serviceType, KeepReason reason) {
+ instantiatedAppServices.add(serviceType);
+
Set<DexType> serviceImplementationTypes =
appView.appServices().serviceImplementationsFor(serviceType);
for (DexType serviceImplementationType : serviceImplementationTypes) {
@@ -1931,6 +1939,11 @@
/** Set of annotation types that are instantiated. */
final SortedSet<DexType> instantiatedAnnotationTypes;
/**
+ * Set of service types (from META-INF/services/) that may have been instantiated reflectively
+ * via ServiceLoader.load() or ServiceLoader.loadInstalled().
+ */
+ public final SortedSet<DexType> instantiatedAppServices;
+ /**
* Set of types that are actually instantiated. These cannot be abstract.
*/
final SortedSet<DexType> instantiatedTypes;
@@ -2092,6 +2105,9 @@
ImmutableSortedSet.orderedBy(PresortedComparable<DexType>::slowCompareTo);
enqueuer.liveAnnotations.items.forEach(annotation -> builder.add(annotation.annotation.type));
this.instantiatedAnnotationTypes = builder.build();
+ this.instantiatedAppServices =
+ ImmutableSortedSet.copyOf(
+ PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAppServices);
this.instantiatedTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
this.instantiatedLambdas =
@@ -2150,6 +2166,7 @@
super(application);
this.liveTypes = previous.liveTypes;
this.instantiatedAnnotationTypes = previous.instantiatedAnnotationTypes;
+ this.instantiatedAppServices = previous.instantiatedAppServices;
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
@@ -2199,6 +2216,8 @@
this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
this.instantiatedAnnotationTypes =
rewriteItems(previous.instantiatedAnnotationTypes, lense::lookupType);
+ this.instantiatedAppServices =
+ rewriteItems(previous.instantiatedAppServices, lense::lookupType);
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
@@ -2277,6 +2296,7 @@
super(previous);
this.liveTypes = previous.liveTypes;
this.instantiatedAnnotationTypes = previous.instantiatedAnnotationTypes;
+ this.instantiatedAppServices = previous.instantiatedAppServices;
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
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 369367e..d22adac 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -355,8 +355,8 @@
DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
archive, resources, programResourcesMainDescriptor);
} else if (outputMode == OutputMode.ClassFile) {
- List<ProgramResource> resources = getClassProgramResourcesForTesting();
- ClassFileConsumer.ArchiveConsumer.writeResources(archive, resources);
+ ClassFileConsumer.ArchiveConsumer.writeResources(
+ archive, getClassProgramResourcesForTesting(), getDataEntryResourcesForTesting());
} else {
throw new Unreachable("Unsupported output-mode for writing: " + outputMode);
}
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 71ebb23..a855ab7 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -191,6 +191,29 @@
assert getDataResourceConsumer() != null;
}
}
+
+ @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.
+ }
+ };
+ }
};
programConsumer = wrapped;
return wrapped;
@@ -225,6 +248,29 @@
assert getDataResourceConsumer() != null;
}
}
+
+ @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.
+ }
+ };
+ }
};
programConsumer = wrapped;
return wrapped;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 4f80d5e..c0b06f4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1784,6 +1784,7 @@
Executors.newSingleThreadExecutor(),
application,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null,
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 8d6953b..298ffe1 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -139,6 +139,7 @@
ApplicationWriter writer =
new ApplicationWriter(
application,
+ null,
options,
null,
null,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 5103951..30db56e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -779,6 +779,7 @@
ApplicationWriter writer =
new ApplicationWriter(
application,
+ null,
options,
null,
null,
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 35b6b4c..15599ef 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -101,6 +101,10 @@
return resources.get(name);
}
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
public int size() {
return resources.size();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index d856f1d..29e5bcd 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -5,11 +5,14 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.BooleanUtils;
@@ -27,16 +30,18 @@
@RunWith(Parameterized.class)
public class ServiceLoaderTest extends TestBase {
+ private final Backend backend;
private final boolean includeWorldGreeter;
private DataResourceConsumerForTesting dataResourceConsumer;
- @Parameters(name = "Include WorldGreeter: {0}")
- public static Boolean[] data() {
- return BooleanUtils.values();
+ @Parameters(name = "Backend: {0}, include WorldGreeter: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(Backend.values(), BooleanUtils.values());
}
- public ServiceLoaderTest(boolean includeWorldGreeter) {
+ public ServiceLoaderTest(Backend backend, boolean includeWorldGreeter) {
+ this.backend = backend;
this.includeWorldGreeter = includeWorldGreeter;
}
@@ -50,12 +55,9 @@
}
CodeInspector inspector =
- testForR8(Backend.DEX)
+ testForR8(backend)
.addInnerClasses(ServiceLoaderTest.class)
.addKeepMainRule(TestClass.class)
- // TODO(b/124181030): It should not be necessary to keep Greeter, but the resource
- // adapter needs to rewrite the resource file names.
- .addKeepRules("-keep interface " + Greeter.class.getTypeName())
.addDataEntryResources(
DataEntryResource.fromBytes(
StringUtils.lines(serviceImplementations).getBytes(),
@@ -72,9 +74,7 @@
.inspector();
ClassSubject greeterSubject = inspector.clazz(Greeter.class);
- // TODO(b/124181030): Greeter should be merged into HelloGreeter when the keep rule above is
- // removed.
- assertThat(greeterSubject, isPresent());
+ assertEquals(includeWorldGreeter, greeterSubject.isPresent());
ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
assertThat(helloGreeterSubject, isPresent());
@@ -82,22 +82,59 @@
ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent());
- // TODO(b/124181030): The resource file name should become:
- // `includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName()`.
+ String serviceFileName =
+ includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName();
List<String> lines =
- dataResourceConsumer.get("META-INF/services/" + greeterSubject.getOriginalName());
+ dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + serviceFileName);
assertEquals(includeWorldGreeter ? 2 : 1, lines.size());
assertEquals(helloGreeterSubject.getFinalName(), lines.get(0));
if (includeWorldGreeter) {
assertEquals(worldGreeterSubject.getFinalName(), lines.get(1));
}
- // TODO(b/124181030): Verify that META-INF/services/...Greeter is removed if there is no call to
- // ServiceLoader.load().
-
// TODO(b/124181030): Verify that -whyareyoukeeping works as intended.
}
+ @Test
+ public void testResourceElimination() throws Exception {
+ String expectedOutput = "Hello world!";
+
+ List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+ if (includeWorldGreeter) {
+ serviceImplementations.add(WorldGreeter.class.getTypeName());
+ }
+
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(ServiceLoaderTest.class)
+ .addKeepMainRule(OtherTestClass.class)
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(serviceImplementations).getBytes(),
+ "META-INF/services/" + Greeter.class.getTypeName(),
+ Origin.unknown()))
+ .addOptionsModification(
+ options -> {
+ dataResourceConsumer =
+ new DataResourceConsumerForTesting(options.dataResourceConsumer);
+ options.dataResourceConsumer = dataResourceConsumer;
+ })
+ .run(OtherTestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+ assertThat(greeterSubject, not(isPresent()));
+
+ ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+ assertThat(helloGreeterSubject, not(isPresent()));
+
+ ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+ assertThat(worldGreeterSubject, not(isPresent()));
+
+ assertTrue(dataResourceConsumer.isEmpty());
+ }
+
static class TestClass {
public static void main(String[] args) {
@@ -107,6 +144,13 @@
}
}
+ static class OtherTestClass {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world!");
+ }
+ }
+
public interface Greeter {
String greeting();
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index fb61a57..b99cbdc 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -113,6 +113,7 @@
ApplicationWriter writer =
new ApplicationWriter(
dexApp,
+ null,
options,
null,
null,