Add support for SBOM generation in soong-only builds

Add an alternative way of collecting metadata in soong-only build, which
is initially done in make.

Bug: 398039178
Test: presubmits
Test: lunch aosp_cf_x86_64_phone-trunk_staging-eng && m && m sbom
Change-Id: I94476db21cf9eac8be7693043f2cd7a2b1bcd8a6
diff --git a/android/Android.bp b/android/Android.bp
index 1cc7ffe..00dc50a 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -97,6 +97,7 @@
         "product_packages_file.go",
         "proto.go",
         "provider.go",
+        "provider_keys.go",
         "raw_files.go",
         "recovery_build_prop.go",
         "register.go",
diff --git a/android/compliance_metadata.go b/android/compliance_metadata.go
index a6dbb8d..7c0ab85 100644
--- a/android/compliance_metadata.go
+++ b/android/compliance_metadata.go
@@ -127,27 +127,32 @@
 // dependencies, built/installed files, etc. It is a wrapper on a map[string]string with some utility
 // methods to get/set properties' values.
 type ComplianceMetadataInfo struct {
-	properties map[string]string
+	properties     map[string]string
+	filesContained []string
 }
 
 type complianceMetadataInfoGob struct {
-	Properties map[string]string
+	Properties     map[string]string
+	FilesContained []string
 }
 
 func NewComplianceMetadataInfo() *ComplianceMetadataInfo {
 	return &ComplianceMetadataInfo{
-		properties: map[string]string{},
+		properties:     map[string]string{},
+		filesContained: make([]string, 0),
 	}
 }
 
 func (m *ComplianceMetadataInfo) ToGob() *complianceMetadataInfoGob {
 	return &complianceMetadataInfoGob{
-		Properties: m.properties,
+		Properties:     m.properties,
+		FilesContained: m.filesContained,
 	}
 }
 
 func (m *ComplianceMetadataInfo) FromGob(data *complianceMetadataInfoGob) {
 	m.properties = data.Properties
+	m.filesContained = data.FilesContained
 }
 
 func (c *ComplianceMetadataInfo) GobEncode() ([]byte, error) {
@@ -169,6 +174,14 @@
 	c.SetStringValue(propertyName, strings.TrimSpace(strings.Join(value, " ")))
 }
 
