[Compose] Extend map parsing and compose with support for preambles
Bug: b/241763080
Change-Id: I6c07c64a65a25141a8deb855cfb3575b553a6b1a
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index a64b883..7db4fbd 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -276,7 +276,8 @@
content,
options.reporter,
false,
- options.testing.enableExperimentalMapFileVersion));
+ options.testing.enableExperimentalMapFileVersion,
+ false));
} catch (IOException | ResourceException e) {
throw new CompilationError("Failure to read proguard map file", e, map.getOrigin());
}
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 668f6de..922fc3d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -28,10 +28,14 @@
import java.nio.charset.StandardCharsets;
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;
import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -45,8 +49,10 @@
public static class Builder extends ProguardMap.Builder {
+ private boolean buildPreamble = false;
+ private final List<String> preamble = new ArrayList<>();
private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
- private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
+ private final LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
private final Map<String, String> originalSourceFiles = new HashMap<>();
@Override
@@ -58,9 +64,22 @@
return classNamingBuilder;
}
+ Builder setBuildPreamble(boolean buildPreamble) {
+ this.buildPreamble = buildPreamble;
+ return this;
+ }
+
+ @Override
+ void addPreambleLine(String line) {
+ if (buildPreamble) {
+ preamble.add(line);
+ }
+ }
+
@Override
public ClassNameMapper build() {
- return new ClassNameMapper(buildClassNameMappings(), mapVersions, originalSourceFiles);
+ return new ClassNameMapper(
+ buildClassNameMappings(), mapVersions, originalSourceFiles, preamble);
}
private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
@@ -100,6 +119,11 @@
return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream(), null);
}
+ public static ClassNameMapper mapperFromStringWithPreamble(String contents) throws IOException {
+ return mapperFromBufferedReader(
+ CharSource.wrap(contents).openBufferedStream(), null, false, false, true);
+ }
+
public static ClassNameMapper mapperFromString(
String contents, DiagnosticsHandler diagnosticsHandler) throws IOException {
return mapperFromBufferedReader(
@@ -110,38 +134,43 @@
String contents,
DiagnosticsHandler diagnosticsHandler,
boolean allowEmptyMappedRanges,
- boolean allowExperimentalMapping)
+ boolean allowExperimentalMapping,
+ boolean readPreamble)
throws IOException {
return mapperFromLineReader(
LineReader.fromBufferedReader(CharSource.wrap(contents).openBufferedStream()),
diagnosticsHandler,
allowEmptyMappedRanges,
- allowExperimentalMapping);
+ allowExperimentalMapping,
+ readPreamble);
}
private static ClassNameMapper mapperFromBufferedReader(
BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
- return mapperFromBufferedReader(reader, diagnosticsHandler, false, false);
+ return mapperFromBufferedReader(reader, diagnosticsHandler, false, false, false);
}
public static ClassNameMapper mapperFromBufferedReader(
BufferedReader reader,
DiagnosticsHandler diagnosticsHandler,
boolean allowEmptyMappedRanges,
- boolean allowExperimentalMapping)
+ boolean allowExperimentalMapping,
+ boolean buildPreamble)
throws IOException {
return mapperFromLineReader(
LineReader.fromBufferedReader(reader),
diagnosticsHandler,
allowEmptyMappedRanges,
- allowExperimentalMapping);
+ allowExperimentalMapping,
+ buildPreamble);
}
public static ClassNameMapper mapperFromLineReader(
LineReader reader,
DiagnosticsHandler diagnosticsHandler,
boolean allowEmptyMappedRanges,
- boolean allowExperimentalMapping)
+ boolean allowExperimentalMapping,
+ boolean buildPreamble)
throws IOException {
try (ProguardMapReader proguardReader =
new ProguardMapReader(
@@ -149,7 +178,7 @@
diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
allowEmptyMappedRanges,
allowExperimentalMapping)) {
- ClassNameMapper.Builder builder = ClassNameMapper.builder();
+ ClassNameMapper.Builder builder = ClassNameMapper.builder().setBuildPreamble(buildPreamble);
proguardReader.parse(builder);
return builder.build();
}
@@ -180,20 +209,27 @@
private final Map<Signature, Signature> signatureMap = new HashMap<>();
private final LinkedHashSet<MapVersionMappingInformation> mapVersions;
private final Map<String, String> originalSourceFiles;
+ private final List<String> preamble;
private ClassNameMapper(
ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
LinkedHashSet<MapVersionMappingInformation> mapVersions,
- Map<String, String> originalSourceFiles) {
+ Map<String, String> originalSourceFiles,
+ List<String> preamble) {
this.classNameMappings = classNameMappings;
this.mapVersions = mapVersions;
this.originalSourceFiles = originalSourceFiles;
+ this.preamble = preamble;
}
public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
return classNameMappings;
}
+ public Collection<String> getPreamble() {
+ return preamble;
+ }
+
private Signature canonicalizeSignature(Signature signature) {
Signature result = signatureMap.get(signature);
if (result != null) {
@@ -274,7 +310,14 @@
// This will overwrite existing source files but the chance of that happening should be very
// slim.
newSourcesFiles.putAll(other.originalSourceFiles);
- return new ClassNameMapper(builder.build(), newMapVersions, newSourcesFiles);
+
+ List<String> newPreamble = Collections.emptyList();
+ if (!this.preamble.isEmpty() || !other.preamble.isEmpty()) {
+ newPreamble = new ArrayList<>();
+ newPreamble.addAll(this.preamble);
+ newPreamble.addAll(other.preamble);
+ }
+ return new ClassNameMapper(builder.build(), newMapVersions, newSourcesFiles, newPreamble);
}
@Override
@@ -301,7 +344,7 @@
ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
classNameMappings.forEach(builder::put);
- return new ClassNameMapper(builder.build(), mapVersions, originalSourceFiles);
+ return new ClassNameMapper(builder.build(), mapVersions, originalSourceFiles, preamble);
}
public boolean verifyIsSorted() {
diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
index d99e801..3919bbb 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -119,7 +119,7 @@
List<ComposingClassBuilder> classBuilders = new ArrayList<>(committed.classBuilders.values());
classBuilders.sort(Comparator.comparing(ComposingClassBuilder::getOriginalName));
StringBuilder sb = new StringBuilder();
- // TODO(b/241763080): Keep preamble of mapping files
+ committed.preamble.forEach(preambleLine -> sb.append(preambleLine).append("\n"));
if (currentMapVersion != null) {
sb.append("# ").append(currentMapVersion.serialize()).append("\n");
}
@@ -152,8 +152,11 @@
private final Map<ClassDescriptorAndMethodName, UpdateOutlineCallsiteInformation>
outlineSourcePositionsUpdated = new HashMap<>();
+ private final List<String> preamble = new ArrayList<>();
+
public void commit(ComposingData current, ClassNameMapper classNameMapper)
throws MappingComposeException {
+ preamble.addAll(classNameMapper.getPreamble());
commitClassBuilders(current);
commitRewriteFrameInformation(current, classNameMapper);
commitOutlineCallsiteInformation(current, classNameMapper);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index 6b95444..38fe729 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -13,6 +13,8 @@
abstract ClassNaming.Builder classNamingBuilder(
String renamedName, String originalName, Position position);
+ abstract void addPreambleLine(String line);
+
abstract Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion);
abstract ProguardMap build();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index a3a3aa0..edb8793 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -69,6 +69,7 @@
private final DiagnosticsHandler diagnosticsHandler;
private final boolean allowEmptyMappedRanges;
private final boolean allowExperimentalMapping;
+ private boolean seenClassMapping = false;
private final CardinalPositionRangeAllocator cardinalRangeCache =
PositionRangeAllocator.createCardinalPositionRangeAllocator();
@@ -147,11 +148,11 @@
}
}
- private boolean nextLine() throws IOException {
+ private boolean nextLine(ProguardMap.Builder mapBuilder) throws IOException {
if (line.length() != lineOffset) {
throw new ParseException("Expected end of line");
}
- return skipLine();
+ return skipLine(mapBuilder);
}
private boolean isEmptyOrCommentLine(String line) {
@@ -184,10 +185,6 @@
return false;
}
- private boolean isClassMapping() {
- return !isEmptyOrCommentLine(line) && line.endsWith(":");
- }
-
private static boolean hasFirstCharJsonBrace(String line, int commentCharIndex) {
for (int i = commentCharIndex + 1; i < line.length(); i++) {
char c = line.charAt(i);
@@ -200,12 +197,17 @@
return false;
}
- private boolean skipLine() throws IOException {
+ private boolean skipLine(ProguardMap.Builder mapBuilder) throws IOException {
lineOffset = 0;
+ boolean isEmptyOrCommentLine;
do {
- lineNo++;
line = reader.readLine();
- } while (hasLine() && isEmptyOrCommentLine(line));
+ lineNo++;
+ isEmptyOrCommentLine = isEmptyOrCommentLine(line);
+ if (!seenClassMapping && isEmptyOrCommentLine) {
+ mapBuilder.addPreambleLine(line);
+ }
+ } while (hasLine() && isEmptyOrCommentLine);
return hasLine();
}
@@ -242,10 +244,7 @@
void parse(ProguardMap.Builder mapBuilder) throws IOException {
// Read the first line.
- do {
- line = reader.readLine();
- lineNo++;
- } while (hasLine() && isEmptyOrCommentLine(line));
+ skipLine(mapBuilder);
parseClassMappings(mapBuilder);
}
@@ -255,17 +254,23 @@
while (hasLine()) {
skipWhitespace();
if (isCommentLineWithJsonBrace()) {
- parseMappingInformation(
+ if (!parseMappingInformation(
info -> {
assert info.isMapVersionMappingInformation()
|| info.isUnknownJsonMappingInformation();
if (info.isMapVersionMappingInformation()) {
mapBuilder.setCurrentMapVersion(info.asMapVersionMappingInformation());
+ } else if (!seenClassMapping) {
+ mapBuilder.addPreambleLine(line);
}
- });
+ })) {
+ if (!seenClassMapping) {
+ mapBuilder.addPreambleLine(line);
+ }
+ }
// Skip reading the rest of the line.
lineOffset = line.length();
- nextLine();
+ nextLine(mapBuilder);
continue;
}
String before = parseType(false);
@@ -284,40 +289,47 @@
String after = parseType(false);
skipWhitespace();
expect(':');
+ seenClassMapping = true;
ClassNaming.Builder currentClassBuilder =
mapBuilder.classNamingBuilder(after, before, getPosition());
skipWhitespace();
- if (nextLine()) {
- parseMemberMappings(currentClassBuilder);
+ if (nextLine(mapBuilder)) {
+ parseMemberMappings(mapBuilder, currentClassBuilder);
}
}
}
- private void parseMappingInformation(Consumer<MappingInformation> onMappingInfo) {
- MappingInformation.fromJsonObject(
- version,
- parseJsonInComment(),
- diagnosticsHandler,
- lineNo,
- info -> {
- MapVersionMappingInformation generatorInfo = info.asMapVersionMappingInformation();
- if (generatorInfo != null) {
- if (generatorInfo.getMapVersion().equals(MapVersion.MAP_VERSION_EXPERIMENTAL)) {
- // A mapping file that is marked "experimental" will be treated as an unversioned
- // file if the compiler/tool is not explicitly running with experimental support.
- version =
- allowExperimentalMapping
- ? MapVersion.MAP_VERSION_EXPERIMENTAL
- : MapVersion.MAP_VERSION_NONE;
- } else {
- version = generatorInfo.getMapVersion();
+ private boolean parseMappingInformation(Consumer<MappingInformation> onMappingInfo) {
+ JsonObject object = parseJsonInComment();
+ if (object != null) {
+ MappingInformation.fromJsonObject(
+ version,
+ object,
+ diagnosticsHandler,
+ lineNo,
+ info -> {
+ MapVersionMappingInformation generatorInfo = info.asMapVersionMappingInformation();
+ if (generatorInfo != null) {
+ if (generatorInfo.getMapVersion().equals(MapVersion.MAP_VERSION_EXPERIMENTAL)) {
+ // A mapping file that is marked "experimental" will be treated as an unversioned
+ // file if the compiler/tool is not explicitly running with experimental support.
+ version =
+ allowExperimentalMapping
+ ? MapVersion.MAP_VERSION_EXPERIMENTAL
+ : MapVersion.MAP_VERSION_NONE;
+ } else {
+ version = generatorInfo.getMapVersion();
+ }
}
- }
- onMappingInfo.accept(info);
- });
+ onMappingInfo.accept(info);
+ });
+ return true;
+ }
+ return false;
}
- private void parseMemberMappings(ClassNaming.Builder classNamingBuilder) throws IOException {
+ private void parseMemberMappings(
+ ProguardMap.Builder mapBuilder, ClassNaming.Builder classNamingBuilder) throws IOException {
MemberNaming lastAddedNaming = null;
MemberNaming activeMemberNaming = null;
MappedRange activeMappedRange = null;
@@ -421,7 +433,7 @@
activeMemberNaming =
new MemberNaming(signature, signature.asRenamed(renamedName), getPosition());
previousMappedRange = mappedRange;
- } while (nextLine());
+ } while (nextLine(mapBuilder));
if (activeMemberNaming != null) {
boolean notAdded =
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index bf8044e..45f7a5b 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -63,6 +63,11 @@
}
@Override
+ void addPreambleLine(String line) {
+ // Do nothing.
+ }
+
+ @Override
ProguardMap.Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion) {
// Do nothing
return this;
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleCommentTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleCommentTest.java
new file mode 100644
index 0000000..98a070d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleCommentTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ComposePreambleCommentTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.unixLines(
+ "# This is a multi line ",
+ "# preamble, with custom information",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+ "# foo bar",
+ "# {'id':'this is invalid json due to no comma' neededInfo:'foobar' }",
+ "com.A -> a:",
+ "# This is a comment that will be removed.",
+ "com.B -> c:");
+ private static final String mappingBar =
+ StringUtils.unixLines(
+ "# Additional multiline ",
+ "# second preamble, with custom information",
+ "# {'id':'bar',neededInfo:'barbaz'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+ "a -> b:",
+ "# This is another comment that will be removed.",
+ "c -> d:");
+ private static final String mappingResult =
+ StringUtils.unixLines(
+ "# This is a multi line ",
+ "# preamble, with custom information",
+ "# foo bar",
+ "# {'id':'this is invalid json due to no comma' neededInfo:'foobar' }",
+ "# Additional multiline ",
+ "# second preamble, with custom information",
+ "# {'id':'bar',neededInfo:'barbaz'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+ "com.A -> b:",
+ "com.B -> d:");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, doubleToSingleQuote(composed));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
index 5a4db90..d1991df 100644
--- a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
@@ -38,7 +38,8 @@
CharSource.wrap(StringUtils.joinLines(lines)).openBufferedStream(),
diagnosticsHandler,
false,
- true);
+ true,
+ false);
}
private static ClassNameMapper read(String... lines) throws IOException {