// Copyright (C) 2024 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 fsgen

import (
	"crypto/sha256"
	"fmt"
	"path/filepath"
	"slices"
	"strconv"
	"strings"

	"android/soong/android"
	"android/soong/filesystem"
	"android/soong/kernel"

	"github.com/google/blueprint"
	"github.com/google/blueprint/parser"
	"github.com/google/blueprint/proptools"
)

var pctx = android.NewPackageContext("android/soong/fsgen")

func init() {
	registerBuildComponents(android.InitRegistrationContext)
}

func registerBuildComponents(ctx android.RegistrationContext) {
	ctx.RegisterModuleType("soong_filesystem_creator", filesystemCreatorFactory)
	ctx.PreDepsMutators(RegisterCollectFileSystemDepsMutators)
}

type generatedPartitionData struct {
	partitionType string
	moduleName    string
	// supported is true if the module was created successfully, false if there was some problem
	// and the module couldn't be created.
	supported   bool
	handwritten bool
}

type allGeneratedPartitionData []generatedPartitionData

func (d allGeneratedPartitionData) moduleNames() []string {
	var result []string
	for _, data := range d {
		if data.supported {
			result = append(result, data.moduleName)
		}
	}
	return result
}

func (d allGeneratedPartitionData) types() []string {
	var result []string
	for _, data := range d {
		if data.supported {
			result = append(result, data.partitionType)
		}
	}
	return result
}

func (d allGeneratedPartitionData) unsupportedTypes() []string {
	var result []string
	for _, data := range d {
		if !data.supported {
			result = append(result, data.partitionType)
		}
	}
	return result
}

func (d allGeneratedPartitionData) names() []string {
	var result []string
	for _, data := range d {
		if data.supported {
			result = append(result, data.moduleName)
		}
	}
	return result
}

func (d allGeneratedPartitionData) nameForType(ty string) string {
	for _, data := range d {
		if data.supported && data.partitionType == ty {
			return data.moduleName
		}
	}
	return ""
}

func (d allGeneratedPartitionData) typeForName(name string) string {
	for _, data := range d {
		if data.supported && data.moduleName == name {
			return data.partitionType
		}
	}
	return ""
}

func (d allGeneratedPartitionData) isHandwritten(name string) bool {
	for _, data := range d {
		if data.supported && data.moduleName == name {
			return data.handwritten
		}
	}
	return false
}

type filesystemCreatorProps struct {
	Unsupported_partition_types []string `blueprint:"mutated"`

	Vbmeta_module_names    []string `blueprint:"mutated"`
	Vbmeta_partition_names []string `blueprint:"mutated"`

	Boot_image                     string `blueprint:"mutated" android:"path_device_first"`
	Boot_16k_image                 string `blueprint:"mutated" android:"path_device_first"`
	Vendor_boot_image              string `blueprint:"mutated" android:"path_device_first"`
	Vendor_boot_debug_image        string `blueprint:"mutated" android:"path_device_first"`
	Vendor_boot_test_harness_image string `blueprint:"mutated" android:"path_device_first"`
	Vendor_kernel_boot_image       string `blueprint:"mutated" android:"path_device_first"`
	Init_boot_image                string `blueprint:"mutated" android:"path_device_first"`
	Super_image                    string `blueprint:"mutated" android:"path_device_first"`
	Radio_image                    string `blueprint:"mutated" android:"path_device_first"`
	Bootloader                     string `blueprint:"mutated" android:"path_device_first"`
	Tzsw                           string `blueprint:"mutated" android:"path_device_first"`
}

type filesystemCreator struct {
	android.ModuleBase

	properties filesystemCreatorProps
}

func filesystemCreatorFactory() android.Module {
	module := &filesystemCreator{}

	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
	module.AddProperties(&module.properties)
	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
		if !shouldEnableFilesystemCreator(ctx) {
			return
		}
		generatedPrebuiltEtcModuleNames := createPrebuiltEtcModules(ctx)
		avbpubkeyGenerated := createAvbpubkeyModule(ctx)
		createFsGenState(ctx, generatedPrebuiltEtcModuleNames, avbpubkeyGenerated)
		module.createAvbKeyFilegroups(ctx)
		module.createMiscFilegroups(ctx)
		module.createInternalModules(ctx)
		module.createBackgroundPicturesForRecovery(ctx)
	})

	return module
}

func shouldEnableFilesystemCreator(ctx android.ConfigContext) bool {
	if ctx.Config().HasUnbundledBuildApps() || ctx.Config().UnbundledBuild() {
		// unbundled builds don't build a device. The android_device's dist artifacts
		// would conflict with the dist artifacts from the unbundled singleton.
		return false
	}
	// We create the filsystem modules even if soong-only mode isn't enabled, so we at least
	// get analysis time checks running everywhere for more real-world coverage.
	return true
}

// This is a build process. It must read all configuration values.
func (f *filesystemCreator) UseGenericConfig() bool {
	return false
}

func generatedPartitions(ctx android.EarlyModuleContext) allGeneratedPartitionData {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse

	var result allGeneratedPartitionData
	addGenerated := func(ty string) {
		result = append(result, generatedPartitionData{
			partitionType: ty,
			moduleName:    generatedModuleNameForPartition(ctx.Config(), ty),
			supported:     true,
		})
	}
	// Always create system and system_ext partition due to still need it for finding deps for vendor or product partition.
	if ctx.Config().UseSoongSystemImage() {
		if ctx.Config().SoongDefinedSystemImage() == "" {
			panic("PRODUCT_SOONG_DEFINED_SYSTEM_IMAGE must be set if USE_SOONG_DEFINED_SYSTEM_IMAGE is true")
		}
		result = append(result, generatedPartitionData{
			partitionType: "system",
			moduleName:    ctx.Config().SoongDefinedSystemImage(),
			supported:     true,
			handwritten:   true,
		})
	} else {
		addGenerated("system")
	}
	// Auto generate system_ext filesystem if SystemExtPath() is 'system_ext' the partition will not needed if its value is 'system/system_ext'.
	if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
		addGenerated("system_ext")
	}
	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
		addGenerated("vendor")
	}
	if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
		addGenerated("product")
	}
	if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
		addGenerated("odm")
	}
	if ctx.DeviceConfig().BuildingUserdataImage() && ctx.DeviceConfig().UserdataPath() == "data" {
		addGenerated("userdata")
	}
	if partitionVars.BuildingSystemDlkmImage {
		addGenerated("system_dlkm")
	}
	if partitionVars.BuildingVendorDlkmImage {
		addGenerated("vendor_dlkm")
	}
	if partitionVars.BuildingOdmDlkmImage {
		addGenerated("odm_dlkm")
	}
	if partitionVars.BuildingRamdiskImage {
		addGenerated("ramdisk")
	}
	if buildingVendorBootImage(partitionVars) {
		addGenerated("vendor_ramdisk")
	}
	if buildingVendorRamdiskFragmentDlkm(ctx, partitionVars) {
		addGenerated("vendor_ramdisk_fragment_dlkm")
	}
	if buildingDebugVendorBootImage(partitionVars) {
		addGenerated("vendor_ramdisk-debug")
		addGenerated("vendor_ramdisk-test-harness")
	}
	if buildingDebugRamdiskImage(partitionVars) {
		addGenerated("debug_ramdisk")
		addGenerated("test_harness_ramdisk")
	}
	if buildingVendorKernelBootImage(partitionVars) {
		addGenerated("vendor_kernel_ramdisk")
	}

	if ctx.DeviceConfig().BuildingRecoveryImage() && ctx.DeviceConfig().RecoveryPath() == "recovery" {
		addGenerated("recovery")
	}
	return result
}

