Reland "[Partition] Correctly capture partition keys and preamble in metadata"

This reverts commit 72e9e579ea3bac44b6f54769715430cb982b400d.

Bug: b/274735214
Change-Id: I0204cb4820784b83c57d87236f033aa4d7319802
diff --git a/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java b/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java
index 0d36470..533a5f5 100644
--- a/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java
@@ -7,6 +7,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.ShortBuffer;
+import java.nio.charset.StandardCharsets;
 
 /**
  * In JDK 9 ByteBuffer ByteBuffer.position(int) started overriding Buffer Buffer.position(int) along
@@ -26,6 +27,10 @@
     return new CompatByteBuffer(ByteBuffer.wrap(bytes));
   }
 
+  public static CompatByteBuffer wrapOrNull(byte[] bytes) {
+    return bytes == null ? null : wrap(bytes);
+  }
+
   private Buffer asBuffer() {
     return buffer;
   }
@@ -136,4 +141,34 @@
   public void put(byte[] bytes) {
     asByteBuffer().put(bytes);
   }
+
+  // ----------------------------------------------------------------------------------------------
+  // Additional custom methods
+  // ----------------------------------------------------------------------------------------------
+
+  public int getUShort() {
+    return buffer.getShort() & 0xffff;
+  }
+
+  public byte[] getBytesOfUByteSize() {
+    int length = getUShort();
+    byte[] data = new byte[length];
+    get(data);
+    return data;
+  }
+
+  public String getUTFOfUByteSize() {
+    return new String(getBytesOfUByteSize(), StandardCharsets.UTF_8);
+  }
+
+  public byte[] getBytesOfIntSize() {
+    int length = buffer.getInt();
+    byte[] data = new byte[length];
+    get(data);
+    return data;
+  }
+
+  public String getUTFOfIntSize() {
+    return new String(getBytesOfIntSize(), StandardCharsets.UTF_8);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 0ee8077..3d30621 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -29,7 +29,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -40,6 +39,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 
 public class ClassNameMapper implements ProguardMap {
 
@@ -51,6 +51,7 @@
   public static class Builder extends ProguardMap.Builder {
 
     private boolean buildPreamble = false;
+    private boolean addVersionAsPreamble = false;
     private final List<String> preamble = new ArrayList<>();
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
     private final LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
@@ -65,13 +66,18 @@
       return classNamingBuilder;
     }
 
-    Builder setBuildPreamble(boolean buildPreamble) {
+    public Builder setBuildPreamble(boolean buildPreamble) {
       this.buildPreamble = buildPreamble;
       return this;
     }
 
+    public Builder setAddVersionAsPreamble(boolean addVersionAsPreamble) {
+      this.addVersionAsPreamble = addVersionAsPreamble;
+      return this;
+    }
+
     @Override
-    void addPreambleLine(String line) {
+    public void addPreambleLine(String line) {
       if (buildPreamble) {
         preamble.add(line);
       }
@@ -93,6 +99,9 @@
     @Override
     public ProguardMap.Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion) {
       mapVersions.add(mapVersion);
+      if (addVersionAsPreamble) {
+        addPreambleLine("# " + mapVersion.serialize());
+      }
       return this;
     }
   }
@@ -148,14 +157,15 @@
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping,
-      boolean readPreamble)
+      boolean buildPreamble)
       throws IOException {
-    return mapperFromLineReader(
+    return mapperFromLineReaderWithFiltering(
         LineReader.fromBufferedReader(CharSource.wrap(contents).openBufferedStream()),
+        MapVersion.MAP_VERSION_NONE,
         diagnosticsHandler,
         allowEmptyMappedRanges,
         allowExperimentalMapping,
-        readPreamble);
+        builder -> builder.setBuildPreamble(buildPreamble));
   }
 
   private static ClassNameMapper mapperFromBufferedReader(
@@ -170,31 +180,13 @@
       boolean allowExperimentalMapping,
       boolean buildPreamble)
       throws IOException {
-    return mapperFromLineReader(
+    return mapperFromLineReaderWithFiltering(
         LineReader.fromBufferedReader(reader),
+        MapVersion.MAP_VERSION_NONE,
         diagnosticsHandler,
         allowEmptyMappedRanges,
         allowExperimentalMapping,
-        buildPreamble);
-  }
-
-  public static ClassNameMapper mapperFromLineReader(
-      LineReader reader,
-      DiagnosticsHandler diagnosticsHandler,
-      boolean allowEmptyMappedRanges,
-      boolean allowExperimentalMapping,
-      boolean buildPreamble)
-      throws IOException {
-    try (ProguardMapReader proguardReader =
-        new ProguardMapReader(
-            reader,
-            diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
-            allowEmptyMappedRanges,
-            allowExperimentalMapping)) {
-      ClassNameMapper.Builder builder = ClassNameMapper.builder().setBuildPreamble(buildPreamble);
-      proguardReader.parse(builder);
-      return builder.build();
-    }
+        builder -> builder.setBuildPreamble(buildPreamble));
   }
 
   public static ClassNameMapper mapperFromLineReaderWithFiltering(
@@ -202,7 +194,8 @@
       MapVersion mapVersion,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
-      boolean allowExperimentalMapping)
+      boolean allowExperimentalMapping,
+      Consumer<ClassNameMapper.Builder> builderConsumer)
       throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
@@ -212,6 +205,7 @@
             allowExperimentalMapping,
             mapVersion)) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
+      builderConsumer.accept(builder);
       proguardReader.parse(builder);
       return builder.build();
     }
@@ -239,7 +233,7 @@
     return classNameMappings;
   }
 
-  public Collection<String> getPreamble() {
+  public List<String> getPreamble() {
     return preamble;
   }
 
@@ -350,7 +344,7 @@
   }
 
   public boolean isEmpty() {
-    return classNameMappings.isEmpty();
+    return classNameMappings.isEmpty() && preamble.isEmpty();
   }
 
   public ClassNameMapper sorted() {
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
index b6181dc..0da8f47 100644
--- a/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
@@ -8,12 +8,13 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.LineReader;
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal;
+import com.android.tools.r8.retrace.internal.MetadataAdditionalInfo;
 import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
-import com.android.tools.r8.retrace.internal.RetracePartitionException;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -39,12 +40,21 @@
   public void run() throws RetracePartitionException {
     MappingPartitionMetadataInternal metadataInternal =
         MappingPartitionMetadataInternal.deserialize(
-            metadata, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler);
+            CompatByteBuffer.wrapOrNull(metadata),
+            MapVersion.MAP_VERSION_UNKNOWN,
+            diagnosticsHandler);
     if (!metadataInternal.canGetPartitionKeys()) {
       throw new RetracePartitionException("Cannot obtain all partition keys from metadata");
     }
-    // TODO(b/274893426): Account for preamble.
-    ClassNameMapper classNameMapper = ClassNameMapper.builder().build();
+    ClassNameMapper.Builder builder = ClassNameMapper.builder();
+    if (metadataInternal.canGetAdditionalInfo()) {
+      MetadataAdditionalInfo additionalInfo = metadataInternal.getAdditionalInfo();
+      if (additionalInfo.hasPreamble()) {
+        builder.setBuildPreamble(true);
+        additionalInfo.getPreamble().forEach(builder::addPreambleLine);
+      }
+    }
+    ClassNameMapper classNameMapper = builder.build();
     for (String partitionKey : metadataInternal.getPartitionKeys()) {
       LineReader reader =
           new ProguardMapReaderWithFilteringInputBuffer(
@@ -52,13 +62,22 @@
       try {
         classNameMapper =
             ClassNameMapper.mapperFromLineReaderWithFiltering(
-                    reader, metadataInternal.getMapVersion(), diagnosticsHandler, true, true)
+                    reader,
+                    metadataInternal.getMapVersion(),
+                    diagnosticsHandler,
+                    true,
+                    true,
+                    partitionBuilder -> partitionBuilder.setBuildPreamble(true))
                 .combine(classNameMapper);
       } catch (IOException e) {
         throw new RetracePartitionException(e);
       }
     }
-    classNameMapper.sorted().write(new ProguardMapWriter(consumer, diagnosticsHandler));
+    ProguardMapWriter consumer = new ProguardMapWriter(this.consumer, diagnosticsHandler);
+    classNameMapper
+        .getPreamble()
+        .forEach(preambleLine -> consumer.accept(preambleLine).accept("\n"));
+    classNameMapper.sorted().write(consumer);
   }
 
   private static class ProguardMapWriter implements ChainableStringConsumer {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java b/src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java
rename to src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java
index 7e6e924..5712f0d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java
@@ -2,7 +2,7 @@
 // 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.retrace.internal;
+package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
index b31d984..6b050c6 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.retrace.internal;
 
 public enum MappingPartitionKeyStrategy {
+  UNKNOWN(-1),
   OBFUSCATED_TYPE_NAME_AS_KEY(0),
   OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS(1);
 
@@ -16,6 +17,17 @@
     this.serializedKey = serializedKey;
   }
 
+  public static MappingPartitionKeyStrategy getByKey(int serializedKey) {
+    switch (serializedKey) {
+      case 0:
+        return OBFUSCATED_TYPE_NAME_AS_KEY;
+      case 1:
+        return OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS;
+      default:
+        return UNKNOWN;
+    }
+  }
+
   public int getSerializedKey() {
     return serializedKey;
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
index 0cee013..9896856 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
@@ -6,11 +6,16 @@
 
 import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY;
 import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS;
+import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.getByKey;
+import static com.android.tools.r8.utils.SerializationUtils.getZeroByte;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.RetracePartitionException;
+import com.android.tools.r8.retrace.internal.MetadataAdditionalInfo.LazyMetadataAdditionalInfo;
 import com.android.tools.r8.retrace.internal.MetadataPartitionCollection.LazyMetadataPartitionCollection;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.google.common.primitives.Ints;
@@ -33,7 +38,14 @@
     return null;
   }
 
-  byte ZERO_BYTE = (byte) 0;
+  default boolean canGetAdditionalInfo() {
+    return false;
+  }
+
+  default MetadataAdditionalInfo getAdditionalInfo() {
+    return MetadataAdditionalInfo.create(null);
+  }
+
   // Magic byte put into the metadata
   byte[] MAGIC = new byte[] {(byte) 0xAA, (byte) 0xA8};
 
@@ -42,20 +54,25 @@
   }
 
   static MappingPartitionMetadataInternal deserialize(
-      byte[] bytes, MapVersion fallBackMapVersion, DiagnosticsHandler diagnosticsHandler) {
-    if (bytes == null) {
+      CompatByteBuffer buffer,
+      MapVersion fallBackMapVersion,
+      DiagnosticsHandler diagnosticsHandler) {
+    if (buffer == null) {
       return ObfuscatedTypeNameAsKeyMetadata.create(fallBackMapVersion);
     }
-    if (bytes.length > 2) {
-      if (startsWithMagic(bytes)) {
-        int serializedKey =
-            Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[magicOffset()], bytes[magicOffset() + 1]);
-        if (serializedKey == OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS.getSerializedKey()) {
-          return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.deserialize(bytes);
-        }
-      } else if (OBFUSCATED_TYPE_NAME_AS_KEY.getSerializedKey()
-          == Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[0], bytes[1])) {
-        return ObfuscatedTypeNameAsKeyMetadata.deserialize(bytes);
+    if (buffer.remaining() > 2) {
+      int magicOrStrategyKey = buffer.getUShort();
+      if (magicOrStrategyKey == Ints.fromBytes(getZeroByte(), getZeroByte(), MAGIC[0], MAGIC[1])) {
+        magicOrStrategyKey = buffer.getShort();
+      }
+      switch (getByKey(magicOrStrategyKey)) {
+        case OBFUSCATED_TYPE_NAME_AS_KEY:
+          return ObfuscatedTypeNameAsKeyMetadata.deserialize(buffer);
+        case OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS:
+          return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.deserialize(buffer);
+        default:
+          throw new RetracePartitionException(
+              "Could not find partition key strategy from serialized key: " + magicOrStrategyKey);
       }
     }
     // If we arrived here then we could not deserialize the metadata.
@@ -65,18 +82,6 @@
     throw exception;
   }
 
-  private static boolean startsWithMagic(byte[] bytes) {
-    if (bytes.length < MAGIC.length) {
-      return false;
-    }
-    for (int i = 0; i < MAGIC.length; i++) {
-      if (bytes[i] != MAGIC[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   class ObfuscatedTypeNameAsKeyMetadata implements MappingPartitionMetadataInternal {
 
     private final MapVersion mapVersion;
@@ -111,8 +116,9 @@
       }
     }
 
-    public static ObfuscatedTypeNameAsKeyMetadata deserialize(byte[] bytes) {
-      MapVersion mapVersion = MapVersion.fromName(new String(bytes, 2, bytes.length - 2));
+    public static ObfuscatedTypeNameAsKeyMetadata deserialize(CompatByteBuffer buffer) {
+      byte[] array = buffer.array();
+      MapVersion mapVersion = MapVersion.fromName(new String(array, 2, array.length - 2));
       return create(mapVersion);
     }
 
@@ -126,17 +132,23 @@
 
     private final MapVersion mapVersion;
     private final MetadataPartitionCollection metadataPartitionCollection;
+    private final MetadataAdditionalInfo metadataAdditionalInfo;
 
     private ObfuscatedTypeNameAsKeyMetadataWithPartitionNames(
-        MapVersion mapVersion, MetadataPartitionCollection metadataPartitionCollection) {
+        MapVersion mapVersion,
+        MetadataPartitionCollection metadataPartitionCollection,
+        MetadataAdditionalInfo metadataAdditionalInfo) {
       this.mapVersion = mapVersion;
       this.metadataPartitionCollection = metadataPartitionCollection;
+      this.metadataAdditionalInfo = metadataAdditionalInfo;
     }
 
     public static ObfuscatedTypeNameAsKeyMetadataWithPartitionNames create(
-        MapVersion mapVersion, MetadataPartitionCollection metadataPartitionCollection) {
+        MapVersion mapVersion,
+        MetadataPartitionCollection metadataPartitionCollection,
+        MetadataAdditionalInfo metadataAdditionalInfo) {
       return new ObfuscatedTypeNameAsKeyMetadataWithPartitionNames(
-          mapVersion, metadataPartitionCollection);
+          mapVersion, metadataPartitionCollection, metadataAdditionalInfo);
     }
 
     @Override
@@ -159,8 +171,18 @@
       return metadataPartitionCollection.getPartitionKeys();
     }
 
+    @Override
+    public boolean canGetAdditionalInfo() {
+      return true;
+    }
+
+    @Override
+    public MetadataAdditionalInfo getAdditionalInfo() {
+      return metadataAdditionalInfo;
+    }
+
     // The format is:
-    // <type:short><map-version-length:short><map-version>[<partition_key>]
+    // <MAGIC><type:short><map-version-length:short><map-version>{partitions}{additionalinfo}
     @Override
     public byte[] getBytes() {
       try {
@@ -168,10 +190,9 @@
         DataOutputStream dataOutputStream = new DataOutputStream(temp);
         dataOutputStream.write(MAGIC);
         dataOutputStream.writeShort(OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS.getSerializedKey());
-        String name = mapVersion.getName();
-        dataOutputStream.writeShort(name.length());
-        dataOutputStream.writeBytes(name);
-        dataOutputStream.write(metadataPartitionCollection.serialize());
+        dataOutputStream.writeUTF(mapVersion.getName());
+        metadataPartitionCollection.serialize(dataOutputStream);
+        metadataAdditionalInfo.serialize(dataOutputStream);
         dataOutputStream.close();
         return temp.toByteArray();
       } catch (IOException e) {
@@ -179,14 +200,16 @@
       }
     }
 
-    public static ObfuscatedTypeNameAsKeyMetadataWithPartitionNames deserialize(byte[] bytes) {
-      int start = magicOffset();
-      int length = Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[start + 2], bytes[start + 3]);
-      MapVersion mapVersion = MapVersion.fromName(new String(bytes, start + 4, length));
-      int partitionCollectionStartIndex = start + 4 + length;
+    public static ObfuscatedTypeNameAsKeyMetadataWithPartitionNames deserialize(
+        CompatByteBuffer buffer) {
+      String utf = buffer.getUTFOfUByteSize();
+      MapVersion mapVersion = MapVersion.fromName(utf);
+      LazyMetadataPartitionCollection metadataPartitionCollection =
+          LazyMetadataPartitionCollection.create(buffer);
+      LazyMetadataAdditionalInfo lazyMetadataAdditionalInfo =
+          LazyMetadataAdditionalInfo.create(buffer);
       return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.create(
-          mapVersion,
-          new LazyMetadataPartitionCollection(bytes, partitionCollectionStartIndex, bytes.length));
+          mapVersion, metadataPartitionCollection, lazyMetadataAdditionalInfo);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java b/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java
new file mode 100644
index 0000000..706584b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2023, 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.retrace.internal;
+
+import com.android.tools.r8.dex.CompatByteBuffer;
+import com.android.tools.r8.retrace.RetracePartitionException;
+import com.android.tools.r8.utils.SerializationUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+public class MetadataAdditionalInfo {
+
+  public enum AdditionalInfoTypes {
+    UNKNOWN(-1),
+    PREAMBLE(0);
+
+    private final int serializedKey;
+
+    AdditionalInfoTypes(int serializedKey) {
+      this.serializedKey = serializedKey;
+    }
+
+    static AdditionalInfoTypes getByKey(int serializedKey) {
+      if (serializedKey == 0) {
+        return PREAMBLE;
+      }
+      return UNKNOWN;
+    }
+  }
+
+  protected final List<String> preamble;
+
+  private MetadataAdditionalInfo(List<String> preamble) {
+    this.preamble = preamble;
+  }
+
+  public boolean hasPreamble() {
+    return preamble != null;
+  }
+
+  public Collection<String> getPreamble() {
+    return preamble;
+  }
+
+  // The serialized format is an extensible list where we first record the offsets for each data
+  // section and then emit the data.
+  // <total-size:int><number-of-elements:short>[<type-i:short><length-i:int><data-i>]
+  public void serialize(DataOutputStream dataOutputStream) throws IOException {
+    ByteArrayOutputStream temp = new ByteArrayOutputStream();
+    DataOutputStream additionalInfoStream = new DataOutputStream(temp);
+    additionalInfoStream.writeShort(1);
+    additionalInfoStream.writeShort(AdditionalInfoTypes.PREAMBLE.serializedKey);
+    SerializationUtils.writeUTFOfIntSize(additionalInfoStream, StringUtils.unixLines(preamble));
+    byte[] payload = temp.toByteArray();
+    dataOutputStream.writeInt(payload.length);
+    dataOutputStream.write(payload);
+  }
+
+  private static MetadataAdditionalInfo deserialize(byte[] bytes) {
+    CompatByteBuffer compatByteBuffer = CompatByteBuffer.wrap(bytes);
+    int numberOfElements = compatByteBuffer.getShort();
+    List<String> preamble = null;
+    for (int i = 0; i < numberOfElements; i++) {
+      // We are parsing <type:short><length:int><bytes>
+      int additionInfoTypeKey = compatByteBuffer.getShort();
+      AdditionalInfoTypes additionalInfoType = AdditionalInfoTypes.getByKey(additionInfoTypeKey);
+      if (additionalInfoType == AdditionalInfoTypes.PREAMBLE) {
+        preamble = StringUtils.splitLines(compatByteBuffer.getUTFOfIntSize());
+      } else {
+        throw new RetracePartitionException(
+            "Could not additional info from key: " + additionInfoTypeKey);
+      }
+    }
+    return new MetadataAdditionalInfo(preamble);
+  }
+
+  public static MetadataAdditionalInfo create(List<String> preamble) {
+    return new MetadataAdditionalInfo(preamble);
+  }
+
+  public static class LazyMetadataAdditionalInfo extends MetadataAdditionalInfo {
+
+    private byte[] bytes;
+    private MetadataAdditionalInfo metadataAdditionalInfo = null;
+
+    public LazyMetadataAdditionalInfo(byte[] bytes) {
+      super(null);
+      this.bytes = bytes;
+    }
+
+    @Override
+    public boolean hasPreamble() {
+      MetadataAdditionalInfo metadataAdditionalInfo = getMetadataAdditionalInfo();
+      return metadataAdditionalInfo != null && metadataAdditionalInfo.hasPreamble();
+    }
+
+    @Override
+    public Collection<String> getPreamble() {
+      MetadataAdditionalInfo metadataAdditionalInfo = getMetadataAdditionalInfo();
+      return metadataAdditionalInfo == null ? null : metadataAdditionalInfo.getPreamble();
+    }
+
+    private MetadataAdditionalInfo getMetadataAdditionalInfo() {
+      if (metadataAdditionalInfo == null) {
+        metadataAdditionalInfo = MetadataAdditionalInfo.deserialize(bytes);
+        bytes = null;
+      }
+      return metadataAdditionalInfo;
+    }
+
+    public static LazyMetadataAdditionalInfo create(CompatByteBuffer buffer) {
+      return new LazyMetadataAdditionalInfo(buffer.getBytesOfIntSize());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java b/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java
index aef50f0..3a3d271 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import com.android.tools.r8.dex.CompatByteBuffer;
+import com.android.tools.r8.utils.SerializationUtils;
 import com.android.tools.r8.utils.StringUtils;
+import java.io.DataOutputStream;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.Collections;
@@ -23,50 +27,43 @@
     return partitionKeys;
   }
 
-  public byte[] serialize() {
-    return StringUtils.join(SEPARATOR + "", partitionKeys).getBytes(StandardCharsets.UTF_8);
+  // The format is:
+  // <length-in-bytes:int><data>
+  public void serialize(DataOutputStream dataOutputStream) throws IOException {
+    SerializationUtils.writeUTFOfIntSize(
+        dataOutputStream, StringUtils.join(SEPARATOR + "", partitionKeys));
+  }
+
+  private static MetadataPartitionCollection deserialize(byte[] bytes) {
+    String allKeys = new String(bytes, StandardCharsets.UTF_8);
+    return create(StringUtils.split(allKeys, SEPARATOR));
   }
 
   public static MetadataPartitionCollection create(Collection<String> partitionKeys) {
     return new MetadataPartitionCollection(partitionKeys);
   }
 
-  public static MetadataPartitionCollection createLazy(
-      byte[] bytes, int partitionCollectionStartIndex, int partitionCollectionEndIndex) {
-    return new LazyMetadataPartitionCollection(
-        bytes, partitionCollectionStartIndex, partitionCollectionEndIndex);
-  }
-
   public static class LazyMetadataPartitionCollection extends MetadataPartitionCollection {
 
-    private final byte[] bytes;
-    private final int partitionCollectionStartIndex;
-    private final int partitionCollectionEndIndex;
+    private byte[] bytes;
     private MetadataPartitionCollection metadataPartitionCollection = null;
 
-    public LazyMetadataPartitionCollection(
-        byte[] bytes, int partitionCollectionStartIndex, int partitionCollectionEndIndex) {
+    private LazyMetadataPartitionCollection(byte[] bytes) {
       super(Collections.emptyList());
       this.bytes = bytes;
-      this.partitionCollectionStartIndex = partitionCollectionStartIndex;
-      this.partitionCollectionEndIndex = partitionCollectionEndIndex;
     }
 
     @Override
     public Collection<String> getPartitionKeys() {
       if (metadataPartitionCollection == null) {
-        metadataPartitionCollection = deserialize();
+        metadataPartitionCollection = deserialize(bytes);
+        bytes = null;
       }
       return metadataPartitionCollection.getPartitionKeys();
     }
 
-    private MetadataPartitionCollection deserialize() {
-      String allKeys =
-          new String(
-              bytes,
-              partitionCollectionStartIndex,
-              partitionCollectionEndIndex - partitionCollectionStartIndex);
-      return create(StringUtils.split(allKeys, SEPARATOR));
+    public static LazyMetadataPartitionCollection create(CompatByteBuffer buffer) {
+      return new LazyMetadataPartitionCollection(buffer.getBytesOfIntSize());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
index 7d663fb..de94d1e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
@@ -7,6 +7,7 @@
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.LineReader;
 import com.android.tools.r8.naming.MapVersion;
@@ -66,7 +67,7 @@
     }
     return mappingPartitionMetadataCache =
         MappingPartitionMetadataInternal.deserialize(
-            metadata, fallbackMapVersion, diagnosticsHandler);
+            CompatByteBuffer.wrapOrNull(metadata), fallbackMapVersion, diagnosticsHandler);
   }
 
   @Override
@@ -114,7 +115,12 @@
                 new ByteArrayInputStream(suppliedPartition), alwaysTrue(), true);
         classNameMapper =
             ClassNameMapper.mapperFromLineReaderWithFiltering(
-                    reader, metadata.getMapVersion(), diagnosticsHandler, true, allowExperimental)
+                    reader,
+                    metadata.getMapVersion(),
+                    diagnosticsHandler,
+                    true,
+                    allowExperimental,
+                    builder -> builder.setBuildPreamble(true))
                 .combine(this.classNameMapper);
       } catch (IOException e) {
         throw new InvalidMappingFileException(e);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
index 06ede47..64e4d90 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.retrace.ProguardMapPartitioner;
 import com.android.tools.r8.retrace.ProguardMapPartitionerBuilder;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetracePartitionException;
 import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadata;
 import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadataWithPartitionNames;
 import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
@@ -100,7 +101,8 @@
             MapVersion.MAP_VERSION_UNKNOWN,
             diagnosticsHandler,
             allowEmptyMappedRanges,
-            allowExperimentalMapping);
+            allowExperimentalMapping,
+            builder -> builder.setBuildPreamble(true).setAddVersionAsPreamble(true));
     reader.forEachClassMapping(
         (classMapping, entries) -> {
           try {
@@ -161,7 +163,9 @@
     } else if (mappingPartitionKeyStrategy
         == MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS) {
       return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.create(
-          mapVersion, MetadataPartitionCollection.create(keys));
+          mapVersion,
+          MetadataPartitionCollection.create(keys),
+          MetadataAdditionalInfo.create(classMapper.getPreamble()));
     } else {
       RetracePartitionException retraceError =
           new RetracePartitionException("Unknown mapping partitioning strategy");
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
index d791a5c..f2d647a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
@@ -119,7 +119,12 @@
                     proguardMapProducer.get(), buildForClass, readPreambleAndSourceFile);
         classNameMapper =
             ClassNameMapper.mapperFromLineReaderWithFiltering(
-                    reader, getMapVersion(), diagnosticsHandler, true, allowExperimental)
+                    reader,
+                    getMapVersion(),
+                    diagnosticsHandler,
+                    true,
+                    allowExperimental,
+                    builder -> builder.setBuildPreamble(true))
                 .combine(classNameMapper);
         builtClassMappings.addAll(pendingClassMappings);
         pendingClassMappings.clear();
diff --git a/src/main/java/com/android/tools/r8/utils/SerializationUtils.java b/src/main/java/com/android/tools/r8/utils/SerializationUtils.java
new file mode 100644
index 0000000..e400b17
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SerializationUtils.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, 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.utils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class SerializationUtils {
+
+  private static final byte ZERO_BYTE = (byte) 0;
+
+  public static byte getZeroByte() {
+    return ZERO_BYTE;
+  }
+
+  public static void writeUTFOfIntSize(DataOutputStream dataOutputStream, String string)
+      throws IOException {
+    // Similar to dataOutputStream.writeUTF except it uses an int for length in bytes.
+    byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
+    dataOutputStream.writeInt(bytes.length);
+    dataOutputStream.write(bytes);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
index 83287ff..3836c08 100644
--- a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
@@ -68,24 +68,6 @@
         .build()
         .run();
     List<String> joinedMapLines = StringUtils.splitLines(builder.toString());
-    // TODO(b/274735214): Partitioning does not capture the preamble of the mapping file yet, so we
-    //  discard it before checking equality.
-    List<String> lines = Files.readAllLines(mappingFile);
-    List<String> filteredLines = lines.subList(computeFirstLine(lines), lines.size());
-    assertListsAreEqual(filteredLines, joinedMapLines);
-  }
-
-  private int computeFirstLine(List<String> lines) {
-    int firstLine = 0;
-    for (int i = 0; i < lines.size(); i++) {
-      String currentLine = lines.get(i).trim();
-      if (!currentLine.startsWith("#")
-          && currentLine.contains(" -> ")
-          && currentLine.endsWith(":")) {
-        firstLine = i;
-        break;
-      }
-    }
-    return firstLine;
+    assertListsAreEqual(Files.readAllLines(mappingFile), joinedMapLines);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
index 376d072..2f5bc20 100644
--- a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
@@ -82,7 +83,7 @@
     byte[] bytes = metadataData.getBytes();
     MappingPartitionMetadataInternal mappingPartitionMetadata =
         MappingPartitionMetadataInternal.deserialize(
-            bytes, MapVersion.MAP_VERSION_NONE, diagnosticsHandler);
+            CompatByteBuffer.wrap(bytes), MapVersion.MAP_VERSION_NONE, diagnosticsHandler);
     assertTrue(mappingPartitionMetadata.canGetPartitionKeys());
     assertEquals(expectedPartitionKeys, mappingPartitionMetadata.getPartitionKeys());
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java
index 01159fa..a006cab 100644
--- a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java
@@ -11,9 +11,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.retrace.RetracePartitionException;
 import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal;
-import com.android.tools.r8.retrace.internal.RetracePartitionException;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import org.junit.Test;
@@ -46,7 +47,7 @@
             RetracePartitionException.class,
             () ->
                 MappingPartitionMetadataInternal.deserialize(
-                    bytes, MapVersion.MAP_VERSION_NONE, diagnosticsHandler));
+                    CompatByteBuffer.wrap(bytes), MapVersion.MAP_VERSION_NONE, diagnosticsHandler));
     assertEquals(
         "Unknown map partition strategy for metadata", retracePartitionException.getMessage());
   }