Wrap the soong installation rules with /bin/bash

Switching between `m` and `m --soong-only` is currently not a null build
since the command line of the install actions changes. This CL wraps the
install cmds with /bin/bash to match the command line of the install
actions in regular `m` builds.

Switching between `m` and `m --soong-only` is still not a null build
(most likely due to `kernel_version_for_uffd_gc.txt), but with this CL
only a subset of the actions (buildprop and rdeps) need to be rebuilt
between the switch

Bug: 390471378
Test: m out/soong/.intermediates/build/soong/fsgen/aosp_cf_x86_64_phone_generated_odm_image/android_common/odm.img
Test: m
out/soong/.intermediates/build/soong/fsgen/aosp_cf_x86_64_phone_generated_odm_image/android_common/odm.img
--soong-only
(only cmp of kernel_version_for_uffd_gc.txt)
Test: m out/soong/.intermediates/build/soong/fsgen/aosp_cf_x86_64_phone_generated_odm_image/android_common/odm.img
(copies kernel_version_for_uffd_gc.txt, runs gen_build_prop followed by
build_img)

Change-Id: If64af772fc6e3506310ffb9f7a0e9058befb9cfa
diff --git a/android/defs.go b/android/defs.go
index 9f3fb1e..4dd267a 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -51,6 +51,14 @@
 		},
 		"cpFlags", "extraCmds")
 
+	// A copy rule wrapped with bash.
+	CpWithBash = pctx.AndroidStaticRule("CpWithBash",
+		blueprint.RuleParams{
+			Command:     "/bin/bash -c \"rm -f $out && cp $cpFlags $cpPreserveSymlinks $in $out$extraCmds\"",
+			Description: "cp $out",
+		},
+		"cpFlags", "extraCmds")
+
 	// A copy rule that doesn't preserve symlinks.
 	CpNoPreserveSymlink = pctx.AndroidStaticRule("CpNoPreserveSymlink",
 		blueprint.RuleParams{
@@ -74,6 +82,14 @@
 		},
 		"cpFlags", "extraCmds")
 
+	// A copy executable rule wrapped with bash
+	CpExecutableWithBash = pctx.AndroidStaticRule("CpExecutableWithBash",
+		blueprint.RuleParams{
+			Command:     "/bin/bash -c \"(rm -f $out && cp $cpFlags $cpPreserveSymlinks $in $out ) && (chmod +x $out$extraCmds )\"",
+			Description: "cp $out",
+		},
+		"cpFlags", "extraCmds")
+
 	// A timestamp touch rule.
 	Touch = pctx.AndroidStaticRule("Touch",
 		blueprint.RuleParams{
@@ -89,6 +105,14 @@
 		},
 		"fromPath")
 
+	// A symlink rule wrapped with bash
+	SymlinkWithBash = pctx.AndroidStaticRule("SymlinkWithBash",
+		blueprint.RuleParams{
+			Command:     "/bin/bash -c \"rm -f $out && ln -sfn $fromPath $out\"",
+			Description: "symlink $out",
+		},
+		"fromPath")
+
 	ErrorRule = pctx.AndroidStaticRule("Error",
 		blueprint.RuleParams{
 			Command:     `echo "$error" && false`,
diff --git a/android/module_context.go b/android/module_context.go
index f6a676d..1620390 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -668,9 +668,9 @@
 				extraFiles:    extraZip,
 			})
 		} else {
-			rule := Cp
+			rule := CpWithBash
 			if executable {
-				rule = CpExecutable
+				rule = CpExecutableWithBash
 			}
 
 			extraCmds := ""
@@ -690,6 +690,7 @@
 				OrderOnly:   orderOnlyDeps,
 				Args: map[string]string{
 					"extraCmds": extraCmds,
+					"cpFlags":   "-f",
 				},
 			})
 		}
@@ -730,7 +731,7 @@
 			// the mtime of the symlink must be updated when the binary is modified, so use a
 			// normal dependency here instead of an order-only dependency.
 			m.Build(pctx, BuildParams{
-				Rule:        Symlink,
+				Rule:        SymlinkWithBash,
 				Description: "install symlink " + fullInstallPath.Base(),
 				Output:      fullInstallPath,
 				Input:       srcPath,
diff --git a/sdk/testing.go b/sdk/testing.go
index f4e2b03..21d457c 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -144,7 +144,7 @@
 	seenBuildNumberFile := false
 	for _, bp := range buildParams {
 		switch bp.Rule.String() {
-		case android.Cp.String():
+		case android.Cp.String(), android.CpWithBash.String():
 			output := bp.Output
 			// Get destination relative to the snapshot root
 			dest := output.Rel()