func (f *filesystemCreator) createInternalModules(ctx android.LoadHookContext) {
	partitions := generatedPartitions(ctx)
	for i := range partitions {
		f.createPartition(ctx, partitions, &partitions[i])
	}
	// Create android_info.prop
	f.createAndroidInfo(ctx)

	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	dtbImg := createDtbImgFilegroup(ctx)

	if buildingBootImage(partitionVars) {
		if createBootImage(ctx, dtbImg) {
			f.properties.Boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "boot")
		} else {
			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "boot")
		}
	}
	if buildingVendorBootImage(partitionVars) {
		if createVendorBootImage(ctx, dtbImg) {
			f.properties.Vendor_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "vendor_boot")
		} else {
			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "vendor_boot")
		}
	}
	if buildingDebugVendorBootImage(partitionVars) {
		if createVendorBootDebugImage(ctx, dtbImg) {
			f.properties.Vendor_boot_debug_image = ":" + generatedModuleNameForPartition(ctx.Config(), "vendor_boot-debug")
		} else {
			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "vendor_boot-debug")
		}
		if createVendorBootTestHarnessImage(ctx, dtbImg) {
			f.properties.Vendor_boot_test_harness_image = ":" + generatedModuleNameForPartition(ctx.Config(), "vendor_boot-test-harness")
		} else {
			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "vendor_boot-test-harness")
		}

	}

	if buildingVendorKernelBootImage(partitionVars) {
		createVendorKernelBootImage(ctx, dtbImg)
		f.properties.Vendor_kernel_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "vendor_kernel_boot")
	} else {
		f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "vendor_kernel_boot")
	}

	if buildingInitBootImage(partitionVars) {
		if createInitBootImage(ctx) {
			f.properties.Init_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "init_boot")
		} else {
			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "init_boot")
		}
	}
	if partitionVars.BoardKernelPath16k != "" {
		if createBootImage16k(ctx) {
			f.properties.Boot_16k_image = ":" + generatedModuleNameForPartition(ctx.Config(), "boot_16k")
		} else {
			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "boot_16k")
		}
	}

	var systemOtherImageName string
	if buildingSystemOtherImage(partitionVars) {
		systemModule := partitions.nameForType("system")
		systemOtherImageName = generatedModuleNameForPartition(ctx.Config(), "system_other")
		ctx.CreateModule(
			filesystem.SystemOtherImageFactory,
			&filesystem.SystemOtherImageProperties{
				System_image:                    &systemModule,
				Preinstall_dexpreopt_files_from: partitions.moduleNames(),
			},
			&struct {
				Name *string
			}{
				Name: proptools.StringPtr(systemOtherImageName),
			},
		)
	}

	if pvmfwProperties := getPvmfwProperties(ctx); pvmfwProperties != nil {
		// Add this to soongGeneratedPartitions. fsgen uses this to create the deps of vbmeta images.
		// vbmeta_system of some products chain pvmfw, and adding this
		// here ensures that this chain will be present in the autogenerated vbmeta images.
		partitions = append(partitions,
			generatedPartitionData{
				partitionType: "pvmfw",
				moduleName:    strings.TrimPrefix(*pvmfwProperties.Image, ":"),
				supported:     true,
				handwritten:   true,
			})
	}
	if tzsw, ok := f.createTzsw(ctx); ok {
		// Add this to soongGeneratedPartitions. fsgen uses this to create the deps of vbmeta images.
		// vbmeta_system of some products chain pvmfw, and adding this
		// here ensures that this chain will be present in the autogenerated vbmeta images.
		partitions = append(partitions,
			generatedPartitionData{
				partitionType: "tzsw",
				moduleName:    tzsw,
				supported:     true,
				handwritten:   false,
			})
		f.properties.Tzsw = tzsw
	}

	if radioImgModuleName := createRadioImg(ctx); radioImgModuleName != "" {
		f.properties.Radio_image = radioImgModuleName
	}
	if bootloader, ok := f.createBootloader(ctx); ok {
		f.properties.Bootloader = bootloader
	}

	for _, x := range f.createVbmetaPartitions(ctx, partitions) {
		f.properties.Vbmeta_module_names = append(f.properties.Vbmeta_module_names, x.moduleName)
		f.properties.Vbmeta_partition_names = append(f.properties.Vbmeta_partition_names, x.partitionName)
	}

	var superImageSubpartitions []string
	if buildingSuperImage(partitionVars) {
		superImageSubpartitions = createSuperImage(ctx, partitions, partitionVars, systemOtherImageName)
		f.properties.Super_image = ":" + generatedModuleNameForPartition(ctx.Config(), "super")
	} else if partitionVars.ProductUseDynamicPartitions {
		createSuperImage(ctx, partitions, partitionVars, systemOtherImageName)
	}

	ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions = partitions
	f.createDeviceModule(ctx, partitions, f.properties.Vbmeta_module_names, superImageSubpartitions)
}

func generatedModuleName(cfg android.Config, suffix string) string {
	prefix := "soong"
	if cfg.HasDeviceProduct() {
		prefix = cfg.DeviceProduct()
	}
	return fmt.Sprintf("%s_generated_%s", prefix, suffix)
}

func generatedModuleNameForPartition(cfg android.Config, partitionType string) string {
	return generatedModuleName(cfg, fmt.Sprintf("%s_image", partitionType))
}

func buildingSystemImage(partitionVars android.PartitionVariables) bool {
	return partitionVars.PartitionQualifiedVariables["system"].BuildingImage
}

func buildingSystemExtImage(partitionVars android.PartitionVariables) bool {
	return partitionVars.PartitionQualifiedVariables["system_ext"].BuildingImage
}

func buildingSystemOtherImage(partitionVars android.PartitionVariables) bool {
	// TODO: Recreate this logic from make instead of just depending on the final result variable:
	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=429;drc=15a0df840e7093f65518003ab80cf24a3d9e8e6a
	return partitionVars.BuildingSystemOtherImage
}

func (f *filesystemCreator) createBootloader(ctx android.LoadHookContext) (string, bool) {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	bootloaderPath := partitionVars.PrebuiltBootloader
	if filePath := android.ExistentPathForSource(ctx, partitionVars.BootloaderFilePath, "bootloader.img"); filePath.Valid() {
		bootloaderPath = filePath.String()
	}
	if len(bootloaderPath) == 0 {
		return "", false
	}

	bootloaderModuleName := generatedModuleName(ctx.Config(), "bootloader")
	bootloaderProps := filesystem.PrebuiltBootloaderProperties{
		Src:               proptools.StringPtr(bootloaderPath),
		Ab_ota_partitions: partitionVars.AbOtaBootloaderPartitions,
		Unpack_tool:       proptools.StringPtr(fmt.Sprintf("vendor/google_devices/%s/prebuilts/misc_bins/fbimg/fbpacktool.py", proptools.String(ctx.Config().ProductVariables().BoardPlatform))),
	}
	ctx.CreateModuleInDirectory(filesystem.PrebuiltBootloaderFactory, ".",
		&struct {
			Name *string
		}{
			Name: &bootloaderModuleName,
		},
		&bootloaderProps)
	return bootloaderModuleName, true
}

func (f *filesystemCreator) createTzsw(ctx android.LoadHookContext) (string, bool) {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	if len(partitionVars.BoardPrebuiltTzswImagePath) == 0 {
		return "", false
	}

	tzswModuleName := generatedModuleName(ctx.Config(), "tzsw")
	props := filesystem.PrebuiltTzswProperties{
		Src: proptools.StringPtr(partitionVars.BoardPrebuiltTzswImagePath),
	}
	ctx.CreateModuleInDirectory(filesystem.PrebuiltTzswFactory, ".",
		&struct {
			Name *string
		}{
			Name: &tzswModuleName,
		},
		&props)
	return tzswModuleName, true
}

func (f *filesystemCreator) createReleaseToolsFilegroup(ctx android.LoadHookContext) (string, bool) {
	releaseToolsDir := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ReleaseToolsExtensionDir
	if releaseToolsDir == "" {
		return "", false
	}

	releaseToolsFilegroupName := generatedModuleName(ctx.Config(), "releasetools")
	filegroupProps := &struct {
		Name       *string
		Srcs       []string
		Visibility []string
	}{
		Name:       proptools.StringPtr(releaseToolsFilegroupName),
		Srcs:       []string{"releasetools.py"},
		Visibility: []string{"//visibility:public"},
	}
	ctx.CreateModuleInDirectory(android.FileGroupFactory, releaseToolsDir, filegroupProps)
	return releaseToolsFilegroupName, true
}

func (f *filesystemCreator) createFastbootInfoFilegroup(ctx android.LoadHookContext) (string, bool) {
	fastbootInfoFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardFastbootInfoFile
	if fastbootInfoFile == "" {
		return "", false
	}

	fastbootInfoFilegroupName := generatedModuleName(ctx.Config(), "fastboot")
	filegroupProps := &struct {
		Name       *string
		Srcs       []string
		Visibility []string
	}{
		Name:       proptools.StringPtr(fastbootInfoFilegroupName),
		Srcs:       []string{fastbootInfoFile},
		Visibility: []string{"//visibility:public"},
	}
	ctx.CreateModuleInDirectory(android.FileGroupFactory, ".", filegroupProps)
	return fastbootInfoFilegroupName, true
}

