reflect.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright 2012 Google Inc.
  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. package main
  15. // This file contains the model construction by reflection.
  16. import (
  17. "bytes"
  18. "encoding/gob"
  19. "flag"
  20. "go/build"
  21. "io/ioutil"
  22. "os"
  23. "os/exec"
  24. "path/filepath"
  25. "runtime"
  26. "text/template"
  27. "github.com/golang/mock/mockgen/model"
  28. )
  29. var (
  30. progOnly = flag.Bool("prog_only", false, "(reflect mode) Only generate the reflection program; write it to stdout and exit.")
  31. execOnly = flag.String("exec_only", "", "(reflect mode) If set, execute this reflection program.")
  32. buildFlags = flag.String("build_flags", "", "(reflect mode) Additional flags for go build.")
  33. )
  34. func writeProgram(importPath string, symbols []string) ([]byte, error) {
  35. var program bytes.Buffer
  36. data := reflectData{
  37. ImportPath: importPath,
  38. Symbols: symbols,
  39. }
  40. if err := reflectProgram.Execute(&program, &data); err != nil {
  41. return nil, err
  42. }
  43. return program.Bytes(), nil
  44. }
  45. // run the given program and parse the output as a model.Package.
  46. func run(program string) (*model.Package, error) {
  47. f, err := ioutil.TempFile("", "")
  48. filename := f.Name()
  49. defer os.Remove(filename)
  50. if err := f.Close(); err != nil {
  51. return nil, err
  52. }
  53. // Run the program.
  54. cmd := exec.Command(program, "-output", filename)
  55. cmd.Stdout = os.Stdout
  56. cmd.Stderr = os.Stderr
  57. if err := cmd.Run(); err != nil {
  58. return nil, err
  59. }
  60. f, err = os.Open(filename)
  61. if err != nil {
  62. return nil, err
  63. }
  64. // Process output.
  65. var pkg model.Package
  66. if err := gob.NewDecoder(f).Decode(&pkg); err != nil {
  67. return nil, err
  68. }
  69. if err := f.Close(); err != nil {
  70. return nil, err
  71. }
  72. return &pkg, nil
  73. }
  74. // runInDir writes the given program into the given dir, runs it there, and
  75. // parses the output as a model.Package.
  76. func runInDir(program []byte, dir string) (*model.Package, error) {
  77. // We use TempDir instead of TempFile so we can control the filename.
  78. tmpDir, err := ioutil.TempDir(dir, "gomock_reflect_")
  79. if err != nil {
  80. return nil, err
  81. }
  82. defer func() { os.RemoveAll(tmpDir) }()
  83. const progSource = "prog.go"
  84. var progBinary = "prog.bin"
  85. if runtime.GOOS == "windows" {
  86. // Windows won't execute a program unless it has a ".exe" suffix.
  87. progBinary += ".exe"
  88. }
  89. if err := ioutil.WriteFile(filepath.Join(tmpDir, progSource), program, 0600); err != nil {
  90. return nil, err
  91. }
  92. cmdArgs := []string{}
  93. cmdArgs = append(cmdArgs, "build")
  94. if *buildFlags != "" {
  95. cmdArgs = append(cmdArgs, *buildFlags)
  96. }
  97. cmdArgs = append(cmdArgs, "-o", progBinary, progSource)
  98. // Build the program.
  99. cmd := exec.Command("go", cmdArgs...)
  100. cmd.Dir = tmpDir
  101. cmd.Stdout = os.Stdout
  102. cmd.Stderr = os.Stderr
  103. if err := cmd.Run(); err != nil {
  104. return nil, err
  105. }
  106. return run(filepath.Join(tmpDir, progBinary))
  107. }
  108. func Reflect(importPath string, symbols []string) (*model.Package, error) {
  109. // TODO: sanity check arguments
  110. if *execOnly != "" {
  111. return run(*execOnly)
  112. }
  113. program, err := writeProgram(importPath, symbols)
  114. if err != nil {
  115. return nil, err
  116. }
  117. if *progOnly {
  118. os.Stdout.Write(program)
  119. os.Exit(0)
  120. }
  121. wd, _ := os.Getwd()
  122. // Try to run the program in the same directory as the input package.
  123. if p, err := build.Import(importPath, wd, build.FindOnly); err == nil {
  124. dir := p.Dir
  125. if p, err := runInDir(program, dir); err == nil {
  126. return p, nil
  127. }
  128. }
  129. // Since that didn't work, try to run it in the current working directory.
  130. if p, err := runInDir(program, wd); err == nil {
  131. return p, nil
  132. }
  133. // Since that didn't work, try to run it in a standard temp directory.
  134. return runInDir(program, "")
  135. }
  136. type reflectData struct {
  137. ImportPath string
  138. Symbols []string
  139. }
  140. // This program reflects on an interface value, and prints the
  141. // gob encoding of a model.Package to standard output.
  142. // JSON doesn't work because of the model.Type interface.
  143. var reflectProgram = template.Must(template.New("program").Parse(`
  144. package main
  145. import (
  146. "encoding/gob"
  147. "flag"
  148. "fmt"
  149. "os"
  150. "path"
  151. "reflect"
  152. "github.com/golang/mock/mockgen/model"
  153. pkg_ {{printf "%q" .ImportPath}}
  154. )
  155. var output = flag.String("output", "", "The output file name, or empty to use stdout.")
  156. func main() {
  157. flag.Parse()
  158. its := []struct{
  159. sym string
  160. typ reflect.Type
  161. }{
  162. {{range .Symbols}}
  163. { {{printf "%q" .}}, reflect.TypeOf((*pkg_.{{.}})(nil)).Elem()},
  164. {{end}}
  165. }
  166. pkg := &model.Package{
  167. // NOTE: This behaves contrary to documented behaviour if the
  168. // package name is not the final component of the import path.
  169. // The reflect package doesn't expose the package name, though.
  170. Name: path.Base({{printf "%q" .ImportPath}}),
  171. }
  172. for _, it := range its {
  173. intf, err := model.InterfaceFromInterfaceType(it.typ)
  174. if err != nil {
  175. fmt.Fprintf(os.Stderr, "Reflection: %v\n", err)
  176. os.Exit(1)
  177. }
  178. intf.Name = it.sym
  179. pkg.Interfaces = append(pkg.Interfaces, intf)
  180. }
  181. outfile := os.Stdout
  182. if len(*output) != 0 {
  183. var err error
  184. outfile, err = os.Create(*output)
  185. if err != nil {
  186. fmt.Fprintf(os.Stderr, "failed to open output file %q", *output)
  187. }
  188. defer func() {
  189. if err := outfile.Close(); err != nil {
  190. fmt.Fprintf(os.Stderr, "failed to close output file %q", *output)
  191. os.Exit(1)
  192. }
  193. }()
  194. }
  195. if err := gob.NewEncoder(outfile).Encode(pkg); err != nil {
  196. fmt.Fprintf(os.Stderr, "gob encode: %v\n", err)
  197. os.Exit(1)
  198. }
  199. }
  200. `))