blob: 6c61415642e5792fc7d7b7801f5180426df8494a [file] [log] [blame]
// Copyright (c) 2024, 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.androidapi;
import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class AndroidApiModelingOptions {
// Flag to specify if we should load the database or not. The api database is used for
// library member rebinding.
private boolean enableLibraryApiModeling =
System.getProperty("com.android.tools.r8.disableApiModeling") == null;
// Flag to specify Android extension libraries (also known as OEM-implemented shared libraries
// or sidecars). All APIs within these libraries are handled as having an API level
// higher than any existing API level as these APIs might not exist on any device independent
// of API level (the nature of an extension API).
public String androidApiExtensionLibraries =
System.getProperty("com.android.tools.r8.androidApiExtensionLibraries");
// TODO(b/326252366): Remove support for list of extension packages in favour of only
// supporting passing extension libraries as JAR files.
// Flag to specify packages for Android extension APIs (also known as OEM-implemented
// shared libraries or sidecars). The packages are specified as java package names
// separated by commas. All APIs within these packages are handled as having an API level
// higher than any existing API level as these APIs might not exist on any device independent
// of API level (the nature of an extension API).
// TODO(b/326252366): This mechanism should be extended to also specify the extension for
// each package to prevent merging of API outline methods fron different extensions.
public String androidApiExtensionPackages =
System.getProperty("com.android.tools.r8.androidApiExtensionPackages");
// The flag enableApiCallerIdentification controls if we can inline or merge targets with
// different api levels. It is also the flag that specifies if we assign api levels to
// references.
private boolean enableApiCallerIdentification = true;
private boolean enableStubbingOfClasses = true;
private boolean enableOutliningOfMethods = true;
private boolean checkAllApiReferencesAreSet = true;
// TODO(b/232823652): Enable when we can compute the offset correctly.
public boolean useMemoryMappedByteBuffer = false;
// A mapping from references to the api-level introducing them.
public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>();
public Map<ClassReference, AndroidApiLevel> classApiMapping = new HashMap<>();
public BiConsumer<MethodReference, ComputedApiLevel> tracedMethodApiLevelCallback = null;
private final InternalOptions options;
public AndroidApiModelingOptions(InternalOptions options) {
this.options = options;
}
public void forEachAndroidApiExtensionClassDescriptor(Consumer<String> consumer) {
assert isApiModelingEnabled();
if (androidApiExtensionLibraries != null) {
StringUtils.split(androidApiExtensionLibraries, ',')
.forEach(
lib -> {
try {
ZipUtils.iter(
Paths.get(lib),
(entry, input) -> {
if (ZipUtils.isClassFile(entry.getName())) {
consumer.accept(extractClassDescriptor(input));
}
});
} catch (Exception e) {
throw new CompilationError("Failed to read extension library " + lib, e);
}
});
}
}
public void forEachAndroidApiExtensionPackage(Consumer<String> consumer) {
assert isApiModelingEnabled();
if (androidApiExtensionPackages != null) {
StringUtils.split(androidApiExtensionPackages, ',').forEach(consumer);
}
}
public boolean isApiModelingEnabled() {
// TODO(b/384426376): Should return false when compiling to CF.
return enableLibraryApiModeling;
}
public boolean isApiCallerIdentificationEnabled() {
return isApiModelingEnabled() && enableApiCallerIdentification;
}
public boolean isStubbingOfClassesEnabled() {
// TODO(b/384426376): Should not check backend when isApiModelingEnabled() return false for CF.
if (!isApiModelingEnabled() || !options.isGeneratingDex() || !enableStubbingOfClasses) {
return false;
}
if (options.partialSubCompilationConfiguration != null
&& options.partialSubCompilationConfiguration.isD8()) {
// Defer stubbing of classes to R8 in R8 partial. This is needed to ensure tree shaking
// correctly keep library method overrides in R8 of R8 partial. See also b/414535123#comment8.
return false;
}
return true;
}
public boolean isOutliningOfMethodsEnabled() {
// TODO(b/384426376): Should not check backend when isApiModelingEnabled() return false for CF.
return isApiModelingEnabled() && options.isGeneratingDex() && enableOutliningOfMethods;
}
public boolean isCfToCfApiOutliningEnabled(AppView<?> appView) {
if (isOutliningOfMethodsEnabled()) {
if (options.partialSubCompilationConfiguration != null) {
// When running R8 partial, enable cf-to-cf desugaring when there is no library desugaring.
return !options.getSubCompilationLibraryDesugaringOptions().isEnabled();
}
if (appView.enableWholeProgramOptimizations()) {
// When running R8, for consistency with R8 partial, enable cf-to-cf desugaring when there
// is no library desugaring.
return !options.getLibraryDesugaringOptions().isEnabled();
} else {
// Always use cf-to-cf desugaring in D8.
return true;
}
}
return false;
}
public boolean isLirToLirApiOutliningEnabled(AppView<?> appView) {
return isOutliningOfMethodsEnabled() && !isCfToCfApiOutliningEnabled(appView);
}
public boolean isCheckAllApiReferencesAreSet() {
return isApiModelingEnabled() && checkAllApiReferencesAreSet;
}
public boolean isReportUnknownApiReferencesEnabled() {
return isApiModelingEnabled()
&& System.getProperty("com.android.tools.r8.reportUnknownApiReferences") != null;
}
public AndroidApiModelingOptions disableApiModeling() {
return setEnableApiModeling(false);
}
public AndroidApiModelingOptions setEnableApiModeling(boolean value) {
enableLibraryApiModeling = value;
return this;
}
public AndroidApiModelingOptions disableApiCallerIdentification() {
return setEnableApiCallerIdentification(false);
}
public AndroidApiModelingOptions setEnableApiCallerIdentification(boolean value) {
enableApiCallerIdentification = value;
return this;
}
public AndroidApiModelingOptions disableOutlining() {
return setEnableOutliningOfMethods(false);
}
public AndroidApiModelingOptions setEnableOutliningOfMethods(boolean value) {
enableOutliningOfMethods = value;
return this;
}
public AndroidApiModelingOptions disableStubbingOfClasses() {
return setEnableStubbingOfClasses(false);
}
public AndroidApiModelingOptions setEnableStubbingOfClasses(boolean value) {
enableStubbingOfClasses = value;
return this;
}
public AndroidApiModelingOptions setCheckAllApiReferencesAreSet(boolean value) {
checkAllApiReferencesAreSet = value;
return this;
}
public void visitMockedApiLevelsForReferences(
DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
assert isApiModelingEnabled();
classApiMapping.forEach(
(classReference, apiLevel) ->
apiLevelConsumer.accept(factory.createType(classReference.getDescriptor()), apiLevel));
fieldApiMapping.forEach(
(fieldReference, apiLevel) ->
apiLevelConsumer.accept(factory.createField(fieldReference), apiLevel));
methodApiMapping.forEach(
(methodReference, apiLevel) ->
apiLevelConsumer.accept(factory.createMethod(methodReference), apiLevel));
}
}