func (f *filesystemCreator) createDeviceModule(
	ctx android.LoadHookContext,
	partitions allGeneratedPartitionData,
	vbmetaPartitions []string,
	superImageSubPartitions []string,
) {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse

	var vendorBlobsLicenseProp *string
	if partitionVars.VendorBlobsLicense != "" {
		vendorBlobsLicenceFilegroupName := generatedModuleName(ctx.Config(), "vendor_blobs_license")
		filegroupProps := &struct {
			Name       *string
			Srcs       []string
			Visibility []string
		}{
			Name:       proptools.StringPtr(vendorBlobsLicenceFilegroupName),
			Srcs:       []string{filepath.Base(partitionVars.VendorBlobsLicense)},
			Visibility: []string{"//build/soong/fsgen:__subpackages__"},
		}
		ctx.CreateModuleInDirectory(android.FileGroupFactory, filepath.Dir(partitionVars.VendorBlobsLicense), filegroupProps)
		vendorBlobsLicenseProp = proptools.StringPtr(":" + vendorBlobsLicenceFilegroupName)
	}

	baseProps := &struct {
		Name *string
	}{
		Name: proptools.StringPtr(generatedModuleName(ctx.Config(), "device")),
	}

	// Currently, only the system and system_ext partition module is created.
	partitionProps := &filesystem.PartitionNameProperties{}
	infoPartitionProps := &filesystem.PartitionNameProperties{}
	if f.properties.Super_image != "" {
		partitionProps.Super_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "super"))
	} else if partitionVars.ProductUseDynamicPartitions {
		infoPartitionProps.Super_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "super"))
	}
	if modName := partitions.nameForType("system"); modName != "" && !android.InList("system", superImageSubPartitions) {
		if buildingSystemImage(partitionVars) {
			partitionProps.System_partition_name = proptools.StringPtr(modName)
		} else {
			infoPartitionProps.System_partition_name = proptools.StringPtr(modName)
		}
	}
	if modName := partitions.nameForType("system_ext"); modName != "" && !android.InList("system_ext", superImageSubPartitions) {
		if buildingSystemExtImage(partitionVars) {
			partitionProps.System_ext_partition_name = proptools.StringPtr(modName)
		} else {
			infoPartitionProps.System_ext_partition_name = proptools.StringPtr(modName)
		}
	}
	if modName := partitions.nameForType("vendor"); modName != "" && !android.InList("vendor", superImageSubPartitions) {
		partitionProps.Vendor_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("product"); modName != "" && !android.InList("product", superImageSubPartitions) {
		partitionProps.Product_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("odm"); modName != "" && !android.InList("odm", superImageSubPartitions) {
		partitionProps.Odm_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("userdata"); modName != "" {
		partitionProps.Userdata_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("recovery"); modName != "" && !ctx.DeviceConfig().BoardMoveRecoveryResourcesToVendorBoot() {
		partitionProps.Recovery_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("system_dlkm"); modName != "" && !android.InList("system_dlkm", superImageSubPartitions) {
		partitionProps.System_dlkm_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("vendor_dlkm"); modName != "" && !android.InList("vendor_dlkm", superImageSubPartitions) {
		partitionProps.Vendor_dlkm_partition_name = proptools.StringPtr(modName)
	}
	if modName := partitions.nameForType("odm_dlkm"); modName != "" && !android.InList("odm_dlkm", superImageSubPartitions) {
		partitionProps.Odm_dlkm_partition_name = proptools.StringPtr(modName)
	}
	if f.properties.Boot_image != "" {
		partitionProps.Boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "boot"))
	}
	if f.properties.Boot_16k_image != "" {
		partitionProps.Boot_16k_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "boot_16k"))
	}
	if f.properties.Vendor_boot_image != "" {
		partitionProps.Vendor_boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_boot"))
	}
	if f.properties.Vendor_boot_debug_image != "" {
		partitionProps.Vendor_boot_debug_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_boot-debug"))
	}
	if f.properties.Vendor_boot_test_harness_image != "" {
		partitionProps.Vendor_boot_test_harness_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_boot-test-harness"))
	}
	if f.properties.Init_boot_image != "" {
		partitionProps.Init_boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "init_boot"))
	} else if partitionVars.BuildingRamdiskImage {
		partitionProps.Ramdisk_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "ramdisk"))
	}

	if f.properties.Vendor_kernel_boot_image != "" {
		partitionProps.Vendor_kernel_boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_kernel_boot"))
	}
	if modName := partitions.nameForType("vendor_kernel_ramdisk"); modName != "" {
		partitionProps.Vendor_kernel_ramdisk_partition_name = proptools.StringPtr(modName)
	}

	partitionProps.Vbmeta_partitions = vbmetaPartitions

	deviceProps := &filesystem.DeviceProperties{
		Main_device:                         proptools.BoolPtr(true),
		Ab_ota_updater:                      proptools.BoolPtr(partitionVars.AbOtaUpdater),
		Ab_ota_partitions:                   partitionVars.AbOtaPartitions,
		Ab_ota_postinstall_config:           partitionVars.AbOtaPostInstallConfig,
		Android_info:                        proptools.StringPtr(generatedModuleName(ctx.Config(), "android_info.prop")),
		Kernel_version:                      ctx.Config().ProductVariables().BoardKernelVersion,
		Partial_ota_update_partitions:       partitionVars.BoardPartialOtaUpdatePartitionsList,
		Flash_block_size:                    proptools.StringPtr(partitionVars.BoardFlashBlockSize),
		Bootloader_in_update_package:        proptools.BoolPtr(partitionVars.BootloaderInUpdatePackage),
		Precompiled_sepolicy_without_vendor: proptools.StringPtr(":precompiled_sepolicy_without_vendor"),
		Vendor_blobs_license:                vendorBlobsLicenseProp,
		InfoPartitionProps:                  *infoPartitionProps,
		Minimal_font_footprint:              proptools.BoolPtr(partitionVars.MinimalFontFootprint),
	}

	if buildingInitBootImage(partitionVars) {
		// https://cs.android.com/android/_/android/platform/build/+/045a3d6a3e359633a14853a5a5e1e4f2a11cbdae:core/Makefile;l=6869-6873;drc=a951ebf0198006f7fd38073a05c442d0eb92f97b;bpv=1;bpt=0
		deviceProps.Ramdisk_node_list = proptools.StringPtr(":ramdisk_node_list")
	}

	if f.properties.Bootloader != "" {
		deviceProps.Bootloader = &f.properties.Bootloader
	}
	if releaseTools, ok := f.createReleaseToolsFilegroup(ctx); ok {
		deviceProps.Releasetools_extension = proptools.StringPtr(":" + releaseTools)
	}
	if fastbootInfo, ok := f.createFastbootInfoFilegroup(ctx); ok {
		deviceProps.FastbootInfo = proptools.StringPtr(":" + fastbootInfo)
	}
	if dtboModuleName := getDtboModuleName(ctx); dtboModuleName != "" {
		deviceProps.Dtbo_image = proptools.StringPtr(dtboModuleName)
	}
	if dtbo16kModuleName := getDtbo16kModuleName(ctx); dtbo16kModuleName != "" {
		deviceProps.Dtbo_image_16k = proptools.StringPtr(dtbo16kModuleName)
	}
	if pvmfwProperties := getPvmfwProperties(ctx); pvmfwProperties != nil {
		deviceProps.Pvmfw = *pvmfwProperties
	}
	if f.properties.Tzsw != "" {
		deviceProps.Tzsw = proptools.StringPtr(f.properties.Tzsw)
	}

	if f.properties.Radio_image != "" {
		deviceProps.Radio_partition_name = &f.properties.Radio_image
	}

	ctx.CreateModule(filesystem.AndroidDeviceFactory, baseProps, partitionProps, deviceProps)
}

func createRadioImg(ctx android.LoadHookContext) string {
	radioFilePath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardRadioImagePath
	if radioFilePath == "" {
		return ""
	}
	if path := android.ExistentPathForSource(ctx, radioFilePath); !path.Valid() {
		return ""
	}
	name := generatedModuleNameForPartition(ctx.Config(), "radio")
	radioImgProps := filesystem.PrebuiltRadioImgProperties{
		Src:               proptools.StringPtr(radioFilePath),
		Ab_ota_partitions: ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.AbOtaRadioPartitions,
		Unpack_tool:       proptools.StringPtr(fmt.Sprintf("vendor/google_devices/%s/prebuilts/misc_bins/unpack.py", proptools.String(ctx.Config().ProductVariables().BoardPlatform))),
	}

	ctx.CreateModuleInDirectory(
		filesystem.PrebuiltRadioImgFactory,
		".",
		&struct {
			Name *string
		}{
			Name: &name,
		},
		&radioImgProps,
	)
	return name
}

// Returns pvfmw properties if the product uses pvmfw.
func getPvmfwProperties(ctx android.LoadHookContext) *filesystem.PvmfwProperties {
	if !ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardUsesPvmfwImage {
		return nil
	}
	var partitionSize *int64
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	boardPvmfwPartitionSize := partitionVars.BoardPvmfwPartitionSize
	if boardPvmfwPartitionSize != "" {
		size, err := strconv.ParseInt(boardPvmfwPartitionSize, 0, 64)
		if err != nil {
			ctx.ModuleErrorf("Error parsing BoardPvmfwPartitionSize %s", err)
		}
		partitionSize = proptools.Int64Ptr(size)
	}
	image := "pvmfw_img"
	if partitionVars.BoardPvmfwImagePrebuilt != "" {
		image = partitionVars.BoardPvmfwImagePrebuilt
	}
	bin := "pvmfw_bin"
	if partitionVars.BoardPvmfwBinPrebuilt != "" {
		bin = partitionVars.BoardPvmfwBinPrebuilt
	}
	avbkey := "pvmfw_embedded_key_pub_bin"
	if partitionVars.BoardPvmfwEmbeddedAvbkeyPrebuilt != "" {
		avbkey = partitionVars.BoardPvmfwEmbeddedAvbkeyPrebuilt
	}

	return &filesystem.PvmfwProperties{
		Image:          proptools.StringPtr(":" + image),
		Binary_name:    proptools.StringPtr(bin),
		Avbkey:         proptools.StringPtr(":" + avbkey),
		Partition_size: partitionSize,
	}
}

