| // Copyright (c) 2018, 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 org.hamcrest.CoreMatchers.containsString; | |
| import com.android.tools.r8.R8Command.Builder; | |
| 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.experimental.graphinfo.GraphConsumer; | |
| import com.android.tools.r8.origin.Origin; | |
| import com.android.tools.r8.shaking.CollectingGraphConsumer; | |
| import com.android.tools.r8.shaking.ProguardConfiguration; | |
| import com.android.tools.r8.shaking.ProguardConfigurationRule; | |
| import com.android.tools.r8.utils.AndroidApiLevel; | |
| import com.android.tools.r8.utils.AndroidApp; | |
| import com.android.tools.r8.utils.FileUtils; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import java.io.IOException; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.function.Consumer; | |
| import java.util.function.Function; | |
| import java.util.function.Supplier; | |
| import java.util.stream.Collectors; | |
| import org.hamcrest.core.IsAnything; | |
| public abstract class R8TestBuilder<T extends R8TestBuilder<T>> | |
| extends TestShrinkerBuilder<R8Command, Builder, R8TestCompileResult, R8TestRunResult, T> { | |
| enum AllowedDiagnosticMessages { | |
| ALL, | |
| ERROR, | |
| INFO, | |
| NONE, | |
| WARNING | |
| } | |
| R8TestBuilder(TestState state, Builder builder, Backend backend) { | |
| super(state, builder, backend); | |
| } | |
| private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE; | |
| private boolean allowUnusedProguardConfigurationRules = false; | |
| private boolean enableConstantArgumentAnnotations = false; | |
| private boolean enableInliningAnnotations = false; | |
| private boolean enableMemberValuePropagationAnnotations = false; | |
| private boolean enableMergeAnnotations = false; | |
| private boolean enableNeverClassInliningAnnotations = false; | |
| private boolean enableNeverReprocessMethodAnnotations = false; | |
| private boolean enableReprocessClassInitializerAnnotations = false; | |
| private boolean enableReprocessMethodAnnotations = false; | |
| private boolean enableSideEffectAnnotations = false; | |
| private boolean enableUnusedArgumentAnnotations = false; | |
| private CollectingGraphConsumer graphConsumer = null; | |
| private List<String> keepRules = new ArrayList<>(); | |
| private List<Path> mainDexRulesFiles = new ArrayList<>(); | |
| private List<String> applyMappingMaps = new ArrayList<>(); | |
| @Override | |
| R8TestCompileResult internalCompile( | |
| Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app) | |
| throws CompilationFailedException { | |
| if (enableConstantArgumentAnnotations | |
| || enableInliningAnnotations | |
| || enableMemberValuePropagationAnnotations | |
| || enableMergeAnnotations | |
| || enableNeverClassInliningAnnotations | |
| || enableNeverReprocessMethodAnnotations | |
| || enableReprocessClassInitializerAnnotations | |
| || enableReprocessMethodAnnotations | |
| || enableSideEffectAnnotations | |
| || enableUnusedArgumentAnnotations) { | |
| ToolHelper.allowTestProguardOptions(builder); | |
| } | |
| if (!keepRules.isEmpty()) { | |
| builder.addProguardConfiguration(keepRules, Origin.unknown()); | |
| } | |
| builder.addMainDexRulesFiles(mainDexRulesFiles); | |
| StringBuilder proguardMapBuilder = new StringBuilder(); | |
| builder.setDisableTreeShaking(!enableTreeShaking); | |
| builder.setDisableMinification(!enableMinification); | |
| builder.setProguardMapConsumer( | |
| new StringConsumer() { | |
| @Override | |
| public void accept(String string, DiagnosticsHandler handler) { | |
| proguardMapBuilder.append(string); | |
| } | |
| @Override | |
| public void finished(DiagnosticsHandler handler) { | |
| // Nothing to do. | |
| } | |
| }); | |
| if (!applyMappingMaps.isEmpty()) { | |
| try { | |
| Path mappingsDir = getState().getNewTempFolder(); | |
| for (int i = 0; i < applyMappingMaps.size(); i++) { | |
| String mapContent = applyMappingMaps.get(i); | |
| Path mapPath = mappingsDir.resolve("mapping" + i + ".map"); | |
| FileUtils.writeTextFile(mapPath, mapContent); | |
| builder.addProguardConfiguration( | |
| Collections.singletonList("-applymapping " + mapPath.toString()), Origin.unknown()); | |
| } | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| class Box { | |
| private List<ProguardConfigurationRule> syntheticProguardRules; | |
| private ProguardConfiguration proguardConfiguration; | |
| } | |
| Box box = new Box(); | |
| ToolHelper.addSyntheticProguardRulesConsumerForTesting( | |
| builder, rules -> box.syntheticProguardRules = rules); | |
| ToolHelper.runR8WithoutResult( | |
| builder.build(), | |
| optionsConsumer.andThen( | |
| options -> box.proguardConfiguration = options.getProguardConfiguration())); | |
| R8TestCompileResult compileResult = | |
| new R8TestCompileResult( | |
| getState(), | |
| getOutputMode(), | |
| app.get(), | |
| box.proguardConfiguration, | |
| box.syntheticProguardRules, | |
| proguardMapBuilder.toString(), | |
| graphConsumer); | |
| switch (allowedDiagnosticMessages) { | |
| case ALL: | |
| compileResult.assertDiagnosticMessageThatMatches(new IsAnything<>()); | |
| break; | |
| case ERROR: | |
| compileResult.assertOnlyErrors(); | |
| break; | |
| case INFO: | |
| compileResult.assertOnlyInfos(); | |
| break; | |
| case NONE: | |
| if (allowUnusedProguardConfigurationRules) { | |
| compileResult | |
| .assertAllInfoMessagesMatch( | |
| containsString("Proguard configuration rule does not match anything")) | |
| .assertNoErrorMessages() | |
| .assertNoWarningMessages(); | |
| } else { | |
| compileResult.assertNoMessages(); | |
| } | |
| break; | |
| case WARNING: | |
| compileResult.assertOnlyWarnings(); | |
| break; | |
| default: | |
| throw new Unreachable(); | |
| } | |
| if (allowUnusedProguardConfigurationRules) { | |
| compileResult.assertInfoMessageThatMatches( | |
| containsString("Proguard configuration rule does not match anything")); | |
| } else { | |
| compileResult.assertNoInfoMessageThatMatches( | |
| containsString("Proguard configuration rule does not match anything")); | |
| } | |
| return compileResult; | |
| } | |
| public Builder getBuilder() { | |
| return builder; | |
| } | |
| public T addProgramResourceProviders(Collection<ProgramResourceProvider> providers) { | |
| for (ProgramResourceProvider provider : providers) { | |
| builder.addProgramResourceProvider(provider); | |
| } | |
| return self(); | |
| } | |
| public T addProgramResourceProviders(ProgramResourceProvider... providers) { | |
| return addProgramResourceProviders(Arrays.asList(providers)); | |
| } | |
| @Override | |
| public T addClasspathClasses(Collection<Class<?>> classes) { | |
| builder.addClasspathResourceProvider(ClassFileResourceProviderFromClasses(classes)); | |
| return self(); | |
| } | |
| @Override | |
| public T addClasspathFiles(Collection<Path> files) { | |
| builder.addClasspathFiles(files); | |
| return self(); | |
| } | |
| public T addDataResources(List<DataEntryResource> resources) { | |
| resources.forEach(builder.getAppBuilder()::addDataResource); | |
| return self(); | |
| } | |
| @Override | |
| public T addDataEntryResources(DataEntryResource... resources) { | |
| return addDataResources(Arrays.asList(resources)); | |
| } | |
| @Override | |
| public T addKeepRuleFiles(List<Path> files) { | |
| builder.addProguardConfigurationFiles(files); | |
| return self(); | |
| } | |
| @Override | |
| public T addKeepRules(Collection<String> rules) { | |
| // Delay adding the actual rules so that we only associate a single origin and unique lines to | |
| // each actual rule. | |
| keepRules.addAll(rules); | |
| return self(); | |
| } | |
| public T addMainDexRules(Collection<String> rules) { | |
| builder.addMainDexRules(new ArrayList<>(rules), Origin.unknown()); | |
| return self(); | |
| } | |
| public T addMainDexRules(String... rules) { | |
| return addMainDexRules(Arrays.asList(rules)); | |
| } | |
| public T addMainDexRuleFiles(List<Path> files) { | |
| mainDexRulesFiles.addAll(files); | |
| return self(); | |
| } | |
| public T addMainDexRuleFiles(Path... files) { | |
| return addMainDexRuleFiles(Arrays.asList(files)); | |
| } | |
| public T addMainDexClassRules(Class<?>... classes) { | |
| for (Class<?> clazz : classes) { | |
| addMainDexRules("-keep class " + clazz.getTypeName()); | |
| } | |
| return self(); | |
| } | |
| public T addMainDexListClasses(Class<?>... classes) { | |
| builder.addMainDexClasses( | |
| Arrays.stream(classes).map(Class::getTypeName).collect(Collectors.toList())); | |
| return self(); | |
| } | |
| public T allowClassInlinerGracefulExit() { | |
| return addOptionsModification(options -> options.testing.allowClassInlinerGracefulExit = true); | |
| } | |
| /** | |
| * Allow info, warning, and error diagnostics. | |
| * | |
| * <p>This should only be used if a test has any of these diagnostic messages. Therefore, it is a | |
| * failure if no such diagnostics are reported. | |
| */ | |
| public T allowDiagnosticMessages() { | |
| assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE; | |
| allowedDiagnosticMessages = AllowedDiagnosticMessages.ALL; | |
| return self(); | |
| } | |
| public T allowDiagnosticInfoMessages() { | |
| return allowDiagnosticInfoMessages(true); | |
| } | |
| /** | |
| * Allow info diagnostics if {@param condition} is true. | |
| * | |
| * <p>This should only be used if a test has at least one diagnostic info message. Therefore, it | |
| * is a failure if no such diagnostics are reported. | |
| */ | |
| public T allowDiagnosticInfoMessages(boolean condition) { | |
| if (condition) { | |
| assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE; | |
| allowedDiagnosticMessages = AllowedDiagnosticMessages.INFO; | |
| } | |
| return self(); | |
| } | |
| public T allowDiagnosticWarningMessages() { | |
| return allowDiagnosticWarningMessages(true); | |
| } | |
| /** | |
| * Allow warning diagnostics if {@param condition} is true. | |
| * | |
| * <p>This should only be used if a test has at least one diagnostic warning message. Therefore, | |
| * it is a failure if no such diagnostics are reported. | |
| */ | |
| public T allowDiagnosticWarningMessages(boolean condition) { | |
| if (condition) { | |
| assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE; | |
| allowedDiagnosticMessages = AllowedDiagnosticMessages.WARNING; | |
| } | |
| return self(); | |
| } | |
| public T allowDiagnosticErrorMessages() { | |
| return allowDiagnosticErrorMessages(true); | |
| } | |
| /** | |
| * Allow error diagnostics if {@param condition} is true. | |
| * | |
| * <p>This should only be used if a test has at least one diagnostic error message. Therefore, it | |
| * is a failure if no such diagnostics are reported. | |
| */ | |
| public T allowDiagnosticErrorMessages(boolean condition) { | |
| if (condition) { | |
| assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE; | |
| allowedDiagnosticMessages = AllowedDiagnosticMessages.ERROR; | |
| } | |
| return self(); | |
| } | |
| public T allowUnusedProguardConfigurationRules() { | |
| return allowUnusedProguardConfigurationRules(true); | |
| } | |
| public T allowUnusedProguardConfigurationRules(boolean condition) { | |
| if (condition) { | |
| allowUnusedProguardConfigurationRules = true; | |
| } | |
| return self(); | |
| } | |
| public T enableAlwaysInliningAnnotations() { | |
| return enableAlwaysInliningAnnotations(AlwaysInline.class.getPackage().getName()); | |
| } | |
| public T enableAlwaysInliningAnnotations(String annotationPackageName) { | |
| if (!enableInliningAnnotations) { | |
| enableInliningAnnotations = true; | |
| addInternalKeepRules( | |
| "-alwaysinline class * { @" + annotationPackageName + ".AlwaysInline *; }"); | |
| } | |
| return self(); | |
| } | |
| public T enableInliningAnnotations() { | |
| return enableInliningAnnotations(NeverInline.class.getPackage().getName()); | |
| } | |
| public T enableInliningAnnotations(String annotationPackageName) { | |
| if (!enableInliningAnnotations) { | |
| enableInliningAnnotations = true; | |
| addInternalKeepRules( | |
| "-neverinline class * { @" + annotationPackageName + ".NeverInline *; }"); | |
| } | |
| return self(); | |
| } | |
| public T enableForceInliningAnnotations() { | |
| return enableForceInliningAnnotations(ForceInline.class.getPackage().getName()); | |
| } | |
| public T enableForceInliningAnnotations(String annotationPackageName) { | |
| if (!enableInliningAnnotations) { | |
| enableInliningAnnotations = true; | |
| addInternalKeepRules( | |
| "-forceinline class * { @" + annotationPackageName + ".ForceInline *; }"); | |
| } | |
| return self(); | |
| } | |
| public T enableNeverClassInliningAnnotations() { | |
| if (!enableNeverClassInliningAnnotations) { | |
| enableNeverClassInliningAnnotations = true; | |
| addInternalKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *"); | |
| } | |
| return self(); | |
| } | |
| public T enableMergeAnnotations() { | |
| if (!enableMergeAnnotations) { | |
| enableMergeAnnotations = true; | |
| addInternalKeepRules("-nevermerge @com.android.tools.r8.NeverMerge class *"); | |
| } | |
| return self(); | |
| } | |
| public T enableMemberValuePropagationAnnotations() { | |
| if (!enableMemberValuePropagationAnnotations) { | |
| enableMemberValuePropagationAnnotations = true; | |
| addInternalKeepRules( | |
| "-neverpropagatevalue class * { @com.android.tools.r8.NeverPropagateValue *; }"); | |
| } | |
| return self(); | |
| } | |
| public T enableReprocessClassInitializerAnnotations() { | |
| if (!enableReprocessClassInitializerAnnotations) { | |
| enableReprocessClassInitializerAnnotations = true; | |
| addInternalKeepRules( | |
| "-reprocessclassinitializer @com.android.tools.r8.ReprocessClassInitializer class *"); | |
| } | |
| return self(); | |
| } | |
| public T enableReprocessMethodAnnotations() { | |
| if (!enableReprocessMethodAnnotations) { | |
| enableReprocessMethodAnnotations = true; | |
| addInternalKeepRules( | |
| "-reprocessmethod class * {", " @com.android.tools.r8.ReprocessMethod <methods>;", "}"); | |
| } | |
| return self(); | |
| } | |
| public T enableNeverReprocessMethodAnnotations() { | |
| if (!enableNeverReprocessMethodAnnotations) { | |
| enableNeverReprocessMethodAnnotations = true; | |
| addInternalKeepRules( | |
| "-neverreprocessmethod class * {", | |
| " @com.android.tools.r8.NeverReprocessMethod <methods>;", | |
| "}"); | |
| } | |
| return self(); | |
| } | |
| public T enableProtoShrinking() { | |
| return enableProtoShrinking(true); | |
| } | |
| public T enableProtoShrinking(boolean traverseOneOfAndRepeatedProtoFields) { | |
| if (traverseOneOfAndRepeatedProtoFields) { | |
| addOptionsModification( | |
| options -> options.protoShrinking().traverseOneOfAndRepeatedProtoFields = true); | |
| } | |
| return addKeepRules("-shrinkunusedprotofields"); | |
| } | |
| public T enableSideEffectAnnotations() { | |
| if (!enableSideEffectAnnotations) { | |
| enableSideEffectAnnotations = true; | |
| addInternalKeepRules( | |
| "-assumemayhavesideeffects class * {", | |
| " @com.android.tools.r8.AssumeMayHaveSideEffects <methods>;", | |
| "}"); | |
| } | |
| return self(); | |
| } | |
| public T assumeAllMethodsMayHaveSideEffects() { | |
| if (!enableSideEffectAnnotations) { | |
| enableSideEffectAnnotations = true; | |
| addInternalKeepRules("-assumemayhavesideeffects class * { <methods>; }"); | |
| } | |
| return self(); | |
| } | |
| public T enableConstantArgumentAnnotations() { | |
| return enableConstantArgumentAnnotations(true); | |
| } | |
| public T enableConstantArgumentAnnotations(boolean value) { | |
| if (value) { | |
| if (!enableConstantArgumentAnnotations) { | |
| enableConstantArgumentAnnotations = true; | |
| addInternalKeepRules( | |
| "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }"); | |
| } | |
| } else { | |
| assert !enableConstantArgumentAnnotations; | |
| } | |
| return self(); | |
| } | |
| public T enableUnusedArgumentAnnotations() { | |
| return enableUnusedArgumentAnnotations(true); | |
| } | |
| public T enableUnusedArgumentAnnotations(boolean value) { | |
| if (value) { | |
| if (!enableUnusedArgumentAnnotations) { | |
| enableUnusedArgumentAnnotations = true; | |
| addInternalKeepRules( | |
| "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }"); | |
| } | |
| } else { | |
| assert !enableUnusedArgumentAnnotations; | |
| } | |
| return self(); | |
| } | |
| public T enableProguardTestOptions() { | |
| builder.allowTestProguardOptions(); | |
| return self(); | |
| } | |
| public T enableGraphInspector() { | |
| return enableGraphInspector(null); | |
| } | |
| public T enableGraphInspector(GraphConsumer subConsumer) { | |
| CollectingGraphConsumer consumer = new CollectingGraphConsumer(subConsumer); | |
| setKeptGraphConsumer(consumer); | |
| graphConsumer = consumer; | |
| return self(); | |
| } | |
| public T setKeptGraphConsumer(GraphConsumer graphConsumer) { | |
| assert this.graphConsumer == null; | |
| builder.setKeptGraphConsumer(graphConsumer); | |
| return self(); | |
| } | |
| public T setMainDexKeptGraphConsumer(GraphConsumer graphConsumer) { | |
| builder.setMainDexKeptGraphConsumer(graphConsumer); | |
| return self(); | |
| } | |
| @Override | |
| public T addApplyMapping(String proguardMap) { | |
| applyMappingMaps.add(proguardMap); | |
| return self(); | |
| } | |
| private void addInternalKeepRules(String... rules) { | |
| // We don't add these to the keep-rule set for other test provided rules. | |
| builder.addProguardConfiguration(Arrays.asList(rules), Origin.unknown()); | |
| } | |
| @Override | |
| public T enableCoreLibraryDesugaring( | |
| AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) { | |
| if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) { | |
| // Use P to mimic current Android Studio. | |
| builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)); | |
| builder.addDesugaredLibraryConfiguration( | |
| StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING)); | |
| builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer); | |
| } | |
| return self(); | |
| } | |
| public T addFeatureSplit(Function<FeatureSplit.Builder, FeatureSplit> featureSplitBuilder) { | |
| builder.addFeatureSplit(featureSplitBuilder); | |
| return self(); | |
| } | |
| } |