+func (c *ComplianceMetadataInfo) SetFilesContained(files []string) {
+	c.filesContained = files
+}
+
+func (c *ComplianceMetadataInfo) GetFilesContained() []string {
+	return c.filesContained
+}
+
 func (c *ComplianceMetadataInfo) getStringValue(propertyName string) string {
 	if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) {
 		panic(fmt.Errorf("Unknown metadata property: %s.", propertyName))
@@ -317,8 +330,25 @@
 	makeModulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-modules.csv")
 
 	if !ctx.Config().KatiEnabled() {
-		WriteFileRule(ctx, makeMetadataCsv, "installed_file,module_path,is_soong_module,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,static_libs,whole_static_libs,license_text")
-		WriteFileRule(ctx, makeModulesCsv, "name,module_path,module_class,module_type,static_libs,whole_static_libs,built_files,installed_files")
+		// In soong-only build the installed file list is from android_device module
+		ctx.VisitAllModuleProxies(func(module ModuleProxy) {
+			if androidDeviceInfo, ok := OtherModuleProvider(ctx, module, AndroidDeviceInfoProvider); !ok || !androidDeviceInfo.Main_device {
+				return
+			}
+			if metadataInfo, ok := OtherModuleProvider(ctx, module, ComplianceMetadataProvider); ok {
+				if len(metadataInfo.filesContained) > 0 {
+					csvHeaders := "installed_file,module_path,is_soong_module,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,static_libs,whole_static_libs,license_text"
+					csvContent := make([]string, 0, len(metadataInfo.filesContained)+1)
+					csvContent = append(csvContent, csvHeaders)
+					for _, file := range metadataInfo.filesContained {
+						csvContent = append(csvContent, file+",,Y,,,,,,,")
+					}
+					WriteFileRuleVerbatim(ctx, makeMetadataCsv, strings.Join(csvContent, "\n"))
+					WriteFileRuleVerbatim(ctx, makeModulesCsv, "name,module_path,module_class,module_type,static_libs,whole_static_libs,built_files,installed_files")
+				}
+				return
+			}
+		})
 	}
 
 	// Import metadata from Make and Soong to sqlite3 database
diff --git a/android/provider_keys.go b/android/provider_keys.go
new file mode 100644
index 0000000..60b383f
--- /dev/null
+++ b/android/provider_keys.go
@@ -0,0 +1,24 @@
+// Copyright 2025 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 "github.com/google/blueprint"
+
+// Providers of package filesystem
+type AndroidDeviceInfo struct {
+	Main_device bool
+}
+
+var AndroidDeviceInfoProvider = blueprint.NewProvider[AndroidDeviceInfo]()
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 005dc34..c49e536 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"path/filepath"
 	"slices"
+	"sort"
 	"strings"
 	"sync/atomic"
 
@@ -274,6 +275,31 @@
 	a.setVbmetaPhonyTargets(ctx)
 
 	a.distFiles(ctx)
+
+	android.SetProvider(ctx, android.AndroidDeviceInfoProvider, android.AndroidDeviceInfo{
+		Main_device: android.Bool(a.deviceProps.Main_device),
+	})
+
+	if proptools.String(a.partitionProps.Super_partition_name) != "" {
+		buildComplianceMetadata(ctx, superPartitionDepTag, filesystemDepTag)
+	} else {
+		buildComplianceMetadata(ctx, filesystemDepTag)
+	}
+}
+
+func buildComplianceMetadata(ctx android.ModuleContext, tags ...blueprint.DependencyTag) {
+	filesContained := make([]string, 0)
+	for _, tag := range tags {
+		ctx.VisitDirectDepsProxyWithTag(tag, func(m android.ModuleProxy) {
+			if complianceMetadataInfo, ok := android.OtherModuleProvider(ctx, m, android.ComplianceMetadataProvider); ok {
+				filesContained = append(filesContained, complianceMetadataInfo.GetFilesContained()...)
+			}
+		})
+	}
+	sort.Strings(filesContained)
+
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	complianceMetadataInfo.SetFilesContained(filesContained)
 }
 
 // Returns a list of modules that are installed, which are collected from the dependency
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index effbd65..2bf0d59 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -263,6 +263,11 @@
 		PublicKey:             extractedPublicKey,
 		Output:                output,
 	})
+
+	// Dump compliance metadata
+	if ramdisk := proptools.String(b.properties.Ramdisk_module); ramdisk != "" {
+		buildComplianceMetadata(ctx, bootimgRamdiskDep)
+	}
 }
 
 var BootimgInfoProvider = blueprint.NewProvider[BootimgInfo]()
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index fc480e6..88de217 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -695,6 +695,14 @@
 	}
 
 	f.setVbmetaPartitionProvider(ctx)
+
+	// Dump metadata that can not be done in android/compliance-metadata.go
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	filesContained := make([]string, 0, len(fullInstallPaths))
+	for _, file := range fullInstallPaths {
+		filesContained = append(filesContained, file.FullInstallPath.String())
+	}
+	complianceMetadataInfo.SetFilesContained(filesContained)
 }
 
 func (f *filesystem) fileystemStagingDirTimestamp(ctx android.ModuleContext) android.WritablePath {
@@ -879,13 +887,24 @@
 		builder.Command().Text("mkdir -p").Text(filepath.Dir(dst.String()))
 		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
 		f.appendToEntry(ctx, dst)
-		// Only add the fullInstallPath logic for files in the rebased dir. The root dir
-		// is harder to install to.
-		if strings.HasPrefix(name, rebasedPrefix) {
+		// Add the fullInstallPath logic for files in the rebased dir, and for non-rebased files in "system" partition
+		// the fullInstallPath is changed to "root" which aligns to the behavior in Make.
+		if f.PartitionType() == "system" {
+			installPath := android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), strings.TrimPrefix(name, rebasedPrefix))
+			if !strings.HasPrefix(name, rebasedPrefix) {
+				installPath = android.PathForModuleInPartitionInstall(ctx, "root", name)
+			}
 			*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
-				FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), strings.TrimPrefix(name, rebasedPrefix)),
+				FullInstallPath: installPath,
 				SymlinkTarget:   target,
 			})
