| // Copyright (c) 2022, 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.test; |
| |
| import com.android.tools.r8.CompilationFailedException; |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.D8TestCompileResult; |
| import com.android.tools.r8.FeatureSplit; |
| import com.android.tools.r8.L8TestBuilder; |
| import com.android.tools.r8.L8TestCompileResult; |
| import com.android.tools.r8.LibraryDesugaringTestConfiguration; |
| import com.android.tools.r8.R8TestBuilder; |
| import com.android.tools.r8.SingleTestRunResult; |
| import com.android.tools.r8.StringResource; |
| import com.android.tools.r8.TestBase.Backend; |
| import com.android.tools.r8.TestCompileResult; |
| import com.android.tools.r8.TestCompilerBuilder; |
| import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestRuntime; |
| import com.android.tools.r8.TestShrinkerBuilder; |
| import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; |
| import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser; |
| import com.android.tools.r8.tracereferences.TraceReferences; |
| import com.android.tools.r8.utils.ConsumerUtils; |
| import com.android.tools.r8.utils.FileUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; |
| import com.google.common.base.Charsets; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import org.junit.Assume; |
| |
| public class DesugaredLibraryTestBuilder<T extends DesugaredLibraryTestBase> { |
| |
| private final T test; |
| private final TestParameters parameters; |
| private final LibraryDesugaringSpecification libraryDesugaringSpecification; |
| private final CompilationSpecification compilationSpecification; |
| private final TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> builder; |
| private String l8ExtraKeepRules = ""; |
| private Consumer<InternalOptions> l8OptionModifier = ConsumerUtils.emptyConsumer(); |
| private boolean l8FinalPrefixVerification = true; |
| private boolean overrideDefaultLibraryFiles = false; |
| |
| private CustomLibrarySpecification customLibrarySpecification = null; |
| private TestingKeepRuleConsumer keepRuleConsumer = null; |
| |
| public DesugaredLibraryTestBuilder( |
| T test, |
| TestParameters parameters, |
| LibraryDesugaringSpecification libraryDesugaringSpecification, |
| CompilationSpecification runSpecification) { |
| this.test = test; |
| this.parameters = parameters; |
| this.libraryDesugaringSpecification = libraryDesugaringSpecification; |
| this.compilationSpecification = runSpecification; |
| this.builder = generateBuilder(); |
| setUp(); |
| } |
| |
| private void setUp() { |
| builder |
| .setMinApi(parameters.getApiLevel()) |
| .setMode(compilationSpecification.getProgramCompilationMode()); |
| LibraryDesugaringTestConfiguration.Builder libraryConfBuilder = |
| LibraryDesugaringTestConfiguration.builder() |
| .addDesugaredLibraryConfiguration( |
| StringResource.fromFile(libraryDesugaringSpecification.getSpecification())); |
| if (compilationSpecification.isL8Shrink() && !compilationSpecification.isCfToCf()) { |
| keepRuleConsumer = new TestingKeepRuleConsumer(); |
| libraryConfBuilder.setKeepRuleConsumer(keepRuleConsumer); |
| } |
| builder.enableCoreLibraryDesugaring(libraryConfBuilder.build()); |
| } |
| |
| private TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> generateBuilder() { |
| if (compilationSpecification.isCfToCf()) { |
| assert !compilationSpecification.isProgramShrink(); |
| if (compilationSpecification.isL8Shrink()) { |
| // L8 with Cf backend and shrinking is not a supported pipeline. |
| Assume.assumeTrue(parameters.getBackend().isDex()); |
| } |
| return test.testForD8(Backend.CF); |
| } |
| // Cf back-end is only allowed in Cf to cf compilations. |
| Assume.assumeTrue(parameters.getBackend().isDex()); |
| if (compilationSpecification.isProgramShrink()) { |
| return test.testForR8(parameters.getBackend()); |
| } |
| return test.testForD8(Backend.DEX); |
| } |
| |
| public DesugaredLibraryTestBuilder<T> setCustomLibrarySpecification( |
| CustomLibrarySpecification customLibrarySpecification) { |
| this.customLibrarySpecification = customLibrarySpecification; |
| builder.addLibraryClasses(customLibrarySpecification.getClasses()); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addL8OptionsModification( |
| Consumer<InternalOptions> optionModifier) { |
| l8OptionModifier = l8OptionModifier.andThen(optionModifier); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addOptionsModification( |
| Consumer<InternalOptions> optionModifier) { |
| builder.addOptionsModification(optionModifier); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addProgramClassesAndInnerClasses(Class<?>... clazz) |
| throws IOException { |
| builder.addProgramClassesAndInnerClasses(clazz); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addInnerClasses(Class<?>... clazz) throws IOException { |
| builder.addInnerClasses(clazz); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addClasspathClasses(Class<?>... clazz) throws IOException { |
| builder.addClasspathClasses(clazz); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addProgramClasses(Class<?>... clazz) throws IOException { |
| builder.addProgramClasses(clazz); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addProgramClasses(Collection<Class<?>> clazz) |
| throws IOException { |
| builder.addProgramClasses(clazz); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addProgramFiles(Path... files) { |
| builder.addProgramFiles(files); |
| return this; |
| } |
| |
| /** |
| * By default the compilation uses as library libraryDesugaringSpecification.getLibraryFiles(), |
| * which is android.jar at the required compilation api level. Use this Api to set different |
| * library files. |
| */ |
| public DesugaredLibraryTestBuilder<T> overrideLibraryFiles(Path... files) { |
| overrideDefaultLibraryFiles = true; |
| builder.addLibraryFiles(files); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addProgramFiles(Collection<Path> files) { |
| builder.addProgramFiles(files); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> ignoreL8FinalPrefixVerification() { |
| l8FinalPrefixVerification = false; |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addProgramClassFileData( |
| Collection<byte[]> programClassFileData) { |
| builder.addProgramClassFileData(programClassFileData); |
| return this; |
| } |
| |
| /** |
| * By default the compilation uses libraryDesugaringSpecification.getProgramCompilationMode() |
| * which maps to the studio set-up: D8-debug, D8-release and R8-release. Use this Api to set a |
| * different compilation mode. |
| */ |
| public DesugaredLibraryTestBuilder<T> overrideCompilationMode(CompilationMode mode) { |
| builder.setMode(mode); |
| return this; |
| } |
| |
| private void withR8TestBuilder(Consumer<R8TestBuilder<?>> consumer) { |
| if (!builder.isTestShrinkerBuilder()) { |
| return; |
| } |
| consumer.accept((R8TestBuilder<?>) builder); |
| } |
| |
| public DesugaredLibraryTestBuilder<T> allowUnusedDontWarnPatterns() { |
| withR8TestBuilder(R8TestBuilder::allowUnusedDontWarnPatterns); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> allowUnusedProguardConfigurationRules() { |
| withR8TestBuilder(R8TestBuilder::allowUnusedProguardConfigurationRules); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> allowDiagnosticMessages() { |
| withR8TestBuilder(R8TestBuilder::allowDiagnosticMessages); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> allowUnusedDontWarnKotlinReflectJvmInternal(boolean allow) { |
| withR8TestBuilder(b -> b.allowUnusedDontWarnKotlinReflectJvmInternal(allow)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> allowDiagnosticInfoMessages() { |
| withR8TestBuilder(R8TestBuilder::allowDiagnosticInfoMessages); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> applyIfR8TestBuilder(Consumer<R8TestBuilder<?>> consumer) { |
| withR8TestBuilder(consumer); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> allowDiagnosticWarningMessages() { |
| withR8TestBuilder(R8TestBuilder::allowDiagnosticWarningMessages); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepRules(String keepRules) { |
| withR8TestBuilder(b -> b.addKeepRules(keepRules)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addL8KeepRules(String keepRules) { |
| if (compilationSpecification.isL8Shrink()) { |
| l8ExtraKeepRules += keepRules + "\n"; |
| } |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepClassAndMembersRules(Class<?>... clazz) { |
| withR8TestBuilder(b -> b.addKeepClassAndMembersRules(clazz)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepAttributes(String... attributes) { |
| withR8TestBuilder(b -> b.addKeepAttributes(attributes)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepAllClassesRuleWithAllowObfuscation() { |
| withR8TestBuilder(TestShrinkerBuilder::addKeepAllClassesRuleWithAllowObfuscation); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepAllClassesRule() { |
| withR8TestBuilder(TestShrinkerBuilder::addKeepAllClassesRule); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepMainRule(Class<?> clazz) { |
| withR8TestBuilder(b -> b.addKeepMainRule(clazz)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepMainRule(String mainClass) { |
| withR8TestBuilder(b -> b.addKeepMainRule(mainClass)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addKeepRuleFiles(Path... files) { |
| withR8TestBuilder(b -> b.addKeepRuleFiles(files)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addFeatureSplit( |
| Function<FeatureSplit.Builder, FeatureSplit> featureSplitBuilder) { |
| withR8TestBuilder(b -> b.addFeatureSplit(featureSplitBuilder)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> enableNeverClassInliningAnnotations() { |
| withR8TestBuilder(R8TestBuilder::enableNeverClassInliningAnnotations); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> enableInliningAnnotations() { |
| withR8TestBuilder(R8TestBuilder::enableInliningAnnotations); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> enableNoVerticalClassMergingAnnotations() { |
| withR8TestBuilder(R8TestBuilder::enableNoVerticalClassMergingAnnotations); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addVerticallyMergedClassesInspector( |
| Consumer<VerticallyMergedClassesInspector> inspector) { |
| withR8TestBuilder(b -> b.addVerticallyMergedClassesInspector(inspector)); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> noMinification() { |
| withR8TestBuilder(R8TestBuilder::addDontObfuscate); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> enableConstantArgumentAnnotations() { |
| withR8TestBuilder(R8TestBuilder::enableConstantArgumentAnnotations); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> applyOnBuilder( |
| Consumer<TestCompilerBuilder<?, ?, ?, ?, ?>> consumer) { |
| consumer.accept(builder); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> applyIf( |
| boolean apply, Consumer<DesugaredLibraryTestBuilder<T>> consumer) { |
| if (apply) { |
| consumer.accept(this); |
| } |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> disableL8AnnotationRemoval() { |
| l8OptionModifier = |
| l8OptionModifier.andThen(options -> options.disableL8AnnotationRemoval = true); |
| return this; |
| } |
| |
| private void prepareCompilation() { |
| if (overrideDefaultLibraryFiles) { |
| return; |
| } |
| builder.addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles()); |
| } |
| |
| public DesugaredLibraryTestCompileResult<T> compile() throws Exception { |
| prepareCompilation(); |
| TestCompileResult<?, ? extends SingleTestRunResult<?>> compile = builder.compile(); |
| return internalCompile(compile); |
| } |
| |
| public DesugaredLibraryTestCompileResult<T> compileWithExpectedDiagnostics( |
| DiagnosticsConsumer consumer) throws Exception { |
| prepareCompilation(); |
| TestCompileResult<?, ? extends SingleTestRunResult<?>> compile = |
| builder.compileWithExpectedDiagnostics(consumer); |
| return internalCompile(compile); |
| } |
| |
| private DesugaredLibraryTestCompileResult<T> internalCompile( |
| TestCompileResult<?, ? extends SingleTestRunResult<?>> compile) throws Exception { |
| L8TestCompileResult l8Compile = compileDesugaredLibrary(compile, keepRuleConsumer); |
| D8TestCompileResult customLibCompile = compileCustomLib(); |
| return new DesugaredLibraryTestCompileResult<>( |
| test, |
| compile, |
| parameters, |
| libraryDesugaringSpecification, |
| compilationSpecification, |
| customLibCompile, |
| l8Compile); |
| } |
| |
| private D8TestCompileResult compileCustomLib() throws CompilationFailedException { |
| if (customLibrarySpecification == null) { |
| return null; |
| } |
| return test.testForD8(parameters.getBackend()) |
| .addProgramClasses(customLibrarySpecification.getClasses()) |
| .setMinApi(customLibrarySpecification.getMinApi()) |
| .compile(); |
| } |
| |
| private L8TestCompileResult compileDesugaredLibrary( |
| TestCompileResult<?, ? extends SingleTestRunResult<?>> compile, |
| KeepRuleConsumer keepRuleConsumer) |
| throws Exception { |
| if (!compilationSpecification.isL8Shrink()) { |
| return compileDesugaredLibrary(null); |
| } |
| if (!compilationSpecification.isTraceReferences()) { |
| // When going to dex we can get the generated keep rule through the keep rule consumer. |
| assert keepRuleConsumer != null; |
| return compileDesugaredLibrary(keepRuleConsumer.get()); |
| } |
| L8TestCompileResult nonShrunk = |
| test.testForL8(parameters.getApiLevel(), Backend.CF) |
| .apply(libraryDesugaringSpecification::configureL8TestBuilder) |
| .apply(b -> configure(b, Backend.CF)) |
| .compile(); |
| String keepRules = |
| collectKeepRulesWithTraceReferences(compile.writeToZip(), nonShrunk.writeToZip()); |
| return compileDesugaredLibrary(keepRules); |
| } |
| |
| private L8TestCompileResult compileDesugaredLibrary(String keepRule) throws Exception { |
| assert !compilationSpecification.isL8Shrink() || keepRule != null; |
| return test.testForL8(parameters.getApiLevel(), parameters.getBackend()) |
| .apply( |
| b -> |
| libraryDesugaringSpecification.configureL8TestBuilder( |
| b, compilationSpecification.isL8Shrink(), keepRule)) |
| .apply(b -> configure(b, parameters.getBackend())) |
| .compile(); |
| } |
| |
| private void configure(L8TestBuilder l8Builder, Backend backend) { |
| l8Builder |
| .applyIf(!l8FinalPrefixVerification, L8TestBuilder::ignoreFinalPrefixVerification) |
| .applyIf( |
| compilationSpecification.isL8Shrink() && !backend.isCf() && !l8ExtraKeepRules.isEmpty(), |
| b -> b.addKeepRules(l8ExtraKeepRules)) |
| .addOptionsModifier(l8OptionModifier); |
| } |
| |
| public String collectKeepRulesWithTraceReferences( |
| Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception { |
| Path generatedKeepRules = test.temp.newFile().toPath(); |
| ArrayList<String> args = new ArrayList<>(); |
| args.add("--keep-rules"); |
| for (Path libraryFile : libraryDesugaringSpecification.getLibraryFiles()) { |
| args.add("--lib"); |
| args.add(libraryFile.toString()); |
| } |
| args.add("--target"); |
| args.add(desugaredLibraryClassFile.toString()); |
| args.add("--source"); |
| args.add(desugaredProgramClassFile.toString()); |
| args.add("--output"); |
| args.add(generatedKeepRules.toString()); |
| args.add("--map-diagnostics"); |
| args.add("error"); |
| args.add("info"); |
| TraceReferences.run(args.toArray(new String[0])); |
| return FileUtils.readTextFile(generatedKeepRules, Charsets.UTF_8); |
| } |
| |
| public SingleTestRunResult<?> run(TestRuntime runtime, Class<?> mainClass, String... args) |
| throws Exception { |
| return compile().run(runtime, mainClass.getTypeName(), args); |
| } |
| |
| public SingleTestRunResult<?> run(TestRuntime runtime, String mainClass, String... args) |
| throws Exception { |
| return compile().run(runtime, mainClass, args); |
| } |
| |
| public DesugaredLibraryTestBuilder<T> supportAllCallbacksFromLibrary( |
| boolean supportAllCallbacksFromLibrary) { |
| addL8OptionsModification(supportLibraryCallbackConsumer(supportAllCallbacksFromLibrary, true)); |
| builder.addOptionsModification( |
| supportLibraryCallbackConsumer(supportAllCallbacksFromLibrary, false)); |
| return this; |
| } |
| |
| private Consumer<InternalOptions> supportLibraryCallbackConsumer( |
| boolean supportAllCallbacksFromLibrary, boolean libraryCompilation) { |
| return opt -> |
| opt.setDesugaredLibrarySpecification( |
| DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecificationforTesting( |
| StringResource.fromFile(libraryDesugaringSpecification.getSpecification()), |
| opt.dexItemFactory(), |
| opt.reporter, |
| libraryCompilation, |
| parameters.getApiLevel().getLevel(), |
| builder -> |
| builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary))); |
| } |
| |
| public DesugaredLibraryTestBuilder<T> addAndroidBuildVersion() { |
| builder.addAndroidBuildVersion(); |
| return this; |
| } |
| |
| public DesugaredLibraryTestBuilder<T> disableDesugaring() { |
| builder.disableDesugaring(); |
| return this; |
| } |
| } |