Add D8 metadata API
Change-Id: I1f52c242abbba425dbb96782d935c5e53138e68c
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index d3b820e..1b7ca868 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING;
import static com.android.tools.r8.utils.MapConsumerUtils.wrapExistingMapConsumerIfNotNull;
+import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
@@ -14,6 +15,7 @@
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.metadata.D8BuildMetadata;
import com.android.tools.r8.naming.MapConsumer;
import com.android.tools.r8.naming.ProguardMapStringConsumer;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
@@ -112,6 +114,7 @@
private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private boolean enableMissingLibraryApiModeling = false;
private boolean enableRewritingOfArtProfilesIsNopCheck = false;
+ private Consumer<? super D8BuildMetadata> buildMetadataConsumer = null;
private Builder() {
this(new DefaultD8DiagnosticsHandler());
@@ -472,6 +475,16 @@
return self();
}
+ /**
+ * Set a consumer for receiving metadata about the current build intended for being stored in
+ * the app bundle.
+ */
+ public Builder setBuildMetadataConsumer(
+ Consumer<? super D8BuildMetadata> buildMetadataConsumer) {
+ this.buildMetadataConsumer = buildMetadataConsumer;
+ return self();
+ }
+
@Override
void validate() {
if (isPrintHelp()) {
@@ -595,6 +608,7 @@
partitionMapConsumer,
enableMissingLibraryApiModeling,
enableRewritingOfArtProfilesIsNopCheck,
+ buildMetadataConsumer,
getAndroidPlatformBuild(),
getArtProfilesForRewriting(),
getStartupProfileProviders(),
@@ -619,6 +633,7 @@
private final boolean enableMissingLibraryApiModeling;
private final boolean enableRewritingOfArtProfilesIsNopCheck;
private final DexItemFactory factory;
+ private final Consumer<? super D8BuildMetadata> buildMetadataConsumer;
public static Builder builder() {
return new Builder();
@@ -695,6 +710,7 @@
PartitionMapConsumer partitionMapConsumer,
boolean enableMissingLibraryApiModeling,
boolean enableRewritingOfArtProfilesIsNopCheck,
+ Consumer<? super D8BuildMetadata> buildMetadataConsumer,
boolean isAndroidPlatformBuild,
List<ArtProfileForRewriting> artProfilesForRewriting,
List<StartupProfileProvider> startupProfileProviders,
@@ -738,6 +754,7 @@
this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
this.enableRewritingOfArtProfilesIsNopCheck = enableRewritingOfArtProfilesIsNopCheck;
this.factory = factory;
+ this.buildMetadataConsumer = buildMetadataConsumer;
}
private D8Command(boolean printHelp, boolean printVersion) {
@@ -757,12 +774,14 @@
enableMissingLibraryApiModeling = false;
enableRewritingOfArtProfilesIsNopCheck = false;
factory = null;
+ buildMetadataConsumer = null;
}
@Override
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions(factory, getReporter());
assert !internal.debug;
+ internal.d8BuildMetadataConsumer = buildMetadataConsumer;
internal.debug = getMode() == CompilationMode.DEBUG;
internal.programConsumer = getProgramConsumer();
if (internal.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 1adf080..70220ac 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1276,7 +1276,7 @@
internal.keptGraphConsumer = keptGraphConsumer;
internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
- internal.buildMetadataConsumer = buildMetadataConsumer;
+ internal.r8BuildMetadataConsumer = buildMetadataConsumer;
internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
internal.featureSplitConfiguration = featureSplitConfiguration;
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 47e8a16..afe61c8 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -701,9 +701,15 @@
}
}
- if (options.buildMetadataConsumer != null) {
+ if (options.d8BuildMetadataConsumer != null) {
+ assert !appView.hasClassHierarchy();
+ options.d8BuildMetadataConsumer.accept(
+ BuildMetadataFactory.create(appView.withoutClassHierarchy()));
+ }
+
+ if (options.r8BuildMetadataConsumer != null) {
assert appView.hasClassHierarchy();
- options.buildMetadataConsumer.accept(
+ options.r8BuildMetadataConsumer.accept(
BuildMetadataFactory.create(appView.withClassHierarchy(), virtualFiles));
}
}
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 486abda..7ebfa5a 100644
--- a/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
+++ b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.Version;
import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.utils.InternalOptions;
@@ -12,6 +13,11 @@
public class BuildMetadataFactory {
+ @SuppressWarnings("UnusedParameter")
+ public static D8BuildMetadata create(AppView<AppInfo> appView) {
+ return D8BuildMetadataImpl.builder().setVersion(Version.LABEL).build();
+ }
+
public static R8BuildMetadata create(
AppView<? extends AppInfoWithClassHierarchy> appView, List<VirtualFile> virtualFiles) {
InternalOptions options = appView.options();
diff --git a/src/main/java/com/android/tools/r8/metadata/D8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/D8BuildMetadata.java
new file mode 100644
index 0000000..652d320
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/D8BuildMetadata.java
@@ -0,0 +1,22 @@
+// 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;
+import com.google.gson.GsonBuilder;
+
+@KeepForApi
+public interface D8BuildMetadata {
+
+ static D8BuildMetadata fromJson(String json) {
+ return new GsonBuilder()
+ .excludeFieldsWithoutExposeAnnotation()
+ .create()
+ .fromJson(json, D8BuildMetadataImpl.class);
+ }
+
+ String getVersion();
+
+ String toJson();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/D8BuildMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/D8BuildMetadataImpl.java
new file mode 100644
index 0000000..a55112a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/D8BuildMetadataImpl.java
@@ -0,0 +1,60 @@
+// 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.metadata.R8BuildMetadataImpl.Builder;
+import com.google.gson.Gson;
+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 D8BuildMetadataImpl implements D8BuildMetadata {
+
+ @Expose
+ @SerializedName("version")
+ private final String version;
+
+ public D8BuildMetadataImpl(String version) {
+ this.version = version;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toJson() {
+ return new Gson().toJson(this);
+ }
+
+ public static class Builder {
+
+ private String version;
+
+ public Builder setVersion(String version) {
+ this.version = version;
+ return this;
+ }
+
+ public D8BuildMetadataImpl build() {
+ return new D8BuildMetadataImpl(version);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 87ea197..0462ec8 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -76,6 +76,7 @@
import com.android.tools.r8.ir.desugar.nest.Nest;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
+import com.android.tools.r8.metadata.D8BuildMetadata;
import com.android.tools.r8.metadata.R8BuildMetadata;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapConsumer;
@@ -253,7 +254,8 @@
private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
private SyntheticInfoConsumer syntheticInfoConsumer = null;
- public Consumer<? super R8BuildMetadata> buildMetadataConsumer = null;
+ public Consumer<? super D8BuildMetadata> d8BuildMetadataConsumer = null;
+ public Consumer<? super R8BuildMetadata> r8BuildMetadataConsumer = null;
public DataResourceConsumer dataResourceConsumer;
public FeatureSplitConfiguration featureSplitConfiguration;
diff --git a/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java
new file mode 100644
index 0000000..a4fb5f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java
@@ -0,0 +1,56 @@
+// 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 static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+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.Version;
+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 D8BuildMetadataTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ D8BuildMetadata buildMetadata =
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .collectBuildMetadata()
+ .setMinApi(parameters)
+ .compile()
+ .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 (!).
+ assertThat(json, containsString("\"version\":\"" + Version.LABEL + "\""));
+ buildMetadata = D8BuildMetadata.fromJson(json);
+ inspectDeserializedBuildMetadata(buildMetadata);
+ }
+
+ private void inspectDeserializedBuildMetadata(D8BuildMetadata buildMetadata) {
+ assertEquals(Version.LABEL, buildMetadata.getVersion());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
rename to src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
index cec355d..7ff0178 100644
--- a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
@@ -33,7 +33,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class BuildMetadataTest extends TestBase {
+public class R8BuildMetadataTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
diff --git a/src/test/testbase/java/com/android/tools/r8/D8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/D8TestBuilder.java
index aeb0e21..2c499f4 100644
--- a/src/test/testbase/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/D8TestBuilder.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.D8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.metadata.D8BuildMetadata;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.profile.art.ArtProfileConsumer;
import com.android.tools.r8.profile.art.ArtProfileProvider;
@@ -13,6 +14,7 @@
import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.InternalOptions;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -37,6 +39,7 @@
private StringBuilder proguardMapOutputBuilder = null;
private boolean enableMissingLibraryApiModeling = true;
private List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
+ private Box<D8BuildMetadata> buildMetadata;
@Override
public boolean isD8TestBuilder() {
@@ -88,6 +91,9 @@
BenchmarkResults benchmarkResults)
throws CompilationFailedException {
libraryDesugaringTestConfiguration.configure(builder);
+ if (buildMetadata != null) {
+ builder.setBuildMetadataConsumer(buildMetadata::set);
+ }
builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
ToolHelper.runAndBenchmarkD8(builder, optionsConsumer, benchmarkResults);
return new D8TestCompileResult(
@@ -97,7 +103,8 @@
getOutputMode(),
libraryDesugaringTestConfiguration,
getMapContent(),
- residualArtProfiles);
+ residualArtProfiles,
+ buildMetadata != null ? buildMetadata.get() : null);
}
private String getMapContent() {
@@ -172,4 +179,10 @@
return addOptionsModification(
options -> options.horizontalClassMergerOptions().disableSyntheticMerging());
}
+
+ public D8TestBuilder collectBuildMetadata() {
+ assert buildMetadata == null;
+ buildMetadata = new Box<>();
+ return self();
+ }
}
diff --git a/src/test/testbase/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/D8TestCompileResult.java
index 35bf33d..b86b826 100644
--- a/src/test/testbase/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/D8TestCompileResult.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.metadata.D8BuildMetadata;
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
import com.android.tools.r8.utils.AndroidApp;
@@ -19,6 +20,7 @@
public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
+ private final D8BuildMetadata buildMetadata;
private final String proguardMap;
private final List<ExternalArtProfile> residualArtProfiles;
@@ -29,8 +31,10 @@
OutputMode outputMode,
LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration,
String proguardMap,
- List<ExternalArtProfile> residualArtProfiles) {
+ List<ExternalArtProfile> residualArtProfiles,
+ D8BuildMetadata buildMetadata) {
super(state, app, minApiLevel, outputMode, libraryDesugaringTestConfiguration);
+ this.buildMetadata = buildMetadata;
this.proguardMap = proguardMap;
this.residualArtProfiles = residualArtProfiles;
}
@@ -40,6 +44,11 @@
return this;
}
+ public D8BuildMetadata getBuildMetadata() {
+ assert buildMetadata != null;
+ return buildMetadata;
+ }
+
@Override
public TestDiagnosticMessages getDiagnosticMessages() {
return state.getDiagnosticsMessages();