github.com/jhump/protoreflect@v1.16.0/desc/sourceinfo/cmd/protoc-gen-gosrcinfo/main.go (about) 1 // Command protoc-gen-gosrcinfo is a protoc plugin. It emits Go code, into files 2 // named "<file>.pb.srcinfo.go". These source files include source code info for 3 // processed proto files and register that info with the srcinfo package. 4 package main 5 6 import ( 7 "bytes" 8 "compress/gzip" 9 "fmt" 10 "path" 11 "strings" 12 13 "github.com/jhump/gopoet" 14 "github.com/jhump/goprotoc/plugins" 15 "google.golang.org/protobuf/proto" 16 "google.golang.org/protobuf/types/pluginpb" 17 18 "github.com/jhump/protoreflect/desc" 19 ) 20 21 func main() { 22 plugins.PluginMain(genSourceInfo) 23 } 24 25 func genSourceInfo(req *plugins.CodeGenRequest, resp *plugins.CodeGenResponse) error { 26 resp.SupportsFeatures(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) 27 args, err := parseArgs(req.Args) 28 if err != nil { 29 return err 30 } 31 names := plugins.GoNames{ 32 ImportMap: args.importMap, 33 ModuleRoot: args.moduleRoot, 34 SourceRelative: args.sourceRelative, 35 } 36 if args.importPath != "" { 37 // if we're overriding import path, go ahead and query 38 // package for each file, which will cache the override name 39 // so all subsequent queries are consistent 40 for _, fd := range req.Files { 41 // Only use the override for files that don't otherwise have an 42 // entry in the specified import map 43 if _, ok := args.importMap[fd.GetName()]; !ok { 44 names.GoPackageForFileWithOverride(fd, args.importPath) 45 } 46 } 47 } 48 for _, fd := range req.Files { 49 if err := generateSourceInfo(fd, &names, resp, args); err != nil { 50 if fe, ok := err.(*gopoet.FormatError); ok { 51 if args.debug { 52 return fmt.Errorf("%s: error in generated Go code: %v:\n%s", fd.GetName(), err, fe.Unformatted) 53 } else { 54 return fmt.Errorf("%s: error in generated Go code: %v (use debug=true arg to show full source)", fd.GetName(), err) 55 } 56 } else { 57 return fmt.Errorf("%s: %v", fd.GetName(), err) 58 } 59 } 60 } 61 return nil 62 } 63 64 func generateSourceInfo(fd *desc.FileDescriptor, names *plugins.GoNames, resp *plugins.CodeGenResponse, args codeGenArgs) error { 65 si := fd.AsFileDescriptorProto().GetSourceCodeInfo() 66 if len(si.GetLocation()) == 0 { 67 return nil 68 } 69 pkg := names.GoPackageForFile(fd) 70 filename := names.OutputFilenameFor(fd, ".pb.srcinfo.go") 71 f := gopoet.NewGoFile(path.Base(filename), pkg.ImportPath, pkg.Name) 72 73 f.FileComment = "Code generated by protoc-gen-gosrcinfo. DO NOT EDIT.\n" + 74 "source: " + fd.GetName() 75 76 siBytes, err := proto.Marshal(si) 77 if err != nil { 78 return fmt.Errorf("failed to serialize source code info: %w", err) 79 } 80 // Source code info has lots of repeated sequences of bytes in the 'path' field 81 // of locations, and the comments tend to be text that is reasonably well 82 // compressed. So we can make binaries a little smaller by gzipping first. 83 var encodedBuf bytes.Buffer 84 zipWriter := gzip.NewWriter(&encodedBuf) 85 if _, err := zipWriter.Write(siBytes); err != nil { 86 return fmt.Errorf("failed to compress source code info: %w", err) 87 } 88 if err := zipWriter.Close(); err != nil { 89 return fmt.Errorf("failed to compress source code info: %w", err) 90 } 91 92 srcInfoPkg := f.RegisterImport("github.com/jhump/protoreflect/desc/sourceinfo", "sourceinfo") 93 94 var srcInfoVarBlock gopoet.CodeBlock 95 srcInfoVarBlock.Println("srcInfo := []byte{") 96 encodedBytes := encodedBuf.Bytes() 97 for len(encodedBytes) > 0 { 98 var chunk []byte 99 if len(encodedBytes) < 16 { 100 chunk = encodedBytes 101 encodedBytes = nil 102 } else { 103 chunk = encodedBytes[:16] 104 encodedBytes = encodedBytes[16:] 105 } 106 for _, b := range chunk { 107 srcInfoVarBlock.Printf(" 0x%02x,", b) 108 } 109 srcInfoVarBlock.Println("") 110 } 111 srcInfoVarBlock.Println("}") 112 f.AddElement(gopoet.NewFunc("init"). 113 AddCode(&srcInfoVarBlock). 114 Printlnf("if err := %sRegisterEncodedSourceInfo(%q, srcInfo); err != nil {", srcInfoPkg, fd.GetName()). 115 Println(" panic(err)"). 116 Println("}")) 117 118 out := resp.OutputFile(filename) 119 return gopoet.WriteGoFile(out, f) 120 } 121 122 func clean(name string) string { 123 name = strings.TrimSuffix(name, ".proto") 124 data := ([]byte)(name) 125 for i, b := range data { 126 if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') { 127 continue 128 } 129 data[i] = '_' 130 } 131 return string(data) 132 } 133 134 type codeGenArgs struct { 135 debug bool 136 importPath string 137 importMap map[string]string 138 moduleRoot string 139 sourceRelative bool 140 } 141 142 func parseArgs(args []string) (codeGenArgs, error) { 143 var result codeGenArgs 144 for _, arg := range args { 145 vals := strings.SplitN(arg, "=", 2) 146 switch vals[0] { 147 case "debug": 148 val, err := boolVal(vals) 149 if err != nil { 150 return result, err 151 } 152 result.debug = val 153 154 case "import_path": 155 if len(vals) == 1 { 156 return result, fmt.Errorf("plugin option 'import_path' requires an argument") 157 } 158 result.importPath = vals[1] 159 160 case "module": 161 if len(vals) == 1 { 162 return result, fmt.Errorf("plugin option 'module' requires an argument") 163 } 164 result.moduleRoot = vals[1] 165 166 case "paths": 167 if len(vals) == 1 { 168 return result, fmt.Errorf("plugin option 'paths' requires an argument") 169 } 170 switch vals[1] { 171 case "import": 172 result.sourceRelative = false 173 case "source_relative": 174 result.sourceRelative = true 175 default: 176 return result, fmt.Errorf("plugin option 'paths' accepts 'import' or 'source_relative' as value, got %q", vals[1]) 177 } 178 179 default: 180 if len(vals[0]) > 1 && vals[0][0] == 'M' { 181 if len(vals) == 1 { 182 return result, fmt.Errorf("plugin 'M' options require an argument: %s", vals[0]) 183 } 184 if result.importMap == nil { 185 result.importMap = map[string]string{} 186 } 187 result.importMap[vals[0][1:]] = vals[1] 188 break 189 } 190 191 return result, fmt.Errorf("unknown plugin option: %s", vals[0]) 192 } 193 } 194 195 if result.sourceRelative && result.moduleRoot != "" { 196 return result, fmt.Errorf("plugin option 'module' cannot be used with 'paths=source_relative'") 197 } 198 199 return result, nil 200 } 201 202 func boolVal(vals []string) (bool, error) { 203 if len(vals) == 1 { 204 // if no value, assume "true" 205 return true, nil 206 } 207 switch strings.ToLower(vals[1]) { 208 case "true", "on", "yes", "1": 209 return true, nil 210 case "false", "off", "no", "0": 211 return false, nil 212 default: 213 return false, fmt.Errorf("invalid boolean arg for option '%s': %s", vals[0], vals[1]) 214 } 215 }