gen.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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. "os"
  16. "os/exec"
  17. "path/filepath"
  18. "regexp"
  19. "strconv"
  20. "text/template"
  21. "time"
  22. )
  23. const genCodecPkg = "codec1978" // keep this in sync with codec.genCodecPkg
  24. const genFrunMainTmpl = `//+build ignore
  25. package main
  26. {{ if .Types }}import "{{ .ImportPath }}"{{ end }}
  27. func main() {
  28. {{ $.PackageName }}.CodecGenTempWrite{{ .RandString }}()
  29. }
  30. `
  31. // const genFrunPkgTmpl = `//+build codecgen
  32. const genFrunPkgTmpl = `
  33. package {{ $.PackageName }}
  34. import (
  35. {{ if not .CodecPkgFiles }}{{ .CodecPkgName }} "{{ .CodecImportPath }}"{{ end }}
  36. {{/*
  37. {{ if .Types }}"{{ .ImportPath }}"{{ end }}
  38. "io"
  39. */}}
  40. "os"
  41. "reflect"
  42. "bytes"
  43. "go/format"
  44. )
  45. {{/* This is not used anymore. Remove it.
  46. func write(w io.Writer, s string) {
  47. if _, err := io.WriteString(w, s); err != nil {
  48. panic(err)
  49. }
  50. }
  51. */}}
  52. func CodecGenTempWrite{{ .RandString }}() {
  53. fout, err := os.Create("{{ .OutFile }}")
  54. if err != nil {
  55. panic(err)
  56. }
  57. defer fout.Close()
  58. var out bytes.Buffer
  59. var typs []reflect.Type
  60. {{ range $index, $element := .Types }}
  61. var t{{ $index }} {{ . }}
  62. typs = append(typs, reflect.TypeOf(t{{ $index }}))
  63. {{ end }}
  64. {{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", {{ .UseUnsafe }}, typs...)
  65. bout, err := format.Source(out.Bytes())
  66. if err != nil {
  67. fout.Write(out.Bytes())
  68. panic(err)
  69. }
  70. fout.Write(bout)
  71. }
  72. `
  73. // Generate is given a list of *.go files to parse, and an output file (fout).
  74. //
  75. // It finds all types T in the files, and it creates 2 tmp files (frun).
  76. // - main package file passed to 'go run'
  77. // - package level file which calls *genRunner.Selfer to write Selfer impls for each T.
  78. // We use a package level file so that it can reference unexported types in the package being worked on.
  79. // Tool then executes: "go run __frun__" which creates fout.
  80. // fout contains Codec(En|De)codeSelf implementations for every type T.
  81. //
  82. func Generate(outfile, buildTag, codecPkgPath string, useUnsafe bool, goRunTag string,
  83. regexName *regexp.Regexp, deleteTempFile bool, infiles ...string) (err error) {
  84. // For each file, grab AST, find each type, and write a call to it.
  85. if len(infiles) == 0 {
  86. return
  87. }
  88. if outfile == "" || codecPkgPath == "" {
  89. err = errors.New("outfile and codec package path cannot be blank")
  90. return
  91. }
  92. // We have to parse dir for package, before opening the temp file for writing (else ImportDir fails).
  93. // Also, ImportDir(...) must take an absolute path.
  94. lastdir := filepath.Dir(outfile)
  95. absdir, err := filepath.Abs(lastdir)
  96. if err != nil {
  97. return
  98. }
  99. pkg, err := build.Default.ImportDir(absdir, build.AllowBinary)
  100. if err != nil {
  101. return
  102. }
  103. type tmplT struct {
  104. CodecPkgName string
  105. CodecImportPath string
  106. ImportPath string
  107. OutFile string
  108. PackageName string
  109. RandString string
  110. BuildTag string
  111. Types []string
  112. CodecPkgFiles bool
  113. UseUnsafe bool
  114. }
  115. tv := tmplT{
  116. CodecPkgName: genCodecPkg,
  117. OutFile: outfile,
  118. CodecImportPath: codecPkgPath,
  119. BuildTag: buildTag,
  120. UseUnsafe: useUnsafe,
  121. RandString: strconv.FormatInt(time.Now().UnixNano(), 10),
  122. }
  123. tv.ImportPath = pkg.ImportPath
  124. if tv.ImportPath == tv.CodecImportPath {
  125. tv.CodecPkgFiles = true
  126. tv.CodecPkgName = "codec"
  127. }
  128. astfiles := make([]*ast.File, len(infiles))
  129. for i, infile := range infiles {
  130. if filepath.Dir(infile) != lastdir {
  131. err = errors.New("in files must all be in same directory as outfile")
  132. return
  133. }
  134. fset := token.NewFileSet()
  135. astfiles[i], err = parser.ParseFile(fset, infile, nil, 0)
  136. if err != nil {
  137. return
  138. }
  139. if i == 0 {
  140. tv.PackageName = astfiles[i].Name.Name
  141. if tv.PackageName == "main" {
  142. // codecgen cannot be run on types in the 'main' package.
  143. // A temporary 'main' package must be created, and should reference the fully built
  144. // package containing the types.
  145. // Also, the temporary main package will conflict with the main package which already has a main method.
  146. err = errors.New("codecgen cannot be run on types in the 'main' package")
  147. return
  148. }
  149. }
  150. }
  151. for _, f := range astfiles {
  152. for _, d := range f.Decls {
  153. if gd, ok := d.(*ast.GenDecl); ok {
  154. for _, dd := range gd.Specs {
  155. if td, ok := dd.(*ast.TypeSpec); ok {
  156. // if len(td.Name.Name) == 0 || td.Name.Name[0] > 'Z' || td.Name.Name[0] < 'A' {
  157. if len(td.Name.Name) == 0 {
  158. continue
  159. }
  160. // only generate for:
  161. // struct: StructType
  162. // primitives (numbers, bool, string): Ident
  163. // map: MapType
  164. // slice, array: ArrayType
  165. // chan: ChanType
  166. // do not generate:
  167. // FuncType, InterfaceType, StarExpr (ptr), etc
  168. switch td.Type.(type) {
  169. case *ast.StructType, *ast.Ident, *ast.MapType, *ast.ArrayType, *ast.ChanType:
  170. if regexName.FindStringIndex(td.Name.Name) != nil {
  171. tv.Types = append(tv.Types, td.Name.Name)
  172. }
  173. }
  174. }
  175. }
  176. }
  177. }
  178. }
  179. if len(tv.Types) == 0 {
  180. return
  181. }
  182. // we cannot use ioutil.TempFile, because we cannot guarantee the file suffix (.go).
  183. // Also, we cannot create file in temp directory,
  184. // because go run will not work (as it needs to see the types here).
  185. // Consequently, create the temp file in the current directory, and remove when done.
  186. // frun, err = ioutil.TempFile("", "codecgen-")
  187. // frunName := filepath.Join(os.TempDir(), "codecgen-"+strconv.FormatInt(time.Now().UnixNano(), 10)+".go")
  188. frunMainName := "codecgen-main-" + tv.RandString + ".generated.go"
  189. frunPkgName := "codecgen-pkg-" + tv.RandString + ".generated.go"
  190. if deleteTempFile {
  191. defer os.Remove(frunMainName)
  192. defer os.Remove(frunPkgName)
  193. }
  194. // var frunMain, frunPkg *os.File
  195. if _, err = gen1(frunMainName, genFrunMainTmpl, &tv); err != nil {
  196. return
  197. }
  198. if _, err = gen1(frunPkgName, genFrunPkgTmpl, &tv); err != nil {
  199. return
  200. }
  201. // remove outfile, so "go run ..." will not think that types in outfile already exist.
  202. os.Remove(outfile)
  203. // execute go run frun
  204. cmd := exec.Command("go", "run", "-tags="+goRunTag, frunMainName) //, frunPkg.Name())
  205. var buf bytes.Buffer
  206. cmd.Stdout = &buf
  207. cmd.Stderr = &buf
  208. if err = cmd.Run(); err != nil {
  209. err = fmt.Errorf("error running 'go run %s': %v, console: %s",
  210. frunMainName, err, buf.Bytes())
  211. return
  212. }
  213. os.Stdout.Write(buf.Bytes())
  214. return
  215. }
  216. func gen1(frunName, tmplStr string, tv interface{}) (frun *os.File, err error) {
  217. os.Remove(frunName)
  218. if frun, err = os.Create(frunName); err != nil {
  219. return
  220. }
  221. defer frun.Close()
  222. t := template.New("")
  223. if t, err = t.Parse(tmplStr); err != nil {
  224. return
  225. }
  226. bw := bufio.NewWriter(frun)
  227. if err = t.Execute(bw, tv); err != nil {
  228. return
  229. }
  230. if err = bw.Flush(); err != nil {
  231. return
  232. }
  233. return
  234. }
  235. func main() {
  236. o := flag.String("o", "", "out file")
  237. c := flag.String("c", genCodecPath, "codec path")
  238. t := flag.String("t", "", "build tag to put in file")
  239. r := flag.String("r", ".*", "regex for type name to match")
  240. rt := flag.String("rt", "", "tags for go run")
  241. x := flag.Bool("x", false, "keep temp file")
  242. u := flag.Bool("u", false, "Use unsafe, e.g. to avoid unnecessary allocation on []byte->string")
  243. flag.Parse()
  244. if err := Generate(*o, *t, *c, *u, *rt,
  245. regexp.MustCompile(*r), !*x, flag.Args()...); err != nil {
  246. fmt.Fprintf(os.Stderr, "codecgen error: %v\n", err)
  247. os.Exit(1)
  248. }
  249. }