+		} else {
+			if strings.HasPrefix(name, rebasedPrefix) {
+				*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+					FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), strings.TrimPrefix(name, rebasedPrefix)),
+					SymlinkTarget:   target,
+				})
+			}
 		}
 	}
 
diff --git a/filesystem/super_image.go b/filesystem/super_image.go
index 5e62fa7..9e4412c 100644
--- a/filesystem/super_image.go
+++ b/filesystem/super_image.go
@@ -183,6 +183,8 @@
 	})
 	ctx.SetOutputFiles([]android.Path{output}, "")
 	ctx.CheckbuildFile(output)
+
+	buildComplianceMetadata(ctx, subImageDepTag)
 }
 
 func (s *superImage) installFileName() string {
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index 9b25e77..d34ae77 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -105,12 +105,14 @@
 					"libgsi":                                    defaultDepCandidateProps(ctx.Config()),
 					"llndk.libraries.txt":                       defaultDepCandidateProps(ctx.Config()),
 					"logpersist.start":                          defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_system":                         defaultDepCandidateProps(ctx.Config()),
 					"update_engine_sideload":                    defaultDepCandidateProps(ctx.Config()),
 					// keep-sorted end
 				},
 				"vendor": {
 					"fs_config_files_vendor":                               defaultDepCandidateProps(ctx.Config()),
 					"fs_config_dirs_vendor":                                defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_vendor":                                    defaultDepCandidateProps(ctx.Config()),
 					generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
 				},
 				"odm": {
@@ -118,34 +120,41 @@
 					// https://cs.android.com/android/_/android/platform/build/+/e4849e87ab660b59a6501b3928693db065ee873b:tools/fs_config/Android.mk;l=34;drc=8d6481b92c4b4e9b9f31a61545b6862090fcc14b;bpv=1;bpt=0
 					"fs_config_files_odm": defaultDepCandidateProps(ctx.Config()),
 					"fs_config_dirs_odm":  defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_odm":      defaultDepCandidateProps(ctx.Config()),
 				},
-				"product": {},
+				"product": {
+					"notice_xml_product": defaultDepCandidateProps(ctx.Config()),
+				},
 				"system_ext": {
 					// VNDK apexes are automatically included.
 					// This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
 					// https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
-					"com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v30":  defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v31":  defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v32":  defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v33":  defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v34":  defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_system_ext": defaultDepCandidateProps(ctx.Config()),
 				},
 				"userdata": {},
 				"system_dlkm": {
 					// these are phony required deps of the phony fs_config_dirs_nonsystem
 					"fs_config_dirs_system_dlkm":  defaultDepCandidateProps(ctx.Config()),
 					"fs_config_files_system_dlkm": defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_system_dlkm":      defaultDepCandidateProps(ctx.Config()),
 					// build props are automatically added to `ALL_DEFAULT_INSTALLED_MODULES`
 					"system_dlkm-build.prop": defaultDepCandidateProps(ctx.Config()),
 				},
 				"vendor_dlkm": {
 					"fs_config_dirs_vendor_dlkm":  defaultDepCandidateProps(ctx.Config()),
 					"fs_config_files_vendor_dlkm": defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_vendor_dlkm":      defaultDepCandidateProps(ctx.Config()),
 					"vendor_dlkm-build.prop":      defaultDepCandidateProps(ctx.Config()),
 				},
 				"odm_dlkm": {
 					"fs_config_dirs_odm_dlkm":  defaultDepCandidateProps(ctx.Config()),
 					"fs_config_files_odm_dlkm": defaultDepCandidateProps(ctx.Config()),
+					"notice_xml_odm_dlkm":      defaultDepCandidateProps(ctx.Config()),
 					"odm_dlkm-build.prop":      defaultDepCandidateProps(ctx.Config()),
 				},
 				"ramdisk":        {},