Add keep rule validation API

Bug: b/437139566

Change-Id: I5466949148999446cf1552ab4f33b2ecbf2e3055
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 49672a7..fa8a50c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -919,13 +919,16 @@
     private ProguardConfiguration makeConfiguration(DexItemFactory factory) {
       ProguardConfigurationParserOptions parserOptions =
           parserOptionsBuilder.setForceProguardCompatibility(forceProguardCompatibility).build();
+      ProguardConfiguration.Builder configurationBuilder =
+          ProguardConfiguration.builder(factory, getReporter())
+              .setForceProguardCompatibility(forceProguardCompatibility);
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(
-              factory, getReporter(), parserOptions, inputDependencyGraphConsumer);
-      ProguardConfiguration.Builder configurationBuilder =
-          parser
-              .getConfigurationBuilder()
-              .setForceProguardCompatibility(forceProguardCompatibility);
+              factory,
+              getReporter(),
+              parserOptions,
+              inputDependencyGraphConsumer,
+              configurationBuilder);
       if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
       }
@@ -948,7 +951,7 @@
       }
 
       // Add embedded keep rules.
-      amendWithRulesAndProvidersForInjarsAndMetaInf(getReporter(), parser);
+      amendWithRulesAndProvidersForInjarsAndMetaInf(getReporter(), parser, configurationBuilder);
 
       // Extract out rules for keep annotations and amend the configuration.
       // TODO(b/248408342): Remove this and parse annotations as part of R8 root-set & enqueuer.
