| // Copyright (c) 2025, 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.partial; |
| |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DirectMappedDexApplication; |
| import com.android.tools.r8.partial.predicate.AllClassesMatcher; |
| import com.android.tools.r8.partial.predicate.ClassNameMatcher; |
| import com.android.tools.r8.partial.predicate.ClassPrefixMatcher; |
| import com.android.tools.r8.partial.predicate.PackageAndSubpackagePrefixMatcher; |
| import com.android.tools.r8.partial.predicate.PackagePrefixMatcher; |
| import com.android.tools.r8.partial.predicate.R8PartialPredicate; |
| import com.android.tools.r8.partial.predicate.R8PartialPredicateCollection; |
| import com.android.tools.r8.partial.predicate.UnnamedPackageMatcher; |
| import com.android.tools.r8.utils.ConsumerUtils; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.google.common.base.Splitter; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Random; |
| import java.util.function.Consumer; |
| |
| public class R8PartialCompilationConfiguration { |
| |
| private final boolean enabled; |
| private final R8PartialPredicateCollection includePredicates; |
| private final R8PartialPredicateCollection excludePredicates; |
| private final Random randomizeForTesting; |
| |
| public Consumer<InternalOptions> d8DexOptionsConsumer = ConsumerUtils.emptyConsumer(); |
| public Consumer<InternalOptions> r8OptionsConsumer = ConsumerUtils.emptyConsumer(); |
| |
| public boolean printPartitioningForTesting = false; |
| |
| private static final R8PartialCompilationConfiguration disabledConfiguration = |
| new R8PartialCompilationConfiguration(false, null, null, null); |
| |
| private R8PartialCompilationConfiguration( |
| boolean enabled, |
| R8PartialPredicateCollection includePredicates, |
| R8PartialPredicateCollection excludePredicates, |
| Random randomizeForTesting) { |
| assert !enabled || !includePredicates.isEmpty(); |
| assert !enabled || excludePredicates != null; |
| this.enabled = enabled; |
| this.includePredicates = includePredicates; |
| this.excludePredicates = excludePredicates; |
| this.randomizeForTesting = randomizeForTesting; |
| } |
| |
| public R8PartialPredicateCollection getIncludePredicates() { |
| return includePredicates; |
| } |
| |
| public R8PartialPredicateCollection getExcludePredicates() { |
| return excludePredicates; |
| } |
| |
| public void partition( |
| DirectMappedDexApplication app, |
| Consumer<DexProgramClass> d8ClassConsumer, |
| Consumer<DexProgramClass> r8ClassConsumer) { |
| Collection<DexProgramClass> classes = |
| randomizeForTesting != null ? app.classesWithDeterministicOrder() : app.classes(); |
| for (DexProgramClass clazz : classes) { |
| if (test(clazz)) { |
| r8ClassConsumer.accept(clazz); |
| } else { |
| d8ClassConsumer.accept(clazz); |
| } |
| } |
| } |
| |
| private boolean test(DexProgramClass clazz) { |
| if (randomizeForTesting != null) { |
| return randomizeForTesting.nextBoolean(); |
| } |
| return includePredicates.test(clazz) && !excludePredicates.test(clazz); |
| } |
| |
| public static R8PartialCompilationConfiguration disabledConfiguration() { |
| return disabledConfiguration; |
| } |
| |
| public static R8PartialCompilationConfiguration fromIncludeExcludePatterns( |
| String includePatterns, String excludePatterns) { |
| boolean enabled = includePatterns != null || excludePatterns != null; |
| if (!enabled) { |
| return disabledConfiguration(); |
| } |
| Builder builder = builder(); |
| if (includePatterns != null) { |
| Splitter.on(",").splitToList(includePatterns).forEach(builder::addJavaTypeIncludePattern); |
| } |
| if (excludePatterns != null) { |
| Splitter.on(",").splitToList(excludePatterns).forEach(builder::addJavaTypeExcludePattern); |
| } |
| return builder.build(); |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public boolean isEnabled() { |
| return enabled; |
| } |
| |
| public boolean isRandomizeForTestingEnabled() { |
| return randomizeForTesting != null; |
| } |
| |
| public static class Builder { |
| private final R8PartialPredicateCollection includePredicates = |
| new R8PartialPredicateCollection(); |
| private final R8PartialPredicateCollection excludePredicates = |
| new R8PartialPredicateCollection(); |
| private Random randomizeForTesting; |
| |
| private Builder() {} |
| |
| public R8PartialCompilationConfiguration build() { |
| return new R8PartialCompilationConfiguration( |
| !includePredicates.isEmpty(), includePredicates, excludePredicates, randomizeForTesting); |
| } |
| |
| public Builder includeAll() { |
| includePredicates.add(new AllClassesMatcher()); |
| return this; |
| } |
| |
| public Builder excludeAll() { |
| excludePredicates.add(new AllClassesMatcher()); |
| return this; |
| } |
| |
| public Builder randomizeForTesting() { |
| return randomizeForTesting(System.currentTimeMillis()); |
| } |
| |
| public Builder randomizeForTesting(long seed) { |
| randomizeForTesting = new Random(); |
| randomizeForTesting.setSeed(seed); |
| System.out.println( |
| "Partial compilation seed: " |
| + seed |
| + ". Use .setPartialCompilationSeed(parameters, " |
| + seed |
| + "L) to reproduce."); |
| return this; |
| } |
| |
| public Builder addJavaTypeIncludePattern(String pattern) { |
| includePredicates.add( |
| createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern))); |
| return this; |
| } |
| |
| public Builder addJavaTypeExcludePattern(String pattern) { |
| excludePredicates.add( |
| createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern))); |
| return this; |
| } |
| |
| public Builder includeClasses(Class<?>... classes) { |
| return includeClasses(Arrays.asList(classes)); |
| } |
| |
| public Builder includeClasses(Collection<Class<?>> classes) { |
| for (Class<?> clazz : classes) { |
| includePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz))); |
| } |
| return this; |
| } |
| |
| public Builder excludeClasses(Class<?>... classes) { |
| return excludeClasses(Arrays.asList(classes)); |
| } |
| |
| public Builder excludeClasses(Collection<Class<?>> classes) { |
| for (Class<?> clazz : classes) { |
| excludePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz))); |
| } |
| return this; |
| } |
| |
| private R8PartialPredicate createPredicate(String descriptorPrefix) { |
| assert descriptorPrefix.startsWith("L"); |
| assert descriptorPrefix.indexOf('.') == -1; |
| if (descriptorPrefix.equals(AllClassesMatcher.PATTERN)) { |
| return new AllClassesMatcher(); |
| } else if (descriptorPrefix.equals(UnnamedPackageMatcher.PATTERN)) { |
| return new UnnamedPackageMatcher(); |
| } else if (descriptorPrefix.endsWith("/**")) { |
| return new PackageAndSubpackagePrefixMatcher( |
| extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 2)); |
| } else if (descriptorPrefix.endsWith("/*")) { |
| return new PackagePrefixMatcher( |
| extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 1)); |
| } else if (descriptorPrefix.endsWith("*")) { |
| return new ClassPrefixMatcher(extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 1)); |
| } else { |
| return new ClassNameMatcher( |
| extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 0) + ';'); |
| } |
| } |
| |
| private static String extractDescriptorPrefixWithoutWildcards( |
| String descriptorPattern, int numberOfWildcardsToRemove) { |
| String descriptorPrefixWithoutWildcards = |
| descriptorPattern.substring(0, descriptorPattern.length() - numberOfWildcardsToRemove); |
| if (descriptorPrefixWithoutWildcards.indexOf('*') >= 0) { |
| throw new IllegalArgumentException( |
| "Expected descriptor prefix without wildcards, got: " |
| + descriptorPrefixWithoutWildcards); |
| } |
| return descriptorPrefixWithoutWildcards; |
| } |
| } |
| } |