func createRamdisk16k(ctx android.LoadHookContext) string {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	kernelPath := partitionVars.BoardKernelPath16k
	if kernelPath == "" {
		return ""
	}

	var systemDlkmModulePatterns []string
	for _, path := range partitionVars.SystemKernelModules {
		systemDlkmModulePatterns = append(systemDlkmModulePatterns, filepath.Base(path))
	}

	// Find kernel modules 16k that the debug symbols will be stripped. All debug symbols of the
	// non-GKI modules will be stripped.
	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=1124;drc=a951ebf0198006f7fd38073a05c442d0eb92f97b
	var kernelModules16KWithStrip []string
	for _, kernelModule16k := range partitionVars.BoardKernelModules16K {
		moduleBase := filepath.Base(kernelModule16k)
		if !android.InList(moduleBase, systemDlkmModulePatterns) {
			kernelModules16KWithStrip = append(kernelModules16KWithStrip, kernelModule16k)
		}
	}

	name := generatedModuleNameForPartition(ctx.Config(), "ramdisk_16k")
	props := filesystem.Ramdisk16kImgProperties{
		Srcs:              partitionVars.BoardKernelModules16K,
		Strip_symbol_srcs: kernelModules16KWithStrip,
		Load:              partitionVars.BoardKernelModulesLoad16K,
		Kernel:            proptools.StringPtr(kernelPath),
	}

	ctx.CreateModuleInDirectory(
		filesystem.Ramdisk16kImgFactory,
		".",
		&struct {
			Name *string
		}{
			Name: &name,
		},
		&props,
	)
	return name
}

func partitionSpecificFsProps(ctx android.EarlyModuleContext, partitions allGeneratedPartitionData, fsProps *filesystem.FilesystemProperties, partitionType string) {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	switch partitionType {
	case "system":
		fsProps.Build_logtags = proptools.BoolPtr(true)
		// https://source.corp.google.com/h/googleplex-android/platform/build//639d79f5012a6542ab1f733b0697db45761ab0f3:core/packaging/flags.mk;l=21;drc=5ba8a8b77507f93aa48cc61c5ba3f31a4d0cbf37;bpv=1;bpt=0
		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
		fsProps.Check_vintf = proptools.BoolPtr(true)
		// Identical to that of the aosp_shared_system_image
		if partitionVars.ProductFsverityGenerateMetadata {
			fsProps.Fsverity.Inputs = proptools.NewSimpleConfigurable([]string{
				"etc/boot-image.prof",
				"etc/dirty-image-objects",
				"etc/preloaded-classes",
				"etc/classpaths/*.pb",
				"framework/*",
				"framework/*/*",     // framework/{arch}
				"framework/oat/*/*", // framework/oat/{arch}
			})
			fsProps.Fsverity.Libs = proptools.NewSimpleConfigurable([]string{":framework-res{.export-package.apk}"})
		}
		fsProps.Symlinks = commonSymlinksFromRoot
		fsProps.Symlinks = append(fsProps.Symlinks,
			[]filesystem.SymlinkDefinition{
				{
					Target: proptools.StringPtr("/data/cache"),
					Name:   proptools.StringPtr("cache"),
				},
				{
					Target: proptools.StringPtr("/storage/self/primary"),
					Name:   proptools.StringPtr("sdcard"),
				},
			}...,
		)
		if ctx.DeviceConfig().VendorPath() == "vendor" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/vendor"),
					Name:   proptools.StringPtr("system/vendor"),
				},
			)
		}
		if ctx.DeviceConfig().ProductPath() == "product" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/product"),
					Name:   proptools.StringPtr("system/product"),
				},
			)
		}
		if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/system_ext"),
					Name:   proptools.StringPtr("system/system_ext"),
				},
			)
		}
		if ctx.DeviceConfig().SystemDlkmPath() == "system_dlkm" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/system_dlkm/lib/modules"),
					Name:   proptools.StringPtr("system/lib/modules"),
				},
			)
		}

		fsProps.Base_dir = proptools.StringPtr("system")
		fsProps.Dirs = proptools.NewSimpleConfigurable(commonPartitionDirs)
		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
		fsProps.Stem = proptools.StringPtr("system.img")
	case "system_ext":
		if partitionVars.ProductFsverityGenerateMetadata {
			fsProps.Fsverity.Inputs = proptools.NewSimpleConfigurable([]string{
				"framework/*",
				"framework/*/*",     // framework/{arch}
				"framework/oat/*/*", // framework/oat/{arch}
			})
			fsProps.Fsverity.Libs = proptools.NewSimpleConfigurable([]string{":framework-res{.export-package.apk}"})
		}
		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
		fsProps.Stem = proptools.StringPtr("system_ext.img")
		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
	case "product":
		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
		if systemName := partitions.nameForType("system"); systemName != "" {
			fsProps.Android_filesystem_deps.System = proptools.StringPtr(systemName)
		}
		if systemExtName := partitions.nameForType("system_ext"); systemExtName != "" {
			fsProps.Android_filesystem_deps.System_ext = proptools.StringPtr(systemExtName)
		}
		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
		fsProps.Stem = proptools.StringPtr("product.img")
	case "vendor":
		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
		fsProps.Check_vintf = proptools.BoolPtr(true)
		if ctx.DeviceConfig().OdmPath() == "odm" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/odm"),
					Name:   proptools.StringPtr("odm"),
				},
			)
		}
		if ctx.DeviceConfig().VendorDlkmPath() == "vendor_dlkm" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/vendor_dlkm/lib/modules"),
					Name:   proptools.StringPtr("lib/modules"),
				},
			)
		}
		if systemName := partitions.nameForType("system"); systemName != "" {
			fsProps.Android_filesystem_deps.System = proptools.StringPtr(systemName)
		}
		if systemExtName := partitions.nameForType("system_ext"); systemExtName != "" {
			fsProps.Android_filesystem_deps.System_ext = proptools.StringPtr(systemExtName)
		}
		if productName := partitions.nameForType("product"); productName != "" {
			fsProps.Android_filesystem_deps.Product = proptools.StringPtr(productName)
		}
		fsProps.Security_patch = proptools.StringPtr(partitionVars.VendorSecurityPatch)
		fsProps.Stem = proptools.StringPtr("vendor.img")
	case "odm":
		if ctx.DeviceConfig().OdmDlkmPath() == "odm_dlkm" {
			fsProps.Symlinks = append(fsProps.Symlinks,
				filesystem.SymlinkDefinition{
					Target: proptools.StringPtr("/odm_dlkm/lib/modules"),
					Name:   proptools.StringPtr("lib/modules"),
				},
			)
		}
		fsProps.Security_patch = proptools.StringPtr(partitionVars.OdmSecurityPatch)
		fsProps.Stem = proptools.StringPtr("odm.img")
	case "userdata":
		fsProps.Stem = proptools.StringPtr("userdata.img")
		if vars, ok := partitionVars.PartitionQualifiedVariables["userdata"]; ok {
			parsed, err := strconv.ParseInt(vars.BoardPartitionSize, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("Partition size must be an int, got %s", vars.BoardPartitionSize))
			}
			fsProps.Partition_size = &parsed
			// Disable avb for userdata partition
			fsProps.Use_avb = nil
		}
		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2265;drc=7f50a123045520f2c5e18e9eb4e83f92244a1459
		if s, err := strconv.ParseBool(partitionVars.ProductFsCasefold); err == nil {
			fsProps.Support_casefolding = proptools.BoolPtr(s)
		} else if len(partitionVars.ProductFsCasefold) > 0 {
			ctx.ModuleErrorf("Unrecognized PRODUCT_FS_CASEFOLD value %s", partitionVars.ProductFsCasefold)
		}
		if s, err := strconv.ParseBool(partitionVars.ProductQuotaProjid); err == nil {
			fsProps.Support_project_quota = proptools.BoolPtr(s)
		} else if len(partitionVars.ProductQuotaProjid) > 0 {
			ctx.ModuleErrorf("Unrecognized PRODUCT_QUOTA_PROJID value %s", partitionVars.ProductQuotaProjid)
		}
		if s, err := strconv.ParseBool(partitionVars.ProductFsCompression); err == nil {
			fsProps.Enable_compression = proptools.BoolPtr(s)
		} else if len(partitionVars.ProductFsCompression) > 0 {
			ctx.ModuleErrorf("Unrecognized PRODUCT_FS_COMPRESSION value %s", partitionVars.ProductFsCompression)
		}

	case "ramdisk":
		// Following the logic in https://cs.android.com/android/platform/superproject/main/+/c3c5063df32748a8806ce5da5dd0db158eab9ad9:build/make/core/Makefile;l=1307
		fsProps.Dirs = android.NewSimpleConfigurable([]string{
			"debug_ramdisk",
			"dev",
			"metadata",
			"mnt",
			"proc",
			"second_stage_resources",
			"sys",
		})
		if partitionVars.BoardUsesGenericKernelImage {
			fsProps.Dirs.AppendSimpleValue([]string{
				"first_stage_ramdisk/debug_ramdisk",
				"first_stage_ramdisk/dev",
				"first_stage_ramdisk/metadata",
				"first_stage_ramdisk/mnt",
				"first_stage_ramdisk/proc",
				"first_stage_ramdisk/second_stage_resources",
				"first_stage_ramdisk/sys",
			})
		}
		fsProps.Stem = proptools.StringPtr("ramdisk.img")
	case "recovery":
		dirs := append(commonPartitionDirs, []string{
			"sdcard",
		}...)

		dirsWithRoot := make([]string, len(dirs))
		for i, dir := range dirs {
			dirsWithRoot[i] = filepath.Join("root", dir)
		}

		fsProps.Dirs = proptools.NewSimpleConfigurable(dirsWithRoot)
		fsProps.Symlinks = symlinksWithNamePrefix(append(commonSymlinksFromRoot, filesystem.SymlinkDefinition{
			Target: proptools.StringPtr("prop.default"),
			Name:   proptools.StringPtr("default.prop"),
		}), "root")
		fsProps.Stem = proptools.StringPtr("recovery.img")
	case "system_dlkm":
		fsProps.Security_patch = proptools.StringPtr(partitionVars.SystemDlkmSecurityPatch)
		fsProps.Stem = proptools.StringPtr("system_dlkm.img")
	case "vendor_dlkm":
		fsProps.Security_patch = proptools.StringPtr(partitionVars.VendorDlkmSecurityPatch)
		fsProps.Stem = proptools.StringPtr("vendor_dlkm.img")
	case "odm_dlkm":
		fsProps.Security_patch = proptools.StringPtr(partitionVars.OdmDlkmSecurityPatch)
		fsProps.Stem = proptools.StringPtr("odm_dlkm.img")
	case "vendor_ramdisk":
		if recoveryName := partitions.nameForType("recovery"); recoveryName != "" {
			fsProps.Include_files_of = []string{recoveryName}
		}
		fsProps.Stem = proptools.StringPtr("vendor_ramdisk.img")
	case "vendor_ramdisk-debug":
		if recoveryName := partitions.nameForType("recovery"); recoveryName != "" {
			fsProps.Include_files_of = []string{recoveryName}
		}
		fsProps.Include_files_of = append(
			fsProps.Include_files_of,
			generatedModuleNameForPartition(ctx.Config(), "vendor_ramdisk"),
			generatedModuleNameForPartition(ctx.Config(), "debug_ramdisk"),
		)
		fsProps.Stem = proptools.StringPtr("vendor_ramdisk-debug.img")
	case "vendor_ramdisk-test-harness":
		if recoveryName := partitions.nameForType("recovery"); recoveryName != "" {
			fsProps.Include_files_of = []string{recoveryName}
		}
		fsProps.Include_files_of = append(
			fsProps.Include_files_of,
			generatedModuleNameForPartition(ctx.Config(), "vendor_ramdisk"),
			generatedModuleNameForPartition(ctx.Config(), "debug_ramdisk"),
			generatedModuleNameForPartition(ctx.Config(), "test_harness_ramdisk"),
		)
		fsProps.Stem = proptools.StringPtr("vendor_ramdisk-test-harness.img")
	case "vendor_kernel_ramdisk":
		fsProps.Stem = proptools.StringPtr("vendor_kernel_ramdisk.img")
	case "vendor_ramdisk_fragment_dlkm":
		fsProps.Ramdisk_fragment_name = proptools.StringPtr("dlkm")
	}
}