@@ -959,7 +962,9 @@
     }
 
     private void amendWithRulesAndProvidersForInjarsAndMetaInf(
-        Reporter reporter, ProguardConfigurationParser parser) {
+        Reporter reporter,
+        ProguardConfigurationParser parser,
+        ProguardConfiguration.Builder configurationBuilder) {
 
       Supplier<SemanticVersion> semanticVersionSupplier =
           SemanticVersionUtils.compilerVersionSemanticVersionSupplier(
@@ -978,7 +983,7 @@
                   .map(ProgramResourceProvider::getDataResourceProvider)
                   .filter(Objects::nonNull)
                   .collect(Collectors.toList()));
-      for (FilteredClassPath injar : parser.getConfigurationBuilder().getInjars()) {
+      for (FilteredClassPath injar : configurationBuilder.getInjars()) {
         if (seen.add(injar)) {
           ArchiveResourceProvider provider = getAppBuilder().createAndAddProvider(injar);
           if (provider != null) {
@@ -999,8 +1004,7 @@
             .map(ClassFileResourceProvider::getDataResourceProvider)
             .filter(Objects::nonNull)
             .forEach(providers::add);
-        for (FilteredClassPath libraryjar :
-            parser.getConfigurationBuilder().build().getLibraryjars()) {
+        for (FilteredClassPath libraryjar : configurationBuilder.build().getLibraryjars()) {
           if (seen.add(libraryjar)) {
             ArchiveResourceProvider provider = getAppBuilder().createAndAddProvider(libraryjar);
             if (provider != null) {
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java
new file mode 100644
index 0000000..3921207
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java
@@ -0,0 +1,37 @@
+// 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.processkeeprules;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@KeepForApi
+public class GlobalLibraryConsumerRuleDiagnostic implements Diagnostic {
+  private final Origin origin;
+  private final Position position;
+  private final String rule;
+
+  public GlobalLibraryConsumerRuleDiagnostic(Origin origin, Position position, String rule) {
+    this.origin = origin;
+    this.position = position;
+    this.rule = rule;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return rule + " not allowed in library consumer rules.";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java
new file mode 100644
index 0000000..c7c1c1a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java
@@ -0,0 +1,39 @@
+// 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.processkeeprules;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@KeepForApi
+public class KeepAttributeLibraryConsumerRuleDiagnostic implements Diagnostic {
+
+  private final Origin origin;
+  private final Position position;
+  private final String attribute;
+
+  public KeepAttributeLibraryConsumerRuleDiagnostic(
+      Origin origin, Position position, String attribute) {
+    this.origin = origin;
+    this.position = position;
+    this.attribute = attribute;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Illegal attempt to keep the attribute '" + attribute + "' in library consumer rules.";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java
new file mode 100644
index 0000000..158894d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java
@@ -0,0 +1,31 @@
+// 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.processkeeprules;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer;
+import com.android.tools.r8.utils.ExceptionUtils;
+
+@KeepForApi
+public class ProcessKeepRules {
+  public static void run(ProcessKeepRulesCommand command) throws CompilationFailedException {
+    ExceptionUtils.withCompilationHandler(
+        command.getReporter(),
+        () -> {
+          // This is the only valid form of keep rule processing for now.
+          assert command.getValidateLibraryConsumerRules();
+          DexItemFactory dexItemFactory = new DexItemFactory();
+          ProguardConfigurationParserConsumer configurationConsumer =
+              new ValidateLibraryConsumerRulesKeepRuleProcessor(command.getReporter());
+          ProguardConfigurationParser parser =
+              new ProguardConfigurationParser(
+                  dexItemFactory, command.getReporter(), configurationConsumer);
+          parser.parse(command.getKeepRules());
+          command.getReporter().failIfPendingErrors();
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java
new file mode 100644
index 0000000..81f18f2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java
@@ -0,0 +1,104 @@
+// 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.processkeeprules;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.utils.Reporter;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@KeepForApi
+public class ProcessKeepRulesCommand {
+  private final Reporter reporter;
+  private final List<ProguardConfigurationSource> keepRules;
+  private final boolean validateLibraryConsumerRules;
+
+  private ProcessKeepRulesCommand(
+      Reporter reporter,
+      List<ProguardConfigurationSource> keepRules,
+      boolean validateLibraryConsumerRules) {
+    this.reporter = reporter;
+    this.keepRules = keepRules;
+    this.validateLibraryConsumerRules = validateLibraryConsumerRules;
+  }
+
+  /**
+   * Utility method for obtaining a <code>ProcessKeepRules.Builder</code> with a default diagnostics
+   * handler.
+   */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /**
+   * Utility method for obtaining a <code>ProcessKeepRules.Builder</code>.
+   *
+   * @param diagnosticsHandler The diagnostics handler for consuming messages.
+   */
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  Reporter getReporter() {
+    return reporter;
+  }
+
+  boolean getValidateLibraryConsumerRules() {
+    return validateLibraryConsumerRules;
+  }
+
+  List<ProguardConfigurationSource> getKeepRules() {
+    return keepRules;
+  }
+
+  @KeepForApi
+  public static class Builder {
+
+    private final Reporter reporter;
+    private List<ProguardConfigurationSource> keepRuleFiles = new ArrayList<>();
+    private boolean validateLibraryConsumerRules = false;
+
+    // TODO(b/447161121) introduce a DefaultDiagnosticHandler instead.
+    private Builder() {
+      this(new DiagnosticsHandler() {});
+    }
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.reporter = new Reporter(diagnosticsHandler);
+    }
+
+    /** Add proguard configuration-file resources. */
+    public Builder addKeepRuleFiles(Collection<Path> paths) {
+      for (Path path : paths) {
+        keepRuleFiles.add(new ProguardConfigurationSourceFile(path));
+      }
+      return this;
+    }
+
+    public Builder setLibraryConsumerRuleValidation(boolean enable) {
+      validateLibraryConsumerRules = enable;
+      return this;
+    }
+
+    public ProcessKeepRulesCommand build() {
+      validate();
+      return new ProcessKeepRulesCommand(reporter, keepRuleFiles, validateLibraryConsumerRules);
+    }
+
+    private void validate() {
+      if (keepRuleFiles.isEmpty()) {
+        reporter.error("No keep rule files provided.");
+      }
+      if (!validateLibraryConsumerRules) {
+        reporter.error("No rule validation enabled.");
+      }
+      reporter.failIfPendingErrors();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
new file mode 100644
index 0000000..371e1cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
@@ -0,0 +1,188 @@
+// 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.processkeeprules;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.shaking.ProguardPathList;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.Reporter;
+import java.nio.file.Path;
+import java.util.List;
+
+class ValidateLibraryConsumerRulesKeepRuleProcessor implements ProguardConfigurationParserConsumer {
+  private final Reporter reporter;
+
+  public ValidateLibraryConsumerRulesKeepRuleProcessor(Reporter reporter) {
+    super();
+    this.reporter = reporter;
+  }
+
+  private void handleGlobalRule(Origin origin, Position position, String rule) {
+    reporter.error(new GlobalLibraryConsumerRuleDiagnostic(origin, position, rule));
+  }
+
+  private void handleKeepAttribute(Origin origin, Position position, String attribute) {
+    reporter.error(new KeepAttributeLibraryConsumerRuleDiagnostic(origin, position, attribute));
+  }
+
+  @Override
+  public void disableOptimization(Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-dontoptimize");
+  }
+
+  @Override
+  public void disableObfuscation(Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-dontobfuscate");
+  }
+
+  @Override
+  public void disableShrinking(Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-dontshrink");
+  }
+
+  @Override
+  public void enableRepackageClasses(Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-repackageclasses");
+  }
+
+  @Override
+  public void enableFlattenPackageHierarchy(Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-flattenpackagehierarchy");
+  }
+
+  @Override
+  public void enableAllowAccessModification(Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-allowaccessmodification");
+  }
+
+  @Override
+  public void setRenameSourceFileAttribute(String s, Origin origin, Position position) {
+    handleGlobalRule(origin, position, "-renamesourcefileattribute");
+  }
+
+  @Override
+  public void addParsedConfiguration(String s) {}
+
+  @Override
+  public void addRule(ProguardConfigurationRule rule) {}
+
+  @Override
+  public void addKeepAttributePatterns(
+      List<String> attributesPatterns, Origin origin, Position position) {
+    // TODO(b/270289387): Add support for more attributes.
+    ProguardKeepAttributes keepAttributes = ProguardKeepAttributes.fromPatterns(attributesPatterns);
+    if (keepAttributes.lineNumberTable) {
+      handleKeepAttribute(origin, position, ProguardKeepAttributes.LINE_NUMBER_TABLE);
+    }
+    if (keepAttributes.runtimeInvisibleAnnotations) {
+      handleKeepAttribute(origin, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS);
+    }
+    if (keepAttributes.runtimeInvisibleTypeAnnotations) {
+      handleKeepAttribute(
+          origin, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+    }
+    if (keepAttributes.runtimeInvisibleParameterAnnotations) {
+      handleKeepAttribute(
+          origin, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+    }
+    if (keepAttributes.sourceFile) {
+      handleKeepAttribute(origin, position, ProguardKeepAttributes.SOURCE_FILE);
+    }
+  }
+
+  @Override
+  public void addKeepPackageNamesPattern(ProguardClassNameList proguardClassNameList) {}
+
+  @Override
+  public void setKeepParameterNames(boolean b, Origin origin, Position position) {}
+
+  @Override
+  public void enableKeepDirectories() {}
+
+  @Override
+  public void addKeepDirectories(ProguardPathList proguardPathList) {}
+
+  @Override
+  public void setPrintUsage(boolean b) {}
+
+  @Override
+  public void setPrintUsageFile(Path path) {}
+
+  @Override
+  public void enableProtoShrinking() {}
+
+  @Override
+  public void setIgnoreWarnings(boolean b) {}
+
+  @Override
+  public void addDontWarnPattern(ProguardClassNameList pattern) {}
+
+  @Override
+  public void addDontNotePattern(ProguardClassNameList pattern) {}
+
+  @Override
+  public void setPrintConfiguration(boolean b) {}
+
+  @Override
+  public void setPrintConfigurationFile(Path path) {}
+
+  @Override
+  public void setPrintMapping(boolean b) {}
+
+  @Override
+  public void setPrintMappingFile(Path path) {}
+
+  @Override
+  public void setApplyMappingFile(Path path) {}
+
+  @Override
+  public void addInjars(List<FilteredClassPath> filteredClassPaths) {}
+
+  @Override
+  public void addLibraryJars(List<FilteredClassPath> filteredClassPaths) {}
+
+  @Override
+  public void setPrintSeeds(boolean b) {}
+
+  @Override
+  public void setSeedFile(Path path) {}
+
+  @Override
+  public void setObfuscationDictionary(Path path) {}
+
+  @Override
+  public void setClassObfuscationDictionary(Path path) {}
+
+  @Override
+  public void setPackageObfuscationDictionary(Path path) {}
+
+  @Override
+  public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {}
+
+  @Override
+  public void addAdaptResourceFileContents(ProguardPathList pattern) {}
+
+  @Override
+  public void addAdaptResourceFilenames(ProguardPathList pattern) {}
+
+  @Override
+  public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {}
+
+  @Override
+  public PackageObfuscationMode getPackageObfuscationMode() {
+    return null;
+  }
+
+  @Override
+  public void setPackagePrefix(String s) {}
+
+  @Override
+  public void setFlattenPackagePrefix(String s) {}
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
index 312bead..a4c6d4b 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -152,7 +152,8 @@
                 .disableObfuscation()
                 .disableOptimization()
                 .addKeepAttributePatterns(ImmutableList.of("*"))
-                .addAdaptResourceFilenames(ProguardPathList.builder().addFileName("**").build())
+                .applyAdaptResourceFilenamesBuilder(
+                    b -> b.addPattern(ProguardPathList.builder().addFileName("**").build()))
                 .build(),
             getReporter());
     options.relocatorCompilation = true;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 6e09df6..2d4e353 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -18,10 +18,11 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class ProguardConfiguration {
 
-  public static class Builder {
+  public static class Builder implements ProguardConfigurationParserConsumer {
 
     private final List<String> parsedConfiguration = new ArrayList<>();
     private final List<FilteredClassPath> injars = new ArrayList<>();
@@ -29,8 +30,6 @@
     private final List<FilteredClassPath> libraryJars = new ArrayList<>();
 
     private final Reporter reporter;
-    private PackageObfuscationMode packageObfuscationMode = PackageObfuscationMode.NONE;
-    private String packagePrefix = "";
     private boolean allowAccessModification;
     private boolean ignoreWarnings;
     private boolean optimizing = true;
@@ -71,6 +70,8 @@
     private boolean forceProguardCompatibility = false;
     private boolean protoShrinking = false;
     private int maxRemovedAndroidLogLevel = MaximumRemovedAndroidLogLevelRule.NOT_SET;
+    PackageObfuscationMode packageObfuscationMode = PackageObfuscationMode.NONE;
+    String packagePrefix = "";
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -81,40 +82,46 @@
       return injars;
     }
 
+    @Override
     public void addParsedConfiguration(String source) {
       parsedConfiguration.add(source);
     }
 
+    @Override
     public void addInjars(List<FilteredClassPath> injars) {
       this.injars.addAll(injars);
     }
 
+    @Override
     public void addLibraryJars(List<FilteredClassPath> libraryJars) {
       this.libraryJars.addAll(libraryJars);
     }
 
-    public PackageObfuscationMode getPackageObfuscationMode() {
-      return packageObfuscationMode;
+    @Override
+    public void enableAllowAccessModification(Origin origin, Position position) {
+      this.allowAccessModification = true;
     }
 
-    public void setPackagePrefix(String packagePrefix) {
-      packageObfuscationMode = PackageObfuscationMode.REPACKAGE;
-      this.packagePrefix = packagePrefix;
-    }
-
-    public void setFlattenPackagePrefix(String packagePrefix) {
-      packageObfuscationMode = PackageObfuscationMode.FLATTEN;
-      this.packagePrefix = packagePrefix;
-    }
-
-    public void setAllowAccessModification(boolean allowAccessModification) {
-      this.allowAccessModification = allowAccessModification;
-    }
-
+    @Override
     public void setIgnoreWarnings(boolean ignoreWarnings) {
       this.ignoreWarnings = ignoreWarnings;
     }
 
+    @Override
+    public void disableOptimization(Origin origin, Position position) {
+      this.optimizing = false;
+    }
+
+    @Override
+    public void disableObfuscation(Origin origin, Position position) {
+      this.obfuscating = false;
+    }
+
+    @Override
+    public void disableShrinking(Origin origin, Position position) {
+      this.shrinking = false;
+    }
+
     public Builder disableOptimization() {
       this.optimizing = false;
       return this;
@@ -146,32 +153,39 @@
       return this;
     }
 
+    @Override
     public void setPrintConfiguration(boolean printConfiguration) {
       this.printConfiguration = printConfiguration;
     }
 
+    @Override
     public void setPrintConfigurationFile(Path file) {
       assert printConfiguration;
       this.printConfigurationFile = file;
     }
 
+    @Override
     public void setPrintUsage(boolean printUsage) {
       this.printUsage = printUsage;
     }
 
+    @Override
     public void setPrintUsageFile(Path printUsageFile) {
       this.printUsageFile = printUsageFile;
     }
 
+    @Override
     public void setPrintMapping(boolean printMapping) {
       this.printMapping = printMapping;
     }
 
+    @Override
     public void setPrintMappingFile(Path file) {
       assert printMapping;
       this.printMappingFile = file;
     }
 
+    @Override
     public void setApplyMappingFile(Path file) {
       this.applyMappingFile = file;
     }
@@ -180,53 +194,113 @@
       return applyMappingFile != null;
     }
 
-    public void setRenameSourceFileAttribute(String renameSourceFileAttribute) {
+    @Override
+    public void setRenameSourceFileAttribute(
+        String renameSourceFileAttribute, Origin origin, Position position) {
       this.renameSourceFileAttribute = renameSourceFileAttribute;
     }
 
+    @Override
+    public void addKeepAttributePatterns(
+        List<String> keepAttributePatterns, Origin origin, Position position) {
+      this.keepAttributePatterns.addAll(keepAttributePatterns);
+    }
+
     public Builder addKeepAttributePatterns(List<String> keepAttributePatterns) {
       this.keepAttributePatterns.addAll(keepAttributePatterns);
       return this;
     }
 
+    @Override
     public void addRule(ProguardConfigurationRule rule) {
       this.rules.add(rule);
     }
 
+    @Override
     public void addKeepPackageNamesPattern(ProguardClassNameList pattern) {
       keepPackageNamesPatterns.addPattern(pattern);
     }
 
+    @Override
+    public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+      assert maxRemovedAndroidLogLevel >= MaximumRemovedAndroidLogLevelRule.NONE;
+      if (this.maxRemovedAndroidLogLevel == MaximumRemovedAndroidLogLevelRule.NOT_SET) {
+        this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+      } else {
+        // If there are multiple -maximumremovedandroidloglevel rules we only allow removing logging
+        // calls that are removable according to all rules.
+        this.maxRemovedAndroidLogLevel =
+            Math.min(this.maxRemovedAndroidLogLevel, maxRemovedAndroidLogLevel);
+      }
+    }
+
+    public int getMaxRemovedAndroidLogLevel() {
+      return maxRemovedAndroidLogLevel;
+    }
+
+    @Override
+    public PackageObfuscationMode getPackageObfuscationMode() {
+      return packageObfuscationMode;
+    }
+
+    @Override
+    public void setPackagePrefix(String packagePrefix) {
+      this.packagePrefix = packagePrefix;
+    }
+
+    @Override
+    public void setFlattenPackagePrefix(String packagePrefix) {
+      this.packagePrefix = packagePrefix;
+    }
+
+    @Override
+    public void enableFlattenPackageHierarchy(Origin origin, Position position) {
+      packageObfuscationMode = PackageObfuscationMode.FLATTEN;
+    }
+
+    @Override
+    public void enableRepackageClasses(Origin origin, Position position) {
+      packageObfuscationMode = PackageObfuscationMode.REPACKAGE;
+    }
+
+    @Override
     public void addDontWarnPattern(ProguardClassNameList pattern) {
       dontWarnPatterns.addPattern(pattern);
     }
 
+    @Override
     public void addDontNotePattern(ProguardClassNameList pattern) {
       dontNotePatterns.addPattern(pattern);
     }
 
+    @Override
     public void setSeedFile(Path seedFile) {
       this.seedFile = seedFile;
     }
 
+    @Override
     public void setPrintSeeds(boolean printSeeds) {
       this.printSeeds = printSeeds;
     }
 
+    @Override
     public void setObfuscationDictionary(Path obfuscationDictionary) {
       this.obfuscationDictionary = obfuscationDictionary;
     }
 
+    @Override
     public void setClassObfuscationDictionary(Path classObfuscationDictionary) {
       this.classObfuscationDictionary = classObfuscationDictionary;
     }
 
+    @Override
     public void setPackageObfuscationDictionary(Path packageObfuscationDictionary) {
       this.packageObfuscationDictionary = packageObfuscationDictionary;
     }
 
-    public void setKeepParameterNames(boolean keepParameterNames, Origin optionOrigin,
-        Position optionPosition) {
+    @Override
+    public void setKeepParameterNames(
+        boolean keepParameterNames, Origin optionOrigin, Position optionPosition) {
       assert optionOrigin != null || !keepParameterNames;
       this.keepParameterNames = keepParameterNames;
       this.keepParameterNamesOptionOrigin = optionOrigin;
@@ -245,23 +319,33 @@
       return keepParameterNamesOptionPosition;
     }
 
+    @Override
     public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {
       adaptClassStrings.addPattern(pattern);
     }
 
-    public Builder addAdaptResourceFilenames(ProguardPathList pattern) {
+    @Override
+    public void addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
+    }
+
+    public Builder applyAdaptResourceFilenamesBuilder(
+        Consumer<ProguardPathFilter.Builder> consumer) {
+      consumer.accept(adaptResourceFilenames);
       return this;
     }
 
+    @Override
     public void addAdaptResourceFileContents(ProguardPathList pattern) {
       adaptResourceFileContents.addPattern(pattern);
     }
 
+    @Override
     public void enableKeepDirectories() {
       keepDirectories.enable();
     }
 
+    @Override
     public void addKeepDirectories(ProguardPathList pattern) {
       keepDirectories.addPattern(pattern);
     }
@@ -275,26 +359,11 @@
       return this;
     }
 
+    @Override
     public void enableProtoShrinking() {
       protoShrinking = true;
     }
 
-    public int getMaxRemovedAndroidLogLevel() {
-      return maxRemovedAndroidLogLevel;
-    }
-
-    public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
-      assert maxRemovedAndroidLogLevel >= MaximumRemovedAndroidLogLevelRule.NONE;
-      if (this.maxRemovedAndroidLogLevel == MaximumRemovedAndroidLogLevelRule.NOT_SET) {
-        this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
-      } else {
-        // If there are multiple -maximumremovedandroidloglevel rules we only allow removing logging
-        // calls that are removable according to all rules.
-        this.maxRemovedAndroidLogLevel =
-            Math.min(this.maxRemovedAndroidLogLevel, maxRemovedAndroidLogLevel);
-      }
-    }
-
     public ProguardConfiguration buildRaw() {
       ProguardKeepAttributes proguardKeepAttributes =
           ProguardKeepAttributes.fromPatterns(keepAttributePatterns);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 33f0bd5..0f505fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -49,7 +49,7 @@
 
 public class ProguardConfigurationParser {
 
-  private final ProguardConfiguration.Builder configurationBuilder;
+  private final ProguardConfigurationParserConsumer configurationConsumer;
 
   private final DexItemFactory dexItemFactory;
   private final ProguardConfigurationParserOptions options;
@@ -129,7 +129,7 @@
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory,
       Reporter reporter,
-      ProguardConfiguration.Builder configurationBuilder) {
+      ProguardConfigurationParserConsumer configurationConsumer) {
     this(
         dexItemFactory,
         reporter,
@@ -141,7 +141,7 @@
             .setEnableTestingOptions(false)
             .build(),
         null,
-        configurationBuilder);
+        configurationConsumer);
   }
 
   public ProguardConfigurationParser(
@@ -169,8 +169,8 @@
       Reporter reporter,
       ProguardConfigurationParserOptions options,
       InputDependencyGraphConsumer inputDependencyConsumer,
-      ProguardConfiguration.Builder configurationBuilder) {
-    this.configurationBuilder = configurationBuilder;
+      ProguardConfigurationParserConsumer configurationConsumer) {
+    this.configurationConsumer = configurationConsumer;
     this.dexItemFactory = dexItemFactory;
     this.options = options;
     this.reporter = reporter;
@@ -194,10 +194,6 @@
     };
   }
 
-  public ProguardConfiguration.Builder getConfigurationBuilder() {
-    return configurationBuilder;
-  }
-
   public void parse(Path path) {
     parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
   }
@@ -250,12 +246,12 @@
       } while (parseOption());
       // This may be unknown, but we want to always ensure that we don't attribute lines to the
       // wrong configuration.
-      configurationBuilder.addParsedConfiguration(
+      configurationConsumer.addParsedConfiguration(
           "# The proguard configuration file for the following section is " + origin.toString());
 
       // Collect the parsed configuration.
-      configurationBuilder.addParsedConfiguration(contents.substring(positionAfterInclude));
-      configurationBuilder.addParsedConfiguration("# End of content from " + origin);
+      configurationConsumer.addParsedConfiguration(contents.substring(positionAfterInclude));
+      configurationConsumer.addParsedConfiguration("# End of content from " + origin);
       reporter.failIfPendingErrors();
     }
 
@@ -286,44 +282,47 @@
         // is not present.
         keepKotlinMetadata.markAsUsed();
         keepKotlinJvmNameAnnotation.markAsUsed();
-        configurationBuilder.addRule(keepKotlinMetadata);
-        configurationBuilder.addRule(keepKotlinJvmNameAnnotation);
-        configurationBuilder.addKeepAttributePatterns(
-            Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS));
-        configurationBuilder.addKeepAttributePatterns(
-            Collections.singletonList(RUNTIME_INVISIBLE_ANNOTATIONS));
+        configurationConsumer.addRule(keepKotlinMetadata);
+        configurationConsumer.addRule(keepKotlinJvmNameAnnotation);
+        configurationConsumer.addKeepAttributePatterns(
+            Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS),
+            origin,
+            getPosition(optionStart));
+        configurationConsumer.addKeepAttributePatterns(
+            Collections.singletonList(RUNTIME_INVISIBLE_ANNOTATIONS),
+            origin,
+            getPosition(optionStart));
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
-        if (isOptionalArgumentGiven()) {
-          configurationBuilder.setRenameSourceFileAttribute(acceptQuotedOrUnquotedString());
-        } else {
-          configurationBuilder.setRenameSourceFileAttribute("");
-        }
+        String renameSourceFileAttribute =
+            isOptionalArgumentGiven() ? acceptQuotedOrUnquotedString() : "";
+        configurationConsumer.setRenameSourceFileAttribute(
+            renameSourceFileAttribute, origin, getPosition(optionStart));
       } else if (acceptString("keepattributes")) {
-        parseKeepAttributes();
+        parseKeepAttributes(getPosition(optionStart));
       } else if (acceptString("keeppackagenames")) {
-        parseClassFilter(configurationBuilder::addKeepPackageNamesPattern);
+        parseClassFilter(configurationConsumer::addKeepPackageNamesPattern);
       } else if (acceptString("keepparameternames")) {
-        configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart));
+        configurationConsumer.setKeepParameterNames(true, origin, getPosition(optionStart));
       } else if (acceptString("checkdiscard")) {
         ProguardCheckDiscardRule rule =
             parseRuleWithClassSpec(optionStart, ProguardCheckDiscardRule.builder());
-        configurationBuilder.addRule(rule);
+        configurationConsumer.addRule(rule);
       } else if (acceptString("checkenumstringsdiscarded")) {
         // Not supported, ignore.
         parseRuleWithClassSpec(optionStart, ProguardCheckDiscardRule.builder());
       } else if (acceptString("keepdirectories")) {
-        configurationBuilder.enableKeepDirectories();
-        parsePathFilter(configurationBuilder::addKeepDirectories);
+        configurationConsumer.enableKeepDirectories();
+        parsePathFilter(configurationConsumer::addKeepDirectories);
       } else if (acceptString("keep")) {
         ProguardKeepRule rule = parseKeepRule(optionStart);
-        configurationBuilder.addRule(rule);
+        configurationConsumer.addRule(rule);
       } else if (acceptString("whyareyoukeeping")) {
         ProguardWhyAreYouKeepingRule rule =
             parseRuleWithClassSpec(optionStart, ProguardWhyAreYouKeepingRule.builder());
-        configurationBuilder.addRule(rule);
+        configurationConsumer.addRule(rule);
       } else if (acceptString("dontoptimize")) {
-        configurationBuilder.disableOptimization();
+        configurationConsumer.disableOptimization(origin, getPosition(optionStart));
       } else if (acceptString("optimizationpasses")) {
         skipWhitespace();
         Integer expectedOptimizationPasses = acceptInteger();
@@ -333,41 +332,42 @@
         }
         infoIgnoringOptions("optimizationpasses", optionStart);
       } else if (acceptString("dontobfuscate")) {
-        configurationBuilder.disableObfuscation();
+        configurationConsumer.disableObfuscation(origin, getPosition(optionStart));
       } else if (acceptString("dontshrink")) {
-        configurationBuilder.disableShrinking();
+        configurationConsumer.disableShrinking(origin, getPosition(optionStart));
       } else if (acceptString("printusage")) {
-        configurationBuilder.setPrintUsage(true);
+        configurationConsumer.setPrintUsage(true);
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.setPrintUsageFile(parseFileName(false));
+          configurationConsumer.setPrintUsageFile(parseFileName(false));
         }
       } else if (acceptString("shrinkunusedprotofields")) {
-        configurationBuilder.enableProtoShrinking();
+        configurationConsumer.enableProtoShrinking();
       } else if (acceptString("ignorewarnings")) {
-        configurationBuilder.setIgnoreWarnings(true);
+        configurationConsumer.setIgnoreWarnings(true);
       } else if (acceptString("dontwarn")) {
-        parseClassFilter(configurationBuilder::addDontWarnPattern);
+        parseClassFilter(configurationConsumer::addDontWarnPattern);
       } else if (acceptString("dontnote")) {
-        parseClassFilter(configurationBuilder::addDontNotePattern);
+        parseClassFilter(configurationConsumer::addDontNotePattern);
       } else if (acceptString(REPACKAGE_CLASSES)) {
-        if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
+        if (configurationConsumer.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
           warnOverridingOptions(REPACKAGE_CLASSES, FLATTEN_PACKAGE_HIERARCHY, optionStart);
         }
         skipWhitespace();
         char quote = acceptQuoteIfPresent();
         if (isQuote(quote)) {
-          configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
+          configurationConsumer.setPackagePrefix(parsePackageNameOrEmptyString());
           expectClosingQuote(quote);
         } else {
           if (hasNextChar('-')) {
-            configurationBuilder.setPackagePrefix("");
+            configurationConsumer.setPackagePrefix("");
           } else {
-            configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
+            configurationConsumer.setPackagePrefix(parsePackageNameOrEmptyString());
           }
         }
+        configurationConsumer.enableRepackageClasses(origin, getPosition(optionStart));
       } else if (acceptString(FLATTEN_PACKAGE_HIERARCHY)) {
-        if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
+        if (configurationConsumer.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
           warnOverridingOptions(REPACKAGE_CLASSES, FLATTEN_PACKAGE_HIERARCHY, optionStart);
           skipWhitespace();
           if (isOptionalArgumentGiven()) {
@@ -377,42 +377,43 @@
           skipWhitespace();
           char quote = acceptQuoteIfPresent();
           if (isQuote(quote)) {
-            configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
+            configurationConsumer.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
             expectClosingQuote(quote);
           } else {
             if (hasNextChar('-')) {
-              configurationBuilder.setFlattenPackagePrefix("");
+              configurationConsumer.setFlattenPackagePrefix("");
             } else {
-              configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
+              configurationConsumer.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
             }
           }
+          configurationConsumer.enableFlattenPackageHierarchy(origin, getPosition(optionStart));
         }
       } else if (acceptString("allowaccessmodification")) {
-        configurationBuilder.setAllowAccessModification(true);
+        configurationConsumer.enableAllowAccessModification(origin, getPosition(optionStart));
       } else if (acceptString("printconfiguration")) {
-        configurationBuilder.setPrintConfiguration(true);
+        configurationConsumer.setPrintConfiguration(true);
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.setPrintConfigurationFile(parseFileName(false));
+          configurationConsumer.setPrintConfigurationFile(parseFileName(false));
         }
       } else if (acceptString("printmapping")) {
-        configurationBuilder.setPrintMapping(true);
+        configurationConsumer.setPrintMapping(true);
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.setPrintMappingFile(parseFileName(false));
+          configurationConsumer.setPrintMappingFile(parseFileName(false));
         }
       } else if (acceptString("applymapping")) {
-        configurationBuilder.setApplyMappingFile(
+        configurationConsumer.setApplyMappingFile(
             parseFileInputDependency(inputDependencyConsumer::acceptProguardApplyMapping));
       } else if (acceptString("assumenosideeffects")) {
         ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart);
-        configurationBuilder.addRule(rule);
+        configurationConsumer.addRule(rule);
       } else if (acceptString("assumevalues")) {
         ProguardAssumeValuesRule rule = parseAssumeValuesRule(optionStart);
-        configurationBuilder.addRule(rule);
+        configurationConsumer.addRule(rule);
       } else if (acceptString("include")) {
         // Collect the parsed configuration until the include.
-        configurationBuilder.addParsedConfiguration(
+        configurationConsumer.addParsedConfiguration(
             contents.substring(positionAfterInclude, position - ("include".length() + 1)));
         skipWhitespace();
         parseInclude();
@@ -421,44 +422,44 @@
         skipWhitespace();
         baseDirectory = parseFileName(false);
       } else if (acceptString("injars")) {
-        configurationBuilder.addInjars(
+        configurationConsumer.addInjars(
             parseClassPath(inputDependencyConsumer::acceptProguardInJars));
       } else if (acceptString("libraryjars")) {
-        configurationBuilder.addLibraryJars(
+        configurationConsumer.addLibraryJars(
             parseClassPath(inputDependencyConsumer::acceptProguardLibraryJars));
       } else if (acceptString("printseeds")) {
-        configurationBuilder.setPrintSeeds(true);
+        configurationConsumer.setPrintSeeds(true);
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.setSeedFile(parseFileName(false));
+          configurationConsumer.setSeedFile(parseFileName(false));
         }
       } else if (acceptString("obfuscationdictionary")) {
-        configurationBuilder.setObfuscationDictionary(
+        configurationConsumer.setObfuscationDictionary(
             parseFileInputDependency(inputDependencyConsumer::acceptProguardObfuscationDictionary));
       } else if (acceptString("classobfuscationdictionary")) {
-        configurationBuilder.setClassObfuscationDictionary(
+        configurationConsumer.setClassObfuscationDictionary(
             parseFileInputDependency(
                 inputDependencyConsumer::acceptProguardClassObfuscationDictionary));
       } else if (acceptString("packageobfuscationdictionary")) {
-        configurationBuilder.setPackageObfuscationDictionary(
+        configurationConsumer.setPackageObfuscationDictionary(
             parseFileInputDependency(
                 inputDependencyConsumer::acceptProguardPackageObfuscationDictionary));
       } else if (acceptString("alwaysinline")) {
         InlineRule rule =
             parseRuleWithClassSpec(
                 optionStart, InlineRule.builder().setType(InlineRuleType.ALWAYS));
-        configurationBuilder.addRule(rule);
+        configurationConsumer.addRule(rule);
       } else if (acceptString("adaptclassstrings")) {
-        parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
+        parseClassFilter(configurationConsumer::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
-        parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
+        parsePathFilter(configurationConsumer::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
-        parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
+        parsePathFilter(configurationConsumer::addAdaptResourceFileContents);
       } else if (acceptString("identifiernamestring")) {
-        configurationBuilder.addRule(
+        configurationConsumer.addRule(
             parseRuleWithClassSpec(optionStart, ProguardIdentifierNameStringRule.builder()));
       } else if (acceptString("if")) {
-        configurationBuilder.addRule(parseIfRule(optionStart));
+        configurationConsumer.addRule(parseIfRule(optionStart));
       } else if (parseMaximumRemovedAndroidLogLevelRule(optionStart)) {
         return true;
       } else {
@@ -479,20 +480,20 @@
       if (acceptString(CheckEnumUnboxedRule.RULE_NAME)) {
         CheckEnumUnboxedRule checkEnumUnboxedRule = parseCheckEnumUnboxedRule(optionStart);
         if (options.isExperimentalCheckEnumUnboxedEnabled()) {
-          configurationBuilder.addRule(checkEnumUnboxedRule);
+          configurationConsumer.addRule(checkEnumUnboxedRule);
         }
         return true;
       }
       if (acceptString(ConvertCheckNotNullRule.RULE_NAME)) {
         ConvertCheckNotNullRule convertCheckNotNullRule = parseConvertCheckNotNullRule(optionStart);
         if (options.isExperimentalConvertCheckNotNullEnabled()) {
-          configurationBuilder.addRule(convertCheckNotNullRule);
+          configurationConsumer.addRule(convertCheckNotNullRule);
         }
         return true;
       }
       if (options.isExperimentalWhyAreYouNotInliningEnabled()) {
         if (acceptString(WhyAreYouNotInliningRule.RULE_NAME)) {
-          configurationBuilder.addRule(
+          configurationConsumer.addRule(
               parseRuleWithClassSpec(optionStart, WhyAreYouNotInliningRule.builder()));
           return true;
         }
@@ -506,123 +507,123 @@
         if (acceptString("assumemayhavesideeffects")) {
           ProguardAssumeMayHaveSideEffectsRule rule =
               parseAssumeMayHaveSideEffectsRule(optionStart);
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(KeepConstantArgumentRule.RULE_NAME)) {
           KeepConstantArgumentRule rule =
               parseRuleWithClassSpec(optionStart, KeepConstantArgumentRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(KeepUnusedArgumentRule.RULE_NAME)) {
           KeepUnusedArgumentRule rule =
               parseRuleWithClassSpec(optionStart, KeepUnusedArgumentRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(KeepUnusedReturnValueRule.RULE_NAME)) {
           KeepUnusedReturnValueRule rule =
               parseRuleWithClassSpec(optionStart, KeepUnusedReturnValueRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString("alwaysclassinline")) {
           ClassInlineRule rule =
               parseRuleWithClassSpec(
                   optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.ALWAYS));
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString("neverclassinline")) {
           ClassInlineRule rule =
               parseRuleWithClassSpec(
                   optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.NEVER));
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString("neverinline")) {
           InlineRule rule =
               parseRuleWithClassSpec(
                   optionStart, InlineRule.builder().setType(InlineRuleType.NEVER));
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString("neversinglecallerinline")) {
           InlineRule rule =
               parseRuleWithClassSpec(
                   optionStart, InlineRule.builder().setType(InlineRuleType.NEVER_SINGLE_CALLER));
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoAccessModificationRule.RULE_NAME)) {
           NoAccessModificationRule rule =
               parseRuleWithClassSpec(optionStart, NoAccessModificationRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoFieldTypeStrengtheningRule.RULE_NAME)) {
           NoFieldTypeStrengtheningRule rule =
               parseRuleWithClassSpec(optionStart, NoFieldTypeStrengtheningRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
           NoUnusedInterfaceRemovalRule rule =
               parseRuleWithClassSpec(optionStart, NoUnusedInterfaceRemovalRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) {
           NoVerticalClassMergingRule rule =
               parseRuleWithClassSpec(optionStart, NoVerticalClassMergingRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoHorizontalClassMergingRule.RULE_NAME)) {
           NoHorizontalClassMergingRule rule =
               parseRuleWithClassSpec(optionStart, NoHorizontalClassMergingRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoMethodStaticizingRule.RULE_NAME)) {
           NoMethodStaticizingRule rule =
               parseRuleWithClassSpec(optionStart, NoMethodStaticizingRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoParameterReorderingRule.RULE_NAME)) {
           NoParameterReorderingRule rule =
               parseRuleWithClassSpec(optionStart, NoParameterReorderingRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoParameterTypeStrengtheningRule.RULE_NAME)) {
           NoParameterTypeStrengtheningRule rule =
               parseRuleWithClassSpec(optionStart, NoParameterTypeStrengtheningRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoRedundantFieldLoadEliminationRule.RULE_NAME)) {
           NoRedundantFieldLoadEliminationRule rule =
               parseRuleWithClassSpec(optionStart, NoRedundantFieldLoadEliminationRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString(NoReturnTypeStrengtheningRule.RULE_NAME)) {
           NoReturnTypeStrengtheningRule rule =
               parseRuleWithClassSpec(optionStart, NoReturnTypeStrengtheningRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString("neverpropagatevalue")) {
           NoValuePropagationRule rule =
               parseRuleWithClassSpec(optionStart, NoValuePropagationRule.builder());
-          configurationBuilder.addRule(rule);
+          configurationConsumer.addRule(rule);
           return true;
         }
         if (acceptString("neverreprocessclassinitializer")) {
-          configurationBuilder.addRule(
+          configurationConsumer.addRule(
               parseRuleWithClassSpec(
                   optionStart,
                   ReprocessClassInitializerRule.builder()
@@ -630,14 +631,14 @@
           return true;
         }
         if (acceptString("neverreprocessmethod")) {
-          configurationBuilder.addRule(
+          configurationConsumer.addRule(
               parseRuleWithClassSpec(
                   optionStart,
                   ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.NEVER)));
           return true;
         }
         if (acceptString("reprocessclassinitializer")) {
-          configurationBuilder.addRule(
+          configurationConsumer.addRule(
               parseRuleWithClassSpec(
                   optionStart,
                   ReprocessClassInitializerRule.builder()
@@ -645,7 +646,7 @@
           return true;
         }
         if (acceptString("reprocessmethod")) {
-          configurationBuilder.addRule(
+          configurationConsumer.addRule(
               parseRuleWithClassSpec(
                   optionStart,
                   ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.ALWAYS)));
@@ -734,7 +735,7 @@
       return true;
     }
 
-    private void parseKeepAttributes() throws ProguardRuleParserException {
+    private void parseKeepAttributes(Position position) throws ProguardRuleParserException {
       List<String> attributesPatterns = acceptKeepAttributesPatternList();
       if (attributesPatterns.isEmpty()) {
         throw parseError("Expected attribute pattern list");
@@ -752,7 +753,7 @@
                       + ")"));
         }
       }
-      configurationBuilder.addKeepAttributePatterns(attributesPatterns);
+      configurationConsumer.addKeepAttributePatterns(attributesPatterns, origin, position);
     }
 
     private boolean skipFlag(String name) {
@@ -929,10 +930,10 @@
         }
         if (builder.hasClassType()) {
           Position end = getPosition();
-          configurationBuilder.addRule(
+          configurationConsumer.addRule(
               builder.setEnd(end).setSource(getSourceSnippet(contents, start, end)).build());
         } else {
-          configurationBuilder.joinMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
+          configurationConsumer.joinMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
         }
         return true;
       }
@@ -2307,4 +2308,4 @@
       this.negated = negated;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
new file mode 100644
index 0000000..f0fbdd6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
@@ -0,0 +1,91 @@
+// 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.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import java.nio.file.Path;
+import java.util.List;
+
+public interface ProguardConfigurationParserConsumer {
+
+  void addParsedConfiguration(String s);
+
+  void addRule(ProguardConfigurationRule rule);
+
+  void addKeepAttributePatterns(List<String> attributesPatterns, Origin origin, Position position);
+
+  void setRenameSourceFileAttribute(String s, Origin origin, Position position);
+
+  void addKeepPackageNamesPattern(ProguardClassNameList proguardClassNameList);
+
+  void setKeepParameterNames(boolean b, Origin origin, Position position);
+
+  void enableKeepDirectories();
+
+  void addKeepDirectories(ProguardPathList proguardPathList);
+
+  void disableOptimization(Origin origin, Position position);
+
+  void disableObfuscation(Origin origin, Position position);
+
+  void disableShrinking(Origin origin, Position position);
+
+  void setPrintUsage(boolean b);
+
+  void setPrintUsageFile(Path path);
+
+  void enableProtoShrinking();
+
+  void setIgnoreWarnings(boolean b);
+
+  void addDontWarnPattern(ProguardClassNameList pattern);
+
+  void addDontNotePattern(ProguardClassNameList pattern);
+
+  void enableAllowAccessModification(Origin origin, Position position);
+
+  void setPrintConfiguration(boolean b);
+
+  void setPrintConfigurationFile(Path path);
+
+  void setPrintMapping(boolean b);
+
+  void setPrintMappingFile(Path path);
+
+  void setApplyMappingFile(Path path);
+
+  void addInjars(List<FilteredClassPath> filteredClassPaths);
+
+  void addLibraryJars(List<FilteredClassPath> filteredClassPaths);
+
+  void setPrintSeeds(boolean b);
+
+  void setSeedFile(Path path);
+
+  void setObfuscationDictionary(Path path);
+
+  void setClassObfuscationDictionary(Path path);
+
+  void setPackageObfuscationDictionary(Path path);
+
+  void addAdaptClassStringsPattern(ProguardClassNameList pattern);
+
+  void addAdaptResourceFileContents(ProguardPathList pattern);
+
+  void addAdaptResourceFilenames(ProguardPathList pattern);
+
+  void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel);
+
+  PackageObfuscationMode getPackageObfuscationMode();
+
+  void setPackagePrefix(String s);
+
+  void setFlattenPackagePrefix(String s);
+
+  void enableRepackageClasses(Origin origin, Position position);
+
+  void enableFlattenPackageHierarchy(Origin origin, Position position);
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 954a409..cf407e6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -46,6 +46,7 @@
   public boolean annotationDefault = false;
   public boolean stackMapTable = false;
   public boolean permittedSubclasses = false;
+  public boolean lineNumberTable = false;
 
   public ProguardKeepAttributes() {}
 
@@ -63,6 +64,7 @@
     annotationDefault = true;
     stackMapTable = true;
     permittedSubclasses = true;
+    lineNumberTable = true;
     return this;
   }
 
@@ -136,6 +138,7 @@
     annotationDefault = update(annotationDefault, ANNOTATION_DEFAULT, patterns);
     stackMapTable = update(stackMapTable, STACK_MAP_TABLE, patterns);
     permittedSubclasses = update(permittedSubclasses, PERMITTED_SUBCLASSES, patterns);
+    lineNumberTable = update(lineNumberTable, LINE_NUMBER_TABLE, patterns);
   }
 
   public void ensureValid(boolean forceProguardCompatibility) {
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
index 522639b..9cd9048 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
@@ -50,13 +50,11 @@
             buildInnerClasses(GenericSignatureCorrectnessHelperTests.class)
                 .addLibraryFile(ToolHelper.getJava8RuntimeJar())
                 .build(),
-            factory -> {
-              ProguardConfiguration.Builder builder =
-                  ProguardConfiguration.builder(
-                      factory, new Reporter(new TestDiagnosticMessagesImpl()));
-              builder.addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE));
-              return builder.build();
-            });
+            factory ->
+                ProguardConfiguration.builder(
+                        factory, new Reporter(new TestDiagnosticMessagesImpl()))
+                    .addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE))
+                    .build());
     GenericSignatureContextBuilder contextBuilder = GenericSignatureContextBuilder.create(appView);
     GenericSignatureCorrectnessHelper.createForVerification(appView, contextBuilder)
         .run(appView.appInfo().classes());
@@ -195,13 +193,11 @@
                 .addClassProgramData(transformations)
                 .addLibraryFile(ToolHelper.getJava8RuntimeJar())
                 .build(),
-            factory -> {
-              ProguardConfiguration.Builder builder =
-                  ProguardConfiguration.builder(
-                      factory, new Reporter(new TestDiagnosticMessagesImpl()));
-              builder.addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE));
-              return builder.build();
-            });
+            factory ->
+                ProguardConfiguration.builder(
+                        factory, new Reporter(new TestDiagnosticMessagesImpl()))
+                    .addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE))
+                    .build());
     GenericSignatureContextBuilder contextBuilder = GenericSignatureContextBuilder.create(appView);
     GenericSignatureCorrectnessHelper check =
         GenericSignatureCorrectnessHelper.createForInitialCheck(appView, contextBuilder);
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index dd432d3..1067960 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -49,7 +49,7 @@
                   R8Command.builder(),
                   pgConfig -> {
                     pgConfig.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}));
-                    pgConfig.setRenameSourceFileAttribute(TEST_FILE);
+                    pgConfig.setRenameSourceFileAttribute(TEST_FILE, null, null);
                     pgConfig.addKeepAttributePatterns(
                         ImmutableList.of("SourceFile", "LineNumberTable"));
                   })
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
new file mode 100644
index 0000000..13ec056
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
@@ -0,0 +1,101 @@
+// 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.processkeeprules;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.OriginMatcher.hasPart;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class ProcessKeepRulesCommandTest extends TestBase {
+
+  private static final Map<String, String> testRules =
+      ImmutableMap.<String, String>builder()
+          .put("-dontoptimize", "-dontoptimize not allowed in library consumer rules.")
+          .put("-dontobfuscate", "-dontobfuscate not allowed in library consumer rules.")
+          .put("-dontshrink", "-dontshrink not allowed in library consumer rules.")
+          .put("-repackageclasses", "-repackageclasses not allowed in library consumer rules.")
+          .put(
+              "-flattenpackagehierarchy",
+              "-flattenpackagehierarchy not allowed in library consumer rules.")
+          .put(
+              "-allowaccessmodification",
+              "-allowaccessmodification not allowed in library consumer rules.")
+          .put(
+              "-keepattributes LineNumberTable",
+              "Illegal attempt to keep LineNumberTable in library consumer rules.")
+          .put(
+              "-keepattributes RuntimeInvisibleAnnotations",
+              "Illegal attempt to keep RuntimeInvisibleAnnotations in library consumer rules.")
+          .put(
+              "-keepattributes RuntimeInvisibleTypeAnnotations",
+              "Illegal attempt to keep RuntimeInvisibleTypeAnnotations in library consumer rules.")
+          .put(
+              "-keepattributes RuntimeInvisibleParameterAnnotations",
+              "Illegal attempt to keep RuntimeInvisibleParameterAnnotations in library consumer"
+                  + " rules.")
+          .put(
+              "-keepattributes SourceFile",
+              "Illegal attempt to keep SourceFile in library consumer rules.")
+          .put(
+              "-renamesourcefileattribute",
+              "-renamesourcefileattribute not allowed in library consumer rules.")
+          .build();
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameter(0)
+  public Map.Entry<String, String> configAndExpectedDiagnostic;
+
+  @Parameterized.Parameters(name = "{1}, configAndExpectedDiagnostic = {0}")
+  public static List<Object[]> data() throws IOException {
+    return buildParameters(testRules.entrySet(), getTestParameters().withNoneRuntime().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    Path tempFile = getStaticTemp().newFile().toPath();
+    Files.write(tempFile, configAndExpectedDiagnostic.getKey().getBytes(StandardCharsets.UTF_8));
+    ProcessKeepRulesCommand command =
+        ProcessKeepRulesCommand.builder(diagnostics)
+            .addKeepRuleFiles(ImmutableList.of(tempFile))
+            .setLibraryConsumerRuleValidation(true)
+            .build();
+    try {
+      ProcessKeepRules.run(command);
+      fail("Expect the compilation to fail.");
+    } catch (CompilationFailedException e) {
+      diagnostics.assertErrorsMatch(
+          allOf(
+              configAndExpectedDiagnostic.getKey().startsWith("-keepattributes")
+                  ? diagnosticType(KeepAttributeLibraryConsumerRuleDiagnostic.class)
+                  : diagnosticType(GlobalLibraryConsumerRuleDiagnostic.class),
+              diagnosticOrigin(hasPart(tempFile.toString())),
+              diagnosticMessage(equalTo(configAndExpectedDiagnostic.getValue()))));
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index 6220f5d..c38ef15 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -949,8 +949,9 @@
             ProguardConfiguration.Builder builder =
                 ProguardConfiguration.builder(factory, new Reporter());
             builder.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}));
-            builder.addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE));
-            return builder.build();
+            return builder
+                .addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE))
+                .build();
           };
     }
     InternalOptions options =