Merge "Add proptools.PrintfIntoConfigurable" into main
diff --git a/proptools/configurable.go b/proptools/configurable.go
index bd98fdc..031d965 100644
--- a/proptools/configurable.go
+++ b/proptools/configurable.go
@@ -557,6 +557,7 @@
 	configuredType() reflect.Type
 	clone() any
 	isEmpty() bool
+	printfInto(value string) error
 }
 
 // Same as configurableReflection, but since initialize needs to take a pointer
@@ -627,6 +628,60 @@
 	}
 }
 
+func (c Configurable[T]) printfInto(value string) error {
+	return c.inner.printfInto(value)
+}
+
+func (c *configurableInner[T]) printfInto(value string) error {
+	for c != nil {
+		if err := c.single.printfInto(value); err != nil {
+			return err
+		}
+		c = c.next
+	}
+	return nil
+}
+
+func (c *singleConfigurable[T]) printfInto(value string) error {
+	for _, c := range c.cases {
+		if c.value == nil {
+			continue
+		}
+		switch v := any(c.value).(type) {
+		case *string:
+			if err := printfIntoString(v, value); err != nil {
+				return err
+			}
+		case *[]string:
+			for i := range *v {
+				if err := printfIntoString(&((*v)[i]), value); err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func printfIntoString(s *string, configValue string) error {
+	count := strings.Count(*s, "%")
+	if count == 0 {
+		return nil
+	}
+
+	if count > 1 {
+		return fmt.Errorf("list/value variable properties only support a single '%%'")
+	}
+
+	if !strings.Contains(*s, "%s") {
+		return fmt.Errorf("unsupported %% in value variable property")
+	}
+
+	*s = fmt.Sprintf(*s, configValue)
+
+	return nil
+}
+
 func (c Configurable[T]) clone() any {
 	return Configurable[T]{
 		propertyName: c.propertyName,
@@ -734,3 +789,10 @@
 		return *t
 	}
 }
+
+// PrintfIntoConfigurable replaces %s occurrences in strings in Configurable properties
+// with the provided string value. It's intention is to support soong config value variables
+// on Configurable properties.
+func PrintfIntoConfigurable(c any, value string) error {
+	return c.(configurableReflection).printfInto(value)
+}