Legacy to Human specification conversion
- desugared library
Bug: 184026720
Change-Id: I51fb9007378210f1bff02d89f5bfde60312926bd
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
new file mode 100644
index 0000000..6a6a2d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -0,0 +1,170 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class HumanDesugaredLibrarySpecification {
+
+ private final boolean libraryCompilation;
+ private final HumanTopLevelFlags topLevelFlags;
+ private final HumanRewritingFlags rewritingFlags;
+ private final PrefixRewritingMapper prefixRewritingMapper;
+
+ public static HumanDesugaredLibrarySpecification withOnlyRewritePrefixForTesting(
+ Map<String, String> prefix, InternalOptions options) {
+ return new HumanDesugaredLibrarySpecification(
+ HumanTopLevelFlags.empty(),
+ HumanRewritingFlags.withOnlyRewritePrefixForTesting(prefix, options),
+ true,
+ options.itemFactory);
+ }
+
+ public static HumanDesugaredLibrarySpecification empty() {
+ return new HumanDesugaredLibrarySpecification(
+ HumanTopLevelFlags.empty(), HumanRewritingFlags.empty(), false, null) {
+
+ @Override
+ public boolean isSupported(DexReference reference, AppView<?> appView) {
+ return false;
+ }
+
+ @Override
+ public boolean isEmptyConfiguration() {
+ return true;
+ }
+ };
+ }
+
+ public HumanDesugaredLibrarySpecification(
+ HumanTopLevelFlags topLevelFlags,
+ HumanRewritingFlags rewritingFlags,
+ boolean libraryCompilation,
+ DexItemFactory factory) {
+ this.libraryCompilation = libraryCompilation;
+ this.topLevelFlags = topLevelFlags;
+ this.rewritingFlags = rewritingFlags;
+ this.prefixRewritingMapper =
+ rewritingFlags.getRewritePrefix().isEmpty()
+ ? PrefixRewritingMapper.empty()
+ : new DesugarPrefixRewritingMapper(
+ rewritingFlags.getRewritePrefix(), factory, libraryCompilation);
+ }
+
+ public boolean supportAllCallbacksFromLibrary() {
+ return topLevelFlags.supportAllCallbacksFromLibrary();
+ }
+
+ public PrefixRewritingMapper getPrefixRewritingMapper() {
+ return prefixRewritingMapper;
+ }
+
+ public AndroidApiLevel getRequiredCompilationApiLevel() {
+ return topLevelFlags.getRequiredCompilationAPILevel();
+ }
+
+ public boolean isLibraryCompilation() {
+ return libraryCompilation;
+ }
+
+ public String getSynthesizedLibraryClassesPackagePrefix() {
+ return topLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
+ }
+
+ public HumanTopLevelFlags getTopLevelFlags() {
+ return topLevelFlags;
+ }
+
+ public HumanRewritingFlags getRewritingFlags() {
+ return rewritingFlags;
+ }
+
+ public String getIdentifier() {
+ return topLevelFlags.getIdentifier();
+ }
+
+ public Map<String, String> getRewritePrefix() {
+ return rewritingFlags.getRewritePrefix();
+ }
+
+ public boolean hasEmulatedLibraryInterfaces() {
+ return !getEmulateLibraryInterface().isEmpty();
+ }
+
+ public Map<DexType, DexType> getEmulateLibraryInterface() {
+ return rewritingFlags.getEmulateLibraryInterface();
+ }
+
+ public boolean isSupported(DexReference reference, AppView<?> appView) {
+ return prefixRewritingMapper.hasRewrittenType(reference.getContextType(), appView);
+ }
+
+ // If the method is retargeted, answers the retargeted method, else null.
+ public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
+ Map<DexMethod, DexType> retargetCoreLibMember = rewritingFlags.getRetargetCoreLibMember();
+ DexType dexType = retargetCoreLibMember.get(method.getReference());
+ if (dexType != null) {
+ return appView
+ .dexItemFactory()
+ .createMethod(
+ dexType,
+ appView.dexItemFactory().prependHolderToProto(method.getReference()),
+ method.getName());
+ }
+ return null;
+ }
+
+ public DexMethod retargetMethod(DexClassAndMethod method, AppView<?> appView) {
+ return retargetMethod(method.getDefinition(), appView);
+ }
+
+ public Map<DexMethod, DexType> getRetargetCoreLibMember() {
+ return rewritingFlags.getRetargetCoreLibMember();
+ }
+
+ public Map<DexType, DexType> getBackportCoreLibraryMember() {
+ return rewritingFlags.getBackportCoreLibraryMember();
+ }
+
+ public Map<DexType, DexType> getCustomConversions() {
+ return rewritingFlags.getCustomConversions();
+ }
+
+ public Set<DexType> getWrapperConversions() {
+ return rewritingFlags.getWrapperConversions();
+ }
+
+ public Set<DexMethod> getDontRewriteInvocation() {
+ return rewritingFlags.getDontRewriteInvocation();
+ }
+
+ public Set<DexType> getDontRetargetLibMember() {
+ return rewritingFlags.getDontRetargetLibMember();
+ }
+
+ public List<String> getExtraKeepRules() {
+ return topLevelFlags.getExtraKeepRules();
+ }
+
+ public String getJsonSource() {
+ return topLevelFlags.getJsonSource();
+ }
+
+ public boolean isEmptyConfiguration() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
new file mode 100644
index 0000000..e4f8241
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,219 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class HumanDesugaredLibrarySpecificationParser {
+
+ static final String IDENTIFIER_KEY = "identifier";
+ static final String REQUIRED_COMPILATION_API_LEVEL_KEY = "required_compilation_api_level";
+ static final String SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY =
+ "synthesized_library_classes_package_prefix";
+
+ static final String COMMON_FLAGS_KEY = "common_flags";
+ static final String LIBRARY_FLAGS_KEY = "library_flags";
+ static final String PROGRAM_FLAGS_KEY = "program_flags";
+
+ static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
+ static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
+ static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
+ static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+ static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member";
+ static final String EMULATE_INTERFACE_KEY = "emulate_interface";
+ static final String DONT_REWRITE_KEY = "dont_rewrite";
+ static final String DONT_RETARGET_LIB_MEMBER_KEY = "dont_retarget_lib_member";
+ static final String BACKPORT_KEY = "backport";
+ static final String SHRINKER_CONFIG_KEY = "shrinker_config";
+ static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
+
+ final DexItemFactory dexItemFactory;
+ final Reporter reporter;
+ private final boolean libraryCompilation;
+ private final int minAPILevel;
+
+ Origin origin;
+ JsonObject jsonConfig;
+
+ public HumanDesugaredLibrarySpecificationParser(
+ DexItemFactory dexItemFactory,
+ Reporter reporter,
+ boolean libraryCompilation,
+ int minAPILevel) {
+ this.dexItemFactory = dexItemFactory;
+ this.reporter = reporter;
+ this.minAPILevel = minAPILevel;
+ this.libraryCompilation = libraryCompilation;
+ }
+
+ JsonElement required(JsonObject json, String key) {
+ if (!json.has(key)) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid desugared library configuration. Expected required key '" + key + "'",
+ origin));
+ }
+ return json.get(key);
+ }
+
+ public HumanDesugaredLibrarySpecification parse(StringResource stringResource) {
+ return parse(stringResource, builder -> {});
+ }
+
+ public HumanDesugaredLibrarySpecification parse(
+ StringResource stringResource, Consumer<HumanTopLevelFlags.Builder> topLevelFlagAmender) {
+ String jsonConfigString = parseJson(stringResource);
+
+ HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, topLevelFlagAmender);
+
+ HumanRewritingFlags legacyRewritingFlags = parseRewritingFlags();
+
+ HumanDesugaredLibrarySpecification config =
+ new HumanDesugaredLibrarySpecification(
+ topLevelFlags, legacyRewritingFlags, libraryCompilation, dexItemFactory);
+ origin = null;
+ return config;
+ }
+
+ String parseJson(StringResource stringResource) {
+ setOrigin(stringResource);
+ String jsonConfigString;
+ try {
+ jsonConfigString = stringResource.getString();
+ JsonParser parser = new JsonParser();
+ jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
+ } catch (Exception e) {
+ throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
+ }
+ return jsonConfigString;
+ }
+
+ void setOrigin(StringResource stringResource) {
+ origin = stringResource.getOrigin();
+ assert origin != null;
+ }
+
+ private HumanRewritingFlags parseRewritingFlags() {
+ HumanRewritingFlags.Builder builder =
+ HumanRewritingFlags.builder(dexItemFactory, reporter, origin);
+ JsonElement commonFlags = required(jsonConfig, COMMON_FLAGS_KEY);
+ JsonElement libraryFlags = required(jsonConfig, LIBRARY_FLAGS_KEY);
+ JsonElement programFlags = required(jsonConfig, PROGRAM_FLAGS_KEY);
+ parseFlagsList(commonFlags.getAsJsonArray(), builder);
+ parseFlagsList(
+ libraryCompilation ? libraryFlags.getAsJsonArray() : programFlags.getAsJsonArray(),
+ builder);
+ return builder.build();
+ }
+
+ HumanTopLevelFlags parseTopLevelFlags(
+ String jsonConfigString, Consumer<HumanTopLevelFlags.Builder> topLevelFlagAmender) {
+ HumanTopLevelFlags.Builder builder = HumanTopLevelFlags.builder();
+
+ builder.setJsonSource(jsonConfigString);
+
+ String identifier = required(jsonConfig, IDENTIFIER_KEY).getAsString();
+ builder.setDesugaredLibraryIdentifier(identifier);
+ builder.setSynthesizedLibraryClassesPackagePrefix(
+ required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString());
+
+ int required_compilation_api_level =
+ required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt();
+ builder.setRequiredCompilationAPILevel(
+ AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level));
+ if (jsonConfig.has(SHRINKER_CONFIG_KEY)) {
+ JsonArray jsonKeepRules = jsonConfig.get(SHRINKER_CONFIG_KEY).getAsJsonArray();
+ List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size());
+ for (JsonElement keepRule : jsonKeepRules) {
+ extraKeepRules.add(keepRule.getAsString());
+ }
+ builder.setExtraKeepRules(extraKeepRules);
+ }
+
+ if (jsonConfig.has(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY)) {
+ boolean supportAllCallbacksFromLibrary =
+ jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean();
+ builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary);
+ }
+
+ topLevelFlagAmender.accept(builder);
+
+ return builder.build();
+ }
+
+ private void parseFlagsList(JsonArray jsonFlags, HumanRewritingFlags.Builder builder) {
+ for (JsonElement jsonFlagSet : jsonFlags) {
+ JsonObject flag = jsonFlagSet.getAsJsonObject();
+ int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+ if (minAPILevel <= api_level_below_or_equal) {
+ parseFlags(flag, builder);
+ }
+ }
+ }
+
+ void parseFlags(JsonObject jsonFlagSet, HumanRewritingFlags.Builder builder) {
+ if (jsonFlagSet.has(REWRITE_PREFIX_KEY)) {
+ for (Map.Entry<String, JsonElement> rewritePrefix :
+ jsonFlagSet.get(REWRITE_PREFIX_KEY).getAsJsonObject().entrySet()) {
+ builder.putRewritePrefix(rewritePrefix.getKey(), rewritePrefix.getValue().getAsString());
+ }
+ }
+ if (jsonFlagSet.has(RETARGET_LIB_MEMBER_KEY)) {
+ for (Map.Entry<String, JsonElement> retarget :
+ jsonFlagSet.get(RETARGET_LIB_MEMBER_KEY).getAsJsonObject().entrySet()) {
+ builder.putRetargetCoreLibMember(retarget.getKey(), retarget.getValue().getAsString());
+ }
+ }
+ if (jsonFlagSet.has(BACKPORT_KEY)) {
+ for (Map.Entry<String, JsonElement> backport :
+ jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) {
+ builder.putBackportCoreLibraryMember(backport.getKey(), backport.getValue().getAsString());
+ }
+ }
+ if (jsonFlagSet.has(EMULATE_INTERFACE_KEY)) {
+ for (Map.Entry<String, JsonElement> itf :
+ jsonFlagSet.get(EMULATE_INTERFACE_KEY).getAsJsonObject().entrySet()) {
+ builder.putEmulateLibraryInterface(itf.getKey(), itf.getValue().getAsString());
+ }
+ }
+ if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) {
+ for (Map.Entry<String, JsonElement> conversion :
+ jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) {
+ builder.putCustomConversion(conversion.getKey(), conversion.getValue().getAsString());
+ }
+ }
+ if (jsonFlagSet.has(WRAPPER_CONVERSION_KEY)) {
+ for (JsonElement wrapper : jsonFlagSet.get(WRAPPER_CONVERSION_KEY).getAsJsonArray()) {
+ builder.addWrapperConversion(wrapper.getAsString());
+ }
+ }
+ if (jsonFlagSet.has(DONT_REWRITE_KEY)) {
+ JsonArray dontRewrite = jsonFlagSet.get(DONT_REWRITE_KEY).getAsJsonArray();
+ for (JsonElement rewrite : dontRewrite) {
+ builder.addDontRewriteInvocation(rewrite.getAsString());
+ }
+ }
+ if (jsonFlagSet.has(DONT_RETARGET_LIB_MEMBER_KEY)) {
+ JsonArray dontRetarget = jsonFlagSet.get(DONT_RETARGET_LIB_MEMBER_KEY).getAsJsonArray();
+ for (JsonElement rewrite : dontRetarget) {
+ builder.addDontRetargetLibMember(rewrite.getAsString());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
new file mode 100644
index 0000000..8adb9b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -0,0 +1,351 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class HumanRewritingFlags {
+
+ private final Map<String, String> rewritePrefix;
+ private final Map<DexType, DexType> emulateLibraryInterface;
+ private final Map<DexMethod, DexType> retargetCoreLibMember;
+ private final Map<DexType, DexType> backportCoreLibraryMember;
+ private final Map<DexType, DexType> customConversions;
+ private final Set<DexMethod> dontRewriteInvocation;
+ private final Set<DexType> dontRetargetLibMember;
+ private final Set<DexType> wrapperConversions;
+
+ HumanRewritingFlags(
+ Map<String, String> rewritePrefix,
+ Map<DexType, DexType> emulateLibraryInterface,
+ Map<DexMethod, DexType> retargetCoreLibMember,
+ Map<DexType, DexType> backportCoreLibraryMember,
+ Map<DexType, DexType> customConversions,
+ Set<DexMethod> dontRewriteInvocation,
+ Set<DexType> dontRetargetLibMember,
+ Set<DexType> wrapperConversions) {
+ this.rewritePrefix = rewritePrefix;
+ this.emulateLibraryInterface = emulateLibraryInterface;
+ this.retargetCoreLibMember = retargetCoreLibMember;
+ this.backportCoreLibraryMember = backportCoreLibraryMember;
+ this.customConversions = customConversions;
+ this.dontRewriteInvocation = dontRewriteInvocation;
+ this.dontRetargetLibMember = dontRetargetLibMember;
+ this.wrapperConversions = wrapperConversions;
+ }
+
+ public static HumanRewritingFlags empty() {
+ return new HumanRewritingFlags(
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ImmutableSet.of(),
+ ImmutableSet.of(),
+ ImmutableSet.of());
+ }
+
+ public static HumanRewritingFlags withOnlyRewritePrefixForTesting(
+ Map<String, String> prefix, InternalOptions options) {
+ Builder builder = builder(options.dexItemFactory(), options.reporter, Origin.unknown());
+ prefix.forEach(builder::putRewritePrefix);
+ return builder.build();
+ }
+
+ public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
+ return new Builder(dexItemFactory, reporter, origin);
+ }
+
+ public Builder newBuilder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
+ return new Builder(
+ dexItemFactory,
+ reporter,
+ origin,
+ rewritePrefix,
+ emulateLibraryInterface,
+ retargetCoreLibMember,
+ backportCoreLibraryMember,
+ customConversions,
+ dontRewriteInvocation,
+ dontRetargetLibMember,
+ wrapperConversions);
+ }
+
+ public Map<String, String> getRewritePrefix() {
+ return rewritePrefix;
+ }
+
+ public Map<DexType, DexType> getEmulateLibraryInterface() {
+ return emulateLibraryInterface;
+ }
+
+ public Map<DexMethod, DexType> getRetargetCoreLibMember() {
+ return retargetCoreLibMember;
+ }
+
+ public Map<DexType, DexType> getBackportCoreLibraryMember() {
+ return backportCoreLibraryMember;
+ }
+
+ public Map<DexType, DexType> getCustomConversions() {
+ return customConversions;
+ }
+
+ public Set<DexMethod> getDontRewriteInvocation() {
+ return dontRewriteInvocation;
+ }
+
+ public Set<DexType> getDontRetargetLibMember() {
+ return dontRetargetLibMember;
+ }
+
+ public Set<DexType> getWrapperConversions() {
+ return wrapperConversions;
+ }
+
+ public static class Builder {
+
+ private static final String SEPARATORS = "\\s+|,\\s+|#|\\(|\\)";
+
+ private final DexItemFactory factory;
+ private final Reporter reporter;
+ private final Origin origin;
+
+ private final Map<String, String> rewritePrefix;
+ private final Map<DexType, DexType> emulateLibraryInterface;
+ private final Map<DexMethod, DexType> retargetCoreLibMember;
+ private final Map<DexType, DexType> backportCoreLibraryMember;
+ private final Map<DexType, DexType> customConversions;
+ private final Set<DexMethod> dontRewriteInvocation;
+ private final Set<DexType> dontRetargetLibMember;
+ private final Set<DexType> wrapperConversions;
+
+ Builder(DexItemFactory factory, Reporter reporter, Origin origin) {
+ this(
+ factory,
+ reporter,
+ origin,
+ new HashMap<>(),
+ new IdentityHashMap<>(),
+ new IdentityHashMap<>(),
+ new IdentityHashMap<>(),
+ new IdentityHashMap<>(),
+ Sets.newIdentityHashSet(),
+ Sets.newIdentityHashSet(),
+ Sets.newIdentityHashSet());
+ }
+
+ Builder(
+ DexItemFactory factory,
+ Reporter reporter,
+ Origin origin,
+ Map<String, String> rewritePrefix,
+ Map<DexType, DexType> emulateLibraryInterface,
+ Map<DexMethod, DexType> retargetCoreLibMember,
+ Map<DexType, DexType> backportCoreLibraryMember,
+ Map<DexType, DexType> customConversions,
+ Set<DexMethod> dontRewriteInvocation,
+ Set<DexType> dontRetargetLibMember,
+ Set<DexType> wrapperConversions) {
+ this.factory = factory;
+ this.reporter = reporter;
+ this.origin = origin;
+ this.rewritePrefix = new HashMap<>(rewritePrefix);
+ this.emulateLibraryInterface = new IdentityHashMap<>(emulateLibraryInterface);
+ this.retargetCoreLibMember = new IdentityHashMap<>(retargetCoreLibMember);
+ this.backportCoreLibraryMember = new IdentityHashMap<>(backportCoreLibraryMember);
+ this.customConversions = new IdentityHashMap<>(customConversions);
+ this.dontRewriteInvocation = dontRewriteInvocation;
+ this.dontRetargetLibMember = dontRetargetLibMember;
+ this.wrapperConversions = wrapperConversions;
+ }
+
+ // Utility to set values. Currently assumes the key is fresh.
+ private <K, V> void put(Map<K, V> map, K key, V value, String desc) {
+ if (map.containsKey(key)) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid desugared library configuration. "
+ + " Duplicate assignment of key: '"
+ + key
+ + "' in sections for '"
+ + desc
+ + "'",
+ origin));
+ }
+ map.put(key, value);
+ }
+
+ public Builder putRewritePrefix(String prefix, String rewrittenPrefix) {
+ put(
+ rewritePrefix,
+ prefix,
+ rewrittenPrefix,
+ HumanDesugaredLibrarySpecificationParser.REWRITE_PREFIX_KEY);
+ return this;
+ }
+
+ public Builder putEmulateLibraryInterface(
+ String emulateLibraryItf, String rewrittenEmulateLibraryItf) {
+ DexType interfaceType = stringClassToDexType(emulateLibraryItf);
+ DexType rewrittenType = stringClassToDexType(rewrittenEmulateLibraryItf);
+ putEmulateLibraryInterface(interfaceType, rewrittenType);
+ return this;
+ }
+
+ public Builder putEmulateLibraryInterface(DexType interfaceType, DexType rewrittenType) {
+ put(
+ emulateLibraryInterface,
+ interfaceType,
+ rewrittenType,
+ HumanDesugaredLibrarySpecificationParser.EMULATE_INTERFACE_KEY);
+ return this;
+ }
+
+ public Builder putCustomConversion(String type, String conversionHolder) {
+ DexType dexType = stringClassToDexType(type);
+ DexType conversionType = stringClassToDexType(conversionHolder);
+ putCustomConversion(dexType, conversionType);
+ return this;
+ }
+
+ public Builder putCustomConversion(DexType dexType, DexType conversionType) {
+ put(
+ customConversions,
+ dexType,
+ conversionType,
+ HumanDesugaredLibrarySpecificationParser.CUSTOM_CONVERSION_KEY);
+ return this;
+ }
+
+ public Builder addWrapperConversion(String type) {
+ DexType dexType = stringClassToDexType(type);
+ addWrapperConversion(dexType);
+ return this;
+ }
+
+ public Builder addWrapperConversion(DexType dexType) {
+ wrapperConversions.add(dexType);
+ return this;
+ }
+
+ public Builder putRetargetCoreLibMember(String retarget, String rewrittenRetarget) {
+ DexMethod key = parseMethod(retarget);
+ DexType rewrittenType = stringClassToDexType(rewrittenRetarget);
+ putRetargetCoreLibMember(key, rewrittenType);
+ return this;
+ }
+
+ public Builder putRetargetCoreLibMember(DexMethod key, DexType rewrittenType) {
+ put(
+ retargetCoreLibMember,
+ key,
+ rewrittenType,
+ HumanDesugaredLibrarySpecificationParser.RETARGET_LIB_MEMBER_KEY);
+ return this;
+ }
+
+ public Builder putBackportCoreLibraryMember(String backport, String rewrittenBackport) {
+ DexType backportType = stringClassToDexType(backport);
+ DexType rewrittenBackportType = stringClassToDexType(rewrittenBackport);
+ putBackportCoreLibraryMember(backportType, rewrittenBackportType);
+ return this;
+ }
+
+ public Builder putBackportCoreLibraryMember(
+ DexType backportType, DexType rewrittenBackportType) {
+ put(
+ backportCoreLibraryMember,
+ backportType,
+ rewrittenBackportType,
+ HumanDesugaredLibrarySpecificationParser.BACKPORT_KEY);
+ return this;
+ }
+
+ public Builder addDontRewriteInvocation(String dontRewriteInvocation) {
+ DexMethod dontRewrite = parseMethod(dontRewriteInvocation);
+ addDontRewriteInvocation(dontRewrite);
+ return this;
+ }
+
+ public Builder addDontRewriteInvocation(DexMethod dontRewrite) {
+ this.dontRewriteInvocation.add(dontRewrite);
+ return this;
+ }
+
+ public Builder addDontRetargetLibMember(String dontRetargetLibMember) {
+ addDontRetargetLibMember(stringClassToDexType(dontRetargetLibMember));
+ return this;
+ }
+
+ public Builder addDontRetargetLibMember(DexType dontRetargetLibMember) {
+ this.dontRetargetLibMember.add(dontRetargetLibMember);
+ return this;
+ }
+
+ private DexMethod parseMethod(String signature) {
+ String[] split = signature.split(SEPARATORS);
+ assert split.length >= 3;
+ DexType returnType = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[0]));
+ DexType holderType = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[1]));
+ DexString name = factory.createString(split[2]);
+ DexType[] argTypes = new DexType[split.length - 3];
+ for (int i = 3; i < split.length; i++) {
+ argTypes[i - 3] = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[i]));
+ }
+ DexProto proto = factory.createProto(returnType, argTypes);
+ return factory.createMethod(holderType, proto, name);
+ }
+
+ private DexType stringClassToDexType(String stringClass) {
+ return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass));
+ }
+
+ public HumanRewritingFlags build() {
+ validate();
+ return new HumanRewritingFlags(
+ ImmutableMap.copyOf(rewritePrefix),
+ ImmutableMap.copyOf(emulateLibraryInterface),
+ ImmutableMap.copyOf(retargetCoreLibMember),
+ ImmutableMap.copyOf(backportCoreLibraryMember),
+ ImmutableMap.copyOf(customConversions),
+ ImmutableSet.copyOf(dontRewriteInvocation),
+ ImmutableSet.copyOf(dontRetargetLibMember),
+ ImmutableSet.copyOf(wrapperConversions));
+ }
+
+ private void validate() {
+ SetView<DexType> dups = Sets.intersection(customConversions.keySet(), wrapperConversions);
+ if (!dups.isEmpty()) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid desugared library configuration. "
+ + "Duplicate types in custom conversions and wrapper conversions: "
+ + String.join(
+ ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
+ origin));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
new file mode 100644
index 0000000..80a13b5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class HumanTopLevelFlags {
+
+ private final AndroidApiLevel requiredCompilationAPILevel;
+ private final String synthesizedLibraryClassesPackagePrefix;
+ private final String identifier;
+ private final String jsonSource;
+ // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs,
+ // more specifically:
+ // - no call-back is generated for emulated interface method overrides (forEach, etc.)
+ // - no call-back is generated inside the desugared library itself.
+ // Such setting decreases significantly the desugared library dex file, but virtual calls from
+ // within the library to desugared library classes instances as receiver may be incorrect, for
+ // example the method forEach in Iterable may be executed over a concrete implementation.
+ private final boolean supportAllCallbacksFromLibrary;
+ private final List<String> extraKeepRules;
+
+ HumanTopLevelFlags(
+ AndroidApiLevel requiredCompilationAPILevel,
+ String synthesizedLibraryClassesPackagePrefix,
+ String identifier,
+ String jsonSource,
+ boolean supportAllCallbacksFromLibrary,
+ List<String> extraKeepRules) {
+ this.requiredCompilationAPILevel = requiredCompilationAPILevel;
+ this.synthesizedLibraryClassesPackagePrefix = synthesizedLibraryClassesPackagePrefix;
+ this.identifier = identifier;
+ this.jsonSource = jsonSource;
+ this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
+ this.extraKeepRules = extraKeepRules;
+ }
+
+ public static HumanTopLevelFlags empty() {
+ return new HumanTopLevelFlags(
+ AndroidApiLevel.B, "unused", null, null, true, ImmutableList.of());
+ }
+
+ public static HumanTopLevelFlags testing() {
+ return new HumanTopLevelFlags(
+ AndroidApiLevel.B, "unused", "testing", null, true, ImmutableList.of());
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public AndroidApiLevel getRequiredCompilationAPILevel() {
+ return requiredCompilationAPILevel;
+ }
+
+ public String getSynthesizedLibraryClassesPackagePrefix() {
+ return synthesizedLibraryClassesPackagePrefix;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public String getJsonSource() {
+ return jsonSource;
+ }
+
+ public boolean supportAllCallbacksFromLibrary() {
+ return supportAllCallbacksFromLibrary;
+ }
+
+ public List<String> getExtraKeepRules() {
+ return extraKeepRules;
+ }
+
+ public static class Builder {
+
+ private AndroidApiLevel requiredCompilationAPILevel;
+ private String synthesizedLibraryClassesPackagePrefix;
+ private String identifier;
+ private String jsonSource;
+ private Boolean supportAllCallbacksFromLibrary;
+ private List<String> extraKeepRules;
+
+ Builder() {}
+
+ public Builder setRequiredCompilationAPILevel(AndroidApiLevel requiredCompilationAPILevel) {
+ this.requiredCompilationAPILevel = requiredCompilationAPILevel;
+ return this;
+ }
+
+ public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) {
+ this.synthesizedLibraryClassesPackagePrefix = prefix.replace('.', '/');
+ return this;
+ }
+
+ public Builder setDesugaredLibraryIdentifier(String identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ public Builder setJsonSource(String jsonSource) {
+ this.jsonSource = jsonSource;
+ return this;
+ }
+
+ public Builder setSupportAllCallbacksFromLibrary(boolean supportAllCallbacksFromLibrary) {
+ this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
+ return this;
+ }
+
+ public Builder setExtraKeepRules(List<String> rules) {
+ extraKeepRules = rules;
+ return this;
+ }
+
+ public HumanTopLevelFlags build() {
+ assert synthesizedLibraryClassesPackagePrefix != null;
+ assert supportAllCallbacksFromLibrary != null;
+ return new HumanTopLevelFlags(
+ requiredCompilationAPILevel,
+ synthesizedLibraryClassesPackagePrefix,
+ identifier,
+ jsonSource,
+ supportAllCallbacksFromLibrary,
+ extraKeepRules);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
new file mode 100644
index 0000000..dff3d13
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+
+public class MultiAPILevelHumanDesugaredLibrarySpecification {
+
+ private final HumanTopLevelFlags topLevelFlags;
+ private final Int2ObjectMap<HumanRewritingFlags> commonFlags;
+ private final Int2ObjectMap<HumanRewritingFlags> libraryFlags;
+ private final Int2ObjectMap<HumanRewritingFlags> programFlags;
+
+ public MultiAPILevelHumanDesugaredLibrarySpecification(
+ HumanTopLevelFlags topLevelFlags,
+ Int2ObjectMap<HumanRewritingFlags> commonFlags,
+ Int2ObjectMap<HumanRewritingFlags> libraryFlags,
+ Int2ObjectMap<HumanRewritingFlags> programFlags) {
+ this.topLevelFlags = topLevelFlags;
+ this.commonFlags = commonFlags;
+ this.libraryFlags = libraryFlags;
+ this.programFlags = programFlags;
+ }
+
+ public HumanTopLevelFlags getTopLevelFlags() {
+ return topLevelFlags;
+ }
+
+ public Int2ObjectMap<HumanRewritingFlags> getCommonFlags() {
+ return commonFlags;
+ }
+
+ public Int2ObjectMap<HumanRewritingFlags> getLibraryFlags() {
+ return libraryFlags;
+ }
+
+ public Int2ObjectMap<HumanRewritingFlags> getProgramFlags() {
+ return programFlags;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
new file mode 100644
index 0000000..769d522
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.*;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.IDENTIFIER_KEY;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.Sets;
+import com.google.gson.Gson;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter {
+
+ public static void export(
+ MultiAPILevelHumanDesugaredLibrarySpecification specification, StringConsumer output) {
+ new MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter()
+ .internalExport(specification, output);
+ }
+
+ private void internalExport(
+ MultiAPILevelHumanDesugaredLibrarySpecification humanSpec, StringConsumer output) {
+ HashMap<String, Object> toJson = new LinkedHashMap<>();
+ toJson.put(IDENTIFIER_KEY, humanSpec.getTopLevelFlags().getIdentifier());
+ toJson.put(
+ REQUIRED_COMPILATION_API_LEVEL_KEY,
+ humanSpec.getTopLevelFlags().getRequiredCompilationAPILevel().getLevel());
+ toJson.put(
+ SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY,
+ humanSpec.getTopLevelFlags().getSynthesizedLibraryClassesPackagePrefix());
+ toJson.put(
+ SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY,
+ humanSpec.getTopLevelFlags().supportAllCallbacksFromLibrary());
+
+ toJson.put(COMMON_FLAGS_KEY, rewritingFlagsToString(humanSpec.getCommonFlags()));
+ toJson.put(PROGRAM_FLAGS_KEY, rewritingFlagsToString(humanSpec.getProgramFlags()));
+ toJson.put(LIBRARY_FLAGS_KEY, rewritingFlagsToString(humanSpec.getLibraryFlags()));
+
+ toJson.put(SHRINKER_CONFIG_KEY, humanSpec.getTopLevelFlags().getExtraKeepRules());
+
+ Gson gson = new Gson();
+ String export = gson.toJson(toJson);
+ output.accept(export, new DiagnosticsHandler() {});
+ }
+
+ private List<Object> rewritingFlagsToString(
+ Int2ObjectMap<HumanRewritingFlags> rewritingFlagsMap) {
+ ArrayList<Object> list = new ArrayList<>();
+ rewritingFlagsMap.forEach(
+ (apiBelowOrEqual, flags) -> {
+ HashMap<String, Object> toJson = new LinkedHashMap<>();
+ toJson.put(API_LEVEL_BELOW_OR_EQUAL_KEY, apiBelowOrEqual);
+ if (!flags.getRewritePrefix().isEmpty()) {
+ toJson.put(REWRITE_PREFIX_KEY, new TreeMap(flags.getRewritePrefix()));
+ }
+ if (!flags.getEmulateLibraryInterface().isEmpty()) {
+ toJson.put(EMULATE_INTERFACE_KEY, mapToString(flags.getEmulateLibraryInterface()));
+ }
+ if (!flags.getDontRewriteInvocation().isEmpty()) {
+ toJson.put(DONT_REWRITE_KEY, setToString(flags.getDontRewriteInvocation()));
+ }
+ if (!flags.getRetargetCoreLibMember().isEmpty()) {
+ toJson.put(RETARGET_LIB_MEMBER_KEY, mapToString(flags.getRetargetCoreLibMember()));
+ }
+ if (!flags.getDontRetargetLibMember().isEmpty()) {
+ toJson.put(DONT_RETARGET_LIB_MEMBER_KEY, setToString(flags.getDontRetargetLibMember()));
+ }
+ if (!flags.getBackportCoreLibraryMember().isEmpty()) {
+ toJson.put(BACKPORT_KEY, mapToString(flags.getBackportCoreLibraryMember()));
+ }
+ if (!flags.getWrapperConversions().isEmpty()) {
+ toJson.put(WRAPPER_CONVERSION_KEY, setToString(flags.getWrapperConversions()));
+ }
+ if (!flags.getCustomConversions().isEmpty()) {
+ toJson.put(CUSTOM_CONVERSION_KEY, mapToString(flags.getCustomConversions()));
+ }
+ list.add(toJson);
+ });
+ return list;
+ }
+
+ private Set<String> setToString(Set<? extends DexItem> set) {
+ Set<String> stringSet = Sets.newHashSet();
+ set.forEach(e -> stringSet.add(toString(e)));
+ return stringSet;
+ }
+
+ private Map<String, String> mapToString(Map<? extends DexItem, ? extends DexItem> map) {
+ Map<String, String> stringMap = new TreeMap<>();
+ map.forEach((k, v) -> stringMap.put(toString(k), toString(v)));
+ return stringMap;
+ }
+
+ private String toString(DexItem o) {
+ if (o instanceof DexType) {
+ return o.toString();
+ }
+ if (o instanceof DexMethod) {
+ DexMethod method = (DexMethod) o;
+ StringBuilder sb =
+ new StringBuilder()
+ .append(method.getReturnType())
+ .append(" ")
+ .append(method.getHolderType())
+ .append("#")
+ .append(method.getName())
+ .append("(");
+ for (int i = 0; i < method.getParameters().size(); i++) {
+ sb.append(method.getParameter(i));
+ if (i != method.getParameters().size() - 1) {
+ sb.append(", ");
+ }
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+ throw new Unreachable();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
new file mode 100644
index 0000000..54c6c46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.humanspecification;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.Reporter;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+
+public class MultiAPILevelHumanDesugaredLibrarySpecificationParser
+ extends HumanDesugaredLibrarySpecificationParser {
+
+ public MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+ DexItemFactory dexItemFactory, Reporter reporter) {
+ super(dexItemFactory, reporter, false, 1);
+ }
+
+ public MultiAPILevelHumanDesugaredLibrarySpecification parseMultiLevelConfiguration(
+ StringResource stringResource) {
+
+ String jsonConfigString = parseJson(stringResource);
+
+ HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, builder -> {});
+
+ Int2ObjectMap<HumanRewritingFlags> commonFlags = parseAllFlags(COMMON_FLAGS_KEY);
+ Int2ObjectMap<HumanRewritingFlags> libraryFlags = parseAllFlags(LIBRARY_FLAGS_KEY);
+ Int2ObjectMap<HumanRewritingFlags> programFlags = parseAllFlags(PROGRAM_FLAGS_KEY);
+
+ return new MultiAPILevelHumanDesugaredLibrarySpecification(
+ topLevelFlags, commonFlags, libraryFlags, programFlags);
+ }
+
+ private Int2ObjectMap<HumanRewritingFlags> parseAllFlags(String flagKey) {
+ JsonElement jsonFlags = required(jsonConfig, flagKey);
+ Int2ObjectMap<HumanRewritingFlags> flags = new Int2ObjectArrayMap<>();
+ for (JsonElement jsonFlagSet : jsonFlags.getAsJsonArray()) {
+ JsonObject flag = jsonFlagSet.getAsJsonObject();
+ int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+ HumanRewritingFlags.Builder builder =
+ flags.containsKey(api_level_below_or_equal)
+ ? flags.get(api_level_below_or_equal).newBuilder(dexItemFactory, reporter, origin)
+ : HumanRewritingFlags.builder(dexItemFactory, reporter, origin);
+ parseFlags(flag, builder);
+ flags.put(api_level_below_or_equal, builder.build());
+ }
+ return flags;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
index 6198ce5..3da0b97 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -68,6 +68,14 @@
rewritingFlags.getRewritePrefix(), factory, libraryCompilation);
}
+ public LegacyTopLevelFlags getTopLevelFlags() {
+ return topLevelFlags;
+ }
+
+ public LegacyRewritingFlags getRewritingFlags() {
+ return rewritingFlags;
+ }
+
public boolean supportAllCallbacksFromLibrary() {
return topLevelFlags.supportAllCallbacksFromLibrary();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
index 76a0b07..0c0bfe6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
@@ -23,4 +23,20 @@
this.libraryFlags = libraryFlags;
this.programFlags = programFlags;
}
+
+ public LegacyTopLevelFlags getTopLevelFlags() {
+ return topLevelFlags;
+ }
+
+ public Int2ObjectMap<LegacyRewritingFlags> getCommonFlags() {
+ return commonFlags;
+ }
+
+ public Int2ObjectMap<LegacyRewritingFlags> getLibraryFlags() {
+ return libraryFlags;
+ }
+
+ public Int2ObjectMap<LegacyRewritingFlags> getProgramFlags() {
+ return programFlags;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
new file mode 100644
index 0000000..6e26eff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -0,0 +1,215 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+public class LegacyToHumanSpecificationConverter implements SpecificationConverter {
+
+ @Override
+ public void convertAllAPILevels(
+ StringResource inputSpecification, Path androidLib, StringConsumer output)
+ throws IOException {
+ InternalOptions options = new InternalOptions();
+ MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec =
+ new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
+ options.dexItemFactory(), options.reporter)
+ .parseMultiLevelConfiguration(inputSpecification);
+ MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
+ convertAllAPILevels(legacySpec, androidLib, options);
+ MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(humanSpec, output);
+ }
+
+ public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
+ MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec,
+ Path androidLib,
+ InternalOptions options)
+ throws IOException {
+ DexApplication app = readApp(androidLib, options);
+ HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
+ Int2ObjectArrayMap<HumanRewritingFlags> commonFlags =
+ convertRewritingFlagMap(legacySpec.getCommonFlags(), app);
+ Int2ObjectArrayMap<HumanRewritingFlags> programFlags =
+ convertRewritingFlagMap(legacySpec.getProgramFlags(), app);
+ Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags =
+ convertRewritingFlagMap(legacySpec.getLibraryFlags(), app);
+
+ legacyLibraryFlagHacks(libraryFlags, app);
+
+ return new MultiAPILevelHumanDesugaredLibrarySpecification(
+ humanTopLevelFlags, commonFlags, libraryFlags, programFlags);
+ }
+
+ public HumanDesugaredLibrarySpecification convert(
+ LegacyDesugaredLibrarySpecification legacySpec, Path androidLib, InternalOptions options)
+ throws IOException {
+ DexApplication app = readApp(androidLib, options);
+ HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
+ HumanRewritingFlags humanRewritingFlags =
+ convertRewritingFlags(legacySpec.getRewritingFlags(), app);
+ return new HumanDesugaredLibrarySpecification(
+ humanTopLevelFlags,
+ humanRewritingFlags,
+ legacySpec.isLibraryCompilation(),
+ app.dexItemFactory());
+ }
+
+ private void legacyLibraryFlagHacks(
+ Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags, DexApplication app) {
+ HumanRewritingFlags humanRewritingFlags = libraryFlags.get(AndroidApiLevel.N_MR1.getLevel());
+ HumanRewritingFlags.Builder builder =
+ humanRewritingFlags.newBuilder(
+ app.dexItemFactory(), app.options.reporter, Origin.unknown());
+ DexItemFactory itemFactory = app.dexItemFactory();
+
+ // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
+ // to j.u.DesugarArrays.deepEquals0.
+ DexString name = itemFactory.createString("deepEquals0");
+ DexProto proto =
+ itemFactory.createProto(
+ itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
+ DexMethod source =
+ itemFactory.createMethod(itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
+ DexType target = itemFactory.createType("Ljava/util/DesugarArrays;");
+ builder.putRetargetCoreLibMember(source, target);
+
+ // TODO(b/181629049): This is only a workaround rewriting invokes of
+ // j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
+ name = itemFactory.createString("getTimeZone");
+ proto =
+ itemFactory.createProto(
+ itemFactory.createType("Ljava/util/TimeZone;"),
+ itemFactory.createType("Ljava/time/ZoneId;"));
+ source = itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
+ target = itemFactory.createType("Ljava/util/DesugarTimeZone;");
+ builder.putRetargetCoreLibMember(source, target);
+
+ libraryFlags.put(25, builder.build());
+ }
+
+ private DirectMappedDexApplication readApp(Path androidLib, InternalOptions options)
+ throws IOException {
+ AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build();
+ ApplicationReader applicationReader =
+ new ApplicationReader(androidApp, options, Timing.empty());
+ ExecutorService executorService = ThreadUtils.getExecutorService(options);
+ return applicationReader.read(executorService).toDirect();
+ }
+
+ private Int2ObjectArrayMap<HumanRewritingFlags> convertRewritingFlagMap(
+ Int2ObjectMap<LegacyRewritingFlags> libFlags, DexApplication app) {
+ Int2ObjectArrayMap<HumanRewritingFlags> map = new Int2ObjectArrayMap<>();
+ libFlags.forEach((key, flags) -> map.put((int) key, convertRewritingFlags(flags, app)));
+ return map;
+ }
+
+ private HumanRewritingFlags convertRewritingFlags(
+ LegacyRewritingFlags flags, DexApplication app) {
+ HumanRewritingFlags.Builder builder =
+ HumanRewritingFlags.builder(app.dexItemFactory(), app.options.reporter, Origin.unknown());
+
+ flags.getRewritePrefix().forEach(builder::putRewritePrefix);
+ flags.getEmulateLibraryInterface().forEach(builder::putEmulateLibraryInterface);
+ flags.getBackportCoreLibraryMember().forEach(builder::putBackportCoreLibraryMember);
+ flags.getCustomConversions().forEach(builder::putCustomConversion);
+ flags.getDontRetargetLibMember().forEach(builder::addDontRetargetLibMember);
+ flags.getWrapperConversions().forEach(builder::addWrapperConversion);
+
+ flags
+ .getRetargetCoreLibMember()
+ .forEach((name, typeMap) -> convertRetargetCoreLibMember(builder, app, name, typeMap));
+ flags
+ .getDontRewriteInvocation()
+ .forEach(pair -> convertDontRewriteInvocation(builder, app, pair));
+
+ return builder.build();
+ }
+
+ private void convertDontRewriteInvocation(
+ HumanRewritingFlags.Builder builder, DexApplication app, Pair<DexType, DexString> pair) {
+ DexClass dexClass = app.definitionFor(pair.getFirst());
+ assert dexClass != null;
+ List<DexClassAndMethod> methodsWithName = findMethodsWithName(pair.getSecond(), dexClass);
+ for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
+ builder.addDontRewriteInvocation(dexClassAndMethod.getReference());
+ }
+ }
+
+ private void convertRetargetCoreLibMember(
+ HumanRewritingFlags.Builder builder,
+ DexApplication app,
+ DexString name,
+ Map<DexType, DexType> typeMap) {
+ typeMap.forEach(
+ (type, rewrittenType) -> {
+ DexClass dexClass = app.definitionFor(type);
+ assert dexClass != null;
+ List<DexClassAndMethod> methodsWithName = findMethodsWithName(name, dexClass);
+ for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
+ builder.putRetargetCoreLibMember(dexClassAndMethod.getReference(), rewrittenType);
+ }
+ });
+ }
+
+ private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
+ List<DexClassAndMethod> found = new ArrayList<>();
+ clazz.forEachClassMethodMatching(definition -> definition.getName() == methodName, found::add);
+ assert !found.isEmpty()
+ : "Should have found a method (library specifications) for "
+ + clazz.toSourceString()
+ + "."
+ + methodName
+ + ". Maybe the library used for the compilation should be newer.";
+ return found;
+ }
+
+ private HumanTopLevelFlags convertTopLevelFlags(LegacyTopLevelFlags topLevelFlags) {
+ return HumanTopLevelFlags.builder()
+ .setDesugaredLibraryIdentifier(topLevelFlags.getIdentifier())
+ .setExtraKeepRules(topLevelFlags.getExtraKeepRules())
+ .setJsonSource(topLevelFlags.getJsonSource())
+ .setRequiredCompilationAPILevel(topLevelFlags.getRequiredCompilationAPILevel())
+ .setSupportAllCallbacksFromLibrary(topLevelFlags.supportAllCallbacksFromLibrary())
+ .setSynthesizedLibraryClassesPackagePrefix(
+ topLevelFlags.getSynthesizedLibraryClassesPackagePrefix())
+ .build();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java
new file mode 100644
index 0000000..85a8c8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.ir.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface SpecificationConverter {
+
+ void convertAllAPILevels(
+ StringResource inputSpecification, Path androidLib, StringConsumer output) throws IOException;
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
new file mode 100644
index 0000000..5797acf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, 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.desugar.desugaredlibrary.specification;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConvertExportReadTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ConvertExportReadTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMultiLevel() throws IOException {
+ LegacyToHumanSpecificationConverter converter = new LegacyToHumanSpecificationConverter();
+
+ InternalOptions options = new InternalOptions();
+
+ MultiAPILevelLegacyDesugaredLibrarySpecification spec =
+ new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
+ options.dexItemFactory(), options.reporter)
+ .parseMultiLevelConfiguration(
+ StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
+
+ MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1 =
+ converter.convertAllAPILevels(spec, ToolHelper.getAndroidJar(31), options);
+
+ Box<String> json = new Box<>();
+ MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(
+ humanSpec1, (string, handler) -> json.set(string));
+ MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2 =
+ new MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+ options.dexItemFactory(), options.reporter)
+ .parseMultiLevelConfiguration(StringResource.fromString(json.get(), Origin.unknown()));
+
+ assertSpecEquals(humanSpec1, humanSpec2);
+ }
+
+ private void assertSpecEquals(
+ MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1,
+ MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2) {
+ assertTopLevelFlagsEquals(humanSpec1.getTopLevelFlags(), humanSpec2.getTopLevelFlags());
+ assertFlagMapEquals(humanSpec1.getCommonFlags(), humanSpec2.getCommonFlags());
+ }
+
+ private void assertFlagMapEquals(
+ Int2ObjectMap<HumanRewritingFlags> commonFlags1,
+ Int2ObjectMap<HumanRewritingFlags> commonFlags2) {
+ assertEquals(commonFlags1.size(), commonFlags2.size());
+ for (int integer : commonFlags1.keySet()) {
+ assertTrue(commonFlags2.containsKey(integer));
+ assertFlagsEquals(commonFlags1.get(integer), commonFlags2.get(integer));
+ }
+ }
+
+ private void assertFlagsEquals(
+ HumanRewritingFlags humanRewritingFlags1, HumanRewritingFlags humanRewritingFlags2) {
+ assertEquals(humanRewritingFlags1.getRewritePrefix(), humanRewritingFlags2.getRewritePrefix());
+ assertEquals(
+ humanRewritingFlags1.getBackportCoreLibraryMember(),
+ humanRewritingFlags2.getBackportCoreLibraryMember());
+ assertEquals(
+ humanRewritingFlags1.getCustomConversions(), humanRewritingFlags2.getCustomConversions());
+ assertEquals(
+ humanRewritingFlags1.getEmulateLibraryInterface(),
+ humanRewritingFlags2.getEmulateLibraryInterface());
+ assertEquals(
+ humanRewritingFlags1.getRetargetCoreLibMember(),
+ humanRewritingFlags2.getRetargetCoreLibMember());
+
+ assertEquals(
+ humanRewritingFlags1.getDontRetargetLibMember(),
+ humanRewritingFlags2.getDontRetargetLibMember());
+ assertEquals(
+ humanRewritingFlags1.getDontRewriteInvocation(),
+ humanRewritingFlags2.getDontRewriteInvocation());
+ assertEquals(
+ humanRewritingFlags1.getWrapperConversions(), humanRewritingFlags2.getWrapperConversions());
+ }
+
+ private void assertTopLevelFlagsEquals(
+ HumanTopLevelFlags topLevelFlags1, HumanTopLevelFlags topLevelFlags2) {
+ assertEquals(topLevelFlags1.getExtraKeepRules(), topLevelFlags2.getExtraKeepRules());
+ assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier());
+ assertEquals(
+ topLevelFlags1.getRequiredCompilationAPILevel().getLevel(),
+ topLevelFlags2.getRequiredCompilationAPILevel().getLevel());
+ assertEquals(
+ topLevelFlags1.getSynthesizedLibraryClassesPackagePrefix(),
+ topLevelFlags2.getSynthesizedLibraryClassesPackagePrefix());
+ }
+}