Merge "Change internal tests to run on r8lib with dependencies"
diff --git a/build.gradle b/build.gradle
index 93d7e1b..7ff46eb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -702,6 +702,11 @@
     workingDir = projectDir
 }
 
+task R8LibApiOnly {
+    dependsOn r8LibCreateTask("Api", "src/main/keep.txt", R8NoManifest, r8LibPath)
+    outputs.file r8LibPath
+}
+
 task R8LibNoDeps {
     dependsOn r8LibCreateTask(
             "NoDeps",
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 228c0e5..ee45cf4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -48,12 +48,15 @@
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationUtils;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.StaticClassMerger;
 import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.shaking.VerticalClassMerger;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -67,6 +70,7 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.VersionProperties;
+import com.google.common.collect.Iterables;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.ByteArrayOutputStream;
@@ -77,8 +81,10 @@
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -252,6 +258,7 @@
       AppView<AppInfoWithSubtyping> appView =
           new AppView<>(
               new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
+      List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       RootSet rootSet;
       String proguardSeedsData = null;
       timing.begin("Strip unused code");
@@ -278,10 +285,26 @@
         ProguardConfiguration.Builder compatibility =
             ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
 
+        // Add synthesized -assumevalues from min api if relevant.
+        if (options.isGeneratingDex()) {
+          if (!ProguardConfigurationUtils.hasExplicitAssumeValuesRuleForMinSdk(
+              options.itemFactory,
+              options.getProguardConfiguration().getRules())) {
+            synthesizedProguardRules.add(
+                ProguardConfigurationUtils.buildAssumeValuesForApiLevel(
+                    options.itemFactory,
+                    AndroidApiLevel.getAndroidApiLevel(options.minApiLevel)));
+          }
+        }
+
         rootSet =
             new RootSetBuilder(
-                    appView, application, options.getProguardConfiguration().getRules(), options)
-                .run(executorService);
+                appView,
+                application,
+                Iterables.concat(
+                    options.getProguardConfiguration().getRules(), synthesizedProguardRules),
+                options
+            ).run(executorService);
 
         Enqueuer enqueuer = new Enqueuer(appView, options, null, compatibility);
         appView.setAppInfo(
@@ -608,6 +631,12 @@
       assert application.classes().stream().allMatch(DexClass::isValid);
       assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), options);
 
+      // Report synthetic rules (only for testing).
+      // TODO(b/120959039): Move this to being reported through the graph consumer.
+      if (options.syntheticProguardRulesConsumer != null) {
+        options.syntheticProguardRulesConsumer.accept(synthesizedProguardRules);
+      }
+
       // Generate the resulting application resources.
       writeApplication(
           executorService,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index cca17bf..e4d1496 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -79,6 +79,7 @@
 
     private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
     private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
+    private Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
     private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
     private boolean disableTreeShaking = false;
     private boolean disableMinification = false;
@@ -453,6 +454,7 @@
               proguardCompatibilityRulesOutput,
               keptGraphConsumer,
               mainDexKeptGraphConsumer,
+              syntheticProguardRulesConsumer,
               isOptimizeMultidexForLinearAlloc());
 
       return command;
@@ -470,6 +472,15 @@
           };
     }
 
+    void addSyntheticProguardRulesConsumerForTesting(
+        Consumer<List<ProguardConfigurationRule>> consumer) {
+      syntheticProguardRulesConsumer =
+          syntheticProguardRulesConsumer == null
+              ? consumer
+              : syntheticProguardRulesConsumer.andThen(consumer);
+
+    }
+
     // Internal for-testing method to add post-processors of the proguard configuration.
     void allowPartiallyImplementedProguardOptions() {
       allowPartiallyImplementedProguardOptions = true;
@@ -520,6 +531,7 @@
   private final Path proguardCompatibilityRulesOutput;
   private final GraphConsumer keptGraphConsumer;
   private final GraphConsumer mainDexKeptGraphConsumer;
+  private final Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer;
 
   /** Get a new {@link R8Command.Builder}. */
   public static Builder builder() {
@@ -586,6 +598,7 @@
       Path proguardCompatibilityRulesOutput,
       GraphConsumer keptGraphConsumer,
       GraphConsumer mainDexKeptGraphConsumer,
+      Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
       boolean optimizeMultidexForLinearAlloc) {
     super(inputApp, mode, programConsumer, mainDexListConsumer, minApiLevel, reporter,
         enableDesugaring, optimizeMultidexForLinearAlloc);
@@ -601,6 +614,7 @@
     this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     this.keptGraphConsumer = keptGraphConsumer;
     this.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
+    this.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
   }
 
   private R8Command(boolean printHelp, boolean printVersion) {
@@ -615,6 +629,7 @@
     proguardCompatibilityRulesOutput = null;
     keptGraphConsumer = null;
     mainDexKeptGraphConsumer = null;
+    syntheticProguardRulesConsumer = null;
   }
 
   /** Get the enable-tree-shaking state. */
@@ -723,6 +738,8 @@
     internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
 
+    internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
+
     // Default is to remove Java assertion code as Dalvik and Art does not reliable support
     // Java assertions. When generation class file output always keep the Java assertions code.
     assert internal.disableAssertions;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b500296..7686444 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.21-dev";
+  public static final String LABEL = "1.4.22-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 668c490..9ae4986 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -80,8 +80,9 @@
       return memberRules;
     }
 
