blob: 2f8680d1e0fc6375444141ff46533c85634bf64a [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// Package args has common command-line flags for generation programs.
18package args
19
20import (
21 "bytes"
22 goflag "flag"
23 "fmt"
24 "io/ioutil"
25 "os"
26 "path"
27 "path/filepath"
28 "strconv"
29 "strings"
30 "time"
31
32 "k8s.io/gengo/generator"
33 "k8s.io/gengo/namer"
34 "k8s.io/gengo/parser"
35 "k8s.io/gengo/types"
36
37 "github.com/spf13/pflag"
38)
39
40// Default returns a defaulted GeneratorArgs. You may change the defaults
41// before calling AddFlags.
42func Default() *GeneratorArgs {
43 return &GeneratorArgs{
44 OutputBase: DefaultSourceTree(),
45 GoHeaderFilePath: filepath.Join(DefaultSourceTree(), "k8s.io/gengo/boilerplate/boilerplate.go.txt"),
46 GeneratedBuildTag: "ignore_autogenerated",
47 GeneratedByCommentTemplate: "// Code generated by GENERATOR_NAME. DO NOT EDIT.",
48 defaultCommandLineFlags: true,
49 }
50}
51
52// GeneratorArgs has arguments that are passed to generators.
53type GeneratorArgs struct {
54 // Which directories to parse.
55 InputDirs []string
56
57 // Source tree to write results to.
58 OutputBase string
59
60 // Package path within the source tree.
61 OutputPackagePath string
62
63 // Output file name.
64 OutputFileBaseName string
65
66 // Where to get copyright header text.
67 GoHeaderFilePath string
68
69 // If GeneratedByCommentTemplate is set, generate a "Code generated by" comment
70 // below the bloilerplate, of the format defined by this string.
71 // Any instances of "GENERATOR_NAME" will be replaced with the name of the code generator.
72 GeneratedByCommentTemplate string
73
74 // If true, only verify, don't write anything.
75 VerifyOnly bool
76
77 // GeneratedBuildTag is the tag used to identify code generated by execution
78 // of this type. Each generator should use a different tag, and different
79 // groups of generators (external API that depends on Kube generations) should
80 // keep tags distinct as well.
81 GeneratedBuildTag string
82
83 // Any custom arguments go here
84 CustomArgs interface{}
85
86 // Whether to use default command line flags
87 defaultCommandLineFlags bool
88}
89
90// WithoutDefaultFlagParsing disables implicit addition of command line flags and parsing.
91func (g *GeneratorArgs) WithoutDefaultFlagParsing() *GeneratorArgs {
92 g.defaultCommandLineFlags = false
93 return g
94}
95
96func (g *GeneratorArgs) AddFlags(fs *pflag.FlagSet) {
97 fs.StringSliceVarP(&g.InputDirs, "input-dirs", "i", g.InputDirs, "Comma-separated list of import paths to get input types from.")
98 fs.StringVarP(&g.OutputBase, "output-base", "o", g.OutputBase, "Output base; defaults to $GOPATH/src/ or ./ if $GOPATH is not set.")
99 fs.StringVarP(&g.OutputPackagePath, "output-package", "p", g.OutputPackagePath, "Base package path.")
100 fs.StringVarP(&g.OutputFileBaseName, "output-file-base", "O", g.OutputFileBaseName, "Base name (without .go suffix) for output files.")
101 fs.StringVarP(&g.GoHeaderFilePath, "go-header-file", "h", g.GoHeaderFilePath, "File containing boilerplate header text. The string YEAR will be replaced with the current 4-digit year.")
102 fs.BoolVar(&g.VerifyOnly, "verify-only", g.VerifyOnly, "If true, only verify existing output, do not write anything.")
103 fs.StringVar(&g.GeneratedBuildTag, "build-tag", g.GeneratedBuildTag, "A Go build tag to use to identify files generated by this command. Should be unique.")
104}
105
106// LoadGoBoilerplate loads the boilerplate file passed to --go-header-file.
107func (g *GeneratorArgs) LoadGoBoilerplate() ([]byte, error) {
108 b, err := ioutil.ReadFile(g.GoHeaderFilePath)
109 if err != nil {
110 return nil, err
111 }
112 b = bytes.Replace(b, []byte("YEAR"), []byte(strconv.Itoa(time.Now().Year())), -1)
113
114 if g.GeneratedByCommentTemplate != "" {
115 if len(b) != 0 {
116 b = append(b, byte('\n'))
117 }
118 generatorName := path.Base(os.Args[0])
119 generatedByComment := strings.Replace(g.GeneratedByCommentTemplate, "GENERATOR_NAME", generatorName, -1)
120 s := fmt.Sprintf("%s\n\n", generatedByComment)
121 b = append(b, []byte(s)...)
122 }
123 return b, nil
124}
125
126// NewBuilder makes a new parser.Builder and populates it with the input
127// directories.
128func (g *GeneratorArgs) NewBuilder() (*parser.Builder, error) {
129 b := parser.New()
130 // Ignore all auto-generated files.
131 b.AddBuildTags(g.GeneratedBuildTag)
132
133 for _, d := range g.InputDirs {
134 var err error
135 if strings.HasSuffix(d, "/...") {
136 err = b.AddDirRecursive(strings.TrimSuffix(d, "/..."))
137 } else {
138 err = b.AddDir(d)
139 }
140 if err != nil {
141 return nil, fmt.Errorf("unable to add directory %q: %v", d, err)
142 }
143 }
144 return b, nil
145}
146
147// InputIncludes returns true if the given package is a (sub) package of one of
148// the InputDirs.
149func (g *GeneratorArgs) InputIncludes(p *types.Package) bool {
150 for _, dir := range g.InputDirs {
151 d := dir
152 if strings.HasSuffix(d, "...") {
153 d = strings.TrimSuffix(d, "...")
154 }
155 if strings.HasPrefix(p.Path, d) {
156 return true
157 }
158 }
159 return false
160}
161
162// DefaultSourceTree returns the /src directory of the first entry in $GOPATH.
163// If $GOPATH is empty, it returns "./". Useful as a default output location.
164func DefaultSourceTree() string {
165 paths := strings.Split(os.Getenv("GOPATH"), string(filepath.ListSeparator))
166 if len(paths) > 0 && len(paths[0]) > 0 {
167 return filepath.Join(paths[0], "src")
168 }
169 return "./"
170}
171
172// Execute implements main().
173// If you don't need any non-default behavior, use as:
174// args.Default().Execute(...)
175func (g *GeneratorArgs) Execute(nameSystems namer.NameSystems, defaultSystem string, pkgs func(*generator.Context, *GeneratorArgs) generator.Packages) error {
176 if g.defaultCommandLineFlags {
177 g.AddFlags(pflag.CommandLine)
178 pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
179 pflag.Parse()
180 }
181
182 b, err := g.NewBuilder()
183 if err != nil {
184 return fmt.Errorf("Failed making a parser: %v", err)
185 }
186
187 c, err := generator.NewContext(b, nameSystems, defaultSystem)
188 if err != nil {
189 return fmt.Errorf("Failed making a context: %v", err)
190 }
191
192 c.Verify = g.VerifyOnly
193 packages := pkgs(c, g)
194 if err := c.ExecutePackages(g.OutputBase, packages); err != nil {
195 return fmt.Errorf("Failed executing generator: %v", err)
196 }
197
198 return nil
199}