var (
	partitionsWithKernelModules = []string{
		"system_dlkm",
		"vendor_dlkm",
		"odm_dlkm",
		"vendor_ramdisk",
		"vendor_ramdisk-debug",
		"vendor_ramdisk-test-harness",
		"vendor_kernel_ramdisk",
		"vendor_ramdisk_fragment_dlkm",
	}
)

// Creates a soong module to build the given partition.
func (f *filesystemCreator) createPartition(ctx android.LoadHookContext, partitions allGeneratedPartitionData, partition *generatedPartitionData) {
	// Nextgen team's handwritten soong system image, don't need to create anything ourselves
	if partition.partitionType == "system" && ctx.Config().UseSoongSystemImage() {
		return
	}

	baseProps := generateBaseProps(proptools.StringPtr(partition.moduleName), ctx.Config())

	fsProps, supported := generateFsProps(ctx, partitions, partition.partitionType)
	if !supported {
		partition.supported = false
		return
	}

	partitionType := partition.partitionType
	if partitionType == "vendor" || partitionType == "product" || partitionType == "system" {
		fsProps.Linker_config.Gen_linker_config = proptools.BoolPtr(true)
		if partitionType != "system" {
			fsProps.Linker_config.Linker_config_srcs = f.createLinkerConfigSourceFilegroups(ctx, partitionType)
		}
	}

	if android.InList(partitionType, partitionsWithKernelModules) {
		f.createPrebuiltKernelModules(ctx, partitionType)
	}
	if partitionType == "vendor_ramdisk_fragment_dlkm" {
		partitionType = "vendor_ramdisk"
	}

	var module android.Module
	if partitionType == "system" {
		module = ctx.CreateModule(filesystem.SystemImageFactory, baseProps, fsProps)
	} else {
		// Explicitly set the partition.
		fsProps.Partition_type = proptools.StringPtr(partitionType)
		module = ctx.CreateModule(filesystem.FilesystemFactory, baseProps, fsProps)
	}
	module.HideFromMake()
	if partitionType == "vendor" {
		f.createVendorBuildProp(ctx)
	}
}

// Creates filegroups for the files specified in BOARD_(partition_)AVB_KEY_PATH
func (f *filesystemCreator) createAvbKeyFilegroups(ctx android.LoadHookContext) {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	var files []string

	if len(partitionVars.BoardAvbKeyPath) > 0 {
		files = append(files, partitionVars.BoardAvbKeyPath)
	}
	for _, partition := range android.SortedKeys(partitionVars.PartitionQualifiedVariables) {
		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partition]
		if len(specificPartitionVars.BoardAvbKeyPath) > 0 {
			files = append(files, specificPartitionVars.BoardAvbKeyPath)
		}
	}

	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
	for _, file := range files {
		if _, ok := fsGenState.avbKeyFilegroups[file]; ok {
			continue
		}
		if file == "external/avb/test/data/testkey_rsa4096.pem" {
			// There already exists a checked-in filegroup for this commonly-used key, just use that
			fsGenState.avbKeyFilegroups[file] = "avb_testkey_rsa4096"
			continue
		}
		dir := filepath.Dir(file)
		base := filepath.Base(file)
		name := fmt.Sprintf("avb_key_%x", strings.ReplaceAll(file, "/", "_"))
		ctx.CreateModuleInDirectory(
			android.FileGroupFactory,
			dir,
			&struct {
				Name       *string
				Srcs       []string
				Visibility []string
			}{
				Name:       proptools.StringPtr(name),
				Srcs:       []string{base},
				Visibility: []string{"//visibility:public"},
			},
		)
		fsGenState.avbKeyFilegroups[file] = name
	}
}

func (f *filesystemCreator) createBackgroundPicturesForRecovery(ctx android.LoadHookContext) {
	name, width := getRecoveryBackgroundPicturesGeneratorModuleName(ctx)
	if name == "" {
		return
	}
	ctx.CreateModule(
		filesystem.RecoveryBackgroundPicturesFactory,
		&struct {
			Name        *string
			Image_width *int64
			Fonts       []string
			Resources   []string
			Recovery    *bool
		}{
			Name:        proptools.StringPtr(name),
			Image_width: proptools.Int64Ptr(width),
			Fonts:       []string{":recovery_noto-fonts_dep", ":recovery_roboto-fonts_dep"},
			Resources:   []string{":bootable_recovery_resources"},
			Recovery:    proptools.BoolPtr(true),
		},
	)

}

// Creates filegroups for miscellaneous other files
func (f *filesystemCreator) createMiscFilegroups(ctx android.LoadHookContext) {
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse

	if partitionVars.BoardErofsCompressorHints != "" {
		dir := filepath.Dir(partitionVars.BoardErofsCompressorHints)
		base := filepath.Base(partitionVars.BoardErofsCompressorHints)
		ctx.CreateModuleInDirectory(
			android.FileGroupFactory,
			dir,
			&struct {
				Name       *string
				Srcs       []string
				Visibility []string
			}{
				Name:       proptools.StringPtr("soong_generated_board_erofs_compress_hints_filegroup"),
				Srcs:       []string{base},
				Visibility: []string{"//visibility:public"},
			},
		)
	}
}

