| // 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; | |
| import static com.android.tools.r8.LibraryDesugaringTestConfiguration.Configuration.DEFAULT; | |
| import static junit.framework.TestCase.assertNotNull; | |
| import static org.hamcrest.CoreMatchers.instanceOf; | |
| import static org.hamcrest.MatcherAssert.assertThat; | |
| import com.android.tools.r8.TestBase.Backend; | |
| import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer; | |
| import com.android.tools.r8.errors.Unreachable; | |
| import com.android.tools.r8.utils.AndroidApiLevel; | |
| import com.google.common.collect.ImmutableList; | |
| import java.io.IOException; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.concurrent.ExecutionException; | |
| public class LibraryDesugaringTestConfiguration { | |
| private static final String RELEASES_DIR = "third_party/openjdk/desugar_jdk_libs_releases/"; | |
| public enum Configuration { | |
| DEFAULT( | |
| ToolHelper.getDesugarJDKLibs(), | |
| ToolHelper.DESUGAR_LIB_CONVERSIONS, | |
| ToolHelper.getDesugarLibJsonForTesting()), | |
| DEFAULT_JDK11( | |
| Paths.get("third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar"), | |
| ToolHelper.DESUGAR_LIB_CONVERSIONS, | |
| Paths.get("src/library_desugar/jdk11/desugar_jdk_libs.json")), | |
| RELEASED_1_0_9("1.0.9"), | |
| RELEASED_1_0_10("1.0.10"), | |
| RELEASED_1_1_0("1.1.0"), | |
| RELEASED_1_1_1("1.1.1"), | |
| RELEASED_1_1_5("1.1.5"); | |
| private final Path desugarJdkLibs; | |
| private final Path customConversions; | |
| private final Path configuration; | |
| Configuration(Path desugarJdkLibs, Path customConversions, Path configuration) { | |
| this.desugarJdkLibs = desugarJdkLibs; | |
| this.customConversions = customConversions; | |
| this.configuration = configuration; | |
| } | |
| Configuration(String version) { | |
| this( | |
| Paths.get(RELEASES_DIR, version, "desugar_jdk_libs.jar"), | |
| Paths.get(RELEASES_DIR, version, "desugar_jdk_libs_configuration.jar"), | |
| Paths.get(RELEASES_DIR, version, "desugar.json")); | |
| } | |
| public static List<Configuration> getReleased() { | |
| return ImmutableList.of( | |
| RELEASED_1_0_9, RELEASED_1_0_10, RELEASED_1_1_0, RELEASED_1_1_1, RELEASED_1_1_5); | |
| } | |
| } | |
| private final AndroidApiLevel minApiLevel; | |
| private final Path desugarJdkLibs; | |
| private final Path customConversions; | |
| private final List<StringResource> desugaredLibraryConfigurationResources; | |
| private final boolean withKeepRuleConsumer; | |
| private final KeepRuleConsumer keepRuleConsumer; | |
| private final CompilationMode mode; | |
| private final boolean addRunClassPath; | |
| public static final LibraryDesugaringTestConfiguration DISABLED = | |
| new LibraryDesugaringTestConfiguration(); | |
| private LibraryDesugaringTestConfiguration() { | |
| this.minApiLevel = null; | |
| this.desugarJdkLibs = null; | |
| this.customConversions = null; | |
| this.keepRuleConsumer = null; | |
| this.withKeepRuleConsumer = false; | |
| this.desugaredLibraryConfigurationResources = null; | |
| this.mode = null; | |
| this.addRunClassPath = false; | |
| } | |
| private LibraryDesugaringTestConfiguration( | |
| AndroidApiLevel minApiLevel, | |
| Path desugarJdkLibs, | |
| Path customConversions, | |
| List<StringResource> desugaredLibraryConfigurationResources, | |
| boolean withKeepRuleConsumer, | |
| KeepRuleConsumer keepRuleConsumer, | |
| CompilationMode mode, | |
| boolean addRunClassPath) { | |
| this.minApiLevel = minApiLevel; | |
| this.desugarJdkLibs = desugarJdkLibs; | |
| this.customConversions = customConversions; | |
| this.desugaredLibraryConfigurationResources = desugaredLibraryConfigurationResources; | |
| this.withKeepRuleConsumer = withKeepRuleConsumer; | |
| this.keepRuleConsumer = keepRuleConsumer; | |
| this.mode = mode; | |
| this.addRunClassPath = addRunClassPath; | |
| } | |
| public static class Builder { | |
| AndroidApiLevel minApiLevel; | |
| private Path desugarJdkLibs; | |
| private Path customConversions; | |
| private final List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>(); | |
| boolean withKeepRuleConsumer = false; | |
| KeepRuleConsumer keepRuleConsumer; | |
| private CompilationMode mode = CompilationMode.DEBUG; | |
| boolean addRunClassPath = true; | |
| private Builder() {} | |
| public Builder setMinApi(AndroidApiLevel minApiLevel) { | |
| this.minApiLevel = minApiLevel; | |
| return this; | |
| } | |
| public Builder setConfiguration(Configuration configuration) { | |
| desugarJdkLibs = configuration.desugarJdkLibs; | |
| customConversions = configuration.customConversions; | |
| desugaredLibraryConfigurationResources.clear(); | |
| desugaredLibraryConfigurationResources.add( | |
| StringResource.fromFile(configuration.configuration)); | |
| return this; | |
| } | |
| public Builder withKeepRuleConsumer() { | |
| withKeepRuleConsumer = true; | |
| return this; | |
| } | |
| public Builder setKeepRuleConsumer(StringConsumer keepRuleConsumer) { | |
| withKeepRuleConsumer = false; | |
| if (keepRuleConsumer == null) { | |
| this.keepRuleConsumer = null; | |
| } else { | |
| assert keepRuleConsumer instanceof KeepRuleConsumer; | |
| this.keepRuleConsumer = (KeepRuleConsumer) keepRuleConsumer; | |
| } | |
| return this; | |
| } | |
| public Builder addDesugaredLibraryConfiguration(StringResource desugaredLibraryConfiguration) { | |
| desugaredLibraryConfigurationResources.add(desugaredLibraryConfiguration); | |
| return this; | |
| } | |
| public Builder setMode(CompilationMode mode) { | |
| this.mode = mode; | |
| return this; | |
| } | |
| public Builder dontAddRunClasspath() { | |
| addRunClassPath = false; | |
| return this; | |
| } | |
| public LibraryDesugaringTestConfiguration build() { | |
| if (desugaredLibraryConfigurationResources.isEmpty()) { | |
| desugaredLibraryConfigurationResources.add( | |
| StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting())); | |
| } | |
| if (withKeepRuleConsumer) { | |
| this.keepRuleConsumer = createKeepRuleConsumer(minApiLevel); | |
| } | |
| return new LibraryDesugaringTestConfiguration( | |
| minApiLevel, | |
| desugarJdkLibs != null ? desugarJdkLibs : DEFAULT.desugarJdkLibs, | |
| customConversions != null ? customConversions : DEFAULT.customConversions, | |
| desugaredLibraryConfigurationResources, | |
| withKeepRuleConsumer, | |
| keepRuleConsumer, | |
| mode, | |
| addRunClassPath); | |
| } | |
| } | |
| public static Builder builder() { | |
| return new Builder(); | |
| } | |
| public boolean isEnabled() { | |
| return this != DISABLED; | |
| } | |
| public boolean isAddRunClassPath() { | |
| return addRunClassPath; | |
| } | |
| public void configure(D8Command.Builder builder) { | |
| if (!isEnabled()) { | |
| return; | |
| } | |
| if (keepRuleConsumer != null) { | |
| builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer); | |
| } | |
| desugaredLibraryConfigurationResources.forEach(builder::addDesugaredLibraryConfiguration); | |
| } | |
| public void configure(R8Command.Builder builder) { | |
| if (!isEnabled()) { | |
| return; | |
| } | |
| if (keepRuleConsumer != null) { | |
| builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer); | |
| } | |
| desugaredLibraryConfigurationResources.forEach(builder::addDesugaredLibraryConfiguration); | |
| } | |
| public Path buildDesugaredLibrary(TestState state) { | |
| String generatedKeepRules = null; | |
| if (withKeepRuleConsumer) { | |
| if (keepRuleConsumer instanceof PresentKeepRuleConsumer) { | |
| generatedKeepRules = keepRuleConsumer.get(); | |
| assertNotNull(generatedKeepRules); | |
| } else { | |
| assertThat(keepRuleConsumer, instanceOf(AbsentKeepRuleConsumer.class)); | |
| } | |
| } | |
| String finalGeneratedKeepRules = generatedKeepRules; | |
| try { | |
| assert desugaredLibraryConfigurationResources.size() == 1 : "There can be only one"; | |
| return L8TestBuilder.create(minApiLevel, Backend.DEX, state) | |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) | |
| .setDesugarJDKLibs(desugarJdkLibs) | |
| .setDesugarJDKLibsConfiguration(customConversions) | |
| .setDesugaredLibraryConfiguration(desugaredLibraryConfigurationResources.get(0)) | |
| .applyIf( | |
| mode == CompilationMode.RELEASE, | |
| builder -> { | |
| if (finalGeneratedKeepRules != null && !finalGeneratedKeepRules.trim().isEmpty()) { | |
| builder.addGeneratedKeepRules(finalGeneratedKeepRules); | |
| } | |
| }, | |
| L8TestBuilder::setDebug) | |
| .compile() | |
| .writeToZip(); | |
| } catch (CompilationFailedException | ExecutionException | IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| public KeepRuleConsumer getKeepRuleConsumer() { | |
| return keepRuleConsumer; | |
| } | |
| public static KeepRuleConsumer createKeepRuleConsumer(TestParameters parameters) { | |
| return createKeepRuleConsumer(parameters.getApiLevel()); | |
| } | |
| private static KeepRuleConsumer createKeepRuleConsumer(AndroidApiLevel apiLevel) { | |
| if (requiresAnyCoreLibDesugaring(apiLevel)) { | |
| return new PresentKeepRuleConsumer(); | |
| } | |
| return new AbsentKeepRuleConsumer(); | |
| } | |
| private static boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel) { | |
| return apiLevel.isLessThan(AndroidApiLevel.O); | |
| } | |
| public static class PresentKeepRuleConsumer implements KeepRuleConsumer { | |
| StringBuilder stringBuilder = new StringBuilder(); | |
| String result = null; | |
| @Override | |
| public void accept(String string, DiagnosticsHandler handler) { | |
| assert stringBuilder != null; | |
| assert result == null; | |
| stringBuilder.append(string); | |
| } | |
| @Override | |
| public void finished(DiagnosticsHandler handler) { | |
| assert stringBuilder != null; | |
| assert result == null; | |
| result = stringBuilder.toString(); | |
| stringBuilder = null; | |
| } | |
| public String get() { | |
| // TODO(clement): remove that branch once StringConsumer has finished again. | |
| if (stringBuilder != null) { | |
| finished(null); | |
| } | |
| assert stringBuilder == null; | |
| assert result != null; | |
| return result; | |
| } | |
| } | |
| public static class AbsentKeepRuleConsumer implements KeepRuleConsumer { | |
| public String get() { | |
| return null; | |
| } | |
| @Override | |
| public void accept(String string, DiagnosticsHandler handler) { | |
| throw new Unreachable("No desugaring on high API levels"); | |
| } | |
| @Override | |
| public void finished(DiagnosticsHandler handler) { | |
| throw new Unreachable("No desugaring on high API levels"); | |
| } | |
| } | |
| } |