cmd.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // go-to-protobuf generates a Protobuf IDL from a Go struct, respecting any
  14. // existing IDL tags on the Go struct.
  15. package protobuf
  16. import (
  17. "bytes"
  18. "fmt"
  19. "log"
  20. "os/exec"
  21. "path/filepath"
  22. "strings"
  23. "k8s.io/kubernetes/cmd/libs/go2idl/args"
  24. "k8s.io/kubernetes/cmd/libs/go2idl/generator"
  25. "k8s.io/kubernetes/cmd/libs/go2idl/namer"
  26. "k8s.io/kubernetes/cmd/libs/go2idl/parser"
  27. "k8s.io/kubernetes/cmd/libs/go2idl/types"
  28. flag "github.com/spf13/pflag"
  29. )
  30. type Generator struct {
  31. Common args.GeneratorArgs
  32. Packages string
  33. OutputBase string
  34. ProtoImport []string
  35. Conditional string
  36. Clean bool
  37. OnlyIDL bool
  38. KeepGogoproto bool
  39. SkipGeneratedRewrite bool
  40. DropEmbeddedFields string
  41. }
  42. func New() *Generator {
  43. sourceTree := args.DefaultSourceTree()
  44. common := args.GeneratorArgs{
  45. OutputBase: sourceTree,
  46. GoHeaderFilePath: filepath.Join(sourceTree, "k8s.io/kubernetes/hack/boilerplate/boilerplate.go.txt"),
  47. }
  48. defaultProtoImport := filepath.Join(sourceTree, "k8s.io", "kubernetes", "vendor", "github.com", "gogo", "protobuf", "protobuf")
  49. return &Generator{
  50. Common: common,
  51. OutputBase: sourceTree,
  52. ProtoImport: []string{defaultProtoImport},
  53. Packages: strings.Join([]string{
  54. `+k8s.io/kubernetes/pkg/util/intstr`,
  55. `+k8s.io/kubernetes/pkg/api/resource`,
  56. `+k8s.io/kubernetes/pkg/runtime`,
  57. `+k8s.io/kubernetes/pkg/watch/versioned`,
  58. `k8s.io/kubernetes/pkg/api/unversioned`,
  59. `k8s.io/kubernetes/pkg/api/v1`,
  60. `k8s.io/kubernetes/pkg/apis/policy/v1alpha1`,
  61. `k8s.io/kubernetes/pkg/apis/extensions/v1beta1`,
  62. `k8s.io/kubernetes/pkg/apis/autoscaling/v1`,
  63. `k8s.io/kubernetes/pkg/apis/authorization/v1beta1`,
  64. `k8s.io/kubernetes/pkg/apis/batch/v1`,
  65. `k8s.io/kubernetes/pkg/apis/batch/v2alpha1`,
  66. `k8s.io/kubernetes/pkg/apis/apps/v1alpha1`,
  67. `k8s.io/kubernetes/pkg/apis/authentication/v1beta1`,
  68. `k8s.io/kubernetes/pkg/apis/rbac/v1alpha1`,
  69. `k8s.io/kubernetes/federation/apis/federation/v1beta1`,
  70. `k8s.io/kubernetes/pkg/apis/certificates/v1alpha1`,
  71. `k8s.io/kubernetes/pkg/apis/imagepolicy/v1alpha1`,
  72. }, ","),
  73. DropEmbeddedFields: "k8s.io/kubernetes/pkg/api/unversioned.TypeMeta",
  74. }
  75. }
  76. func (g *Generator) BindFlags(flag *flag.FlagSet) {
  77. flag.StringVarP(&g.Common.GoHeaderFilePath, "go-header-file", "h", g.Common.GoHeaderFilePath, "File containing boilerplate header text. The string YEAR will be replaced with the current 4-digit year.")
  78. flag.BoolVar(&g.Common.VerifyOnly, "verify-only", g.Common.VerifyOnly, "If true, only verify existing output, do not write anything.")
  79. flag.StringVarP(&g.Packages, "packages", "p", g.Packages, "comma-separated list of directories to get input types from. Directories prefixed with '-' are not generated, directories prefixed with '+' only create types with explicit IDL instructions.")
  80. flag.StringVarP(&g.OutputBase, "output-base", "o", g.OutputBase, "Output base; defaults to $GOPATH/src/")
  81. flag.StringSliceVar(&g.ProtoImport, "proto-import", g.ProtoImport, "The search path for the core protobuf .protos, required, defaults to GODEPS on path.")
  82. flag.StringVar(&g.Conditional, "conditional", g.Conditional, "An optional Golang build tag condition to add to the generated Go code")
  83. flag.BoolVar(&g.Clean, "clean", g.Clean, "If true, remove all generated files for the specified Packages.")
  84. flag.BoolVar(&g.OnlyIDL, "only-idl", g.OnlyIDL, "If true, only generate the IDL for each package.")
  85. flag.BoolVar(&g.KeepGogoproto, "keep-gogoproto", g.KeepGogoproto, "If true, the generated IDL will contain gogoprotobuf extensions which are normally removed")
  86. flag.BoolVar(&g.SkipGeneratedRewrite, "skip-generated-rewrite", g.SkipGeneratedRewrite, "If true, skip fixing up the generated.pb.go file (debugging only).")
  87. flag.StringVar(&g.DropEmbeddedFields, "drop-embedded-fields", g.DropEmbeddedFields, "Comma-delimited list of embedded Go types to omit from generated protobufs")
  88. }
  89. func Run(g *Generator) {
  90. if g.Common.VerifyOnly {
  91. g.OnlyIDL = true
  92. g.Clean = false
  93. }
  94. b := parser.New()
  95. b.AddBuildTags("proto")
  96. omitTypes := map[types.Name]struct{}{}
  97. for _, t := range strings.Split(g.DropEmbeddedFields, ",") {
  98. name := types.Name{}
  99. if i := strings.LastIndex(t, "."); i != -1 {
  100. name.Package, name.Name = t[:i], t[i+1:]
  101. } else {
  102. name.Name = t
  103. }
  104. if len(name.Name) == 0 {
  105. log.Fatalf("--drop-embedded-types requires names in the form of [GOPACKAGE.]TYPENAME: %v", t)
  106. }
  107. omitTypes[name] = struct{}{}
  108. }
  109. boilerplate, err := g.Common.LoadGoBoilerplate()
  110. if err != nil {
  111. log.Fatalf("Failed loading boilerplate: %v", err)
  112. }
  113. protobufNames := NewProtobufNamer()
  114. outputPackages := generator.Packages{}
  115. for _, d := range strings.Split(g.Packages, ",") {
  116. generateAllTypes, outputPackage := true, true
  117. switch {
  118. case strings.HasPrefix(d, "+"):
  119. d = d[1:]
  120. generateAllTypes = false
  121. case strings.HasPrefix(d, "-"):
  122. d = d[1:]
  123. outputPackage = false
  124. }
  125. if strings.Contains(d, "-") {
  126. log.Fatalf("Package names must be valid protobuf package identifiers, which allow only [a-z0-9_]: %s", d)
  127. }
  128. name := protoSafePackage(d)
  129. parts := strings.SplitN(d, "=", 2)
  130. if len(parts) > 1 {
  131. d = parts[0]
  132. name = parts[1]
  133. }
  134. p := newProtobufPackage(d, name, generateAllTypes, omitTypes)
  135. header := append([]byte{}, boilerplate...)
  136. header = append(header, p.HeaderText...)
  137. p.HeaderText = header
  138. protobufNames.Add(p)
  139. if outputPackage {
  140. outputPackages = append(outputPackages, p)
  141. }
  142. }
  143. if !g.Common.VerifyOnly {
  144. for _, p := range outputPackages {
  145. if err := p.(*protobufPackage).Clean(g.OutputBase); err != nil {
  146. log.Fatalf("Unable to clean package %s: %v", p.Name(), err)
  147. }
  148. }
  149. }
  150. if g.Clean {
  151. return
  152. }
  153. for _, p := range protobufNames.List() {
  154. if err := b.AddDir(p.Path()); err != nil {
  155. log.Fatalf("Unable to add directory %q: %v", p.Path(), err)
  156. }
  157. }
  158. c, err := generator.NewContext(
  159. b,
  160. namer.NameSystems{
  161. "public": namer.NewPublicNamer(3),
  162. "proto": protobufNames,
  163. },
  164. "public",
  165. )
  166. if err != nil {
  167. log.Fatalf("Failed making a context: %v", err)
  168. }
  169. c.Verify = g.Common.VerifyOnly
  170. c.FileTypes["protoidl"] = NewProtoFile()
  171. if err := protobufNames.AssignTypesToPackages(c); err != nil {
  172. log.Fatalf("Failed to identify Common types: %v", err)
  173. }
  174. if err := c.ExecutePackages(g.OutputBase, outputPackages); err != nil {
  175. log.Fatalf("Failed executing generator: %v", err)
  176. }
  177. if g.OnlyIDL {
  178. return
  179. }
  180. if _, err := exec.LookPath("protoc"); err != nil {
  181. log.Fatalf("Unable to find 'protoc': %v", err)
  182. }
  183. searchArgs := []string{"-I", ".", "-I", g.OutputBase}
  184. if len(g.ProtoImport) != 0 {
  185. for _, s := range g.ProtoImport {
  186. searchArgs = append(searchArgs, "-I", s)
  187. }
  188. }
  189. args := append(searchArgs, fmt.Sprintf("--gogo_out=%s", g.OutputBase))
  190. buf := &bytes.Buffer{}
  191. if len(g.Conditional) > 0 {
  192. fmt.Fprintf(buf, "// +build %s\n\n", g.Conditional)
  193. }
  194. buf.Write(boilerplate)
  195. for _, outputPackage := range outputPackages {
  196. p := outputPackage.(*protobufPackage)
  197. path := filepath.Join(g.OutputBase, p.ImportPath())
  198. outputPath := filepath.Join(g.OutputBase, p.OutputPath())
  199. // generate the gogoprotobuf protoc
  200. cmd := exec.Command("protoc", append(args, path)...)
  201. out, err := cmd.CombinedOutput()
  202. if len(out) > 0 {
  203. log.Printf(string(out))
  204. }
  205. if err != nil {
  206. log.Println(strings.Join(cmd.Args, " "))
  207. log.Fatalf("Unable to generate protoc on %s: %v", p.PackageName, err)
  208. }
  209. if g.SkipGeneratedRewrite {
  210. continue
  211. }
  212. // alter the generated protobuf file to remove the generated types (but leave the serializers) and rewrite the
  213. // package statement to match the desired package name
  214. if err := RewriteGeneratedGogoProtobufFile(outputPath, p.ExtractGeneratedType, p.OptionalTypeName, buf.Bytes()); err != nil {
  215. log.Fatalf("Unable to rewrite generated %s: %v", outputPath, err)
  216. }
  217. // sort imports
  218. cmd = exec.Command("goimports", "-w", outputPath)
  219. out, err = cmd.CombinedOutput()
  220. if len(out) > 0 {
  221. log.Printf(string(out))
  222. }
  223. if err != nil {
  224. log.Println(strings.Join(cmd.Args, " "))
  225. log.Fatalf("Unable to rewrite imports for %s: %v", p.PackageName, err)
  226. }
  227. // format and simplify the generated file
  228. cmd = exec.Command("gofmt", "-s", "-w", outputPath)
  229. out, err = cmd.CombinedOutput()
  230. if len(out) > 0 {
  231. log.Printf(string(out))
  232. }
  233. if err != nil {
  234. log.Println(strings.Join(cmd.Args, " "))
  235. log.Fatalf("Unable to apply gofmt for %s: %v", p.PackageName, err)
  236. }
  237. }
  238. if g.SkipGeneratedRewrite {
  239. return
  240. }
  241. if !g.KeepGogoproto {
  242. // generate, but do so without gogoprotobuf extensions
  243. for _, outputPackage := range outputPackages {
  244. p := outputPackage.(*protobufPackage)
  245. p.OmitGogo = true
  246. }
  247. if err := c.ExecutePackages(g.OutputBase, outputPackages); err != nil {
  248. log.Fatalf("Failed executing generator: %v", err)
  249. }
  250. }
  251. for _, outputPackage := range outputPackages {
  252. p := outputPackage.(*protobufPackage)
  253. if len(p.StructTags) == 0 {
  254. continue
  255. }
  256. pattern := filepath.Join(g.OutputBase, p.PackagePath, "*.go")
  257. files, err := filepath.Glob(pattern)
  258. if err != nil {
  259. log.Fatalf("Can't glob pattern %q: %v", pattern, err)
  260. }
  261. for _, s := range files {
  262. if strings.HasSuffix(s, "_test.go") {
  263. continue
  264. }
  265. if err := RewriteTypesWithProtobufStructTags(s, p.StructTags); err != nil {
  266. log.Fatalf("Unable to rewrite with struct tags %s: %v", s, err)
  267. }
  268. }
  269. }
  270. }