Export provenance metadata for prebuilt APKs and APEXes.
Bug: 217434690 Test: atest --host gen_provenance_metadata_test Test: m provenance_metadata Change-Id: I91c184b6e6fe5ccfc3fc65b55b09e7a3da9502a0
This commit is contained in:
36
provenance/Android.bp
Normal file
36
provenance/Android.bp
Normal file
@@ -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",
|
||||
],
|
||||
}
|
34
provenance/provenance_metadata_proto/Android.bp
Normal file
34
provenance/provenance_metadata_proto/Android.bp
Normal file
@@ -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,
|
||||
},
|
||||
}
|
@@ -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;
|
||||
}
|
112
provenance/provenance_singleton.go
Normal file
112
provenance/provenance_singleton.go
Normal file
@@ -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
|
||||
}
|
48
provenance/provenance_singleton_test.go
Normal file
48
provenance/provenance_singleton_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
47
provenance/tools/Android.bp
Normal file
47
provenance/tools/Android.bp
Normal file
@@ -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"],
|
||||
}
|
66
provenance/tools/gen_provenance_metadata.py
Normal file
66
provenance/tools/gen_provenance_metadata.py
Normal file
@@ -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:])
|
125
provenance/tools/gen_provenance_metadata_test.py
Normal file
125
provenance/tools/gen_provenance_metadata_test.py
Normal file
@@ -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)
|
Reference in New Issue
Block a user