Populate R8 build metadata

Bug: b/290258417
Fixes: b/361751071
Change-Id: I136a26047ca4c5edd9d56232d80c0417573e6055
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 3c858a9..316020f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -449,7 +449,7 @@
       options.reporter.failIfPendingErrors();
       // Supply info to all additional resource consumers.
       if (!(programConsumer instanceof ConvertedCfFiles)) {
-        supplyAdditionalConsumers(appView);
+        supplyAdditionalConsumers(appView, virtualFiles);
       }
     } finally {
       timing.end();
@@ -651,7 +651,7 @@
   }
 
   @SuppressWarnings("DefaultCharset")
-  public static void supplyAdditionalConsumers(AppView<?> appView) {
+  public static void supplyAdditionalConsumers(AppView<?> appView, List<VirtualFile> virtualFiles) {
     InternalOptions options = appView.options();
     Reporter reporter = options.reporter;
     appView.getArtProfileCollection().supplyConsumers(appView);
@@ -729,7 +729,7 @@
     if (options.buildMetadataConsumer != null) {
       assert appView.hasClassHierarchy();
       options.buildMetadataConsumer.accept(
-          BuildMetadataFactory.create(appView.withClassHierarchy()));
+          BuildMetadataFactory.create(appView.withClassHierarchy(), virtualFiles));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index dc35e67..b58266c 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -80,6 +80,7 @@
   private final DexString primaryClassDescriptor;
   private final DexString primaryClassSynthesizingContextDescriptor;
   private DebugRepresentation debugRepresentation;
+  private boolean startup = false;
 
   VirtualFile(int id, AppView<?> appView) {
     this(id, appView, null, null, StartupProfile.empty());
@@ -169,6 +170,14 @@
     return debugRepresentation;
   }
 
+  public void setStartup() {
+    startup = true;
+  }
+
+  public boolean isStartup() {
+    return startup;
+  }
+
   public static String deriveCommonPrefixAndSanityCheck(List<String> fileNames) {
     Iterator<String> nameIterator = fileNames.iterator();
     String first = nameIterator.next();
@@ -1466,6 +1475,7 @@
       boolean isSingleStartupDexFile = hasSpaceForTransaction(virtualFile, options);
       if (isSingleStartupDexFile) {
         virtualFile.commitTransaction();
+        virtualFile.setStartup();
       } else {
         virtualFile.abortTransaction();
 
@@ -1473,6 +1483,7 @@
         MultiStartupDexDistributor distributor =
             MultiStartupDexDistributor.get(options, startupProfile);
         distributor.distribute(classPartioning.getStartupClasses(), this, virtualFile, cycler);
+        cycler.filesForDistribution.forEach(VirtualFile::setStartup);
 
         options.reporter.warning(
             createStartupClassesOverflowDiagnostic(cycler.filesForDistribution.size()));
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index b5b5299..20de02a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -62,6 +62,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
@@ -186,7 +187,7 @@
       }
       globalsConsumer.finished(appView);
     }
-    ApplicationWriter.supplyAdditionalConsumers(appView);
+    ApplicationWriter.supplyAdditionalConsumers(appView, Collections.emptyList());
   }
 
   private void writeClassCatchingErrors(
diff --git a/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
index be06792..486abda 100644
--- a/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
+++ b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
@@ -4,13 +4,24 @@
 package com.android.tools.r8.metadata;
 
 import com.android.tools.r8.Version;
+import com.android.tools.r8.dex.VirtualFile;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
 
 public class BuildMetadataFactory {
 
-  @SuppressWarnings("UnusedVariable")
-  public static R8BuildMetadata create(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return R8BuildMetadataImpl.builder().setVersion(Version.LABEL).build();
+  public static R8BuildMetadata create(
+      AppView<? extends AppInfoWithClassHierarchy> appView, List<VirtualFile> virtualFiles) {
+    InternalOptions options = appView.options();
+    return R8BuildMetadataImpl.builder()
+        .setOptions(new R8OptionsImpl(options))
+        .setBaselineProfileRewritingOptions(R8BaselineProfileRewritingOptionsImpl.create(options))
+        .setResourceOptimizationOptions(R8ResourceOptimizationOptionsImpl.create(options))
+        .setStartupOptimizationOptions(
+            R8StartupOptimizationOptionsImpl.create(options, virtualFiles))
+        .setVersion(Version.LABEL)
+        .build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptions.java b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptions.java
new file mode 100644
index 0000000..7598aee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptions.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8BaselineProfileRewritingOptions {}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptionsImpl.java
new file mode 100644
index 0000000..1585469
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptionsImpl.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.utils.InternalOptions;
+
+public class R8BaselineProfileRewritingOptionsImpl implements R8BaselineProfileRewritingOptions {
+
+  private R8BaselineProfileRewritingOptionsImpl() {}
+
+  public static R8BaselineProfileRewritingOptionsImpl create(InternalOptions options) {
+    if (options.getArtProfileOptions().getArtProfilesForRewriting().isEmpty()) {
+      return null;
+    }
+    return new R8BaselineProfileRewritingOptionsImpl();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
index e186575..c5b0dd6 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
@@ -4,15 +4,52 @@
 package com.android.tools.r8.metadata;
 
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
-import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
 
 @KeepForApi
 public interface R8BuildMetadata {
 
   static R8BuildMetadata fromJson(String json) {
-    return new Gson().fromJson(json, R8BuildMetadataImpl.class);
+    return new GsonBuilder()
+        .excludeFieldsWithoutExposeAnnotation()
+        .registerTypeAdapter(R8Options.class, deserializeTo(R8OptionsImpl.class))
+        .registerTypeAdapter(
+            R8BaselineProfileRewritingOptions.class,
+            deserializeTo(R8BaselineProfileRewritingOptionsImpl.class))
+        .registerTypeAdapter(
+            R8KeepAttributesOptions.class, deserializeTo(R8KeepAttributesOptionsImpl.class))
+        .registerTypeAdapter(
+            R8ResourceOptimizationOptions.class,
+            deserializeTo(R8ResourceOptimizationOptionsImpl.class))
+        .registerTypeAdapter(
+            R8StartupOptimizationOptions.class,
+            deserializeTo(R8StartupOptimizationOptionsImpl.class))
+        .create()
+        .fromJson(json, R8BuildMetadataImpl.class);
   }
 
+  private static <T> JsonDeserializer<T> deserializeTo(Class<T> implClass) {
+    return (element, type, context) -> context.deserialize(element, implClass);
+  }
+
+  R8Options getOptions();
+
+  /**
+   * @return null if baseline profile rewriting is disabled.
+   */
+  R8BaselineProfileRewritingOptions getBaselineProfileRewritingOptions();
+
+  /**
+   * @return null if resource optimization is disabled.
+   */
+  R8ResourceOptimizationOptions getResourceOptimizationOptions();
+
+  /**
+   * @return null if startup optimization is disabled.
+   */
+  R8StartupOptimizationOptions getStartupOptizationOptions();
+
   String getVersion();
 
   String toJson();
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
index 1032696..d59db11 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.UsedByReflection;
 import com.google.gson.Gson;
+import com.google.gson.annotations.Expose;
 import com.google.gson.annotations.SerializedName;
 
 @UsedByReflection(
@@ -20,10 +21,36 @@
     fieldAnnotatedByClassConstant = SerializedName.class)
 public class R8BuildMetadataImpl implements R8BuildMetadata {
 
+  @Expose
+  @SerializedName("options")
+  private final R8Options options;
+
+  @Expose
+  @SerializedName("baselineProfileRewritingOptions")
+  private final R8BaselineProfileRewritingOptions baselineProfileRewritingOptions;
+
+  @Expose
+  @SerializedName("resourceOptimizationOptions")
+  private final R8ResourceOptimizationOptions resourceOptimizationOptions;
+
+  @Expose
+  @SerializedName("startupOptimizationOptions")
+  private final R8StartupOptimizationOptions startupOptimizationOptions;
+
+  @Expose
   @SerializedName("version")
   private final String version;
 
-  public R8BuildMetadataImpl(String version) {
+  public R8BuildMetadataImpl(
+      R8Options options,
+      R8BaselineProfileRewritingOptions baselineProfileRewritingOptions,
+      R8ResourceOptimizationOptions resourceOptimizationOptions,
+      R8StartupOptimizationOptions startupOptimizationOptions,
+      String version) {
+    this.options = options;
+    this.baselineProfileRewritingOptions = baselineProfileRewritingOptions;
+    this.resourceOptimizationOptions = resourceOptimizationOptions;
+    this.startupOptimizationOptions = startupOptimizationOptions;
     this.version = version;
   }
 
@@ -32,6 +59,26 @@
   }
 
   @Override
+  public R8Options getOptions() {
+    return options;
+  }
+
+  @Override
+  public R8BaselineProfileRewritingOptions getBaselineProfileRewritingOptions() {
+    return baselineProfileRewritingOptions;
+  }
+
+  @Override
+  public R8ResourceOptimizationOptions getResourceOptimizationOptions() {
+    return resourceOptimizationOptions;
+  }
+
+  @Override
+  public R8StartupOptimizationOptions getStartupOptizationOptions() {
+    return startupOptimizationOptions;
+  }
+
+  @Override
   public String getVersion() {
     return version;
   }
@@ -43,15 +90,47 @@
 
   public static class Builder {
 
+    private R8Options options;
+    private R8BaselineProfileRewritingOptions baselineProfileRewritingOptions;
+    private R8ResourceOptimizationOptions resourceOptimizationOptions;
+    private R8StartupOptimizationOptions startupOptimizationOptions;
     private String version;
 
+    public Builder setOptions(R8Options options) {
+      this.options = options;
+      return this;
+    }
+
+    public Builder setBaselineProfileRewritingOptions(
+        R8BaselineProfileRewritingOptions baselineProfileRewritingOptions) {
+      this.baselineProfileRewritingOptions = baselineProfileRewritingOptions;
+      return this;
+    }
+
+    public Builder setResourceOptimizationOptions(
+        R8ResourceOptimizationOptions resourceOptimizationOptions) {
+      this.resourceOptimizationOptions = resourceOptimizationOptions;
+      return this;
+    }
+
+    public Builder setStartupOptimizationOptions(
+        R8StartupOptimizationOptions startupOptimizationOptions) {
+      this.startupOptimizationOptions = startupOptimizationOptions;
+      return this;
+    }
+
     public Builder setVersion(String version) {
       this.version = version;
       return this;
     }
 
     public R8BuildMetadataImpl build() {
-      return new R8BuildMetadataImpl(version);
+      return new R8BuildMetadataImpl(
+          options,
+          baselineProfileRewritingOptions,
+          resourceOptimizationOptions,
+          startupOptimizationOptions,
+          version);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptions.java b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptions.java
new file mode 100644
index 0000000..b590c7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptions.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8KeepAttributesOptions {
+
+  boolean isAnnotationDefaultKept();
+
+  boolean isEnclosingMethodKept();
+
+  boolean isExceptionsKept();
+
+  boolean isInnerClassesKept();
+
+  boolean isLocalVariableTableKept();
+
+  boolean isLocalVariableTypeTableKept();
+
+  boolean isMethodParametersKept();
+
+  boolean isPermittedSubclassesKept();
+
+  boolean isRuntimeInvisibleAnnotationsKept();
+
+  boolean isRuntimeInvisibleParameterAnnotationsKept();
+
+  boolean isRuntimeInvisibleTypeAnnotationsKept();
+
+  boolean isRuntimeVisibleAnnotationsKept();
+
+  boolean isRuntimeVisibleParameterAnnotationsKept();
+
+  boolean isRuntimeVisibleTypeAnnotationsKept();
+
+  boolean isSignatureKept();
+
+  boolean isSourceDebugExtensionKept();
+
+  boolean isSourceDirKept();
+
+  boolean isSourceFileKept();
+
+  boolean isStackMapTableKept();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptionsImpl.java
new file mode 100644
index 0000000..3e12905
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptionsImpl.java
@@ -0,0 +1,218 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+    description = "Keep and preserve @SerializedName for correct (de)serialization",
+    constraints = {KeepConstraint.LOOKUP},
+    constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+    kind = KeepItemKind.CLASS_AND_FIELDS,
+    fieldAccess = {FieldAccessFlags.PRIVATE},
+    fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8KeepAttributesOptionsImpl implements R8KeepAttributesOptions {
+
+  @Expose
+  @SerializedName("isAnnotationDefaultKept")
+  private final boolean isAnnotationDefaultKept;
+
+  @Expose
+  @SerializedName("isEnclosingMethodKept")
+  private final boolean isEnclosingMethodKept;
+
+  @Expose
+  @SerializedName("isExceptionsKept")
+  private final boolean isExceptionsKept;
+
+  @Expose
+  @SerializedName("isInnerClassesKept")
+  private final boolean isInnerClassesKept;
+
+  @Expose
+  @SerializedName("isLocalVariableTableKept")
+  private final boolean isLocalVariableTableKept;
+
+  @Expose
+  @SerializedName("isLocalVariableTypeTableKept")
+  private final boolean isLocalVariableTypeTableKept;
+
+  @Expose
+  @SerializedName("isMethodParametersKept")
+  private final boolean isMethodParametersKept;
+
+  @Expose
+  @SerializedName("isPermittedSubclassesKept")
+  private final boolean isPermittedSubclassesKept;
+
+  @Expose
+  @SerializedName("isRuntimeInvisibleAnnotationsKept")
+  private final boolean isRuntimeInvisibleAnnotationsKept;
+
+  @Expose
+  @SerializedName("isRuntimeInvisibleParameterAnnotationsKept")
+  private final boolean isRuntimeInvisibleParameterAnnotationsKept;
+
+  @Expose
+  @SerializedName("isRuntimeInvisibleTypeAnnotationsKept")
+  private final boolean isRuntimeInvisibleTypeAnnotationsKept;
+
+  @Expose
+  @SerializedName("isRuntimeVisibleAnnotationsKept")
+  private final boolean isRuntimeVisibleAnnotationsKept;
+
+  @Expose
+  @SerializedName("isRuntimeVisibleParameterAnnotationsKept")
+  private final boolean isRuntimeVisibleParameterAnnotationsKept;
+
+  @Expose
+  @SerializedName("isRuntimeVisibleTypeAnnotationsKept")
+  private final boolean isRuntimeVisibleTypeAnnotationsKept;
+
+  @Expose
+  @SerializedName("isSignatureKept")
+  private final boolean isSignatureKept;
+
+  @Expose
+  @SerializedName("isSourceDebugExtensionKept")
+  private final boolean isSourceDebugExtensionKept;
+
+  @Expose
+  @SerializedName("isSourceDirKept")
+  private final boolean isSourceDirKept;
+
+  @Expose
+  @SerializedName("isSourceFileKept")
+  private final boolean isSourceFileKept;
+
+  @Expose
+  @SerializedName("isStackMapTableKept")
+  private final boolean isStackMapTableKept;
+
+  public R8KeepAttributesOptionsImpl(ProguardKeepAttributes keepAttributes) {
+    this.isAnnotationDefaultKept = keepAttributes.annotationDefault;
+    this.isEnclosingMethodKept = keepAttributes.enclosingMethod;
+    this.isExceptionsKept = keepAttributes.exceptions;
+    this.isInnerClassesKept = keepAttributes.innerClasses;
+    this.isLocalVariableTableKept = keepAttributes.localVariableTable;
+    this.isLocalVariableTypeTableKept = keepAttributes.localVariableTypeTable;
+    this.isMethodParametersKept = keepAttributes.methodParameters;
+    this.isPermittedSubclassesKept = keepAttributes.permittedSubclasses;
+    this.isRuntimeInvisibleAnnotationsKept = keepAttributes.runtimeInvisibleAnnotations;
+    this.isRuntimeInvisibleParameterAnnotationsKept =
+        keepAttributes.runtimeInvisibleParameterAnnotations;
+    this.isRuntimeInvisibleTypeAnnotationsKept = keepAttributes.runtimeInvisibleTypeAnnotations;
+    this.isRuntimeVisibleAnnotationsKept = keepAttributes.runtimeVisibleAnnotations;
+    this.isRuntimeVisibleParameterAnnotationsKept =
+        keepAttributes.runtimeVisibleParameterAnnotations;
+    this.isRuntimeVisibleTypeAnnotationsKept = keepAttributes.runtimeVisibleTypeAnnotations;
+    this.isSignatureKept = keepAttributes.signature;
+    this.isSourceDebugExtensionKept = keepAttributes.sourceDebugExtension;
+    this.isSourceDirKept = keepAttributes.sourceDir;
+    this.isSourceFileKept = keepAttributes.sourceFile;
+    this.isStackMapTableKept = keepAttributes.stackMapTable;
+  }
+
+  @Override
+  public boolean isAnnotationDefaultKept() {
+    return isAnnotationDefaultKept;
+  }
+
+  @Override
+  public boolean isEnclosingMethodKept() {
+    return isEnclosingMethodKept;
+  }
+
+  @Override
+  public boolean isExceptionsKept() {
+    return isExceptionsKept;
+  }
+
+  @Override
+  public boolean isInnerClassesKept() {
+    return isInnerClassesKept;
+  }
+
+  @Override
+  public boolean isLocalVariableTableKept() {
+    return isLocalVariableTableKept;
+  }
+
+  @Override
+  public boolean isLocalVariableTypeTableKept() {
+    return isLocalVariableTypeTableKept;
+  }
+
+  @Override
+  public boolean isMethodParametersKept() {
+    return isMethodParametersKept;
+  }
+
+  @Override
+  public boolean isPermittedSubclassesKept() {
+    return isPermittedSubclassesKept;
+  }
+
+  @Override
+  public boolean isRuntimeInvisibleAnnotationsKept() {
+    return isRuntimeInvisibleAnnotationsKept;
+  }
+
+  @Override
+  public boolean isRuntimeInvisibleParameterAnnotationsKept() {
+    return isRuntimeInvisibleParameterAnnotationsKept;
+  }
+
+  @Override
+  public boolean isRuntimeInvisibleTypeAnnotationsKept() {
+    return isRuntimeInvisibleTypeAnnotationsKept;
+  }
+
+  @Override
+  public boolean isRuntimeVisibleAnnotationsKept() {
+    return isRuntimeVisibleAnnotationsKept;
+  }
+
+  @Override
+  public boolean isRuntimeVisibleParameterAnnotationsKept() {
+    return isRuntimeVisibleParameterAnnotationsKept;
+  }
+
+  @Override
+  public boolean isRuntimeVisibleTypeAnnotationsKept() {
+    return isRuntimeVisibleTypeAnnotationsKept;
+  }
+
+  @Override
+  public boolean isSignatureKept() {
+    return isSignatureKept;
+  }
+
+  @Override
+  public boolean isSourceDebugExtensionKept() {
+    return isSourceDebugExtensionKept;
+  }
+
+  @Override
+  public boolean isSourceDirKept() {
+    return isSourceDirKept;
+  }
+
+  @Override
+  public boolean isSourceFileKept() {
+    return isSourceFileKept;
+  }
+
+  @Override
+  public boolean isStackMapTableKept() {
+    return isStackMapTableKept;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8Options.java b/src/main/java/com/android/tools/r8/metadata/R8Options.java
new file mode 100644
index 0000000..42fe2fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8Options.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8Options {
+
+  /**
+   * @return null if no ProGuard configuration is provided.
+   */
+  R8KeepAttributesOptions getKeepAttributesOptions();
+
+  int getMinApiLevel();
+
+  boolean isAccessModificationEnabled();
+
+  boolean isDebugModeEnabled();
+
+  boolean isProGuardCompatibilityModeEnabled();
+
+  boolean isObfuscationEnabled();
+
+  boolean isOptimizationsEnabled();
+
+  boolean isShrinkingEnabled();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8OptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8OptionsImpl.java
new file mode 100644
index 0000000..a65dfc7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8OptionsImpl.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+    description = "Keep and preserve @SerializedName for correct (de)serialization",
+    constraints = {KeepConstraint.LOOKUP},
+    constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+    kind = KeepItemKind.CLASS_AND_FIELDS,
+    fieldAccess = {FieldAccessFlags.PRIVATE},
+    fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8OptionsImpl implements R8Options {
+
+  @Expose
+  @SerializedName("keepAttributesOptions")
+  private final R8KeepAttributesOptions keepAttributesOptions;
+
+  @Expose
+  @SerializedName("minApiLevel")
+  private final int minApiLevel;
+
+  @Expose
+  @SerializedName("isAccessModificationEnabled")
+  private final boolean isAccessModificationEnabled;
+
+  @Expose
+  @SerializedName("isDebugModeEnabled")
+  private final boolean isDebugModeEnabled;
+
+  @Expose
+  @SerializedName("isProGuardCompatibilityModeEnabled")
+  private final boolean isProGuardCompatibilityModeEnabled;
+
+  @Expose
+  @SerializedName("isObfuscationEnabled")
+  private final boolean isObfuscationEnabled;
+
+  @Expose
+  @SerializedName("isOptimizationsEnabled")
+  private final boolean isOptimizationsEnabled;
+
+  @Expose
+  @SerializedName("isShrinkingEnabled")
+  private final boolean isShrinkingEnabled;
+
+  public R8OptionsImpl(InternalOptions options) {
+    this.keepAttributesOptions =
+        options.hasProguardConfiguration()
+            ? new R8KeepAttributesOptionsImpl(
+                options.getProguardConfiguration().getKeepAttributes())
+            : null;
+    this.minApiLevel = options.getMinApiLevel().getLevel();
+    this.isAccessModificationEnabled = options.isAccessModificationEnabled();
+    this.isDebugModeEnabled = options.debug;
+    this.isProGuardCompatibilityModeEnabled = options.forceProguardCompatibility;
+    this.isObfuscationEnabled = options.isMinifying();
+    this.isOptimizationsEnabled = options.isOptimizing();
+    this.isShrinkingEnabled = options.isShrinking();
+  }
+
+  @Override
+  public R8KeepAttributesOptions getKeepAttributesOptions() {
+    return keepAttributesOptions;
+  }
+
+  @Override
+  public int getMinApiLevel() {
+    return minApiLevel;
+  }
+
+  @Override
+  public boolean isAccessModificationEnabled() {
+    return isAccessModificationEnabled;
+  }
+
+  @Override
+  public boolean isDebugModeEnabled() {
+    return isDebugModeEnabled;
+  }
+
+  @Override
+  public boolean isProGuardCompatibilityModeEnabled() {
+    return isProGuardCompatibilityModeEnabled;
+  }
+
+  @Override
+  public boolean isObfuscationEnabled() {
+    return isObfuscationEnabled;
+  }
+
+  @Override
+  public boolean isOptimizationsEnabled() {
+    return isOptimizationsEnabled;
+  }
+
+  @Override
+  public boolean isShrinkingEnabled() {
+    return isShrinkingEnabled;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptions.java b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptions.java
new file mode 100644
index 0000000..693286e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptions.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8ResourceOptimizationOptions {
+
+  boolean isOptimizedShrinkingEnabled();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptionsImpl.java
new file mode 100644
index 0000000..c9cc341
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptionsImpl.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.ResourceShrinkerConfiguration;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+    description = "Keep and preserve @SerializedName for correct (de)serialization",
+    constraints = {KeepConstraint.LOOKUP},
+    constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+    kind = KeepItemKind.CLASS_AND_FIELDS,
+    fieldAccess = {FieldAccessFlags.PRIVATE},
+    fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8ResourceOptimizationOptionsImpl implements R8ResourceOptimizationOptions {
+
+  @Expose
+  @SerializedName("isOptimizedShrinkingEnabled")
+  private final boolean isOptimizedShrinkingEnabled;
+
+  private R8ResourceOptimizationOptionsImpl(
+      ResourceShrinkerConfiguration resourceShrinkerConfiguration) {
+    this.isOptimizedShrinkingEnabled = resourceShrinkerConfiguration.isOptimizedShrinking();
+  }
+
+  public static R8ResourceOptimizationOptionsImpl create(InternalOptions options) {
+    if (options.androidResourceProvider == null) {
+      return null;
+    }
+    return new R8ResourceOptimizationOptionsImpl(options.resourceShrinkerConfiguration);
+  }
+
+  @Override
+  public boolean isOptimizedShrinkingEnabled() {
+    return isOptimizedShrinkingEnabled;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptions.java b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptions.java
new file mode 100644
index 0000000..a57b01e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptions.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8StartupOptimizationOptions {
+
+  int getNumberOfStartupDexFiles();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptionsImpl.java
new file mode 100644
index 0000000..07b0a0f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptionsImpl.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2024, 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.metadata;
+
+import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+@UsedByReflection(
+    description = "Keep and preserve @SerializedName for correct (de)serialization",
+    constraints = {KeepConstraint.LOOKUP},
+    constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+    kind = KeepItemKind.CLASS_AND_FIELDS,
+    fieldAccess = {FieldAccessFlags.PRIVATE},
+    fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8StartupOptimizationOptionsImpl implements R8StartupOptimizationOptions {
+
+  @Expose
+  @SerializedName("numberOfStartupDexFiles")
+  private final int numberOfStartupDexFiles;
+
+  public R8StartupOptimizationOptionsImpl(List<VirtualFile> virtualFiles) {
+    this.numberOfStartupDexFiles =
+        (int) virtualFiles.stream().filter(VirtualFile::isStartup).count();
+  }
+
+  public static R8StartupOptimizationOptionsImpl create(
+      InternalOptions options, List<VirtualFile> virtualFiles) {
+    if (options.getStartupOptions().getStartupProfileProviders().isEmpty()) {
+      return null;
+    }
+    return new R8StartupOptimizationOptionsImpl(virtualFiles);
+  }
+
+  @Override
+  public int getNumberOfStartupDexFiles() {
+    return numberOfStartupDexFiles;
+  }
+}
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 445e0b0..ce5e106 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -56,15 +56,7 @@
   public boolean stackMapTable = false;
   public boolean permittedSubclasses = false;
 
-  private ProguardKeepAttributes() {
-  }
-
-  public static ProguardKeepAttributes filterOnlySignatures() {
-    ProguardKeepAttributes result = new ProguardKeepAttributes();
-    result.applyPatterns(KEEP_ALL);
-    result.signature = false;
-    return result;
-  }
+  private ProguardKeepAttributes() {}
 
   /**
    * Implements ProGuards attribute matching rules.
diff --git a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
index 0f90b82..cec355d 100644
--- a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
@@ -3,12 +3,29 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.metadata;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,19 +45,63 @@
 
   @Test
   public void test() throws Exception {
+    ClassReference mainReference = Reference.classFromClass(Main.class);
+    List<ExternalStartupItem> startupProfile =
+        ImmutableList.of(ExternalStartupClass.builder().setClassReference(mainReference).build());
     R8BuildMetadata buildMetadata =
         testForR8(parameters.getBackend())
             .addInnerClasses(getClass())
             .addKeepMainRule(Main.class)
+            .addArtProfileForRewriting(
+                ExternalArtProfile.builder().addClassRule(mainReference).build())
+            .apply(StartupTestingUtils.addStartupProfile(startupProfile))
+            .applyIf(
+                parameters.isDexRuntime(),
+                testBuilder ->
+                    testBuilder.addAndroidResources(getTestResources()).enableOptimizedShrinking())
+            .allowDiagnosticInfoMessages(parameters.canUseNativeMultidex())
             .collectBuildMetadata()
             .setMinApi(parameters)
-            .compile()
+            .compileWithExpectedDiagnostics(
+                diagnostics -> {
+                  if (parameters.canUseNativeMultidex()) {
+                    diagnostics.assertInfosMatch(
+                        diagnosticMessage(containsString("Startup DEX files contains")));
+                  } else {
+                    diagnostics.assertNoMessages();
+                  }
+                })
             .getBuildMetadata();
     String json = buildMetadata.toJson();
     // Inspecting the exact contents is not important here, but it *is* important to test that the
     // property names are unobfuscated when testing with R8lib (!).
-    assertEquals("{\"version\":\"" + Version.LABEL + "\"}", json);
+    assertThat(json, containsString("\"version\":\"" + Version.LABEL + "\""));
     buildMetadata = R8BuildMetadata.fromJson(json);
+    inspectDeserializedBuildMetadata(buildMetadata);
+  }
+
+  private AndroidTestResource getTestResources() throws IOException {
+    return new AndroidTestResourceBuilder().withSimpleManifestAndAppNameString().build(temp);
+  }
+
+  private void inspectDeserializedBuildMetadata(R8BuildMetadata buildMetadata) {
+    assertNotNull(buildMetadata.getBaselineProfileRewritingOptions());
+    assertNotNull(buildMetadata.getOptions());
+    assertNotNull(buildMetadata.getOptions().getKeepAttributesOptions());
+    if (parameters.isDexRuntime()) {
+      R8ResourceOptimizationOptions resourceOptimizationOptions =
+          buildMetadata.getResourceOptimizationOptions();
+      assertNotNull(resourceOptimizationOptions);
+      assertTrue(resourceOptimizationOptions.isOptimizedShrinkingEnabled());
+    } else {
+      assertNull(buildMetadata.getResourceOptimizationOptions());
+    }
+    R8StartupOptimizationOptions startupOptimizationOptions =
+        buildMetadata.getStartupOptizationOptions();
+    assertNotNull(startupOptimizationOptions);
+    assertEquals(
+        parameters.isDexRuntime() && parameters.canUseNativeMultidex() ? 1 : 0,
+        startupOptimizationOptions.getNumberOfStartupDexFiles());
     assertEquals(Version.LABEL, buildMetadata.getVersion());
   }