-    public void setMemberRules(List<ProguardMemberRule> memberRules) {
+    public B setMemberRules(List<ProguardMemberRule> memberRules) {
       this.memberRules = memberRules;
+      return self();
     }
 
     public boolean getInheritanceIsExtends() {
@@ -116,16 +117,18 @@
       return classNames;
     }
 
-    public void setClassNames(ProguardClassNameList classNames) {
+    public B setClassNames(ProguardClassNameList classNames) {
       this.classNames = classNames;
+      return self();
     }
 
     public ProguardClassType getClassType() {
       return classType;
     }
 
-    public void setClassType(ProguardClassType classType) {
+    public B setClassType(ProguardClassType classType) {
       this.classType = classType;
+      return self();
     }
 
     public boolean getClassTypeNegated() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 0f1588f..163c60b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -8,11 +8,14 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.LongInterval;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.List;
@@ -156,4 +159,93 @@
     builder.getMemberRules().add(memberRuleBuilder.build());
     return builder.build();
   }
+
+  public static ProguardAssumeValuesRule buildAssumeValuesForApiLevel(
+      DexItemFactory factory, AndroidApiLevel apiLevel) {
+    Origin synthesizedFromApiLevel =
+        new Origin(Origin.root()) {
+          @Override
+          public String part() {
+            return "<SYNTHESIZED_FROM_API_LEVEL_" + apiLevel.getLevel() + ">";
+          }
+        };
+
+    ProguardAccessFlags publicStaticFinalFlags = new ProguardAccessFlags();
+    publicStaticFinalFlags.setPublic();
+    publicStaticFinalFlags.setStatic();
+    publicStaticFinalFlags.setFinal();
+
+    return ProguardAssumeValuesRule
+        .builder()
+        .setOrigin(synthesizedFromApiLevel)
+        .setClassType(ProguardClassType.CLASS)
+        .setClassNames(
+            ProguardClassNameList.singletonList(
+                ProguardTypeMatcher.create(factory.createType("Landroid/os/Build$VERSION;"))))
+        .setMemberRules(ImmutableList.of(
+            ProguardMemberRule.builder()
+                .setAccessFlags(publicStaticFinalFlags)
+                .setRuleType(ProguardMemberType.FIELD)
+                .setTypeMatcher(ProguardTypeMatcher.create(factory.intType))
+                .setName(IdentifierPatternWithWildcards.withoutWildcards("SDK_INT"))
+                .setReturnValue(
+                    new ProguardMemberRuleReturnValue(
+                        new LongInterval(apiLevel.getLevel(), Integer.MAX_VALUE)))
+                .build()
+        ))
+        .build();
+  }
+
+  /**
+   * Check if an explicit rule matching the field
+   *   public static final int android.os.Build$VERSION.SDK_INT
+   * is present.
+   */
+  public static boolean hasExplicitAssumeValuesRuleForMinSdk(
+      DexItemFactory factory, List<ProguardConfigurationRule> rules) {
+    for (ProguardConfigurationRule rule : rules) {
+      if (!(rule instanceof ProguardAssumeValuesRule)) {
+        continue;
+      }
+      if (rule.getClassType() != ProguardClassType.CLASS) {
+        continue;
+      }
+      if (rule.hasInheritanceClassName()
+          && !rule.getInheritanceClassName().matches(factory.objectType)) {
+        continue;
+      }
+      if (!rule.getClassNames().matches(factory.createType("Landroid/os/Build$VERSION;"))) {
+        continue;
+      }
+      for (ProguardMemberRule memberRule : rule.getMemberRules()) {
+        if (memberRule.getRuleType() == ProguardMemberType.ALL
+            || memberRule.getRuleType() == ProguardMemberType.ALL_FIELDS) {
+          return true;
+        }
+        if (memberRule.getRuleType() != ProguardMemberType.FIELD) {
+          continue;
+        }
+        if (memberRule.getAccessFlags().isProtected()
+            || memberRule.getAccessFlags().isPrivate()
+            || memberRule.getAccessFlags().isAbstract()
+            || memberRule.getAccessFlags().isTransient()
+            || memberRule.getAccessFlags().isVolatile()) {
+          continue;
+        }
+        if (memberRule.getNegatedAccessFlags().isPublic()
+            || memberRule.getNegatedAccessFlags().isStatic()
+            || memberRule.getNegatedAccessFlags().isFinal()) {
+          continue;
+        }
+        if (!memberRule.getType().matches(factory.intType)) {
+          continue;
+        }
+        if (!memberRule.getName().matches("SDK_INT")) {
+          continue;
+        }
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index ae54a80..86b29a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -42,8 +42,9 @@
       return accessFlags;
     }
 
-    public void setAccessFlags(ProguardAccessFlags flags) {
+    public Builder setAccessFlags(ProguardAccessFlags flags) {
       accessFlags = flags;
+      return this;
     }
 
     public ProguardAccessFlags getNegatedAccessFlags() {
@@ -54,28 +55,32 @@
       negatedAccessFlags = flags;
     }
 
-    public void setRuleType(ProguardMemberType ruleType) {
+    public Builder setRuleType(ProguardMemberType ruleType) {
       this.ruleType = ruleType;
+      return this;
     }
 
     public ProguardTypeMatcher getTypeMatcher() {
       return type;
     }
 
-    public void setTypeMatcher(ProguardTypeMatcher type) {
+    public Builder setTypeMatcher(ProguardTypeMatcher type) {
       this.type = type;
+      return this;
     }
 
-    public void setName(IdentifierPatternWithWildcards identifierPatternWithWildcards) {
+    public Builder setName(IdentifierPatternWithWildcards identifierPatternWithWildcards) {
       this.name = ProguardNameMatcher.create(identifierPatternWithWildcards);
+      return this;
     }
 
     public void setArguments(List<ProguardTypeMatcher> arguments) {
       this.arguments = arguments;
     }
 
-    public void setReturnValue(ProguardMemberRuleReturnValue value) {
+    public Builder setReturnValue(ProguardMemberRuleReturnValue value) {
       returnValue = value;
+      return this;
     }
 
     public boolean isValid() {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index e86966f..46d3a68 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -60,7 +60,7 @@
 
   private final AppView<? extends AppInfo> appView;
   private final DirectMappedDexApplication application;
-  private final Collection<ProguardConfigurationRule> rules;
+  private final Iterable<? extends ProguardConfigurationRule> rules;
   private final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
   private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
   private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
@@ -89,11 +89,11 @@
   public RootSetBuilder(
       AppView<? extends AppInfo> appView,
       DexApplication application,
-      Collection<? extends ProguardConfigurationRule> rules,
+      Iterable<? extends ProguardConfigurationRule> rules,
       InternalOptions options) {
     this.appView = appView;
     this.application = application.asDirect();
-    this.rules = rules == null ? null : Collections.unmodifiableCollection(rules);
+    this.rules = rules;
     this.options = options;
   }
 
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 1e39494..4921c79 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -34,6 +34,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Consumer;
 
 public class InternalOptions {
 
@@ -355,6 +356,7 @@
   public GraphConsumer mainDexKeptGraphConsumer = null;
 
   public Path proguardCompatibilityRulesOutput = null;
+  public Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
 
   public static boolean assertionsEnabled() {
     boolean assertionsEnabled = false;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 1c39971..bb5677f 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -9,6 +9,8 @@
 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.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.nio.file.Path;
@@ -67,9 +69,26 @@
     builder.setDisableTreeShaking(!enableTreeShaking);
     builder.setDisableMinification(!enableMinification);
     builder.setProguardMapConsumer((string, ignore) -> proguardMapBuilder.append(string));
-    ToolHelper.runR8WithoutResult(builder.build(), optionsConsumer);
+
+    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()));
     return new R8TestCompileResult(
-        getState(), backend, app.get(), proguardMapBuilder.toString(), graphConsumer);
+        getState(),
+        backend,
+        app.get(),
+        box.proguardConfiguration,
+        box.syntheticProguardRules,
+        proguardMapBuilder.toString(),
+        graphConsumer);
   }
 
   public R8TestBuilder addDataResources(List<DataEntryResource> resources) {
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 8a4c44d..a9c02ff 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -6,15 +6,21 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 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.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
 
   private final Backend backend;
+  private final ProguardConfiguration proguardConfiguration;
+  private final List<ProguardConfigurationRule> syntheticProguardRules;
   private final String proguardMap;
   private final CollectingGraphConsumer graphConsumer;
 
@@ -22,10 +28,14 @@
       TestState state,
       Backend backend,
       AndroidApp app,
+      ProguardConfiguration proguardConfiguration,
+      List<ProguardConfigurationRule> syntheticProguardRules,
       String proguardMap,
       CollectingGraphConsumer graphConsumer) {
     super(state, app);
     this.backend = backend;
+    this.proguardConfiguration = proguardConfiguration;
+    this.syntheticProguardRules = syntheticProguardRules;
     this.proguardMap = proguardMap;
     this.graphConsumer = graphConsumer;
   }
@@ -55,6 +65,26 @@
     return new GraphInspector(graphConsumer, inspector());
   }
 
+  public ProguardConfiguration getProguardConfiguration() {
+    return proguardConfiguration;
+  }
+
+  public R8TestCompileResult inspectProguardConfiguration(
+      Consumer<ProguardConfiguration> consumer) {
+    consumer.accept(getProguardConfiguration());
+    return self();
+  }
+
+  public List<ProguardConfigurationRule> getSyntheticProguardRules() {
+    return syntheticProguardRules;
+  }
+
+  public R8TestCompileResult inspectSyntheticProguardRules(
+      Consumer<List<ProguardConfigurationRule>> consumer) {
+    consumer.accept(getSyntheticProguardRules());
+    return self();
+  }
+
   @Override
   public R8TestRunResult createRunResult(ProcessResult result) {
     return new R8TestRunResult(app, result, proguardMap, this::graphInspector);
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 5d5229b..0c84e7c 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -15,13 +15,17 @@
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import org.hamcrest.Matcher;
 
 public abstract class TestCompileResult<
@@ -29,6 +33,7 @@
 
   final TestState state;
   public final AndroidApp app;
+  final List<Path> additionalRunClassPath = new ArrayList<>();
 
   TestCompileResult(TestState state, AndroidApp app) {
     this.state = state;
@@ -50,14 +55,19 @@
   public RR run(String mainClass) throws IOException {
     switch (getBackend()) {
       case DEX:
-        return runArt(mainClass);
+        return runArt(additionalRunClassPath, mainClass);
       case CF:
-        return runJava(mainClass);
+        return runJava(additionalRunClassPath, mainClass);
       default:
         throw new Unreachable();
     }
   }
 
+  public CR addRunClasspath(List<Path> classpath) {
+    additionalRunClassPath.addAll(classpath);
+    return self();
+  }
+
   public CR writeToZip(Path file) throws IOException {
     app.writeToZip(file, getBackend() == DEX ? OutputMode.DexIndexed : OutputMode.ClassFile);
     return self();
@@ -160,17 +170,25 @@
     }
   }
 
-  private RR runJava(String mainClass) throws IOException {
+  private RR runJava(List<Path> additionalClassPath, String mainClass) throws IOException {
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.ClassFile);
-    ProcessResult result = ToolHelper.runJava(out, mainClass);
+    List<Path> classPath = ImmutableList.<Path>builder()
+        .addAll(additionalClassPath)
+        .add(out)
+        .build();
+    ProcessResult result = ToolHelper.runJava(classPath, mainClass);
     return createRunResult(result);
   }
 
-  private RR runArt(String mainClass) throws IOException {
+  private RR runArt(List<Path> additionalClassPath, String mainClass) throws IOException {
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.DexIndexed);
-    ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
+    List<String> classPath = ImmutableList.<String>builder()
+        .addAll(additionalClassPath.stream().map(Path::toString).collect(Collectors.toList()))
+        .add(out.toString())
+        .build();
+    ProcessResult result = ToolHelper.runArtRaw(classPath, mainClass, dummy -> {});
     return createRunResult(result);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 6f42792..64c735d 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
+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.AndroidAppConsumers;
@@ -1695,6 +1696,12 @@
     return builder;
   }
 
+  public static R8Command.Builder addSyntheticProguardRulesConsumerForTesting(
+      R8Command.Builder builder, Consumer<List<ProguardConfigurationRule>> consumer) {
+    builder.addSyntheticProguardRulesConsumerForTesting(consumer);
+    return builder;
+  }
+
   public static R8Command.Builder allowPartiallyImplementedProguardOptions(
       R8Command.Builder builder) {
     builder.allowPartiallyImplementedProguardOptions();
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
new file mode 100644
index 0000000..dd11dc1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -0,0 +1,327 @@
+// Copyright (c) 2019, 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.shaking.assumevalues;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.shaking.ProguardAssumeValuesRule;
+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.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SynthesizedRulesFromApiLevelTest extends TestBase {
+
+  private final Backend backend;
+  private final String mainClassName = "MainClass";
+  private final String compatLibraryClassName = "CompatLibrary";
+
+  public SynthesizedRulesFromApiLevelTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  // Simple mock implementation of class android.os.Build$VERSION with just the SDK_INT field.
+  private Path mockAndroidRuntimeLibrary(int sdkInt) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder classBuilder;
+
+    classBuilder = builder.addClass("android.os.Build$VERSION");
+    classBuilder.addStaticFinalField("SDK_INT", "I", Integer.toString(sdkInt));
+
+    classBuilder = builder.addClass("android.os.Native");
+    classBuilder.addStaticMethod("method", ImmutableList.of(), "V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc \" Native\"",
+        "  invokevirtual java/io/PrintStream/print(Ljava.lang.String;)V",
+        "  return"
+    );
+
+    return writeToJar(builder);
+  }
+
+  private Path buildMockAndroidRuntimeLibrary(AndroidApiLevel apiLevel) throws Exception {
+    // Build the mock library containing android.os.Build.VERSION with D8.
+    Path library = temp.newFolder().toPath().resolve("library.jar");
+    D8.run(
+        D8Command
+            .builder()
+            .addProgramFiles(mockAndroidRuntimeLibrary(apiLevel.getLevel()))
+            .setOutput(library, OutputMode.DexIndexed)
+            .build());
+    return library;
+  }
+
+  private Path buildApp(AndroidApiLevel apiLevelForNative) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder classBuilder;
+
+    classBuilder = builder.addClass(compatLibraryClassName);
+
+    classBuilder.addStaticMethod("compatMethod", ImmutableList.of(), "V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc \" Compat\"",
+        "  invokevirtual java/io/PrintStream/print(Ljava.lang.String;)V",
+        "  return"
+    );
+
+    classBuilder.addStaticMethod("method", ImmutableList.of(), "V",
+        ".limit stack 2",
+        "  getstatic android/os/Build$VERSION/SDK_INT I",
+        "  ldc " + apiLevelForNative.getLevel(),
+        "if_icmpge Native",
+        "  invokestatic " + compatLibraryClassName +"/compatMethod()V",
+        "  return",
+        "Native:",
+        "  invokestatic android.os.Native/method()V",
+        "  return"
+    );
+
+    classBuilder = builder.addClass(mainClassName);
+
+    classBuilder.addStaticMethod("getSdkInt", ImmutableList.of(), "I",
+        ".limit stack 1",
+        "getstatic android/os/Build$VERSION/SDK_INT I",
+        "ireturn"
+    );
+
+    classBuilder.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "invokestatic " + mainClassName + "/getSdkInt()I",
+        "invokevirtual java/io/PrintStream/print(I)V",
+
+        "invokestatic " + compatLibraryClassName + "/method()V",
+
+        "return");
+
+    return writeToJar(builder);
+  }
+
+  private enum SynthesizedRule {
+    PRESENT,
+    NOT_PRESENT
+  }
+
+  private void checkSynthesizedRuleExpectation(
+      List<ProguardConfigurationRule> synthesizedRules, SynthesizedRule expected) {
+    for (ProguardConfigurationRule rule : synthesizedRules) {
+      if (rule instanceof ProguardAssumeValuesRule
+          && rule.getOrigin().part().contains("SYNTHESIZED_FROM_API_LEVEL")) {
+        assertEquals(expected, SynthesizedRule.PRESENT);
+        return;
+      }
+    }
+    assertEquals(expected, SynthesizedRule.NOT_PRESENT);
+  }
+
+  private void noSynthesizedRules(ProguardConfiguration proguardConfiguration) {
+    for (ProguardConfigurationRule rule : proguardConfiguration.getRules()) {
+      if (rule instanceof ProguardAssumeValuesRule) {
+        assertFalse(rule.getOrigin().part().contains("SYNTHESIZED_FROM_API_LEVEL"));
+      }
+    }
+  }
+
+  private void runTest(
+      AndroidApiLevel buildApiLevel,
+      AndroidApiLevel runtimeApiLevel,
+      AndroidApiLevel nativeApiLevel,
+      String expectedOutput,
+      Consumer<CodeInspector> inspector,
+      List<String> additionalKeepRules,
+      SynthesizedRule synthesizedRule) throws Exception{
+    assertTrue(runtimeApiLevel.getLevel() >= buildApiLevel.getLevel());
+    if (backend == Backend.DEX) {
+      testForR8(backend)
+          .setMinApi(buildApiLevel)
+          .addProgramFiles(buildApp(nativeApiLevel))
+          .enableProguardTestOptions()
+          .addKeepRules("-neverinline class " + compatLibraryClassName + " { *; }")
+          .addKeepMainRule(mainClassName)
+          .addKeepRules(additionalKeepRules)
+          .compile()
+          .inspectSyntheticProguardRules(
+              syntheticProguardRules ->
+                  checkSynthesizedRuleExpectation(syntheticProguardRules, synthesizedRule))
+          .inspect(inspector)
+          .addRunClasspath(ImmutableList.of(buildMockAndroidRuntimeLibrary(runtimeApiLevel)))
+          .run(mainClassName)
+          .assertSuccessWithOutput(expectedOutput);
+    } else {
+      assert backend == Backend.CF;
+      testForR8(backend)
+          .addProgramFiles(buildApp(nativeApiLevel))
+          .addKeepMainRule(mainClassName)
+          .addKeepRules(additionalKeepRules)
+          .compile()
+          .inspectProguardConfiguration(this::noSynthesizedRules)
+          .addRunClasspath(
+              ImmutableList.of(mockAndroidRuntimeLibrary(AndroidApiLevel.D.getLevel())))
+          .run(mainClassName)
+          .assertSuccessWithOutput(expectedResultForCompat(AndroidApiLevel.D));
+    }
+  }
+
+  private void runTest(
+      AndroidApiLevel buildApiLevel,
+      AndroidApiLevel runtimeApiLevel,
+      AndroidApiLevel nativeApiLevel,
+      String expectedOutput,
+      Consumer<CodeInspector> inspector) throws Exception{
+    runTest(
+        buildApiLevel,
+        runtimeApiLevel,
+        nativeApiLevel,
+        expectedOutput,
+        inspector,
+        ImmutableList.of(),
+        SynthesizedRule.PRESENT);
+  }
+
+  private String expectedResultForNative(AndroidApiLevel runtimeApiLevel) {
+    return runtimeApiLevel.getLevel() + " Native";
+  }
+
+  private String expectedResultForCompat(AndroidApiLevel runtimeApiLevel) {
+    return runtimeApiLevel.getLevel() + " Compat";
+  }
+
+  private void compatCodePresent(CodeInspector inspector) {
+    ClassSubject compatLibrary = inspector.clazz(compatLibraryClassName);
+    assertThat(compatLibrary, isPresent());
+    assertThat(compatLibrary.uniqueMethodWithName("compatMethod"), isPresent());
+  }
+
+  private void compatCodeNotPresent(CodeInspector inspector) {
+    ClassSubject compatLibrary = inspector.clazz(compatLibraryClassName);
+    assertThat(compatLibrary, isPresent());
+    assertThat(compatLibrary.uniqueMethodWithName("compatMethod"), not(isPresent()));
+  }
+
+  @Test
+  public void testNoExplicitAssumeValuesRuleNative() throws Exception {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST));
+    runTest(
+        AndroidApiLevel.O_MR1,
+        AndroidApiLevel.O_MR1,
+        AndroidApiLevel.O_MR1,
+        expectedResultForNative(AndroidApiLevel.O_MR1),
+        this::compatCodeNotPresent);
+  }
+
+  @Test
+  public void testNoExplicitAssumeValuesRuleCompatPresent() throws Exception {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST));
+    runTest(
+        AndroidApiLevel.O,
+        AndroidApiLevel.O_MR1,
+        AndroidApiLevel.O_MR1,
+        expectedResultForNative(AndroidApiLevel.O_MR1),
+        this::compatCodePresent);
+  }
+
+  @Test
+  public void testNoExplicitAssumeValuesRuleCompatUsed() throws Exception {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST));
+    runTest(
+        AndroidApiLevel.O,
+        AndroidApiLevel.O,
+        AndroidApiLevel.O_MR1,
+        expectedResultForCompat(AndroidApiLevel.O),
+        this::compatCodePresent);
+  }
+
+  @Test
+  public void testExplicitAssumeValuesRuleWhichMatchAndDontKeepCompat() throws Exception {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST));
+    runTest(
+        AndroidApiLevel.O_MR1,
+        AndroidApiLevel.O_MR1,
+        AndroidApiLevel.O_MR1,
+        expectedResultForNative(AndroidApiLevel.O_MR1),
+        this::compatCodeNotPresent,
+        ImmutableList.of(
+            "-assumevalues class android.os.Build$VERSION { public static final int SDK_INT return "
+            + AndroidApiLevel.O_MR1.getLevel() + "..1000; }"),
+        SynthesizedRule.NOT_PRESENT);
+  }
+
+  @Test
+  public void testExplicitAssumeValuesRulesWhichMatchAndKeepCompat() throws Exception {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST));
+    String[] rules = new String[] {
+        "-assumevalues class * { int SDK_INT return 1..1000; }",
+        "-assumevalues class * { % SDK_INT return 1..1000; }",
+        "-assumevalues class * { int * return 1..1000; }",
+        "-assumevalues class * extends java.lang.Object { int SDK_INT return 1..1000; }",
+        "-assumevalues class * { <fields>; }",
+        "-assumevalues class * { *; }"
+    };
+
+    for (String rule : rules) {
+      runTest(
+          AndroidApiLevel.O_MR1,
+          AndroidApiLevel.O_MR1,
+          AndroidApiLevel.O_MR1,
+          expectedResultForNative(AndroidApiLevel.O_MR1),
+          this::compatCodePresent,
+          ImmutableList.of(rule),
+          SynthesizedRule.NOT_PRESENT);
+    }
+  }
+
+  @Test
+  public void testExplicitAssumeValuesRulesWhichDoesNotMatch() throws Exception {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST));
+    String[] rules = new String[] {
+        "-assumevalues class * { !public int SDK_INT return 1..1000; }",
+        "-assumevalues class * { !static int SDK_INT return 1..1000; }",
+        "-assumevalues class * { !final int SDK_INT return 1..1000; }",
+        "-assumevalues class * { protected int SDK_INT return 1..1000; }",
+        "-assumevalues class * { private int SDK_INT return 1..1000; }"
+    };
+    for (String rule : rules) {
+      runTest(
+          AndroidApiLevel.O_MR1,
+          AndroidApiLevel.O_MR1,
+          AndroidApiLevel.O_MR1,
+          expectedResultForNative(AndroidApiLevel.O_MR1),
+          this::compatCodeNotPresent,
+          ImmutableList.of(rule),
+          SynthesizedRule.PRESENT);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
index e5f9799..ce9b84d 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
@@ -6,12 +6,12 @@
 
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 
 public abstract class FieldsTestBase extends TestBase {
 
diff --git a/tools/golem_build.py b/tools/golem_build.py
index d55def1..30d46bc 100755
--- a/tools/golem_build.py
+++ b/tools/golem_build.py
@@ -8,8 +8,8 @@
 import gradle
 import sys
 
-GRADLE_ARGS = ['--no-daemon', '-Pno_internal']
-BUILD_TARGETS = ['R8', 'D8', 'R8Lib', 'buildExampleJars', 'CompatDx',
+GRADLE_ARGS = ['--no-daemon']
+BUILD_TARGETS = ['R8', 'D8', 'R8LibApiOnly', 'buildExampleJars', 'CompatDx',
                  'downloadAndroidCts', 'downloadDx']
 
 def Main():