Generate SBOM of products in Soong.

Bug: 324465531
Test: CIs
Test: m soong-sbom
Change-Id: If76776851d49282829a79bfb1c33f05b8f57de31
diff --git a/android/Android.bp b/android/Android.bp
index 774d24a..ce27241 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -93,6 +93,7 @@
         "register.go",
         "rule_builder.go",
         "sandbox.go",
+        "sbom.go",
         "sdk.go",
         "sdk_version.go",
         "shared_properties.go",
diff --git a/android/sbom.go b/android/sbom.go
new file mode 100644
index 0000000..dd2d2fa
--- /dev/null
+++ b/android/sbom.go
@@ -0,0 +1,100 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// 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 android
+
+import (
+	"io"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	// Command line tool to generate SBOM in Soong
+	genSbom = pctx.HostBinToolVariable("genSbom", "gen_sbom")
+
+	// Command to generate SBOM in Soong.
+	genSbomRule = pctx.AndroidStaticRule("genSbomRule", blueprint.RuleParams{
+		Command:     "rm -rf $out && ${genSbom} --output_file ${out} --metadata ${in} --product_out ${productOut} --soong_out ${soongOut} --build_version \"$$(cat ${buildFingerprintFile})\" --product_mfr \"${productManufacturer}\" --json",
+		CommandDeps: []string{"${genSbom}"},
+	}, "productOut", "soongOut", "buildFingerprintFile", "productManufacturer")
+)
+
+func init() {
+	RegisterSbomSingleton(InitRegistrationContext)
+}
+
+func RegisterSbomSingleton(ctx RegistrationContext) {
+	ctx.RegisterParallelSingletonType("sbom_singleton", sbomSingletonFactory)
+}
+
+// sbomSingleton is used to generate build actions of generating SBOM of products.
+type sbomSingleton struct{}
+
+func sbomSingletonFactory() Singleton {
+	return &sbomSingleton{}
+}
+
+// Generates SBOM of products
+func (this *sbomSingleton) GenerateBuildActions(ctx SingletonContext) {
+	if !ctx.Config().HasDeviceProduct() {
+		return
+	}
+	// Get all METADATA files and add them as implicit input
+	metadataFileListFile := PathForArbitraryOutput(ctx, ".module_paths", "METADATA.list")
+	f, err := ctx.Config().fs.Open(metadataFileListFile.String())
+	if err != nil {
+		panic(err)
+	}
+	b, err := io.ReadAll(f)
+	if err != nil {
+		panic(err)
+	}
+	allMetadataFiles := strings.Split(string(b), "\n")
+	implicits := []Path{metadataFileListFile}
+	for _, path := range allMetadataFiles {
+		implicits = append(implicits, PathForSource(ctx, path))
+	}
+	prodVars := ctx.Config().productVariables
+	buildFingerprintFile := PathForArbitraryOutput(ctx, "target", "product", String(prodVars.DeviceName), "build_fingerprint.txt")
+	implicits = append(implicits, buildFingerprintFile)
+
+	// Add installed_files.stamp as implicit input, which depends on all installed files of the product.
+	installedFilesStamp := PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "installed_files.stamp")
+	implicits = append(implicits, installedFilesStamp)
+
+	metadataDb := PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "compliance-metadata.db")
+	sbomFile := PathForOutput(ctx, "sbom", ctx.Config().DeviceProduct(), "sbom.spdx.json")
+	ctx.Build(pctx, BuildParams{
+		Rule:      genSbomRule,
+		Input:     metadataDb,
+		Implicits: implicits,
+		Output:    sbomFile,
+		Args: map[string]string{
+			"productOut":           filepath.Join(ctx.Config().OutDir(), "target", "product", String(prodVars.DeviceName)),
+			"soongOut":             ctx.Config().soongOutDir,
+			"buildFingerprintFile": buildFingerprintFile.String(),
+			"productManufacturer":  ctx.Config().ProductVariables().ProductManufacturer,
+		},
+	})
+
+	// Phony rule "soong-sbom". "m soong-sbom" to generate product SBOM in Soong.
+	ctx.Build(pctx, BuildParams{
+		Rule:   blueprint.Phony,
+		Inputs: []Path{sbomFile},
+		Output: PathForPhony(ctx, "soong-sbom"),
+	})
+}