Bp2build genrule commands as configurable properties

So that users can use soong config variables / product variables
to adjust a genrule's command.

Bug: 295910468
Test: m nothing
Change-Id: I9fedf8d5d52e515c3fdb913411ce1b3fecb7ba81
diff --git a/bp2build/genrule_conversion_test.go b/bp2build/genrule_conversion_test.go
index 5a73969..2dcc5e4 100644
--- a/bp2build/genrule_conversion_test.go
+++ b/bp2build/genrule_conversion_test.go
@@ -772,3 +772,77 @@
 		})
 	}
 }
+
+func TestGenruleWithConfiguredCmd(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+		hod        android.HostOrDeviceSupported
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+			hod:        android.DeviceSupported,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+			hod:        android.DeviceSupported,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+			hod:        android.HostSupported,
+		},
+	}
+
+	bp := `
+soong_config_module_type {
+    name: "my_genrule",
+    module_type: "%s",
+    config_namespace: "my_namespace",
+    bool_variables: ["my_variable"],
+    properties: ["cmd"],
+}
+
+my_genrule {
+    name: "foo",
+    out: ["foo.txt"],
+    cmd: "echo 'no variable' > $(out)",
+    soong_config_variables: {
+        my_variable: {
+            cmd: "echo 'with variable' > $(out)",
+        },
+    },
+    bazel_module: { bp2build_available: true },
+}
+`
+
+	for _, tc := range testCases {
+		moduleAttrs := AttrNameToString{
+			"cmd": `select({
+        "//build/bazel/product_config/config_settings:my_namespace__my_variable": "echo 'with variable' > $(OUTS)",
+        "//conditions:default": "echo 'no variable' > $(OUTS)",
+    })`,
+			"outs": `["foo.txt"]`,
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTargetHostOrDevice("genrule", "foo", moduleAttrs, tc.hod),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			RunBp2BuildTestCase(t, func(ctx android.RegistrationContext) { android.RegisterSoongConfigModuleBuildComponents(ctx) },
+				Bp2buildTestCase{
+					Blueprint:                  fmt.Sprintf(bp, tc.moduleType),
+					ModuleTypeUnderTest:        tc.moduleType,
+					ModuleTypeUnderTestFactory: tc.factory,
+					ExpectedBazelTargets:       expectedBazelTargets,
+				})
+		})
+	}
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 8a8d605..aa4295d 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -869,7 +869,7 @@
 	Srcs             bazel.LabelListAttribute
 	Output_extension *string
 	Tools            bazel.LabelListAttribute
-	Cmd              string
+	Cmd              bazel.StringAttribute
 	Data             bazel.LabelListAttribute
 }
 
@@ -917,7 +917,7 @@
 	Srcs  bazel.LabelListAttribute
 	Outs  []string
 	Tools bazel.LabelListAttribute
-	Cmd   string
+	Cmd   bazel.StringAttribute
 }
 
 // ConvertWithBp2build converts a Soong module -> Bazel target.
@@ -967,14 +967,13 @@
 		}
 	}
 
-	// Replace in and out variables with $< and $@
-	var cmd string
-	if m.properties.Cmd != nil {
+	replaceVariables := func(cmd string) string {
+		// Replace in and out variables with $< and $@
 		if ctx.ModuleType() == "gensrcs" {
-			cmd = strings.ReplaceAll(*m.properties.Cmd, "$(in)", "$(SRC)")
+			cmd = strings.ReplaceAll(cmd, "$(in)", "$(SRC)")
 			cmd = strings.ReplaceAll(cmd, "$(out)", "$(OUT)")
 		} else {
-			cmd = strings.Replace(*m.properties.Cmd, "$(in)", "$(SRCS)", -1)
+			cmd = strings.Replace(cmd, "$(in)", "$(SRCS)", -1)
 			cmd = strings.Replace(cmd, "$(out)", "$(OUTS)", -1)
 		}
 		cmd = strings.Replace(cmd, "$(genDir)", "$(RULEDIR)", -1)
@@ -990,6 +989,21 @@
 			cmd = strings.Replace(cmd, bpLoc, bazelLoc, -1)
 			cmd = strings.Replace(cmd, bpLocs, bazelLocs, -1)
 		}
+		return cmd
+	}
+
+	var cmdProp bazel.StringAttribute
+	cmdProp.SetValue(replaceVariables(proptools.String(m.properties.Cmd)))
+	allProductVariableProps := android.ProductVariableProperties(ctx, m)
+	if productVariableProps, ok := allProductVariableProps["Cmd"]; ok {
+		for productVariable, value := range productVariableProps {
+			var cmd string
+			if strValue, ok := value.(*string); ok && strValue != nil {
+				cmd = *strValue
+			}
+			cmd = replaceVariables(cmd)
+			cmdProp.SetSelectValue(productVariable.ConfigurationAxis(), productVariable.SelectKey(), &cmd)
+		}
 	}
 
 	tags := android.ApexAvailableTagsWithoutTestApexes(ctx, m)
@@ -1003,7 +1017,7 @@
 		attrs := &bazelGensrcsAttributes{
 			Srcs:             srcs,
 			Output_extension: outputExtension,
-			Cmd:              cmd,
+			Cmd:              cmdProp,
 			Tools:            tools,
 			Data:             data,
 		}
@@ -1026,7 +1040,7 @@
 		attrs := &bazelGenruleAttributes{
 			Srcs:  srcs,
 			Outs:  outs,
-			Cmd:   cmd,
+			Cmd:   cmdProp,
 			Tools: tools,
 		}
 		props := bazel.BazelTargetModuleProperties{
@@ -1056,7 +1070,6 @@
 			Name: m.Name() + genruleHeaderLibrarySuffix,
 			Tags: tags,
 		}, attrs)
-
 	}
 }