// Command protoc-gen-gitaly-protolist is designed to be used as a protobuf compiler to generate a // list of protobuf files via a publicly accessible variable. // // This plugin can be accessed by invoking the protoc compiler with the following arguments: // // protoc --plugin=protoc-gen-gitaly-protolist --gitaly_protolist_out=. // // The plugin accepts a protobuf message in STDIN that describes the parsed protobuf files. A // response is sent back on STDOUT that contains any errors. package main import ( "bytes" "fmt" "go/format" "io" "log" "os" "path/filepath" "strings" "text/template" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/pluginpb" ) const ( gitalyProtoDirArg = "proto_dir" gitalypbDirArg = "gitalypb_dir" ) func main() { data, err := io.ReadAll(os.Stdin) if err != nil { log.Fatalf("reading input: %s", err) } req := &pluginpb.CodeGeneratorRequest{} if err := proto.Unmarshal(data, req); err != nil { log.Fatalf("parsing input proto: %s", err) } if err := generateProtolistGo(req); err != nil { log.Fatal(err) } } func parseArgs(argString string) (gitalyProtoDir string, gitalypbDir string) { for _, arg := range strings.Split(argString, ",") { argKeyValue := strings.Split(arg, "=") if len(argKeyValue) != 2 { continue } switch argKeyValue[0] { case gitalyProtoDirArg: gitalyProtoDir = argKeyValue[1] case gitalypbDirArg: gitalypbDir = argKeyValue[1] } } return gitalyProtoDir, gitalypbDir } func generateProtolistGo(req *pluginpb.CodeGeneratorRequest) error { var err error gitalyProtoDir, gitalypbDir := parseArgs(req.GetParameter()) if gitalyProtoDir == "" { return fmt.Errorf("%s not provided", gitalyProtoDirArg) } if gitalypbDir == "" { return fmt.Errorf("%s not provided", gitalypbDirArg) } var protoNames []string if gitalyProtoDir, err = filepath.Abs(gitalyProtoDir); err != nil { return fmt.Errorf("failed to get absolute path for %s: %v", gitalyProtoDir, err) } files, err := os.ReadDir(gitalyProtoDir) if err != nil { return fmt.Errorf("failed to read %s: %v", gitalyProtoDir, err) } for _, fi := range files { if !fi.IsDir() && strings.HasSuffix(fi.Name(), ".proto") { protoNames = append(protoNames, fmt.Sprintf(`"%s"`, fi.Name())) } } f, err := os.Create(filepath.Join(gitalypbDir, "protolist.go")) if err != nil { return fmt.Errorf("could not create protolist.go: %v", err) } defer f.Close() if err = renderProtoList(f, protoNames); err != nil { return fmt.Errorf("could not render go code: %v", err) } return nil } // renderProtoList generate a go file with a list of gitaly protos func renderProtoList(dest io.WriteCloser, protoNames []string) error { joinFunc := template.FuncMap{"join": strings.Join} protoList := `package gitalypb // Code generated by protoc-gen-gitaly. DO NOT EDIT // GitalyProtos is a list of gitaly protobuf files var GitalyProtos = []string{ {{join . ",\n"}}, } ` protoListTempl, err := template.New("protoList").Funcs(joinFunc).Parse(protoList) if err != nil { return fmt.Errorf("could not create go code template: %v", err) } var rawGo bytes.Buffer if err := protoListTempl.Execute(&rawGo, protoNames); err != nil { return fmt.Errorf("could not execute go code template: %v", err) } formattedGo, err := format.Source(rawGo.Bytes()) if err != nil { return fmt.Errorf("could not format go code: %v", err) } if _, err = io.Copy(dest, bytes.NewBuffer(formattedGo)); err != nil { return fmt.Errorf("failed to write protolist.go file: %v", err) } return nil }