// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // The protoc-gen-go binary is a protoc plugin to generate a Go protocol // buffer package. package main import ( "bytes" "compress/gzip" "crypto/sha256" "encoding/hex" "fmt" "strconv" "strings" "github.com/golang/protobuf/proto" descpb "github.com/golang/protobuf/protoc-gen-go/descriptor" "google.golang.org/proto/protogen" ) func main() { protogen.Run(func(gen *protogen.Plugin) error { for _, f := range gen.Files { if !f.Generate { continue } genFile(gen, f) } return nil }) } type File struct { *protogen.File locationMap map[string][]*descpb.SourceCodeInfo_Location } func genFile(gen *protogen.Plugin, file *protogen.File) { f := &File{ File: file, locationMap: make(map[string][]*descpb.SourceCodeInfo_Location), } for _, loc := range file.Proto.GetSourceCodeInfo().GetLocation() { key := pathKey(loc.Path) f.locationMap[key] = append(f.locationMap[key], loc) } g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath) g.P("// Code generated by protoc-gen-go. DO NOT EDIT.") g.P("// source: ", f.Desc.Path()) g.P() const filePackageField = 2 // FileDescriptorProto.package genComment(g, f, []int32{filePackageField}) g.P() g.P("package ", f.GoPackageName) g.P() for _, message := range f.Messages { genMessage(gen, g, f, message) } genFileDescriptor(gen, g, f) } func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File) { // Determine the name of the var holding the file descriptor: // // fileDescriptor_ filenameHash := sha256.Sum256([]byte(f.Desc.Path())) varName := fmt.Sprintf("fileDescriptor_%s", hex.EncodeToString(filenameHash[:8])) // Trim the source_code_info from the descriptor. // Marshal and gzip it. descProto := proto.Clone(f.Proto).(*descpb.FileDescriptorProto) descProto.SourceCodeInfo = nil b, err := proto.Marshal(descProto) if err != nil { gen.Error(err) return } var buf bytes.Buffer w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression) w.Write(b) w.Close() b = buf.Bytes() g.P("func init() { proto.RegisterFile(", strconv.Quote(f.Desc.Path()), ", ", varName, ") }") g.P() g.P("var ", varName, " = []byte{") g.P("// ", len(b), " bytes of a gzipped FileDescriptorProto") for len(b) > 0 { n := 16 if n > len(b) { n = len(b) } s := "" for _, c := range b[:n] { s += fmt.Sprintf("0x%02x,", c) } g.P(s) b = b[n:] } g.P("}") g.P() } func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) { genComment(g, f, message.Path) g.P("type ", message.GoIdent, " struct {") g.P("}") g.P() for _, nested := range message.Messages { genMessage(gen, g, f, nested) } } func genComment(g *protogen.GeneratedFile, f *File, path []int32) { for _, loc := range f.locationMap[pathKey(path)] { if loc.LeadingComments == nil { continue } for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") { g.P("//", line) } return } } // pathKey converts a location path to a string suitable for use as a map key. func pathKey(path []int32) string { var buf []byte for i, x := range path { if i != 0 { buf = append(buf, ',') } buf = strconv.AppendInt(buf, int64(x), 10) } return string(buf) }