gen.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
  2. // Use of this source code is governed by a MIT license found in the LICENSE file.
  3. // codecgen generates codec.Selfer implementations for a set of types.
  4. package main
  5. import (
  6. "bufio"
  7. "bytes"
  8. "errors"
  9. "flag"
  10. "fmt"
  11. "go/ast"
  12. "go/build"
  13. "go/parser"
  14. "go/token"
  15. "math/rand"
  16. "os"
  17. "os/exec"
  18. "path/filepath"
  19. "regexp"
  20. "strconv"
  21. "strings"
  22. "text/template"
  23. "time"
  24. )
  25. const genCodecPkg = "codec1978" // keep this in sync with codec.genCodecPkg
  26. const genFrunMainTmpl = `//+build ignore
  27. // Code generated - temporary main package for codecgen - DO NOT EDIT.
  28. package main
  29. {{ if .Types }}import "{{ .ImportPath }}"{{ end }}
  30. func main() {
  31. {{ $.PackageName }}.CodecGenTempWrite{{ .RandString }}()
  32. }
  33. `
  34. // const genFrunPkgTmpl = `//+build codecgen
  35. const genFrunPkgTmpl = `
  36. // Code generated - temporary package for codecgen - DO NOT EDIT.
  37. package {{ $.PackageName }}
  38. import (
  39. {{ if not .CodecPkgFiles }}{{ .CodecPkgName }} "{{ .CodecImportPath }}"{{ end }}
  40. "os"
  41. "reflect"
  42. "bytes"
  43. "strings"
  44. "go/format"
  45. )
  46. func CodecGenTempWrite{{ .RandString }}() {
  47. fout, err := os.Create("{{ .OutFile }}")
  48. if err != nil {
  49. panic(err)
  50. }
  51. defer fout.Close()
  52. var out bytes.Buffer
  53. var typs []reflect.Type
  54. {{ range $index, $element := .Types }}
  55. var t{{ $index }} {{ . }}
  56. typs = append(typs, reflect.TypeOf(t{{ $index }}))
  57. {{ end }}
  58. {{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .NoExtensions }}, {{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}NewTypeInfos(strings.Split("{{ .StructTags }}", ",")), typs...)
  59. bout, err := format.Source(out.Bytes())
  60. if err != nil {
  61. fout.Write(out.Bytes())
  62. panic(err)
  63. }
  64. fout.Write(bout)
  65. }
  66. `
  67. // Generate is given a list of *.go files to parse, and an output file (fout).
  68. //
  69. // It finds all types T in the files, and it creates 2 tmp files (frun).
  70. // - main package file passed to 'go run'
  71. // - package level file which calls *genRunner.Selfer to write Selfer impls for each T.
  72. // We use a package level file so that it can reference unexported types in the package being worked on.
  73. // Tool then executes: "go run __frun__" which creates fout.
  74. // fout contains Codec(En|De)codeSelf implementations for every type T.
  75. //
  76. func Generate(outfile, buildTag, codecPkgPath string,
  77. uid int64,
  78. goRunTag string, st string,
  79. regexName, notRegexName *regexp.Regexp,
  80. deleteTempFile, noExtensions bool,
  81. infiles ...string) (err error) {
  82. // For each file, grab AST, find each type, and write a call to it.
  83. if len(infiles) == 0 {
  84. return
  85. }
  86. if outfile == "" || codecPkgPath == "" {
  87. err = errors.New("outfile and codec package path cannot be blank")
  88. return
  89. }
  90. if uid < 0 {
  91. uid = -uid
  92. }
  93. if uid == 0 {
  94. rr := rand.New(rand.NewSource(time.Now().UnixNano()))
  95. uid = 101 + rr.Int63n(9777)
  96. }
  97. // We have to parse dir for package, before opening the temp file for writing (else ImportDir fails).
  98. // Also, ImportDir(...) must take an absolute path.
  99. lastdir := filepath.Dir(outfile)
  100. absdir, err := filepath.Abs(lastdir)
  101. if err != nil {
  102. return
  103. }
  104. pkg, err := build.Default.ImportDir(absdir, build.AllowBinary)
  105. if err != nil {
  106. return
  107. }
  108. type tmplT struct {
  109. CodecPkgName string
  110. CodecImportPath string
  111. ImportPath string
  112. OutFile string
  113. PackageName string
  114. RandString string
  115. BuildTag string
  116. StructTags string
  117. Types []string
  118. CodecPkgFiles bool
  119. NoExtensions bool
  120. }
  121. tv := tmplT{
  122. CodecPkgName: genCodecPkg,
  123. OutFile: outfile,
  124. CodecImportPath: codecPkgPath,
  125. BuildTag: buildTag,
  126. RandString: strconv.FormatInt(uid, 10),
  127. StructTags: st,
  128. NoExtensions: noExtensions,
  129. }
  130. tv.ImportPath = pkg.ImportPath
  131. if tv.ImportPath == tv.CodecImportPath {
  132. tv.CodecPkgFiles = true
  133. tv.CodecPkgName = "codec"
  134. } else {
  135. // HACK: always handle vendoring. It should be typically on in go 1.6, 1.7
  136. tv.ImportPath = stripVendor(tv.ImportPath)
  137. }
  138. astfiles := make([]*ast.File, len(infiles))
  139. for i, infile := range infiles {
  140. if filepath.Dir(infile) != lastdir {
  141. err = errors.New("in files must all be in same directory as outfile")
  142. return
  143. }
  144. fset := token.NewFileSet()
  145. astfiles[i], err = parser.ParseFile(fset, infile, nil, 0)
  146. if err != nil {
  147. return
  148. }
  149. if i == 0 {
  150. tv.PackageName = astfiles[i].Name.Name
  151. if tv.PackageName == "main" {
  152. // codecgen cannot be run on types in the 'main' package.
  153. // A temporary 'main' package must be created, and should reference the fully built
  154. // package containing the types.
  155. // Also, the temporary main package will conflict with the main package which already has a main method.
  156. err = errors.New("codecgen cannot be run on types in the 'main' package")
  157. return
  158. }
  159. }
  160. }
  161. // keep track of types with selfer methods
  162. // selferMethods := []string{"CodecEncodeSelf", "CodecDecodeSelf"}
  163. selferEncTyps := make(map[string]bool)
  164. selferDecTyps := make(map[string]bool)
  165. for _, f := range astfiles {
  166. for _, d := range f.Decls {
  167. // if fd, ok := d.(*ast.FuncDecl); ok && fd.Recv != nil && fd.Recv.NumFields() == 1 {
  168. if fd, ok := d.(*ast.FuncDecl); ok && fd.Recv != nil && len(fd.Recv.List) == 1 {
  169. recvType := fd.Recv.List[0].Type
  170. if ptr, ok := recvType.(*ast.StarExpr); ok {
  171. recvType = ptr.X
  172. }
  173. if id, ok := recvType.(*ast.Ident); ok {
  174. switch fd.Name.Name {
  175. case "CodecEncodeSelf":
  176. selferEncTyps[id.Name] = true
  177. case "CodecDecodeSelf":
  178. selferDecTyps[id.Name] = true
  179. }
  180. }
  181. }
  182. }
  183. }
  184. // now find types
  185. for _, f := range astfiles {
  186. for _, d := range f.Decls {
  187. if gd, ok := d.(*ast.GenDecl); ok {
  188. for _, dd := range gd.Specs {
  189. if td, ok := dd.(*ast.TypeSpec); ok {
  190. // if len(td.Name.Name) == 0 || td.Name.Name[0] > 'Z' || td.Name.Name[0] < 'A' {
  191. if len(td.Name.Name) == 0 {
  192. continue
  193. }
  194. // only generate for:
  195. // struct: StructType
  196. // primitives (numbers, bool, string): Ident
  197. // map: MapType
  198. // slice, array: ArrayType
  199. // chan: ChanType
  200. // do not generate:
  201. // FuncType, InterfaceType, StarExpr (ptr), etc
  202. switch td.Type.(type) {
  203. case *ast.StructType, *ast.Ident, *ast.MapType, *ast.ArrayType, *ast.ChanType:
  204. // only add to tv.Types iff
  205. // - it matches per the -r parameter
  206. // - it doesn't match per the -nr parameter
  207. // - it doesn't have any of the Selfer methods in the file
  208. if regexName.FindStringIndex(td.Name.Name) != nil &&
  209. notRegexName.FindStringIndex(td.Name.Name) == nil &&
  210. !selferEncTyps[td.Name.Name] &&
  211. !selferDecTyps[td.Name.Name] {
  212. tv.Types = append(tv.Types, td.Name.Name)
  213. }
  214. }
  215. }
  216. }
  217. }
  218. }
  219. }
  220. if len(tv.Types) == 0 {
  221. return
  222. }
  223. // we cannot use ioutil.TempFile, because we cannot guarantee the file suffix (.go).
  224. // Also, we cannot create file in temp directory,
  225. // because go run will not work (as it needs to see the types here).
  226. // Consequently, create the temp file in the current directory, and remove when done.
  227. // frun, err = ioutil.TempFile("", "codecgen-")
  228. // frunName := filepath.Join(os.TempDir(), "codecgen-"+strconv.FormatInt(time.Now().UnixNano(), 10)+".go")
  229. frunMainName := "codecgen-main-" + tv.RandString + ".generated.go"
  230. frunPkgName := "codecgen-pkg-" + tv.RandString + ".generated.go"
  231. if deleteTempFile {
  232. defer os.Remove(frunMainName)
  233. defer os.Remove(frunPkgName)
  234. }
  235. // var frunMain, frunPkg *os.File
  236. if _, err = gen1(frunMainName, genFrunMainTmpl, &tv); err != nil {
  237. return
  238. }
  239. if _, err = gen1(frunPkgName, genFrunPkgTmpl, &tv); err != nil {
  240. return
  241. }
  242. // remove outfile, so "go run ..." will not think that types in outfile already exist.
  243. os.Remove(outfile)
  244. // execute go run frun
  245. cmd := exec.Command("go", "run", "-tags", "codecgen.exec safe "+goRunTag, frunMainName) //, frunPkg.Name())
  246. var buf bytes.Buffer
  247. cmd.Stdout = &buf
  248. cmd.Stderr = &buf
  249. if err = cmd.Run(); err != nil {
  250. err = fmt.Errorf("error running 'go run %s': %v, console: %s",
  251. frunMainName, err, buf.Bytes())
  252. return
  253. }
  254. os.Stdout.Write(buf.Bytes())
  255. return
  256. }
  257. func gen1(frunName, tmplStr string, tv interface{}) (frun *os.File, err error) {
  258. os.Remove(frunName)
  259. if frun, err = os.Create(frunName); err != nil {
  260. return
  261. }
  262. defer frun.Close()
  263. t := template.New("")
  264. if t, err = t.Parse(tmplStr); err != nil {
  265. return
  266. }
  267. bw := bufio.NewWriter(frun)
  268. if err = t.Execute(bw, tv); err != nil {
  269. return
  270. }
  271. if err = bw.Flush(); err != nil {
  272. return
  273. }
  274. return
  275. }
  276. // copied from ../gen.go (keep in sync).
  277. func stripVendor(s string) string {
  278. // HACK: Misbehaviour occurs in go 1.5. May have to re-visit this later.
  279. // if s contains /vendor/ OR startsWith vendor/, then return everything after it.
  280. const vendorStart = "vendor/"
  281. const vendorInline = "/vendor/"
  282. if i := strings.LastIndex(s, vendorInline); i >= 0 {
  283. s = s[i+len(vendorInline):]
  284. } else if strings.HasPrefix(s, vendorStart) {
  285. s = s[len(vendorStart):]
  286. }
  287. return s
  288. }
  289. func main() {
  290. o := flag.String("o", "", "out file")
  291. c := flag.String("c", genCodecPath, "codec path")
  292. t := flag.String("t", "", "build tag to put in file")
  293. r := flag.String("r", ".*", "regex for type name to match")
  294. nr := flag.String("nr", "^$", "regex for type name to exclude")
  295. rt := flag.String("rt", "", "tags for go run")
  296. st := flag.String("st", "codec,json", "struct tag keys to introspect")
  297. x := flag.Bool("x", false, "keep temp file")
  298. _ = flag.Bool("u", false, "*IGNORED - kept for backwards compatibility*: Allow unsafe use")
  299. d := flag.Int64("d", 0, "random identifier for use in generated code")
  300. nx := flag.Bool("nx", false, "no extensions")
  301. flag.Parse()
  302. if err := Generate(*o, *t, *c, *d, *rt, *st,
  303. regexp.MustCompile(*r), regexp.MustCompile(*nr), !*x, *nx,
  304. flag.Args()...); err != nil {
  305. fmt.Fprintf(os.Stderr, "codecgen error: %v\n", err)
  306. os.Exit(1)
  307. }
  308. }