Add Factory methods, WriteFormattedMessage

Adds:
 - FlagArtifactFactory()
 - FlagArtifactsFactory()
 - WriteFormattedMessage()

Bug: 328495189
Test: manual
Change-Id: I8b3c1e1e7ea3e52e9e7e8b1f8162fedd3e83dd33
diff --git a/cmd/release_config/release_config_lib/flag_artifact.go b/cmd/release_config/release_config_lib/flag_artifact.go
index cba1b5c..cdd5af9 100644
--- a/cmd/release_config/release_config_lib/flag_artifact.go
+++ b/cmd/release_config/release_config_lib/flag_artifact.go
@@ -15,7 +15,9 @@
 package release_config_lib
 
 import (
+	"cmp"
 	"fmt"
+	"slices"
 
 	rc_proto "android/soong/cmd/release_config/release_config_proto"
 
@@ -45,6 +47,63 @@
 // Key is flag name.
 type FlagArtifacts map[string]*FlagArtifact
 
+func FlagArtifactFactory(declPath string) *FlagArtifact {
+	fd := &rc_proto.FlagDeclaration{}
+	fa := &FlagArtifact{
+		FlagDeclaration:  fd,
+		DeclarationIndex: -1,
+		Traces:           []*rc_proto.Tracepoint{},
+	}
+	if declPath != "" {
+		LoadMessage(declPath, fd)
+		fa.Value = fd.GetValue()
+		fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(declPath), Value: fa.Value})
+	}
+	return fa
+}
+
+func FlagArtifactsFactory(artifactsPath string) *FlagArtifacts {
+	ret := make(FlagArtifacts)
+	if artifactsPath != "" {
+		fas := &rc_proto.FlagArtifacts{}
+		LoadMessage(artifactsPath, fas)
+		for _, fa_pb := range fas.FlagArtifacts {
+			fa := &FlagArtifact{}
+			fa.FlagDeclaration = fa_pb.GetFlagDeclaration()
+			if val := fa_pb.GetValue(); val != nil {
+				fa.Value = val
+			}
+			if traces := fa_pb.GetTraces(); traces != nil {
+				fa.Traces = traces
+			}
+			ret[*fa.FlagDeclaration.Name] = fa
+		}
+	}
+	return &ret
+}
+
+func (fa *FlagArtifact) GenerateFlagArtifact() *rc_proto.FlagArtifact {
+	ret := &rc_proto.FlagArtifact{FlagDeclaration: fa.FlagDeclaration}
+	if fa.Value != nil {
+		ret.Value = fa.Value
+	}
+	if len(fa.Traces) > 0 {
+		ret.Traces = fa.Traces
+	}
+	return ret
+}
+
+func (fas *FlagArtifacts) GenerateFlagArtifacts() *rc_proto.FlagArtifacts {
+	ret := &rc_proto.FlagArtifacts{FlagArtifacts: []*rc_proto.FlagArtifact{}}
+	for _, fa := range *fas {
+		ret.FlagArtifacts = append(ret.FlagArtifacts, fa.GenerateFlagArtifact())
+	}
+	slices.SortFunc(ret.FlagArtifacts, func(a, b *rc_proto.FlagArtifact) int {
+		return cmp.Compare(*a.FlagDeclaration.Name, *b.FlagDeclaration.Name)
+	})
+	return ret
+}
+
 // Create a clone of the flag artifact.
 //
 // Returns:
diff --git a/cmd/release_config/release_config_lib/util.go b/cmd/release_config/release_config_lib/util.go
index c0ea789..040820b 100644
--- a/cmd/release_config/release_config_lib/util.go
+++ b/cmd/release_config/release_config_lib/util.go
@@ -58,13 +58,36 @@
 //
 //	error: any error encountered.
 func WriteMessage(path string, message proto.Message) (err error) {
+	format := filepath.Ext(path)
+	if len(format) > 1 {
+		// Strip any leading dot.
+		format = format[1:]
+	}
+	return WriteFormattedMessage(path, format, message)
+}
+
+// Write a marshalled message to a file.
+//
+// Marshal the message using the given format.
+//
+// Args:
+//
+//	path string: the path of the file to write to.  Directories are not created.
+//	  Supported extensions are: ".json", ".pb", and ".textproto".
+//	format string: one of "json", "pb", or "textproto".
+//	message proto.Message: the message to write.
+//
+// Returns:
+//
+//	error: any error encountered.
+func WriteFormattedMessage(path, format string, message proto.Message) (err error) {
 	var data []byte
-	switch filepath.Ext(path) {
-	case ".json":
+	switch format {
+	case "json":
 		data, err = json.MarshalIndent(message, "", "  ")
-	case ".pb":
+	case "pb", "binaryproto", "protobuf":
 		data, err = proto.Marshal(message)
-	case ".textproto":
+	case "textproto":
 		data, err = prototext.MarshalOptions{Multiline: true}.Marshal(message)
 	default:
 		return fmt.Errorf("Unknown message format for %s", path)
@@ -95,7 +118,7 @@
 	switch filepath.Ext(path) {
 	case ".json":
 		return json.Unmarshal(data, message)
-	case ".pb":
+	case ".pb", ".protobuf", ".binaryproto":
 		return proto.Unmarshal(data, message)
 	case ".textproto":
 		return prototext.Unmarshal(data, message)