// createPrebuiltKernelModules creates `prebuilt_kernel_modules`. These modules will be added to deps of the
// autogenerated *_dlkm filsystem modules. Each _dlkm partition should have a single prebuilt_kernel_modules dependency.
// This ensures that the depmod artifacts (modules.* installed in /lib/modules/) are generated with a complete view.
func (f *filesystemCreator) createPrebuiltKernelModules(ctx android.LoadHookContext, partitionType string) {
	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
	name := generatedModuleName(ctx.Config(), fmt.Sprintf("%s-kernel-modules", partitionType))
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	props := &struct {
		Name                  *string
		Srcs                  []string
		Src_filenames_to_load []string
		Srcs_16k              []string
		System_deps           []string
		System_dlkm_specific  *bool
		Vendor_dlkm_specific  *bool
		Odm_dlkm_specific     *bool
		Vendor_ramdisk        *bool
		Vendor_kernel_ramdisk *bool
		Load_by_default       *bool
		Blocklist_file        *string
		Options_file          *string
		Strip_debug_symbols   *bool
	}{
		Name: proptools.StringPtr(name),
	}
	switch partitionType {
	case "system_dlkm":
		props.Srcs = android.ExistentPathsForSources(ctx, partitionVars.SystemKernelModules).Strings()
		props.System_dlkm_specific = proptools.BoolPtr(true)
		if len(partitionVars.SystemKernelLoadModules) == 0 {
			// Create empty modules.load file for system
			// https://source.corp.google.com/h/googleplex-android/platform/build/+/ef55daac9954896161b26db4f3ef1781b5a5694c:core/Makefile;l=695-700;drc=549fe2a5162548bd8b47867d35f907eb22332023;bpv=1;bpt=0
			props.Load_by_default = proptools.BoolPtr(false)
		}
		if blocklistFile := partitionVars.SystemKernelBlocklistFile; blocklistFile != "" {
			props.Blocklist_file = proptools.StringPtr(blocklistFile)
		}
		props.Strip_debug_symbols = proptools.BoolPtr(false)
	case "vendor_dlkm":
		props.Srcs = android.ExistentPathsForSources(ctx, partitionVars.VendorKernelModules).Strings()
		props.Src_filenames_to_load = partitionVars.VendorKernelModulesLoad
		props.Srcs_16k = android.ExistentPathsForSources(ctx, partitionVars.VendorKernelModules2ndStage16kbMode).Strings()
		if len(partitionVars.SystemKernelModules) > 0 {
			props.System_deps = []string{":" + generatedModuleName(ctx.Config(), "system_dlkm-kernel-modules") + "{.modules}"}
		}
		props.Vendor_dlkm_specific = proptools.BoolPtr(true)
		if blocklistFile := partitionVars.VendorKernelBlocklistFile; blocklistFile != "" {
			props.Blocklist_file = proptools.StringPtr(blocklistFile)
		}
		if partitionVars.DoNotStripVendorModules {
			props.Strip_debug_symbols = proptools.BoolPtr(false)
		}
	case "odm_dlkm":
		props.Srcs = android.ExistentPathsForSources(ctx, partitionVars.OdmKernelModules).Strings()
		props.Odm_dlkm_specific = proptools.BoolPtr(true)
		if blocklistFile := partitionVars.OdmKernelBlocklistFile; blocklistFile != "" {
			props.Blocklist_file = proptools.StringPtr(blocklistFile)
		}
		props.Strip_debug_symbols = proptools.BoolPtr(false)
	case "vendor_ramdisk", "vendor_ramdisk-debug", "vendor_ramdisk-test-harness", "vendor_ramdisk_fragment_dlkm":
		if partitionType == "vendor_ramdisk" && buildingVendorRamdiskFragmentDlkm(ctx, partitionVars) {
			// Skip including the kernel modules in vendor_ramdisk.
			// The kernel modules will come from the dlkm ramdisk fragment.
		} else {
			props.Srcs = android.ExistentPathsForSources(ctx, partitionVars.VendorRamdiskKernelModules).Strings()
		}
		props.Vendor_ramdisk = proptools.BoolPtr(true)
		if blocklistFile := partitionVars.VendorRamdiskKernelBlocklistFile; blocklistFile != "" {
			props.Blocklist_file = proptools.StringPtr(blocklistFile)
		}
		if optionsFile := partitionVars.VendorRamdiskKernelOptionsFile; optionsFile != "" {
			props.Options_file = proptools.StringPtr(optionsFile)
		}
		if partitionVars.DoNotStripVendorRamdiskModules {
			props.Strip_debug_symbols = proptools.BoolPtr(false)
		}
	case "vendor_kernel_ramdisk":
		props.Srcs = android.ExistentPathsForSources(ctx, partitionVars.VendorKernelRamdiskKernelModules).Strings()
		props.Vendor_kernel_ramdisk = proptools.BoolPtr(true)
		props.Strip_debug_symbols = proptools.BoolPtr(false)
	default:
		ctx.ModuleErrorf("DLKM is not supported for %s\n", partitionType)
	}

	if len(props.Srcs) == 0 {
		return // do not generate `prebuilt_kernel_modules` if there are no sources
	}

	kernelModule := ctx.CreateModuleInDirectory(
		kernel.PrebuiltKernelModulesFactory,
		".", // create in root directory for now
		props,
	)
	kernelModule.HideFromMake()
	// Add to deps
	(*fsGenState.fsDeps[partitionType])[name] = defaultDepCandidateProps(ctx.Config())
}

// Create an android_info module. This will be used to create /vendor/build.prop
func (f *filesystemCreator) createAndroidInfo(ctx android.LoadHookContext) {
	// Create a android_info for vendor
	// The board info files might be in a directory outside the root soong namespace, so create
	// the module in "."
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	androidInfoProps := &android.AndroidInfoProperties{

		Board_info_files: partitionVars.BoardInfoFiles,
		Stem:             proptools.StringPtr("android-info.txt"),
	}
	if len(androidInfoProps.Board_info_files) == 0 {
		androidInfoProps.Bootloader_board_name = proptools.StringPtr(partitionVars.BootLoaderBoardName)
	}
	androidInfoProp := ctx.CreateModuleInDirectory(
		android.AndroidInfoFactory,
		".",
		&struct {
			Name *string
		}{
			Name: proptools.StringPtr(generatedModuleName(ctx.Config(), "android_info.prop")),
		},
		androidInfoProps,
	)
	androidInfoProp.HideFromMake()
}

func (f *filesystemCreator) createVendorBuildProp(ctx android.LoadHookContext) {
	vendorBuildProps := &struct {
		Name           *string
		Vendor         *bool
		Stem           *string
		Product_config *string
		Android_info   *string
		Licenses       []string
		Dist           android.Dist
	}{
		Name:           proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
		Vendor:         proptools.BoolPtr(true),
		Stem:           proptools.StringPtr("build.prop"),
		Product_config: proptools.StringPtr(":product_config"),
		Android_info:   proptools.StringPtr(":" + generatedModuleName(ctx.Config(), "android_info.prop")),
		Dist: android.Dist{
			Targets: []string{"droidcore-unbundled"},
			Dest:    proptools.StringPtr("build.prop-vendor"),
		},
		Licenses: []string{"Android-Apache-2.0"},
	}
	vendorBuildProp := ctx.CreateModule(
		android.BuildPropFactory,
		vendorBuildProps,
	)
	// We don't want this to conflict with the make-built vendor build.prop, but unfortunately
	// calling HideFromMake() prevents disting files, even in soong-only mode. So only call
	// HideFromMake() on soong+make builds.
	if ctx.Config().KatiEnabled() {
		vendorBuildProp.HideFromMake()
	}
}

func createRecoveryBuildProp(ctx android.LoadHookContext) string {
	moduleName := generatedModuleName(ctx.Config(), "recovery-prop.default")

	var vendorBuildProp *string
	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
		vendorBuildProp = proptools.StringPtr(":" + generatedModuleName(ctx.Config(), "vendor-build.prop"))
	}

	recoveryBuildProps := &struct {
		Name                  *string
		System_build_prop     *string
		Vendor_build_prop     *string
		Odm_build_prop        *string
		Product_build_prop    *string
		System_ext_build_prop *string

		Recovery        *bool
		No_full_install *bool
		Visibility      []string
	}{
		Name:                  proptools.StringPtr(moduleName),
		System_build_prop:     proptools.StringPtr(":system-build.prop"),
		Vendor_build_prop:     vendorBuildProp,
		Odm_build_prop:        proptools.StringPtr(":odm-build.prop"),
		Product_build_prop:    proptools.StringPtr(":product-build.prop"),
		System_ext_build_prop: proptools.StringPtr(":system_ext-build.prop"),

		Recovery:        proptools.BoolPtr(true),
		No_full_install: proptools.BoolPtr(true),
		Visibility:      []string{"//visibility:public"},
	}

	ctx.CreateModule(android.RecoveryBuildPropModuleFactory, recoveryBuildProps)

	return moduleName
}

// createLinkerConfigSourceFilegroups creates filegroup modules to generate linker.config.pb for the following partitions
// 1. vendor: Using PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS (space separated file list)
// 1. product: Using PRODUCT_PRODUCT_LINKER_CONFIG_FRAGMENTS (space separated file list)
// It creates a filegroup for each file in the fragment list
// The filegroup modules are then added to `linker_config_srcs` of the autogenerated vendor `android_filesystem`.
func (f *filesystemCreator) createLinkerConfigSourceFilegroups(ctx android.LoadHookContext, partitionType string) []string {
	ret := []string{}
	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	var linkerConfigSrcs []string
	if partitionType == "vendor" {
		linkerConfigSrcs = android.FirstUniqueStrings(partitionVars.VendorLinkerConfigSrcs)
	} else if partitionType == "product" {
		linkerConfigSrcs = android.FirstUniqueStrings(partitionVars.ProductLinkerConfigSrcs)
	} else {
		ctx.ModuleErrorf("linker.config.pb is only supported for vendor and product partitions. For system partition, use `android_system_image`")
	}

	if len(linkerConfigSrcs) > 0 {
		// Create a filegroup, and add `:<filegroup_name>` to ret.
		for index, linkerConfigSrc := range linkerConfigSrcs {
			dir := filepath.Dir(linkerConfigSrc)
			base := filepath.Base(linkerConfigSrc)
			fgName := generatedModuleName(ctx.Config(), fmt.Sprintf("%s-linker-config-src%s", partitionType, strconv.Itoa(index)))
			srcs := []string{base}
			fgProps := &struct {
				Name *string
				Srcs proptools.Configurable[[]string]
			}{
				Name: proptools.StringPtr(fgName),
				Srcs: proptools.NewSimpleConfigurable(srcs),
			}
			ctx.CreateModuleInDirectory(
				android.FileGroupFactory,
				dir,
				fgProps,
			)
			ret = append(ret, ":"+fgName)
		}
	}
	return ret
}

