Export provenance metadata for prebuilt APKs and APEXes.
Bug: 217434690
Test: atest --host gen_provenance_metadata_test
Test: m provenance_metadata
Change-Id: I91c184b6e6fe5ccfc3fc65b55b09e7a3da9502a0
diff --git a/provenance/Android.bp b/provenance/Android.bp
new file mode 100644
index 0000000..6fd67aa
--- /dev/null
+++ b/provenance/Android.bp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+ name: "soong-provenance",
+ pkgPath: "android/soong/provenance",
+ srcs: [
+ "provenance_singleton.go",
+ ],
+ deps: [
+ "soong-android",
+ ],
+ testSrcs: [
+ "provenance_singleton_test.go",
+ ],
+ pluginFor: [
+ "soong_build",
+ ],
+}
diff --git a/provenance/provenance_metadata_proto/Android.bp b/provenance/provenance_metadata_proto/Android.bp
new file mode 100644
index 0000000..7fc47a9
--- /dev/null
+++ b/provenance/provenance_metadata_proto/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_library_host {
+ name: "provenance_metadata_proto",
+ version: {
+ py3: {
+ enabled: true,
+ },
+ },
+ srcs: [
+ "provenance_metadata.proto",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ },
+}
diff --git a/provenance/provenance_metadata_proto/provenance_metadata.proto b/provenance/provenance_metadata_proto/provenance_metadata.proto
new file mode 100644
index 0000000..f42aba7
--- /dev/null
+++ b/provenance/provenance_metadata_proto/provenance_metadata.proto
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package provenance_metadata_proto;
+option go_package = "android/soong/provenance/provenance_metadata_proto";
+
+// Provenance metadata of artifacts.
+message ProvenanceMetadata {
+ // Name of the module/target that creates the artifact.
+ // It is either a Soong module name or Bazel target label.
+ string module_name = 1;
+
+ // The path to the prebuilt artifacts, which is relative to the source tree
+ // directory. For example, “prebuilts/runtime/mainline/i18n/apex/com.android.i18n-arm.apex”.
+ string artifact_path = 2;
+
+ // The SHA256 hash of the artifact.
+ string artifact_sha256 = 3;
+
+ // The install path of the artifact in filesystem images.
+ // This is the absolute path of the artifact on the device.
+ string artifact_install_path = 4;
+
+ // Path of the attestation file of a prebuilt artifact, which is relative to
+ // the source tree directory. This is for prebuilt artifacts which have
+ // corresponding attestation files checked in the source tree.
+ string attestation_path = 5;
+}
+
+message ProvenanceMetaDataList {
+ repeated ProvenanceMetadata metadata = 1;
+}
\ No newline at end of file
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
new file mode 100644
index 0000000..ae96e1f
--- /dev/null
+++ b/provenance/provenance_singleton.go
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package provenance
+
+import (
+ "android/soong/android"
+ "github.com/google/blueprint"
+)
+
+var (
+ pctx = android.NewPackageContext("android/soong/provenance")
+ rule = pctx.HostBinToolVariable("gen_provenance_metadata", "gen_provenance_metadata")
+
+ genProvenanceMetaData = pctx.AndroidStaticRule("genProvenanceMetaData",
+ blueprint.RuleParams{
+ Command: `rm -rf "$out" && ` +
+ `${gen_provenance_metadata} --module_name=${module_name} ` +
+ `--artifact_path=$in --install_path=${install_path} --metadata_path=$out`,
+ CommandDeps: []string{"${gen_provenance_metadata}"},
+ }, "module_name", "install_path")
+
+ mergeProvenanceMetaData = pctx.AndroidStaticRule("mergeProvenanceMetaData",
+ blueprint.RuleParams{
+ Command: `rm -rf $out $out.temp && ` +
+ `echo -e "# proto-file: build/soong/provenance/proto/provenance_metadata.proto\n# proto-message: ProvenanceMetaDataList" > $out && ` +
+ `touch $out.temp && cat $out.temp $in | grep -v "^#.*" >> $out && rm -rf $out.temp`,
+ })
+)
+
+type ProvenanceMetadata interface {
+ ProvenanceMetaDataFile() android.OutputPath
+}
+
+func init() {
+ RegisterProvenanceSingleton(android.InitRegistrationContext)
+}
+
+func RegisterProvenanceSingleton(ctx android.RegistrationContext) {
+ ctx.RegisterSingletonType("provenance_metadata_singleton", provenanceInfoSingletonFactory)
+}
+
+var PrepareForTestWithProvenanceSingleton = android.FixtureRegisterWithContext(RegisterProvenanceSingleton)
+
+func provenanceInfoSingletonFactory() android.Singleton {
+ return &provenanceInfoSingleton{}
+}
+
+type provenanceInfoSingleton struct {
+}
+
+func (b *provenanceInfoSingleton) GenerateBuildActions(context android.SingletonContext) {
+ allMetaDataFiles := make([]android.Path, 0)
+ context.VisitAllModulesIf(moduleFilter, func(module android.Module) {
+ if p, ok := module.(ProvenanceMetadata); ok {
+ allMetaDataFiles = append(allMetaDataFiles, p.ProvenanceMetaDataFile())
+ }
+ })
+ mergedMetaDataFile := android.PathForOutput(context, "provenance_metadata.textproto")
+ context.Build(pctx, android.BuildParams{
+ Rule: mergeProvenanceMetaData,
+ Description: "merge provenance metadata",
+ Inputs: allMetaDataFiles,
+ Output: mergedMetaDataFile,
+ })
+
+ context.Build(pctx, android.BuildParams{
+ Rule: blueprint.Phony,
+ Description: "phony rule of merge provenance metadata",
+ Inputs: []android.Path{mergedMetaDataFile},
+ Output: android.PathForPhony(context, "provenance_metadata"),
+ })
+}
+
+func moduleFilter(module android.Module) bool {
+ if !module.Enabled() || module.IsSkipInstall() {
+ return false
+ }
+ if p, ok := module.(ProvenanceMetadata); ok {
+ return p.ProvenanceMetaDataFile().String() != ""
+ }
+ return false
+}
+
+func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.OutputPath {
+ onDevicePathOfInstalledFile := android.InstallPathToOnDevicePath(ctx, installedFile)
+ artifactMetaDataFile := android.PathForIntermediates(ctx, "provenance_metadata", ctx.ModuleDir(), ctx.ModuleName(), "provenance_metadata.textproto")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: genProvenanceMetaData,
+ Description: "generate artifact provenance metadata",
+ Inputs: []android.Path{artifactPath},
+ Output: artifactMetaDataFile,
+ Args: map[string]string{
+ "module_name": ctx.ModuleName(),
+ "install_path": onDevicePathOfInstalledFile,
+ }})
+
+ return artifactMetaDataFile
+}
diff --git a/provenance/provenance_singleton_test.go b/provenance/provenance_singleton_test.go
new file mode 100644
index 0000000..0f1eae2
--- /dev/null
+++ b/provenance/provenance_singleton_test.go
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package provenance
+
+import (
+ "strings"
+ "testing"
+
+ "android/soong/android"
+)
+
+func TestProvenanceSingleton(t *testing.T) {
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithProvenanceSingleton,
+ android.PrepareForTestWithAndroidMk).RunTestWithBp(t, "")
+
+ outputs := result.SingletonForTests("provenance_metadata_singleton").AllOutputs()
+ for _, output := range outputs {
+ testingBuildParam := result.SingletonForTests("provenance_metadata_singleton").Output(output)
+ switch {
+ case strings.Contains(output, "soong/provenance_metadata.textproto"):
+ android.AssertStringEquals(t, "Invalid build rule", "android/soong/provenance.mergeProvenanceMetaData", testingBuildParam.Rule.String())
+ android.AssertIntEquals(t, "Invalid input", len(testingBuildParam.Inputs), 0)
+ android.AssertStringDoesContain(t, "Invalid output path", output, "soong/provenance_metadata.textproto")
+ android.AssertIntEquals(t, "Invalid args", len(testingBuildParam.Args), 0)
+
+ case strings.HasSuffix(output, "provenance_metadata"):
+ android.AssertStringEquals(t, "Invalid build rule", "<builtin>:phony", testingBuildParam.Rule.String())
+ android.AssertStringEquals(t, "Invalid input", testingBuildParam.Inputs[0].String(), "out/soong/provenance_metadata.textproto")
+ android.AssertStringEquals(t, "Invalid output path", output, "provenance_metadata")
+ android.AssertIntEquals(t, "Invalid args", len(testingBuildParam.Args), 0)
+ }
+ }
+}
diff --git a/provenance/tools/Android.bp b/provenance/tools/Android.bp
new file mode 100644
index 0000000..1f959bb
--- /dev/null
+++ b/provenance/tools/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+python_binary_host {
+ name: "gen_provenance_metadata",
+ srcs: [
+ "gen_provenance_metadata.py",
+ ],
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
+ libs: [
+ "provenance_metadata_proto",
+ "libprotobuf-python",
+ ],
+}
+
+python_test_host {
+ name: "gen_provenance_metadata_test",
+ main: "gen_provenance_metadata_test.py",
+ srcs: [
+ "gen_provenance_metadata_test.py",
+ ],
+ data: [
+ ":gen_provenance_metadata",
+ ],
+ libs: [
+ "provenance_metadata_proto",
+ "libprotobuf-python",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/provenance/tools/gen_provenance_metadata.py b/provenance/tools/gen_provenance_metadata.py
new file mode 100644
index 0000000..b33f911
--- /dev/null
+++ b/provenance/tools/gen_provenance_metadata.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import hashlib
+import sys
+
+import google.protobuf.text_format as text_format
+import provenance_metadata_pb2
+
+def Log(*info):
+ if args.verbose:
+ for i in info:
+ print(i)
+
+def ParseArgs(argv):
+ parser = argparse.ArgumentParser(description='Create provenance metadata for a prebuilt artifact')
+ parser.add_argument('-v', '--verbose', action='store_true', help='Print more information in execution')
+ parser.add_argument('--module_name', help='Module name', required=True)
+ parser.add_argument('--artifact_path', help='Relative path of the prebuilt artifact in source tree', required=True)
+ parser.add_argument('--install_path', help='Absolute path of the artifact in the filesystem images', required=True)
+ parser.add_argument('--metadata_path', help='Path of the provenance metadata file created for the artifact', required=True)
+ return parser.parse_args(argv)
+
+def main(argv):
+ global args
+ args = ParseArgs(argv)
+ Log("Args:", vars(args))
+
+ provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata()
+ provenance_metadata.module_name = args.module_name
+ provenance_metadata.artifact_path = args.artifact_path
+ provenance_metadata.artifact_install_path = args.install_path
+
+ Log("Generating SHA256 hash")
+ h = hashlib.sha256()
+ with open(args.artifact_path, "rb") as artifact_file:
+ h.update(artifact_file.read())
+ provenance_metadata.artifact_sha256 = h.hexdigest()
+
+ text_proto = [
+ "# proto-file: build/soong/provenance/proto/provenance_metadata.proto",
+ "# proto-message: ProvenanceMetaData",
+ "",
+ text_format.MessageToString(provenance_metadata)
+ ]
+ with open(args.metadata_path, "wt") as metadata_file:
+ file_content = "\n".join(text_proto)
+ Log("Writing provenance metadata in textproto:", file_content)
+ metadata_file.write(file_content)
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/provenance/tools/gen_provenance_metadata_test.py b/provenance/tools/gen_provenance_metadata_test.py
new file mode 100644
index 0000000..2fc04bf
--- /dev/null
+++ b/provenance/tools/gen_provenance_metadata_test.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import hashlib
+import logging
+import os
+import subprocess
+import tempfile
+import unittest
+
+import google.protobuf.text_format as text_format
+import provenance_metadata_pb2
+
+logger = logging.getLogger(__name__)
+
+def run(args, verbose=None, **kwargs):
+ """Creates and returns a subprocess.Popen object.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+ universal_newlines will default to True, as most of the users in
+ releasetools expect string output.
+
+ Returns:
+ A subprocess.Popen object.
+ """
+ if 'stdout' not in kwargs and 'stderr' not in kwargs:
+ kwargs['stdout'] = subprocess.PIPE
+ kwargs['stderr'] = subprocess.STDOUT
+ if 'universal_newlines' not in kwargs:
+ kwargs['universal_newlines'] = True
+ if verbose:
+ logger.info(" Running: \"%s\"", " ".join(args))
+ return subprocess.Popen(args, **kwargs)
+
+
+def run_and_check_output(args, verbose=None, **kwargs):
+ """Runs the given command and returns the output.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+
+ Returns:
+ The output string.
+
+ Raises:
+ ExternalError: On non-zero exit from the command.
+ """
+ proc = run(args, verbose=verbose, **kwargs)
+ output, _ = proc.communicate()
+ if output is None:
+ output = ""
+ if verbose:
+ logger.info("%s", output.rstrip())
+ if proc.returncode != 0:
+ raise RuntimeError(
+ "Failed to run command '{}' (exit code {}):\n{}".format(
+ args, proc.returncode, output))
+ return output
+
+def run_host_command(args, verbose=None, **kwargs):
+ host_build_top = os.environ.get("ANDROID_BUILD_TOP")
+ if host_build_top:
+ host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin")
+ args[0] = os.path.join(host_command_dir, args[0])
+ return run_and_check_output(args, verbose, **kwargs)
+
+def sha256(s):
+ h = hashlib.sha256()
+ h.update(bytearray(s, 'utf-8'))
+ return h.hexdigest()
+
+class ProvenanceMetaDataToolTest(unittest.TestCase):
+
+ def test_gen_provenance_metadata(self):
+ artifact_content = "test artifact"
+ artifact_file = tempfile.mktemp()
+ with open(artifact_file,"wt") as f:
+ f.write(artifact_content)
+ metadata_file = tempfile.mktemp()
+ cmd = ["gen_provenance_metadata"]
+ cmd.extend(["--module_name", "a"])
+ cmd.extend(["--artifact_path", artifact_file])
+ cmd.extend(["--install_path", "b"])
+ cmd.extend(["--metadata_path", metadata_file])
+ output = run_host_command(cmd)
+ self.assertEqual(output, "")
+
+ with open(metadata_file,"rt") as f:
+ data = f.read()
+ provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata()
+ text_format.Parse(data, provenance_metadata)
+ self.assertEqual(provenance_metadata.module_name, "a")
+ self.assertEqual(provenance_metadata.artifact_path, artifact_file)
+ self.assertEqual(provenance_metadata.artifact_install_path, "b")
+ self.assertEqual(provenance_metadata.artifact_sha256, sha256(artifact_content))
+
+ os.remove(artifact_file)
+ os.remove(metadata_file)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
\ No newline at end of file