From 340ee8e699b3ff4fa3b02c64b5582e8c9f07fd0f Mon Sep 17 00:00:00 2001 From: Wei Li Date: Fri, 18 Mar 2022 17:33:24 -0700 Subject: [PATCH] Export provenance metadata for prebuilt APKs and APEXes. Bug: 217434690 Test: atest --host gen_provenance_metadata_test Test: m provenance_metadata Change-Id: I91c184b6e6fe5ccfc3fc65b55b09e7a3da9502a0 --- apex/Android.bp | 1 + apex/apex_test.go | 28 +++- apex/prebuilt.go | 9 +- cmd/soong_build/Android.bp | 1 + java/Android.bp | 1 + java/app_import.go | 9 ++ java/app_import_test.go | 149 +++++++++++++----- provenance/Android.bp | 36 +++++ .../provenance_metadata_proto/Android.bp | 34 ++++ .../provenance_metadata.proto | 47 ++++++ provenance/provenance_singleton.go | 112 +++++++++++++ provenance/provenance_singleton_test.go | 48 ++++++ provenance/tools/Android.bp | 47 ++++++ provenance/tools/gen_provenance_metadata.py | 66 ++++++++ .../tools/gen_provenance_metadata_test.py | 125 +++++++++++++++ 15 files changed, 672 insertions(+), 41 deletions(-) create mode 100644 provenance/Android.bp create mode 100644 provenance/provenance_metadata_proto/Android.bp create mode 100644 provenance/provenance_metadata_proto/provenance_metadata.proto create mode 100644 provenance/provenance_singleton.go create mode 100644 provenance/provenance_singleton_test.go create mode 100644 provenance/tools/Android.bp create mode 100644 provenance/tools/gen_provenance_metadata.py create mode 100644 provenance/tools/gen_provenance_metadata_test.py diff --git a/apex/Android.bp b/apex/Android.bp index b9b54286b..41224ecd5 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -14,6 +14,7 @@ bootstrap_go_package { "soong-cc", "soong-filesystem", "soong-java", + "soong-provenance", "soong-python", "soong-rust", "soong-sh", diff --git a/apex/apex_test.go b/apex/apex_test.go index 5706a2c76..3e01f2608 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -3889,7 +3889,7 @@ func TestVndkApexWithBinder32(t *testing.T) { }), withBinder32bit, withTargets(map[android.OsType][]android.Target{ - android.Android: []android.Target{ + android.Android: { {Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""}, }, @@ -4570,12 +4570,20 @@ func TestPrebuilt(t *testing.T) { } `) - prebuilt := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt) + testingModule := ctx.ModuleForTests("myapex", "android_common_myapex") + prebuilt := testingModule.Module().(*Prebuilt) expectedInput := "myapex-arm64.apex" if prebuilt.inputApex.String() != expectedInput { t.Errorf("inputApex invalid. expected: %q, actual: %q", expectedInput, prebuilt.inputApex.String()) } + android.AssertStringDoesContain(t, "Invalid provenance metadata file", + prebuilt.ProvenanceMetaDataFile().String(), "soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto") + rule := testingModule.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "myapex-arm64.apex", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "myapex", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/apex/myapex.apex", rule.Args["install_path"]) } func TestPrebuiltMissingSrc(t *testing.T) { @@ -4595,12 +4603,18 @@ func TestPrebuiltFilenameOverride(t *testing.T) { } `) - p := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt) + testingModule := ctx.ModuleForTests("myapex", "android_common_myapex") + p := testingModule.Module().(*Prebuilt) expected := "notmyapex.apex" if p.installFilename != expected { t.Errorf("installFilename invalid. expected: %q, actual: %q", expected, p.installFilename) } + rule := testingModule.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "myapex-arm.apex", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "myapex", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/apex/notmyapex.apex", rule.Args["install_path"]) } func TestApexSetFilenameOverride(t *testing.T) { @@ -4643,13 +4657,19 @@ func TestPrebuiltOverrides(t *testing.T) { } `) - p := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt").Module().(*Prebuilt) + testingModule := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt") + p := testingModule.Module().(*Prebuilt) expected := []string{"myapex"} actual := android.AndroidMkEntriesForTest(t, ctx, p)[0].EntryMap["LOCAL_OVERRIDES_MODULES"] if !reflect.DeepEqual(actual, expected) { t.Errorf("Incorrect LOCAL_OVERRIDES_MODULES value '%s', expected '%s'", actual, expected) } + rule := testingModule.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "myapex-arm.apex", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex.prebuilt/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "myapex.prebuilt", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/apex/myapex.prebuilt.apex", rule.Args["install_path"]) } func TestPrebuiltApexName(t *testing.T) { diff --git a/apex/prebuilt.go b/apex/prebuilt.go index 158c8046f..187e0df09 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -23,7 +23,7 @@ import ( "android/soong/android" "android/soong/java" - + "android/soong/provenance" "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) @@ -482,6 +482,8 @@ type Prebuilt struct { properties PrebuiltProperties inputApex android.Path + + provenanceMetaDataFile android.OutputPath } type ApexFileProperties struct { @@ -778,9 +780,14 @@ func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) { if p.installable() { p.installedFile = ctx.InstallFile(p.installDir, p.installFilename, p.inputApex, p.compatSymlinks.Paths()...) + p.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, p.inputApex, p.installedFile) } } +func (p *Prebuilt) ProvenanceMetaDataFile() android.OutputPath { + return p.provenanceMetaDataFile +} + // prebuiltApexExtractorModule is a private module type that is only created by the prebuilt_apex // module. It extracts the correct apex to use and makes it available for use by apex_set. type prebuiltApexExtractorModule struct { diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp index e85163e20..72af3e00c 100644 --- a/cmd/soong_build/Android.bp +++ b/cmd/soong_build/Android.bp @@ -25,6 +25,7 @@ blueprint_go_binary { "golang-protobuf-android", "soong", "soong-android", + "soong-provenance", "soong-bp2build", "soong-ui-metrics_proto", ], diff --git a/java/Android.bp b/java/Android.bp index 4bcae4ff7..df0d1eb3d 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -15,6 +15,7 @@ bootstrap_go_package { "soong-dexpreopt", "soong-genrule", "soong-java-config", + "soong-provenance", "soong-python", "soong-remoteexec", "soong-tradefed", diff --git a/java/app_import.go b/java/app_import.go index 3e5f972a4..a1c4d5859 100644 --- a/java/app_import.go +++ b/java/app_import.go @@ -22,6 +22,7 @@ import ( "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/provenance" ) func init() { @@ -57,6 +58,8 @@ type AndroidAppImport struct { installPath android.InstallPath hideApexVariantFromMake bool + + provenanceMetaDataFile android.OutputPath } type AndroidAppImportProperties struct { @@ -343,6 +346,8 @@ func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext if apexInfo.IsForPlatform() { a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile) + artifactPath := android.PathForModuleSrc(ctx, *a.properties.Apk) + a.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, artifactPath, a.installPath) } // TODO: androidmk converter jni libs @@ -368,6 +373,10 @@ func (a *AndroidAppImport) Certificate() Certificate { return a.certificate } +func (a *AndroidAppImport) ProvenanceMetaDataFile() android.OutputPath { + return a.provenanceMetaDataFile +} + var dpiVariantGroupType reflect.Type var archVariantGroupType reflect.Type var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"} diff --git a/java/app_import_test.go b/java/app_import_test.go index efa52c178..8f6c75fa9 100644 --- a/java/app_import_test.go +++ b/java/app_import_test.go @@ -53,6 +53,11 @@ func TestAndroidAppImport(t *testing.T) { if expected != signingFlag { t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) } + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"]) } func TestAndroidAppImport_NoDexPreopt(t *testing.T) { @@ -74,6 +79,12 @@ func TestAndroidAppImport_NoDexPreopt(t *testing.T) { variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil { t.Errorf("dexpreopt shouldn't have run.") } + + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"]) } func TestAndroidAppImport_Presigned(t *testing.T) { @@ -102,6 +113,12 @@ func TestAndroidAppImport_Presigned(t *testing.T) { if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil { t.Errorf("can't find aligning rule") } + + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"]) } func TestAndroidAppImport_SigningLineage(t *testing.T) { @@ -137,6 +154,12 @@ func TestAndroidAppImport_SigningLineage(t *testing.T) { if expected != signingFlag { t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) } + + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"]) } func TestAndroidAppImport_SigningLineageFilegroup(t *testing.T) { @@ -163,6 +186,12 @@ func TestAndroidAppImport_SigningLineageFilegroup(t *testing.T) { if expected != signingFlag { t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) } + + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"]) } func TestAndroidAppImport_DefaultDevCert(t *testing.T) { @@ -192,6 +221,12 @@ func TestAndroidAppImport_DefaultDevCert(t *testing.T) { if expected != signingFlag { t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) } + + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"]) } func TestAndroidAppImport_DpiVariants(t *testing.T) { @@ -214,40 +249,46 @@ func TestAndroidAppImport_DpiVariants(t *testing.T) { } ` testCases := []struct { - name string - aaptPreferredConfig *string - aaptPrebuiltDPI []string - expected string + name string + aaptPreferredConfig *string + aaptPrebuiltDPI []string + expected string + expectedProvenanceMetaDataArtifactPath string }{ { - name: "no preferred", - aaptPreferredConfig: nil, - aaptPrebuiltDPI: []string{}, - expected: "verify_uses_libraries/apk/app.apk", + name: "no preferred", + aaptPreferredConfig: nil, + aaptPrebuiltDPI: []string{}, + expected: "verify_uses_libraries/apk/app.apk", + expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app.apk", }, { - name: "AAPTPreferredConfig matches", - aaptPreferredConfig: proptools.StringPtr("xhdpi"), - aaptPrebuiltDPI: []string{"xxhdpi", "ldpi"}, - expected: "verify_uses_libraries/apk/app_xhdpi.apk", + name: "AAPTPreferredConfig matches", + aaptPreferredConfig: proptools.StringPtr("xhdpi"), + aaptPrebuiltDPI: []string{"xxhdpi", "ldpi"}, + expected: "verify_uses_libraries/apk/app_xhdpi.apk", + expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xhdpi.apk", }, { - name: "AAPTPrebuiltDPI matches", - aaptPreferredConfig: proptools.StringPtr("mdpi"), - aaptPrebuiltDPI: []string{"xxhdpi", "xhdpi"}, - expected: "verify_uses_libraries/apk/app_xxhdpi.apk", + name: "AAPTPrebuiltDPI matches", + aaptPreferredConfig: proptools.StringPtr("mdpi"), + aaptPrebuiltDPI: []string{"xxhdpi", "xhdpi"}, + expected: "verify_uses_libraries/apk/app_xxhdpi.apk", + expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xxhdpi.apk", }, { - name: "non-first AAPTPrebuiltDPI matches", - aaptPreferredConfig: proptools.StringPtr("mdpi"), - aaptPrebuiltDPI: []string{"ldpi", "xhdpi"}, - expected: "verify_uses_libraries/apk/app_xhdpi.apk", + name: "non-first AAPTPrebuiltDPI matches", + aaptPreferredConfig: proptools.StringPtr("mdpi"), + aaptPrebuiltDPI: []string{"ldpi", "xhdpi"}, + expected: "verify_uses_libraries/apk/app_xhdpi.apk", + expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xhdpi.apk", }, { - name: "no matches", - aaptPreferredConfig: proptools.StringPtr("mdpi"), - aaptPrebuiltDPI: []string{"ldpi", "xxxhdpi"}, - expected: "verify_uses_libraries/apk/app.apk", + name: "no matches", + aaptPreferredConfig: proptools.StringPtr("mdpi"), + aaptPrebuiltDPI: []string{"ldpi", "xxxhdpi"}, + expected: "verify_uses_libraries/apk/app.apk", + expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app.apk", }, } @@ -270,6 +311,12 @@ func TestAndroidAppImport_DpiVariants(t *testing.T) { if strings.HasSuffix(matches[1], test.expected) { t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1]) } + + provenanceMetaDataRule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", test.expectedProvenanceMetaDataArtifactPath, provenanceMetaDataRule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", provenanceMetaDataRule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", provenanceMetaDataRule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", provenanceMetaDataRule.Args["install_path"]) } } @@ -290,16 +337,25 @@ func TestAndroidAppImport_Filename(t *testing.T) { `) testCases := []struct { - name string - expected string + name string + expected string + onDevice string + expectedArtifactPath string + expectedMetaDataPath string }{ { - name: "foo", - expected: "foo.apk", + name: "foo", + expected: "foo.apk", + onDevice: "/system/app/foo/foo.apk", + expectedArtifactPath: "prebuilts/apk/app.apk", + expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", }, { - name: "bar", - expected: "bar_sample.apk", + name: "bar", + expected: "bar_sample.apk", + onDevice: "/system/app/bar/bar_sample.apk", + expectedArtifactPath: "prebuilts/apk/app.apk", + expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/bar/provenance_metadata.textproto", }, } @@ -316,15 +372,23 @@ func TestAndroidAppImport_Filename(t *testing.T) { t.Errorf("Incorrect LOCAL_INSTALLED_MODULE_STEM value '%s', expected '%s'", actualValues, expectedValues) } + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", test.expectedArtifactPath, rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", test.expectedMetaDataPath, rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", test.name, rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", test.onDevice, rule.Args["install_path"]) } } func TestAndroidAppImport_ArchVariants(t *testing.T) { // The test config's target arch is ARM64. testCases := []struct { - name string - bp string - expected string + name string + bp string + expected string + artifactPath string + metaDataPath string + installPath string }{ { name: "matching arch", @@ -343,7 +407,9 @@ func TestAndroidAppImport_ArchVariants(t *testing.T) { }, } `, - expected: "verify_uses_libraries/apk/app_arm64.apk", + expected: "verify_uses_libraries/apk/app_arm64.apk", + artifactPath: "prebuilts/apk/app_arm64.apk", + installPath: "/system/app/foo/foo.apk", }, { name: "no matching arch", @@ -362,7 +428,9 @@ func TestAndroidAppImport_ArchVariants(t *testing.T) { }, } `, - expected: "verify_uses_libraries/apk/app.apk", + expected: "verify_uses_libraries/apk/app.apk", + artifactPath: "prebuilts/apk/app.apk", + installPath: "/system/app/foo/foo.apk", }, { name: "no matching arch without default", @@ -380,7 +448,9 @@ func TestAndroidAppImport_ArchVariants(t *testing.T) { }, } `, - expected: "", + expected: "", + artifactPath: "prebuilts/apk/app_arm.apk", + installPath: "/system/app/foo/foo.apk", }, } @@ -393,6 +463,8 @@ func TestAndroidAppImport_ArchVariants(t *testing.T) { if variant.Module().Enabled() { t.Error("module should have been disabled, but wasn't") } + rule := variant.MaybeRule("genProvenanceMetaData") + android.AssertDeepEquals(t, "Provenance metadata is not empty", android.TestingBuildParams{}, rule) continue } jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command @@ -403,6 +475,11 @@ func TestAndroidAppImport_ArchVariants(t *testing.T) { if strings.HasSuffix(matches[1], test.expected) { t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1]) } + rule := variant.Rule("genProvenanceMetaData") + android.AssertStringEquals(t, "Invalid input", test.artifactPath, rule.Inputs[0].String()) + android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String()) + android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"]) + android.AssertStringEquals(t, "Invalid args", test.installPath, rule.Args["install_path"]) } } diff --git a/provenance/Android.bp b/provenance/Android.bp new file mode 100644 index 000000000..6fd67aabf --- /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 000000000..7fc47a935 --- /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 000000000..f42aba7a0 --- /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 000000000..ae96e1f70 --- /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 000000000..0f1eae220 --- /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", ":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 000000000..1f959bbf2 --- /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 000000000..b33f9112b --- /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 000000000..2fc04bf12 --- /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