type filesystemBaseProperty struct {
	Name                    *string
	Compile_multilib        *string
	Native_bridge_supported *bool
	Visibility              []string
}

func generateBaseProps(namePtr *string, config android.Config) *filesystemBaseProperty {
	return &filesystemBaseProperty{
		Name:                    namePtr,
		Compile_multilib:        proptools.StringPtr("both"),
		Native_bridge_supported: proptools.BoolPtr(config.ProductVariables().NativeBridgeArch != nil),
		// The vbmeta modules are currently in the root directory and depend on the partitions
		Visibility: []string{"//.", "//build/soong:__subpackages__"},
	}
}

func generateFsProps(ctx android.EarlyModuleContext, partitions allGeneratedPartitionData, partitionType string) (*filesystem.FilesystemProperties, bool) {
	fsProps := &filesystem.FilesystemProperties{}

	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	var avbInfo avbInfo
	var fsType string
	if strings.Contains(partitionType, "ramdisk") {
		fsType = "compressed_cpio"
	} else {
		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
		fsType = specificPartitionVars.BoardFileSystemType
		avbInfo = getAvbInfo(ctx.Config(), partitionType)
		if fsType == "" {
			fsType = "ext4" //default
		}
	}

	fsProps.Type = proptools.StringPtr(fsType)
	if filesystem.GetFsTypeFromString(ctx, *fsProps.Type).IsUnknown() {
		// Currently the android_filesystem module type only supports a handful of FS types like ext4, erofs
		return nil, false
	}

	switch *fsProps.Type {
	case "erofs":
		if partitionVars.BoardErofsCompressor != "" {
			fsProps.Erofs.Compressor = proptools.StringPtr(partitionVars.BoardErofsCompressor)
		}
		if partitionVars.BoardErofsCompressorHints != "" {
			fsProps.Erofs.Compress_hints = proptools.StringPtr(":soong_generated_board_erofs_compress_hints_filegroup")
		}
		if s, err := strconv.ParseBool(partitionVars.BoardErofsShareDupBlocks); err == nil {
			fsProps.Share_dup_blocks = proptools.BoolPtr(s)
		}
		if len(partitionVars.BoardErofsPclusterSize) > 0 {
			parsed, err := strconv.ParseInt(partitionVars.BoardErofsPclusterSize, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("erofs pcluster size must be an int, got %s", partitionVars.BoardErofsPclusterSize))
			}
			fsProps.Erofs.Pcluster_size = &parsed
		}
		// BOARD_*IMAGE_PCLUSTER_SIZE overrides BOARD_EROFS_PCLUSTER_SIZE
		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
		if len(specificPartitionVars.BoardErofsPclusterSize) > 0 {
			parsed, err := strconv.ParseInt(specificPartitionVars.BoardErofsPclusterSize, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("%s erofs pcluster size must be an int, got %s", partitionType, specificPartitionVars.BoardErofsPclusterSize))
			}
			fsProps.Erofs.Pcluster_size = &parsed
		}
		if len(partitionVars.BoardErofsBlockSize) > 0 {
			parsed, err := strconv.ParseInt(partitionVars.BoardErofsBlockSize, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("erofs pcluster size must be an int, got %s", partitionVars.BoardErofsBlockSize))
			}
			fsProps.Erofs.Block_size = &parsed
		}
		// BOARD_*IMAGE_EROFS_BLOCKSIZE overrides BOARD_EROFS_BLOCKSIZE
		if len(specificPartitionVars.BoardErofsBlockSize) > 0 {
			parsed, err := strconv.ParseInt(specificPartitionVars.BoardErofsBlockSize, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("%s erofs block size must be an int, got %s", partitionType, specificPartitionVars.BoardErofsBlockSize))
			}
			fsProps.Erofs.Block_size = &parsed
		}
	case "ext4":
		if s, err := strconv.ParseBool(partitionVars.BoardExt4ShareDupBlocks); err == nil {
			fsProps.Share_dup_blocks = proptools.BoolPtr(s)
		}
	}

	// Don't build this module on checkbuilds, the soong-built partitions are still in-progress
	// and sometimes don't build.
	fsProps.Unchecked_module = proptools.BoolPtr(true)

	// BOARD_AVB_ENABLE
	fsProps.Use_avb = avbInfo.avbEnable
	// BOARD_AVB_KEY_PATH
	fsProps.Avb_private_key = avbInfo.avbkeyFilegroup
	// BOARD_AVB_ALGORITHM
	fsProps.Avb_algorithm = avbInfo.avbAlgorithm
	// BOARD_AVB_SYSTEM_ROLLBACK_INDEX
	fsProps.Rollback_index = avbInfo.avbRollbackIndex
	// BOARD_AVB_SYSTEM_ROLLBACK_INDEX_LOCATION
	fsProps.Rollback_index_location = avbInfo.avbRollbackIndexLocation
	fsProps.Avb_hash_algorithm = avbInfo.avbHashAlgorithm

	fsProps.Partition_name = proptools.StringPtr(partitionType)

	switch partitionType {
	// The partitions that support file_contexts came from here:
	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2270;drc=ad7cfb56010cb22c3aa0e70cf71c804352553526
	case "system", "userdata", "cache", "vendor", "product", "system_ext", "odm", "vendor_dlkm", "odm_dlkm", "system_dlkm", "oem":
		fsProps.Precompiled_file_contexts = proptools.StringPtr(":file_contexts_bin_gen")
	}

	fsProps.Is_auto_generated = proptools.BoolPtr(true)
	if partitionType != "system" {
		mountPoint := proptools.StringPtr(partitionType)
		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/tools/releasetools/build_image.py;l=1012;drc=3f576a753594bad3fc838ccb8b1b72f7efac1d50
		if partitionType == "userdata" {
			mountPoint = proptools.StringPtr("data")
		}
		fsProps.Mount_point = mountPoint

	}

	fsProps.Enable_host_init_verifier_check = proptools.BoolPtr(false)

	partitionSpecificFsProps(ctx, partitions, fsProps, partitionType)

	return fsProps, true
}

type avbInfo struct {
	avbEnable                *bool
	avbKeyPath               *string
	avbkeyFilegroup          *string
	avbAlgorithm             *string
	avbRollbackIndex         *int64
	avbRollbackIndexLocation *int64
	avbMode                  *string
	avbHashAlgorithm         *string
}

func getAvbInfo(config android.Config, partitionType string) avbInfo {
	partitionVars := config.ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
	var result avbInfo
	boardAvbEnable := partitionVars.BoardAvbEnable
	if boardAvbEnable {
		result.avbEnable = proptools.BoolPtr(true)
		// There are "global" and "specific" copies of a lot of these variables. Sometimes they
		// choose the specific and then fall back to the global one if it's not set, other times
		// the global one actually only applies to the vbmeta partition.
		if partitionType == "vbmeta" {
			if partitionVars.BoardAvbKeyPath != "" {
				result.avbKeyPath = proptools.StringPtr(partitionVars.BoardAvbKeyPath)
			}
			if partitionVars.BoardAvbRollbackIndex != "" {
				parsed, err := strconv.ParseInt(partitionVars.BoardAvbRollbackIndex, 10, 64)
				if err != nil {
					panic(fmt.Sprintf("Rollback index must be an int, got %s", partitionVars.BoardAvbRollbackIndex))
				}
				result.avbRollbackIndex = &parsed
			}
		}
		if specificPartitionVars.BoardAvbKeyPath != "" {
			result.avbKeyPath = proptools.StringPtr(specificPartitionVars.BoardAvbKeyPath)
		}
		if specificPartitionVars.BoardAvbAlgorithm != "" {
			result.avbAlgorithm = proptools.StringPtr(specificPartitionVars.BoardAvbAlgorithm)
		} else if partitionVars.BoardAvbAlgorithm != "" {
			result.avbAlgorithm = proptools.StringPtr(partitionVars.BoardAvbAlgorithm)
		}
		if specificPartitionVars.BoardAvbRollbackIndex != "" {
			parsed, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndex, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("Rollback index must be an int, got %s", specificPartitionVars.BoardAvbRollbackIndex))
			}
			result.avbRollbackIndex = &parsed
		}
		if specificPartitionVars.BoardAvbRollbackIndexLocation != "" {
			parsed, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndexLocation, 10, 64)
			if err != nil {
				panic(fmt.Sprintf("Rollback index location must be an int, got %s", specificPartitionVars.BoardAvbRollbackIndexLocation))
			}
			result.avbRollbackIndexLocation = &parsed
		}

		// Make allows you to pass arbitrary arguments to avbtool via this variable, but in practice
		// it's only used for --hash_algorithm. The soong module has a dedicated property for the
		// hashtree algorithm, and doesn't allow custom arguments, so just extract the hashtree
		// algorithm out of the arbitrary arguments.
		addHashtreeFooterArgs := strings.Split(specificPartitionVars.BoardAvbAddHashtreeFooterArgs, " ")
		if i := slices.Index(addHashtreeFooterArgs, "--hash_algorithm"); i >= 0 {
			result.avbHashAlgorithm = &addHashtreeFooterArgs[i+1]
		}

		result.avbMode = proptools.StringPtr("make_legacy")
	}
	if result.avbKeyPath != nil {
		fsGenState := config.Get(fsGenStateOnceKey).(*FsGenState)
		filegroup := fsGenState.avbKeyFilegroups[*result.avbKeyPath]
		result.avbkeyFilegroup = proptools.StringPtr(":" + filegroup)
	}
	return result
}

