Add API for outputting build metadata
Bug: b/356283022
Change-Id: I9674b33779927cc2eb4713e3a2840dc43873c0d3
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a5caffa..bf1f0c0 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.metadata.R8BuildMetadata;
import com.android.tools.r8.naming.MapConsumer;
import com.android.tools.r8.naming.ProguardMapStringConsumer;
import com.android.tools.r8.naming.SourceFileRewriter;
@@ -133,6 +134,7 @@
private GraphConsumer keptGraphConsumer = null;
private GraphConsumer mainDexKeptGraphConsumer = null;
private InputDependencyGraphConsumer inputDependencyGraphConsumer = null;
+ private Consumer<? super R8BuildMetadata> buildMetadataConsumer = null;
private final FeatureSplitConfiguration.Builder featureSplitConfigurationBuilder =
FeatureSplitConfiguration.builder();
private String synthesizedClassPrefix = "";
@@ -407,6 +409,16 @@
}
/**
+ * Set a consumer for receiving metadata about the current build intended for being stored in
+ * the app bundle.
+ */
+ public Builder setBuildMetadataConsumer(
+ Consumer<? super R8BuildMetadata> buildMetadataConsumer) {
+ this.buildMetadataConsumer = buildMetadataConsumer;
+ return self();
+ }
+
+ /**
* Set the output path-and-mode.
*
* <p>Setting the output path-and-mode will override any previous set consumer or any previous
@@ -777,7 +789,8 @@
androidResourceProvider,
androidResourceConsumer,
resourceShrinkerConfiguration,
- keepSpecifications);
+ keepSpecifications,
+ buildMetadataConsumer);
if (inputDependencyGraphConsumer != null) {
inputDependencyGraphConsumer.finished();
@@ -972,6 +985,7 @@
private final AndroidResourceProvider androidResourceProvider;
private final AndroidResourceConsumer androidResourceConsumer;
private final ResourceShrinkerConfiguration resourceShrinkerConfiguration;
+ private final Consumer<? super R8BuildMetadata> buildMetadataConsumer;
/** Get a new {@link R8Command.Builder}. */
public static Builder builder() {
@@ -1070,7 +1084,8 @@
AndroidResourceProvider androidResourceProvider,
AndroidResourceConsumer androidResourceConsumer,
ResourceShrinkerConfiguration resourceShrinkerConfiguration,
- List<KeepSpecificationSource> keepSpecifications) {
+ List<KeepSpecificationSource> keepSpecifications,
+ Consumer<? super R8BuildMetadata> buildMetadataConsumer) {
super(
inputApp,
mode,
@@ -1119,6 +1134,7 @@
this.androidResourceProvider = androidResourceProvider;
this.androidResourceConsumer = androidResourceConsumer;
this.resourceShrinkerConfiguration = resourceShrinkerConfiguration;
+ this.buildMetadataConsumer = buildMetadataConsumer;
}
private R8Command(boolean printHelp, boolean printVersion) {
@@ -1147,6 +1163,7 @@
androidResourceProvider = null;
androidResourceConsumer = null;
resourceShrinkerConfiguration = null;
+ buildMetadataConsumer = null;
}
public DexItemFactory getDexItemFactory() {
@@ -1259,6 +1276,7 @@
internal.keptGraphConsumer = keptGraphConsumer;
internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
+ internal.buildMetadataConsumer = 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 4d1c64e..e4aa9dc 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -47,6 +47,7 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.metadata.BuildMetadataFactory;
import com.android.tools.r8.naming.KotlinModuleSynthesizer;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
@@ -724,6 +725,12 @@
kotlinModuleSynthesizer);
}
}
+
+ if (options.buildMetadataConsumer != null) {
+ assert appView.hasClassHierarchy();
+ options.buildMetadataConsumer.accept(
+ BuildMetadataFactory.create(appView.withClassHierarchy()));
+ }
}
private static void adaptAndPassDataResources(
diff --git a/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
new file mode 100644
index 0000000..7d5b12d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
@@ -0,0 +1,16 @@
+// 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.Version;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+
+public class BuildMetadataFactory {
+
+ @SuppressWarnings("UnusedVariable")
+ public static R8BuildMetadata create(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return R8BuildMetadataImpl.builder().setVersion(Version.getVersionString()).build();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
new file mode 100644
index 0000000..e186575
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
@@ -0,0 +1,19 @@
+// 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.Gson;
+
+@KeepForApi
+public interface R8BuildMetadata {
+
+ static R8BuildMetadata fromJson(String json) {
+ return new Gson().fromJson(json, R8BuildMetadataImpl.class);
+ }
+
+ 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
new file mode 100644
index 0000000..1032696
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
@@ -0,0 +1,57 @@
+// 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.google.gson.Gson;
+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 R8BuildMetadataImpl implements R8BuildMetadata {
+
+ @SerializedName("version")
+ private final String version;
+
+ public R8BuildMetadataImpl(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 R8BuildMetadataImpl build() {
+ return new R8BuildMetadataImpl(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 8cee41d..615f440 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.R8BuildMetadata;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapConsumer;
import com.android.tools.r8.naming.MapVersion;
@@ -252,6 +253,7 @@
private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
private SyntheticInfoConsumer syntheticInfoConsumer = null;
+ public Consumer<? super R8BuildMetadata> buildMetadataConsumer = null;
public DataResourceConsumer dataResourceConsumer;
public FeatureSplitConfiguration featureSplitConfiguration;
diff --git a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
new file mode 100644
index 0000000..da2b2d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
@@ -0,0 +1,51 @@
+// 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.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 BuildMetadataTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8BuildMetadata buildMetadata =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .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 (!).
+ assertEquals("{\"version\":\"main (build engineering)\"}", json);
+ buildMetadata = R8BuildMetadata.fromJson(json);
+ assertEquals(Version.getVersionString(), buildMetadata.getVersion());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {}
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
index 0b7d432..be791e9 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.keepanno.KeepAnnoTestUtils;
+import com.android.tools.r8.metadata.R8BuildMetadata;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.profile.art.ArtProfileConsumer;
@@ -37,6 +38,7 @@
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MapIdTemplateProvider;
@@ -89,6 +91,7 @@
private final List<Path> features = new ArrayList<>();
private Path resourceShrinkerOutput = null;
private HashMap<String, Path> resourceShrinkerOutputForFeatures = new HashMap<>();
+ private Box<R8BuildMetadata> buildMetadata;
@Override
public boolean isR8TestBuilder() {
@@ -148,6 +151,9 @@
builder.setEnableIsolatedSplits(enableIsolatedSplits);
builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
builder.setEnableStartupLayoutOptimization(enableStartupLayoutOptimization);
+ if (buildMetadata != null) {
+ builder.setBuildMetadataConsumer(buildMetadata::set);
+ }
StringBuilder pgConfOutput = wrapProguardConfigConsumer(builder);
ToolHelper.runAndBenchmarkR8WithoutResult(builder, optionsConsumer, benchmarkResults);
R8TestCompileResult compileResult =
@@ -164,7 +170,8 @@
features,
residualArtProfiles,
resourceShrinkerOutput,
- resourceShrinkerOutputForFeatures);
+ resourceShrinkerOutputForFeatures,
+ buildMetadata.get());
switch (allowedDiagnosticMessages) {
case ALL:
compileResult.getDiagnosticMessages().assertAllDiagnosticsMatch(new IsAnything<>());
@@ -1003,4 +1010,10 @@
getBuilder().setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output, input));
return self();
}
+
+ public T collectBuildMetadata() {
+ assert buildMetadata == null;
+ buildMetadata = new Box<>();
+ return self();
+ }
}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
index 4edcc33..f9db531 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.ResourceTableInspector;
import com.android.tools.r8.benchmarks.BenchmarkResults;
import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
+import com.android.tools.r8.metadata.R8BuildMetadata;
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
@@ -46,6 +47,7 @@
private final List<ExternalArtProfile> residualArtProfiles;
private final Path resourceShrinkerOutput;
private final Map<String, Path> resourceShrinkerOutputForFeatures;
+ private final R8BuildMetadata buildMetadata;
R8TestCompileResult(
TestState state,
@@ -60,7 +62,8 @@
List<Path> features,
List<ExternalArtProfile> residualArtProfiles,
Path resourceShrinkerOutput,
- HashMap<String, Path> resourceShrinkerOutputForFeatures) {
+ HashMap<String, Path> resourceShrinkerOutputForFeatures,
+ R8BuildMetadata buildMetadata) {
super(state, app, minApiLevel, outputMode, libraryDesugaringTestConfiguration);
this.proguardConfiguration = proguardConfiguration;
this.syntheticProguardRules = syntheticProguardRules;
@@ -70,6 +73,7 @@
this.residualArtProfiles = residualArtProfiles;
this.resourceShrinkerOutput = resourceShrinkerOutput;
this.resourceShrinkerOutputForFeatures = resourceShrinkerOutputForFeatures;
+ this.buildMetadata = buildMetadata;
}
public R8TestCompileResult benchmarkResourceSize(BenchmarkResults results) throws IOException {
@@ -82,6 +86,11 @@
return this;
}
+ public R8BuildMetadata getBuildMetadata() {
+ assert buildMetadata != null;
+ return buildMetadata;
+ }
+
@Override
public TestDiagnosticMessages getDiagnosticMessages() {
return state.getDiagnosticsMessages();