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/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