Auto generate recovery build.prop module

Implementation details:
- Introduce recovery_build_prop module type given that the build rule of
  the recovery partition's build.prop file is quite different from that
  of the other partitions' build.prop files.
- Modify the visibility of the Soong-defined build_prop modules to allow
  the recovery_build_prop module to depend on them.
- Auto generate the recovery_build_prop module and add it to deps in
  FsGenState.

Test: build soong generated recovery partition and inspect the content of the generated prop.default file
Bug: 381888358
Change-Id: I17eaa969da245863c11fab259fbbbb81aa46dfc4
diff --git a/Android.bp b/Android.bp
index 434ee9f..d78379a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -179,6 +179,7 @@
     visibility: [
         "//build/make/target/product/generic",
         "//build/make/target/product/gsi",
+        "//build/soong/fsgen",
         "//packages/modules/Virtualization/build/microdroid",
         "//frameworks/base/ravenwood",
     ],
@@ -190,7 +191,10 @@
     system_ext_specific: true,
     product_config: ":product_config",
     relative_install_path: "etc", // system_ext/etc/build.prop
-    visibility: ["//build/make/target/product/gsi"],
+    visibility: [
+        "//build/make/target/product/gsi",
+        "//build/soong/fsgen",
+    ],
 }
 
 build_prop {
@@ -199,7 +203,10 @@
     product_specific: true,
     product_config: ":product_config",
     relative_install_path: "etc", // product/etc/build.prop
-    visibility: ["//build/make/target/product/gsi"],
+    visibility: [
+        "//build/make/target/product/gsi",
+        "//build/soong/fsgen",
+    ],
 }
 
 build_prop {
@@ -208,7 +215,7 @@
     device_specific: true,
     product_config: ":product_config",
     relative_install_path: "etc", // odm/etc/build.prop
-    visibility: ["//visibility:private"],
+    visibility: ["//build/soong/fsgen"],
 }
 
 build_prop {
diff --git a/android/Android.bp b/android/Android.bp
index 20cd28b..79969a8 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -95,6 +95,7 @@
         "proto.go",
         "provider.go",
         "raw_files.go",
+        "recovery_build_prop.go",
         "register.go",
         "rule_builder.go",
         "sandbox.go",
diff --git a/android/recovery_build_prop.go b/android/recovery_build_prop.go
new file mode 100644
index 0000000..91d1904
--- /dev/null
+++ b/android/recovery_build_prop.go
@@ -0,0 +1,111 @@
+// 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 "github.com/google/blueprint/proptools"
+
+func init() {
+	RegisterModuleType("recovery_build_prop", RecoveryBuildPropModuleFactory)
+}
+
+type recoveryBuildPropProperties struct {
+	// Path to the system build.prop file
+	System_build_prop *string `android:"path"`
+
+	// Path to the vendor build.prop file
+	Vendor_build_prop *string `android:"path"`
+
+	// Path to the odm build.prop file
+	Odm_build_prop *string `android:"path"`
+
+	// Path to the product build.prop file
+	Product_build_prop *string `android:"path"`
+
+	// Path to the system_ext build.prop file
+	System_ext_build_prop *string `android:"path"`
+}
+
+type recoveryBuildPropModule struct {
+	ModuleBase
+	properties recoveryBuildPropProperties
+
+	outputFilePath ModuleOutPath
+
+	installPath InstallPath
+}
+
+func RecoveryBuildPropModuleFactory() Module {
+	module := &recoveryBuildPropModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
+
+// Overrides ctx.Module().InstallInRoot().
+// recovery_build_prop module always installs in root so that the prop.default
+// file is installed in recovery/root instead of recovery/root/system
+func (r *recoveryBuildPropModule) InstallInRoot() bool {
+	return true
+}
+
+func (r *recoveryBuildPropModule) appendRecoveryUIProperties(ctx ModuleContext, rule *RuleBuilder) {
+	rule.Command().Text("echo '#' >>").Output(r.outputFilePath)
+	rule.Command().Text("echo '# RECOVERY UI BUILD PROPERTIES' >>").Output(r.outputFilePath)
+	rule.Command().Text("echo '#' >>").Output(r.outputFilePath)
+
+	for propName, val := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.PrivateRecoveryUiProperties {
+		if len(val) > 0 {
+			rule.Command().
+				Textf("echo ro.recovery.ui.%s=%s >>", propName, val).
+				Output(r.outputFilePath)
+		}
+	}
+}
+
+func (r *recoveryBuildPropModule) getBuildProps(ctx ModuleContext) Paths {
+	var buildProps Paths
+	for _, buildProp := range []*string{
+		r.properties.System_build_prop,
+		r.properties.Vendor_build_prop,
+		r.properties.Odm_build_prop,
+		r.properties.Product_build_prop,
+		r.properties.System_ext_build_prop,
+	} {
+		if buildPropPath := PathForModuleSrc(ctx, proptools.String(buildProp)); buildPropPath != nil {
+			buildProps = append(buildProps, buildPropPath)
+		}
+	}
+	return buildProps
+}
+
+func (r *recoveryBuildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if !r.InstallInRecovery() {
+		ctx.ModuleErrorf("recovery_build_prop module must set `recovery` property to true")
+	}
+	r.outputFilePath = PathForModuleOut(ctx, ctx.ModuleName(), "prop.default")
+
+	// Replicates the logic in https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2733;drc=0585bb1bcf4c89065adaf709f48acc8b869fd3ce
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().Text("rm").FlagWithOutput("-f ", r.outputFilePath)
+	rule.Command().Text("cat").
+		Inputs(r.getBuildProps(ctx)).
+		Text(">>").
+		Output(r.outputFilePath)
+	r.appendRecoveryUIProperties(ctx, rule)
+
+	rule.Build(ctx.ModuleName(), "generating recovery prop.default")
+	r.installPath = PathForModuleInstall(ctx)
+	ctx.InstallFile(r.installPath, "prop.default", r.outputFilePath)
+}
diff --git a/android/variable.go b/android/variable.go
index 19f63e3..69e0337 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -688,6 +688,8 @@
 	ProductFsverityGenerateMetadata bool `json:",omitempty"`
 
 	TargetScreenDensity string `json:",omitempty"`
+
+	PrivateRecoveryUiProperties map[string]string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index b9fddca..2dc5077 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -596,6 +596,43 @@
 	vendorBuildProp.HideFromMake()
 }
 
+func createRecoveryBuildProp(ctx android.LoadHookContext) string {
+	moduleName := generatedModuleName(ctx.Config(), "recovery-prop.default")
+
+	var vendorBuildProp *string
+	if android.InList("vendor", generatedPartitions(ctx)) {
+		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)
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index 20e4c3e..de0a1cb 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -165,6 +165,7 @@
 		// Add common resources `prebuilt_res` module as dep of recovery partition
 		(*fsGenState.fsDeps["recovery"])[fmt.Sprintf("recovery-resources-common-%s", getDpi(ctx))] = defaultDepCandidateProps(ctx.Config())
 		(*fsGenState.fsDeps["recovery"])[getRecoveryFontModuleName(ctx)] = defaultDepCandidateProps(ctx.Config())
+		(*fsGenState.fsDeps["recovery"])[createRecoveryBuildProp(ctx)] = defaultDepCandidateProps(ctx.Config())
 
 		return &fsGenState
 	}).(*FsGenState)