Desugared library: fail on incompatible desugared library version
- add marker property for desugared library
Bug: 154106502
Bug: 155051133
Change-Id: I6cac0d0c4cf292ee4226ede3cd86420f851a068d
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index aadb671..07765bb 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,6 +1,8 @@
{
- "configuration_format_version": 3,
- "version": "0.11.1",
+ "configuration_format_version": 4,
+ "group_id" : "com.tools.android",
+ "artifact_id" : "desugar_jdk_libs",
+ "version": "0.11.2",
"required_compilation_api_level": 26,
"synthesized_library_classes_package_prefix": "j$.",
"library_flags": [
diff --git a/src/library_desugar/desugar_jdk_libs_comments.md b/src/library_desugar/desugar_jdk_libs_comments.md
index f08da27..2ba5908 100644
--- a/src/library_desugar/desugar_jdk_libs_comments.md
+++ b/src/library_desugar/desugar_jdk_libs_comments.md
@@ -1,4 +1,4 @@
-# Description of the core library configuration file
+# Description of the desugared library configuration file
## Version
@@ -7,27 +7,33 @@
Non-backward compatible changes to the desugared library increase the version number, and such
library cannot be compiled without upgrading R8/D8 to the latest version.
-The second field `version` holds the version of the content for the configuration. This number
+The fields `group_id` and `artifact_id` are maven-coordinated ids for the desugared library
+configuration file.
+
+The field `version` holds the version of the content for the configuration. This number
must be updated each time the configuration is changed.
+A unique identifier is generated for the desugared library configuration using
+`group_id:artifact_id:version`.
+
## Required compilation API level
-The third field `required_compilation_api_level` encodes the minimal Android API level required for
+The field `required_compilation_api_level` encodes the minimal Android API level required for
the desugared library to be compiled correctly. If the API of library used for compilation of the
library or a program using the library is lower than this level, one has to upgrade the SDK version
used to be able to use desugared libraries.
## Library and program flags
-The fourth and fifth fields are `library_flags` and `program_flags`. They include the set of flags
-required for respectively the library and the program using the desugared library compilation. The
-sets of flags are different depending on the min API level used. The flags are in a list, where
-each list entry specifies up to which min API level the set of flags should be applied. During
-compilation, R8/D8 adds up all the required flags for the min API level specified at compilation.
+The fields `library_flags` and `program_flags` include the set of flags required for respectively
+the library and the program using the desugared library compilation. The sets of flags are
+different depending on the min API level used. The flags are in a list, where each list entry
+specifies up to which min API level the set of flags should be applied. During compilation,
+R8/D8 adds up all the required flags for the min API level specified at compilation.
-For example, let's say the `program_flags` have entries for `api_level_below_or_equal` 20, 24 and 26.
-If compiling the program for min API 24, R8/D8 will use both the set of flags for API 24 and 26
-(24 <= 24, 24 <= 26 but !(24 <= 20)).
+For example, let's say the `program_flags` have entries for `api_level_below_or_equal` 20, 24 and
+26. If compiling the program for min API 24, R8/D8 will use both the set of flags for API 24 and
+26 (24 <= 24, 24 <= 26 but !(24 <= 20)).
## Extra keep rules
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index ea07dfc..e8c6bb1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -239,6 +239,7 @@
if (marker != null && hasClassResources) {
markers.add(marker);
}
+ Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
InspectorImpl.runInspections(options.outputInspections, app);
if (options.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index f8e7a07..1484f02 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -5,25 +5,30 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.graph.DexString;
+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 com.google.gson.JsonSyntaxException;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.Map.Entry;
+import java.util.Set;
-/**
- * Abstraction for hidden dex marker intended for the main dex file.
- */
+/** Abstraction for hidden dex marker intended for the main dex file. */
public class Marker {
public static final String VERSION = "version";
public static final String MIN_API = "min-api";
+ public static final String DESUGARED_LIBRARY_IDENTIFIERS = "desugared-library-identifiers";
public static final String SHA1 = "sha-1";
public static final String COMPILATION_MODE = "compilation-mode";
public static final String HAS_CHECKSUMS = "has-checksums";
public static final String PG_MAP_ID = "pg-map-id";
public static final String R8_MODE = "r8-mode";
+ private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>";
public enum Tool {
D8,
@@ -54,6 +59,48 @@
this.jsonObject = jsonObject;
}
+ public static void checkCompatibleDesugaredLibrary(Set<Marker> markers, Reporter reporter) {
+ if (markers.size() <= 1) {
+ return;
+ }
+ // In L8 compilation, the compilation has two markers, a L8 marker, which has a desugared
+ // library property, and either a D8 or a R8 marker, which has no desugared library property.
+ // In other compilations, the desugared library versions have to be consistent.
+ Set<String> desugaredLibraryIdentifiers = new HashSet<>();
+ for (Marker marker : markers) {
+ if (marker.tool == Tool.L8) {
+ assert marker.getDesugaredLibraryIdentifiers().length > 0;
+ assert markers.stream()
+ .allMatch(m -> m.tool == Tool.L8 || m.getDesugaredLibraryIdentifiers().length == 0);
+ } else {
+ String[] identifiers = marker.getDesugaredLibraryIdentifiers();
+ String identifier;
+ switch (identifiers.length) {
+ case 0:
+ identifier = NO_LIBRARY_DESUGARING;
+ break;
+ case 1:
+ identifier = identifiers[0];
+ break;
+ default:
+ // To be implemented once D8/R8 compilation supports multiple desugared libraries.
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Merging program compiled with multiple desugared libraries."));
+ }
+ desugaredLibraryIdentifiers.add(identifier);
+ }
+ }
+
+ if (desugaredLibraryIdentifiers.size() > 1) {
+ reporter.error(
+ new StringDiagnostic(
+ "The compilation is merging inputs with different desugared library desugaring "
+ + desugaredLibraryIdentifiers
+ + ", which may lead to unexpected runtime errors."));
+ }
+ }
+
public Tool getTool() {
return tool;
}
@@ -94,6 +141,28 @@
return this;
}
+ public String[] getDesugaredLibraryIdentifiers() {
+ if (jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS)) {
+ JsonArray array = jsonObject.get(DESUGARED_LIBRARY_IDENTIFIERS).getAsJsonArray();
+ String[] identifiers = new String[array.size()];
+ for (int i = 0; i < array.size(); i++) {
+ identifiers[i] = array.get(i).getAsString();
+ }
+ return identifiers;
+ }
+ return new String[0];
+ }
+
+ public Marker setDesugaredLibraryIdentifiers(String... identifiers) {
+ assert !jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS);
+ JsonArray jsonIdentifiers = new JsonArray();
+ for (String identifier : identifiers) {
+ jsonIdentifiers.add(identifier);
+ }
+ jsonObject.add(DESUGARED_LIBRARY_IDENTIFIERS, jsonIdentifiers);
+ return this;
+ }
+
public String getSha1() {
return jsonObject.get(SHA1).getAsString();
}
@@ -148,8 +217,7 @@
public String toString() {
// In order to make printing of markers deterministic we sort the entries by key.
final JsonObject sortedJson = new JsonObject();
- jsonObject.entrySet()
- .stream()
+ jsonObject.entrySet().stream()
.sorted(Comparator.comparing(Entry::getKey))
.forEach(entry -> sortedJson.add(entry.getKey(), entry.getValue()));
return PREFIX + tool + sortedJson;
@@ -191,7 +259,7 @@
private static Marker internalParse(Tool tool, String str) {
try {
- JsonElement result = new JsonParser().parse(str);
+ JsonElement result = new JsonParser().parse(str);
if (result.isJsonObject()) {
return new Marker(tool, result.getAsJsonObject());
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index baa6317..1590484 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -32,6 +32,7 @@
private final AndroidApiLevel requiredCompilationAPILevel;
private final boolean libraryCompilation;
private final String synthesizedLibraryClassesPackagePrefix;
+ private final String identifier;
private final Map<String, String> rewritePrefix;
private final Map<DexType, DexType> emulateLibraryInterface;
private final Map<DexString, Map<DexType, DexType>> retargetCoreLibMember;
@@ -50,6 +51,7 @@
AndroidApiLevel.B,
true,
FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
+ "testingOnlyVersion",
prefix,
ImmutableMap.of(),
ImmutableMap.of(),
@@ -64,6 +66,7 @@
AndroidApiLevel.B,
false,
FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
+ null,
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableMap.of(),
@@ -77,6 +80,7 @@
AndroidApiLevel requiredCompilationAPILevel,
boolean libraryCompilation,
String packagePrefix,
+ String identifier,
Map<String, String> rewritePrefix,
Map<DexType, DexType> emulateLibraryInterface,
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
@@ -87,6 +91,7 @@
this.requiredCompilationAPILevel = requiredCompilationAPILevel;
this.libraryCompilation = libraryCompilation;
this.synthesizedLibraryClassesPackagePrefix = packagePrefix;
+ this.identifier = identifier;
this.rewritePrefix = rewritePrefix;
this.emulateLibraryInterface = emulateLibraryInterface;
this.retargetCoreLibMember = retargetCoreLibMember;
@@ -114,6 +119,10 @@
return synthesizedLibraryClassesPackagePrefix;
}
+ public String getIdentifier() {
+ return identifier;
+ }
+
public Map<String, String> getRewritePrefix() {
return rewritePrefix;
}
@@ -164,6 +173,7 @@
private boolean libraryCompilation = false;
private String synthesizedLibraryClassesPackagePrefix =
FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX;
+ private String identifier;
private Map<String, String> rewritePrefix = new HashMap<>();
private Map<DexType, DexType> emulateLibraryInterface = new HashMap<>();
private Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = new IdentityHashMap<>();
@@ -177,8 +187,12 @@
}
public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) {
- String replace = prefix.replace('.', '/');
- this.synthesizedLibraryClassesPackagePrefix = replace;
+ this.synthesizedLibraryClassesPackagePrefix = prefix.replace('.', '/');
+ return this;
+ }
+
+ public Builder setDesugaredLibraryIdentifier(String identifier) {
+ this.identifier = identifier;
return this;
}
@@ -268,6 +282,7 @@
requiredCompilationAPILevel,
libraryCompilation,
synthesizedLibraryClassesPackagePrefix,
+ identifier,
ImmutableMap.copyOf(rewritePrefix),
ImmutableMap.copyOf(emulateLibraryInterface),
ImmutableMap.copyOf(retargetCoreLibMember),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index b66432e..a659cc6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -19,7 +19,7 @@
public class DesugaredLibraryConfigurationParser {
- private static final int MAX_SUPPORTED_VERSION = 3;
+ private static final int MAX_SUPPORTED_VERSION = 4;
private final DesugaredLibraryConfiguration.Builder configurationBuilder;
private final Reporter reporter;
@@ -53,14 +53,14 @@
JsonParser parser = new JsonParser();
JsonObject jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
- int version = jsonConfig.get("configuration_format_version").getAsInt();
- if (version > MAX_SUPPORTED_VERSION) {
+ int formatVersion = jsonConfig.get("configuration_format_version").getAsInt();
+ if (formatVersion > MAX_SUPPORTED_VERSION) {
throw reporter.fatalError(
new StringDiagnostic(
"Unsupported desugared library configuration version, please upgrade the D8/R8"
+ " compiler."));
}
- if (version == 1) {
+ if (formatVersion == 1) {
reporter.warning(
new StringDiagnostic(
"You are using an experimental version of the desugared library configuration, "
@@ -68,6 +68,19 @@
+ "production releases and to fix this warning."));
}
+ String version = jsonConfig.get("version").getAsString();
+ String groupID;
+ String artifactID;
+ if (formatVersion < 4) {
+ groupID = "com.tools.android";
+ artifactID = "desugar_jdk_libs";
+ } else {
+ groupID = jsonConfig.get("group_id").getAsString();
+ artifactID = jsonConfig.get("artifact_id").getAsString();
+ }
+ String identifier = String.join(":", groupID, artifactID, version);
+ configurationBuilder.setDesugaredLibraryIdentifier(identifier);
+
if (jsonConfig.has("synthesized_library_classes_package_prefix")) {
configurationBuilder.setSynthesizedLibraryClassesPackagePrefix(
jsonConfig.get("synthesized_library_classes_package_prefix").getAsString());
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a42ed5d..7e34093 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -334,6 +334,9 @@
if (!isGeneratingClassFiles()) {
marker.setMinApi(minApiLevel);
}
+ if (desugaredLibraryConfiguration.getIdentifier() != null) {
+ marker.setDesugaredLibraryIdentifiers(desugaredLibraryConfiguration.getIdentifier());
+ }
if (Version.isDevelopmentVersion()) {
marker.setSha1(VersionProperties.INSTANCE.getSha());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index eefe0f7..03052cc 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.desugar.desugaredlibrary;
-import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -16,6 +19,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -39,39 +43,53 @@
@Test
public void testMergeDesugaredAndNonDesugared() throws Exception {
- D8TestCompileResult compileResult =
- testForD8()
- .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
- .compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel());
- // TODO(b/154106502): This should raise a proper warning. The dex files are incompatible,
- // so the behavior is undefined regarding desugared types.
- if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
- compileResult
- .run(parameters.getRuntime(), Part1.class)
- .assertSuccessWithOutputLines(J$_RESULT);
- } else {
- compileResult
- .run(parameters.getRuntime(), Part1.class)
- .assertSuccessWithOutputLines(JAVA_RESULT);
+ D8TestCompileResult compileResult;
+ try {
+ compileResult =
+ testForD8()
+ .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(this::assertError);
+ assertFalse(expectError());
+ } catch (CompilationFailedException e) {
+ assertTrue(expectError());
+ return;
}
- if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
- < AndroidApiLevel.N.getLevel()) {
- compileResult
- .run(parameters.getRuntime(), Part2.class)
- .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
- } else {
+ assert !expectError();
+ assert compileResult != null;
+ compileResult.addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel());
+ compileResult
+ .run(parameters.getRuntime(), Part1.class)
+ .assertSuccessWithOutputLines(JAVA_RESULT);
+ compileResult
+ .run(parameters.getRuntime(), Part2.class)
+ .assertSuccessWithOutputLines(JAVA_RESULT);
+ }
- compileResult
- .run(parameters.getRuntime(), Part2.class)
- .assertSuccessWithOutputLines(JAVA_RESULT);
+ private void assertError(TestDiagnosticMessages m) {
+ List<Diagnostic> errors = m.getErrors();
+ if (expectError()) {
+ assertEquals(1, errors.size());
+ assertTrue(
+ errors.stream()
+ .anyMatch(
+ w ->
+ w.getDiagnosticMessage()
+ .contains(
+ "The compilation is merging inputs with different"
+ + " desugared library desugaring")));
+ } else {
+ assertEquals(0, errors.size());
}
}
+ private boolean expectError() {
+ return parameters.getApiLevel().getLevel() <= AndroidApiLevel.N.getLevel();
+ }
+
@Test
public void testMergeDesugaredAndClassFile() throws Exception {
D8TestCompileResult compileResult =