blob: 7fedd7c572d1e814e584299ba62086f564393540 [file] [log] [blame]
// 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.graph;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
/** 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<?> appView;
// The graph lens that was previously used to rewrite this instance.
private final GraphLens applied;
// Mapping from service types to service implementation types.
private final Map<DexType, Map<FeatureSplit, List<DexType>>> services;
private AppServices(AppView<?> appView, Map<DexType, Map<FeatureSplit, List<DexType>>> services) {
this.appView = appView;
this.applied = appView.graphLens();
this.services = services;
}
public boolean isEmpty() {
return services.isEmpty();
}
public Set<DexType> allServiceTypes() {
assert verifyRewrittenWithLens();
return services.keySet();
}
public Set<DexType> computeAllServiceImplementations() {
assert verifyRewrittenWithLens();
Set<DexType> serviceImplementations = Sets.newIdentityHashSet();
services.forEach(
(serviceType, featureSplitListMap) ->
featureSplitListMap.forEach(
(feature, featureServiceImplementations) ->
serviceImplementations.addAll(featureServiceImplementations)));
return serviceImplementations;
}
public List<DexType> serviceImplementationsFor(DexType serviceType) {
assert verifyRewrittenWithLens();
Map<FeatureSplit, List<DexType>> featureSplitListMap = services.get(serviceType);
if (featureSplitListMap == null) {
assert false
: "Unexpected attempt to get service implementations for non-service type `"
+ serviceType.toSourceString()
+ "`";
return ImmutableList.of();
}
ImmutableList.Builder<DexType> builder = ImmutableList.builder();
for (List<DexType> implementations : featureSplitListMap.values()) {
builder.addAll(implementations);
}
return builder.build();
}
public boolean hasServiceImplementationsInFeature(
AppView<AppInfoWithLiveness> appView, DexType serviceType) {
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
if (classToFeatureSplitMap.isEmpty()) {
return false;
}
Map<FeatureSplit, List<DexType>> featureImplementations = services.get(serviceType);
if (featureImplementations == null || featureImplementations.isEmpty()) {
assert false
: "Unexpected attempt to get service implementations for non-service type `"
+ serviceType.toSourceString()
+ "`";
return true;
}
if (featureImplementations.size() > 1
|| !featureImplementations.containsKey(FeatureSplit.BASE)) {
return true;
}
// Check if service is defined feature
DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
if (serviceClass != null
&& classToFeatureSplitMap.isInFeature(serviceClass, appView.getSyntheticItems())) {
return true;
}
for (DexType implementationType : featureImplementations.get(FeatureSplit.BASE)) {
DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
if (implementationClass != null
&& classToFeatureSplitMap.isInFeature(implementationClass, appView.getSyntheticItems())) {
return true;
}
}
return false;
}
public AppServices rewrittenWithLens(GraphLens graphLens) {
ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenFeatureMappings =
ImmutableMap.builder();
for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
DexType rewrittenServiceType = graphLens.lookupClassType(entry.getKey(), applied);
ImmutableMap.Builder<FeatureSplit, List<DexType>> rewrittenFeatureImplementations =
ImmutableMap.builder();
for (Entry<FeatureSplit, List<DexType>> featureSplitImpls : entry.getValue().entrySet()) {
ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes =
ImmutableList.builder();
for (DexType serviceImplementationType : featureSplitImpls.getValue()) {
rewrittenServiceImplementationTypes.add(
graphLens.lookupClassType(serviceImplementationType, applied));
}
rewrittenFeatureImplementations.put(
featureSplitImpls.getKey(), rewrittenServiceImplementationTypes.build());
}
rewrittenFeatureMappings.put(rewrittenServiceType, rewrittenFeatureImplementations.build());
}
return new AppServices(appView, rewrittenFeatureMappings.build());
}
public AppServices prunedCopy(PrunedItems prunedItems) {
ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenServicesBuilder =
ImmutableMap.builder();
for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
if (prunedItems.getRemovedClasses().contains(entry.getKey())) {
continue;
}
ImmutableMap.Builder<FeatureSplit, List<DexType>> prunedFeatureSplitImpls =
ImmutableMap.builder();
for (Entry<FeatureSplit, List<DexType>> featureSplitEntry : entry.getValue().entrySet()) {
ImmutableList.Builder<DexType> rewrittenServiceImplementationTypesBuilder =
ImmutableList.builder();
for (DexType serviceImplementationType : featureSplitEntry.getValue()) {
if (!prunedItems.getRemovedClasses().contains(serviceImplementationType)) {
rewrittenServiceImplementationTypesBuilder.add(serviceImplementationType);
}
}
List<DexType> prunedFeatureSplitImplementations =
rewrittenServiceImplementationTypesBuilder.build();
if (prunedFeatureSplitImplementations.size() > 0) {
prunedFeatureSplitImpls.put(
featureSplitEntry.getKey(), rewrittenServiceImplementationTypesBuilder.build());
}
}
ImmutableMap<FeatureSplit, List<DexType>> prunedServiceImplementations =
prunedFeatureSplitImpls.build();
if (prunedServiceImplementations.size() > 0) {
rewrittenServicesBuilder.put(entry.getKey(), prunedServiceImplementations);
}
}
return new AppServices(appView, rewrittenServicesBuilder.build());
}
public boolean verifyRewrittenWithLens() {
for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
assert entry.getKey() == appView.graphLens().lookupClassType(entry.getKey(), applied);
for (Entry<FeatureSplit, List<DexType>> featureEntry : entry.getValue().entrySet()) {
for (DexType type : featureEntry.getValue()) {
assert type == appView.graphLens().lookupClassType(type, applied);
}
}
}
return true;
}
public void visit(BiConsumer<DexType, List<DexType>> consumer) {
services.forEach(
(type, featureImpls) -> {
ImmutableList.Builder<DexType> builder = ImmutableList.builder();
featureImpls.values().forEach(builder::addAll);
consumer.accept(type, builder.build());
});
}
public static Builder builder(AppView<?> appView) {
return new Builder(appView);
}
public static class Builder {
private final AppView<?> appView;
private final InternalOptions options;
private final Map<DexType, Map<FeatureSplit, List<DexType>>> services = new LinkedHashMap<>();
private Builder(AppView<?> appView) {
this.appView = appView;
this.options = appView.options();
}
public AppServices build() {
for (DataResourceProvider provider : appView.appInfo().app().dataResourceProviders) {
readServices(provider, FeatureSplit.BASE);
}
if (options.featureSplitConfiguration != null) {
List<FeatureSplit> featureSplits = options.featureSplitConfiguration.getFeatureSplits();
for (FeatureSplit featureSplit : featureSplits) {
for (ProgramResourceProvider provider : featureSplit.getProgramResourceProviders()) {
DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
if (dataResourceProvider != null) {
readServices(dataResourceProvider, featureSplit);
}
}
}
}
return new AppServices(appView, services);
}
private void readServices(
DataResourceProvider dataResourceProvider, FeatureSplit featureSplit) {
try {
dataResourceProvider.accept(new DataResourceProviderVisitor(featureSplit));
} catch (ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
}
private class DataResourceProviderVisitor implements Visitor {
private final FeatureSplit featureSplit;
public DataResourceProviderVisitor(FeatureSplit featureSplit) {
this.featureSplit = featureSplit;
}
@Override
public void visit(DataDirectoryResource directory) {
// Ignore.
}
@Override
public void visit(DataEntryResource file) {
try {
String name = file.getName();
if (!name.startsWith(SERVICE_DIRECTORY_NAME)) {
return;
}
String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
if (!DescriptorUtils.isValidJavaType(serviceName)) {
return;
}
String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
if (appView.enableWholeProgramOptimizations()) {
DexClass serviceClass =
appView.appInfo().definitionForWithoutExistenceAssert(serviceType);
if (serviceClass == null) {
warn(
"Unexpected reference to missing service class: META-INF/services/"
+ serviceType.toSourceString()
+ ".",
serviceType,
file.getOrigin());
}
}
byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
String contents = new String(bytes, Charset.defaultCharset());
Map<FeatureSplit, List<DexType>> featureSplitImplementations =
services.computeIfAbsent(serviceType, k -> new LinkedHashMap<>());
List<DexType> serviceImplementations =
featureSplitImplementations.computeIfAbsent(featureSplit, f -> new ArrayList<>());
readServiceImplementationsForService(
contents, file.getOrigin(), serviceType, serviceImplementations);
} catch (IOException | ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
}
private void readServiceImplementationsForService(
String contents,
Origin origin,
DexType serviceType,
List<DexType> serviceImplementations) {
if (contents != null) {
StringUtils.splitLines(contents).stream()
.map(String::trim)
.map(this::prefixUntilCommentChar)
.filter(line -> !line.isEmpty())
.filter(DescriptorUtils::isValidJavaType)
.map(DescriptorUtils::javaTypeToDescriptor)
.map(appView.dexItemFactory()::createType)
.filter(
serviceImplementationType -> {
if (!serviceImplementationType.isClassType()) {
// Should never happen.
warn(
"Unexpected service implementation found in META-INF/services/"
+ serviceType.toSourceString()
+ ": "
+ serviceImplementationType.toSourceString()
+ ".",
serviceImplementationType,
origin);
return false;
}
if (appView.enableWholeProgramOptimizations()) {
DexClass serviceImplementationClass =
appView
.appInfo()
.definitionForWithoutExistenceAssert(serviceImplementationType);
if (serviceImplementationClass == null) {
warn(
"Unexpected reference to missing service implementation class in "
+ "META-INF/services/"
+ serviceType.toSourceString()
+ ": "
+ serviceImplementationType.toSourceString()
+ ".",
serviceImplementationType,
origin);
}
}
// Only keep one of each implementation type in the list.
return !serviceImplementations.contains(serviceImplementationType);
})
.forEach(serviceImplementations::add);
}
}
private String prefixUntilCommentChar(String line) {
int commentCharIndex = line.indexOf('#');
return commentCharIndex > -1 ? line.substring(0, commentCharIndex) : line;
}
private void warn(String message, DexType type, Origin origin) {
if (!appView.getDontWarnConfiguration().matches(type)) {
options.reporter.warning(new StringDiagnostic(message, origin));
}
}
}
}
}