func (f *filesystemCreator) createFileListDiffTest(ctx android.ModuleContext, partitionType string, partitionModuleName string) android.Path {
	partitionImage := ctx.GetDirectDepProxyWithTag(partitionModuleName, generatedFilesystemDepTag)
	filesystemInfo, ok := android.OtherModuleProvider(ctx, partitionImage, filesystem.FilesystemProvider)
	if !ok {
		ctx.ModuleErrorf("Expected module %s to provide FileysystemInfo", partitionModuleName)
		return nil
	}
	makeFileList := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/file_list.txt", ctx.Config().DeviceName(), partitionType))
	diffTestResultFile := android.PathForModuleOut(ctx, fmt.Sprintf("diff_test_%s.txt", partitionModuleName))

	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().BuiltTool("file_list_diff").
		Input(makeFileList).
		Input(filesystemInfo.FileListFile).
		Text(partitionModuleName)
	builder.Command().Text("touch").Output(diffTestResultFile)
	builder.Build(partitionModuleName+" diff test", partitionModuleName+" diff test")
	return diffTestResultFile
}

func createFailingCommand(ctx android.ModuleContext, message string) android.Path {
	hasher := sha256.New()
	hasher.Write([]byte(message))
	filename := fmt.Sprintf("failing_command_%x.txt", hasher.Sum(nil))
	file := android.PathForModuleOut(ctx, filename)
	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().Textf("echo %s", proptools.NinjaAndShellEscape(message))
	builder.Command().Text("exit 1 #").Output(file)
	builder.Build("failing command "+filename, "failing command "+filename)
	return file
}

func createVbmetaDiff(ctx android.ModuleContext, vbmetaModuleName string, vbmetaPartitionName string) android.Path {
	vbmetaModule := ctx.GetDirectDepProxyWithTag(vbmetaModuleName, generatedVbmetaPartitionDepTag)
	outputFilesProvider, ok := android.OtherModuleProvider(ctx, vbmetaModule, android.OutputFilesProvider)
	if !ok {
		ctx.ModuleErrorf("Expected module %s to provide OutputFiles", vbmetaModule)
	}
	if len(outputFilesProvider.DefaultOutputFiles) != 1 {
		ctx.ModuleErrorf("Expected 1 output file from module %s", vbmetaModule)
	}
	soongVbMetaFile := outputFilesProvider.DefaultOutputFiles[0]
	makeVbmetaFile := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/%s.img", ctx.Config().DeviceName(), vbmetaPartitionName))

	diffTestResultFile := android.PathForModuleOut(ctx, fmt.Sprintf("diff_test_%s.txt", vbmetaModuleName))
	createDiffTest(ctx, diffTestResultFile, soongVbMetaFile, makeVbmetaFile)
	return diffTestResultFile
}

func createDiffTest(ctx android.ModuleContext, diffTestResultFile android.WritablePath, file1 android.Path, file2 android.Path) {
	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().Text("diff").
		Input(file1).
		Input(file2)
	builder.Command().Text("touch").Output(diffTestResultFile)
	builder.Build("diff test "+diffTestResultFile.String(), "diff test")
}

type imageDepTagType struct {
	blueprint.BaseDependencyTag
}

var generatedFilesystemDepTag imageDepTagType
var generatedVbmetaPartitionDepTag imageDepTagType

func (f *filesystemCreator) DepsMutator(ctx android.BottomUpMutatorContext) {
	if !shouldEnableFilesystemCreator(ctx) {
		return
	}
	for _, name := range ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions.names() {
		if android.InList(ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions.typeForName(name), []string{"pvmfw", "tzsw"}) {
			ctx.AddFarVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), generatedFilesystemDepTag, name)
		} else {
			ctx.AddDependency(ctx.Module(), generatedFilesystemDepTag, name)
		}
	}
	for _, vbmetaModule := range f.properties.Vbmeta_module_names {
		ctx.AddDependency(ctx.Module(), generatedVbmetaPartitionDepTag, vbmetaModule)
	}
}

func (f *filesystemCreator) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	if !shouldEnableFilesystemCreator(ctx) {
		return
	}
	if ctx.ModuleDir() != "build/soong/fsgen" {
		ctx.ModuleErrorf("There can only be one soong_filesystem_creator in build/soong/fsgen")
	}
	f.HideFromMake()

	partitions := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions

	var content strings.Builder
	generatedBp := android.PathForModuleOut(ctx, "soong_generated_product_config.bp")
	for _, partition := range partitions {
		if partition.handwritten {
			continue // no need to create a module in the autogenerated Android.bp file
		}
		content.WriteString(generateBpContent(ctx, partition.partitionType))
		content.WriteString("\n")
	}
	android.WriteFileRule(ctx, generatedBp, content.String())

	ctx.Phony("product_config_to_bp", generatedBp)

	if !ctx.Config().KatiEnabled() {
		// Cannot diff since the kati packaging rules will not be created.
		return
	}
	var diffTestFiles []android.Path
	for _, partitionType := range partitions.types() {
		if android.InList(partitionType, []string{
			"debug_ramdisk",
			"vendor_ramdisk-debug",
			"vendor_ramdisk-test-harness",
			"vendor_kernel_ramdisk",
			"pvmfw",
		}) {
			continue // Make packaging does not create a filter file for this partition.
		}
		diffTestFile := f.createFileListDiffTest(ctx, partitionType, partitions.nameForType(partitionType))
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", partitionType), diffTestFile)
	}
	for _, partitionType := range slices.Concat(partitions.unsupportedTypes(), f.properties.Unsupported_partition_types) {
		diffTestFile := createFailingCommand(ctx, fmt.Sprintf("Couldn't build %s partition", partitionType))
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", partitionType), diffTestFile)
	}
	for i, vbmetaModule := range f.properties.Vbmeta_module_names {
		diffTestFile := createVbmetaDiff(ctx, vbmetaModule, f.properties.Vbmeta_partition_names[i])
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", f.properties.Vbmeta_partition_names[i]), diffTestFile)
	}
	if f.properties.Boot_image != "" {
		diffTestFile := android.PathForModuleOut(ctx, "boot_diff_test.txt")
		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Boot_image)
		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/boot.img", ctx.Config().DeviceName()))
		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony("soong_generated_boot_filesystem_test", diffTestFile)
	}
	if f.properties.Vendor_boot_image != "" {
		diffTestFile := android.PathForModuleOut(ctx, "vendor_boot_diff_test.txt")
		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Vendor_boot_image)
		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/vendor_boot.img", ctx.Config().DeviceName()))
		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony("soong_generated_vendor_boot_filesystem_test", diffTestFile)
	}
	if f.properties.Init_boot_image != "" {
		diffTestFile := android.PathForModuleOut(ctx, "init_boot_diff_test.txt")
		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Init_boot_image)
		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/init_boot.img", ctx.Config().DeviceName()))
		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony("soong_generated_init_boot_filesystem_test", diffTestFile)
	}
	if f.properties.Super_image != "" {
		diffTestFile := android.PathForModuleOut(ctx, "super_diff_test.txt")
		soongSuperImg := android.PathForModuleSrc(ctx, f.properties.Super_image)
		makeSuperImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/super.img", ctx.Config().DeviceName()))
		createDiffTest(ctx, diffTestFile, soongSuperImg, makeSuperImage)
		diffTestFiles = append(diffTestFiles, diffTestFile)
		ctx.Phony("soong_generated_super_filesystem_test", diffTestFile)
	}
	ctx.Phony("soong_generated_filesystem_tests", diffTestFiles...)
}

func generateBpContent(ctx android.EarlyModuleContext, partitionType string) string {
	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
	fsProps, fsTypeSupported := generateFsProps(ctx, fsGenState.soongGeneratedPartitions, partitionType)
	if !fsTypeSupported {
		return ""
	}
	if partitionType == "tzsw" {
		return "" // TODO: Add support for autogenerating an Android.bp file for tzsw
	}

	baseProps := generateBaseProps(proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), partitionType)), ctx.Config())
	deps := fsGenState.fsDeps[partitionType]
	highPriorityDeps := fsGenState.generatedPrebuiltEtcModuleNames
	var overriddenDeps []string
	if deps, ok := fsGenState.overriddenModuleNames[partitionType]; ok {
		overriddenDeps = deps
	}
	depProps := generateDepStruct(*deps, highPriorityDeps, overriddenDeps)

	result, err := proptools.RepackProperties([]interface{}{baseProps, fsProps, depProps})
	if err != nil {
		ctx.ModuleErrorf("%s", err.Error())
		return ""
	}

	moduleType := "android_filesystem"
	if partitionType == "system" {
		moduleType = "android_system_image"
	}

	file := &parser.File{
		Defs: []parser.Definition{
			&parser.Module{
				Type: moduleType,
				Map:  *result,
			},
		},
	}
	bytes, err := parser.Print(file)
	if err != nil {
		ctx.ModuleErrorf(err.Error())
	}
	return strings.TrimSpace(string(bytes))
}
