go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/grpc/cmd/cproto/discovery.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bytes" 19 "compress/gzip" 20 "fmt" 21 "go/build" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "strings" 26 "text/template" 27 28 "google.golang.org/protobuf/types/descriptorpb" 29 30 "go.chromium.org/luci/common/errors" 31 ) 32 33 const ( 34 discoveryPackagePath = "go.chromium.org/luci/grpc/discovery" 35 ) 36 37 // discoveryTmpl is template for generated Go discovery file. 38 // The result of execution will also be passed through gofmt. 39 var discoveryTmpl = template.Must(template.New("").Parse(strings.TrimSpace(` 40 // Code generated by cproto. DO NOT EDIT. 41 42 package {{.GoPkg}}; 43 44 {{if .ImportDiscovery}} 45 import "go.chromium.org/luci/grpc/discovery" 46 {{end}} 47 import "google.golang.org/protobuf/types/descriptorpb" 48 49 func init() { 50 {{if .ImportDiscovery}}discovery.{{end}}RegisterDescriptorSetCompressed( 51 []string{ 52 {{range .ServiceNames}}"{{.}}",{{end}} 53 }, 54 {{.CompressedBytes}}, 55 ) 56 } 57 58 // FileDescriptorSet returns a descriptor set for this proto package, which 59 // includes all defined services, and all transitive dependencies. 60 // 61 // Will not return nil. 62 // 63 // Do NOT modify the returned descriptor. 64 func FileDescriptorSet() *descriptorpb.FileDescriptorSet { 65 // We just need ONE of the service names to look up the FileDescriptorSet. 66 ret, err := {{if .ImportDiscovery}}discovery.{{end}}GetDescriptorSet("{{index .ServiceNames 0 }}") 67 if err != nil { 68 panic(err) 69 } 70 return ret 71 } 72 `))) 73 74 // genDiscoveryFile generates a Go discovery file that calls 75 // discovery.RegisterDescriptorSetCompressed(serviceNames, compressedDescBytes) 76 // in an init function. 77 func genDiscoveryFile(output, goPkg string, desc []*descriptorpb.FileDescriptorProto, raw []byte) error { 78 var serviceNames []string 79 for _, f := range desc { 80 for _, s := range f.Service { 81 serviceNames = append(serviceNames, fmt.Sprintf("%s.%s", f.GetPackage(), s.GetName())) 82 } 83 } 84 if len(serviceNames) == 0 { 85 // no services, no discovery. 86 return nil 87 } 88 89 // Get the package name for "package ..." statement: it may be different from 90 // the directory name. Note that pkg.ImportPath almost always end up "." here 91 // when running in Go Modules mode, so we still need to keep `goPkg` argument. 92 pkg, err := build.ImportDir(filepath.Dir(output), 0) 93 if err != nil { 94 return errors.Annotate(err, "failed to figure out Go package name for %q", output).Err() 95 } 96 97 compressedDescBytes, err := compress(raw) 98 if err != nil { 99 return errors.Annotate(err, "failed to compress the descriptor set proto").Err() 100 } 101 102 var buf bytes.Buffer 103 err = discoveryTmpl.Execute(&buf, map[string]any{ 104 "GoPkg": pkg.Name, 105 "ImportDiscovery": goPkg != discoveryPackagePath, 106 "ServiceNames": serviceNames, 107 "CompressedBytes": asByteArray(compressedDescBytes), 108 }) 109 if err != nil { 110 return errors.Annotate(err, "failed to execute discovery file template").Err() 111 } 112 113 src := buf.Bytes() 114 formatted, err := gofmt(src) 115 if err != nil { 116 return errors.Annotate(err, "failed to gofmt the generated discovery file").Err() 117 } 118 119 return os.WriteFile(output, formatted, 0666) 120 } 121 122 // asByteArray converts blob to a valid []byte Go literal. 123 func asByteArray(blob []byte) string { 124 out := &bytes.Buffer{} 125 fmt.Fprintf(out, "[]byte{") 126 for i := 0; i < len(blob); i++ { 127 fmt.Fprintf(out, "%d, ", blob[i]) 128 if i%14 == 1 { 129 fmt.Fprintln(out) 130 } 131 } 132 fmt.Fprintf(out, "}") 133 return out.String() 134 } 135 136 // gofmt applies "gofmt -s" to the content of the buffer. 137 func gofmt(blob []byte) ([]byte, error) { 138 out := bytes.Buffer{} 139 cmd := exec.Command("gofmt", "-s") 140 cmd.Stdin = bytes.NewReader(blob) 141 cmd.Stdout = &out 142 cmd.Stderr = os.Stderr 143 if err := cmd.Run(); err != nil { 144 return nil, err 145 } 146 return out.Bytes(), nil 147 } 148 149 // compress compresses data with gzip. 150 func compress(data []byte) ([]byte, error) { 151 var buf bytes.Buffer 152 w := gzip.NewWriter(&buf) 153 if _, err := w.Write(data); err != nil { 154 return nil, err 155 } 156 if err := w.Close(); err != nil { 157 return nil, err 158 } 159 return buf.Bytes(), nil 160 }