github.com/furusax0621/goa-v1@v1.4.3/goagen/meta/generator.go (about) 1 package meta 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "sort" 10 "strconv" 11 "strings" 12 "text/template" 13 14 "github.com/goadesign/goa/goagen/codegen" 15 "github.com/goadesign/goa/version" 16 ) 17 18 // Generator generates the code of, compiles and runs generators. 19 // This extra step is necessary to compile in the end user design package so 20 // that generator code can iterate through it. 21 type Generator struct { 22 // Genfunc contains the name of the generator entry point function. 23 // The function signature must be: 24 // 25 // func <Genfunc>([]dslengine.Root) ([]string, error) 26 Genfunc string 27 28 // Imports list the imports that are specific for that generator that 29 // should be added to the main Go file. 30 Imports []*codegen.ImportSpec 31 32 // Flags is the list of flags to be used when invoking the final 33 // generator on the command line. 34 Flags map[string]string 35 36 // CustomFlags is the list of arguments that appear after the -- separator. 37 // These arguments are appended verbatim to the final generator command line. 38 CustomFlags []string 39 40 // OutDir is the final output directory. 41 OutDir string 42 43 // DesignPkgPath is the Go import path to the design package. 44 DesignPkgPath string 45 46 debug bool 47 } 48 49 // NewGenerator returns a meta generator that can run an actual Generator 50 // given its factory method and command line flags. 51 func NewGenerator(genfunc string, imports []*codegen.ImportSpec, flags map[string]string, customflags []string) (*Generator, error) { 52 var ( 53 outDir, designPkgPath string 54 debug bool 55 ) 56 57 if o, ok := flags["out"]; ok { 58 outDir = o 59 } 60 if d, ok := flags["design"]; ok { 61 designPkgPath = d 62 } 63 if d, ok := flags["debug"]; ok { 64 var err error 65 debug, err = strconv.ParseBool(d) 66 if err != nil { 67 return nil, fmt.Errorf("failed to parse debug flag: %s", err) 68 } 69 } 70 71 return &Generator{ 72 Genfunc: genfunc, 73 Imports: imports, 74 Flags: flags, 75 CustomFlags: customflags, 76 OutDir: outDir, 77 DesignPkgPath: designPkgPath, 78 debug: debug, 79 }, nil 80 } 81 82 // Generate compiles and runs the generator and returns the generated filenames. 83 func (m *Generator) Generate() ([]string, error) { 84 // Sanity checks 85 if m.OutDir == "" { 86 return nil, fmt.Errorf("missing output directory flag") 87 } 88 if m.DesignPkgPath == "" { 89 return nil, fmt.Errorf("missing design package flag") 90 } 91 92 // Create output directory 93 if err := os.MkdirAll(m.OutDir, 0755); err != nil { 94 return nil, err 95 } 96 97 // Create temporary workspace used for generation 98 wd, err := os.Getwd() 99 if err != nil { 100 return nil, err 101 } 102 tmpDir, err := ioutil.TempDir(wd, "goagen") 103 if err != nil { 104 if _, ok := err.(*os.PathError); ok { 105 err = fmt.Errorf(`invalid output directory path "%s"`, m.OutDir) 106 } 107 return nil, err 108 } 109 defer func() { 110 if !m.debug { 111 os.RemoveAll(tmpDir) 112 } 113 }() 114 if m.debug { 115 fmt.Printf("** Code generator source dir: %s\n", tmpDir) 116 } 117 118 pkgSourcePath, err := codegen.PackageSourcePath(m.DesignPkgPath) 119 if err != nil { 120 return nil, fmt.Errorf("invalid design package import path: %s", err) 121 } 122 pkgName, err := codegen.PackageName(pkgSourcePath) 123 if err != nil { 124 return nil, err 125 } 126 127 // Generate tool source code. 128 pkgPath := filepath.Join(tmpDir, pkgName) 129 p, err := codegen.PackageFor(pkgPath) 130 if err != nil { 131 return nil, err 132 } 133 m.generateToolSourceCode(p) 134 135 // Compile and run generated tool. 136 if m.debug { 137 fmt.Printf("** Compiling with:\n%s", strings.Join(os.Environ(), "\n")) 138 } 139 genbin, err := p.Compile("goagen") 140 if err != nil { 141 return nil, err 142 } 143 return m.spawn(genbin) 144 } 145 146 func (m *Generator) generateToolSourceCode(pkg *codegen.Package) { 147 file, err := pkg.CreateSourceFile("main.go") 148 if err != nil { 149 panic(err) // bug 150 } 151 defer file.Close() 152 imports := append(m.Imports, 153 codegen.SimpleImport("fmt"), 154 codegen.SimpleImport("strings"), 155 codegen.SimpleImport("github.com/goadesign/goa/dslengine"), 156 codegen.NewImport("_", filepath.ToSlash(m.DesignPkgPath)), 157 ) 158 file.WriteHeader("Code Generator", "main", imports) 159 tmpl, err := template.New("generator").Parse(mainTmpl) 160 if err != nil { 161 panic(err) // bug 162 } 163 pkgName, err := codegen.PackageName(pkg.Abs()) 164 if err != nil { 165 panic(err) 166 } 167 context := map[string]string{ 168 "Genfunc": m.Genfunc, 169 "DesignPackage": m.DesignPkgPath, 170 "PkgName": pkgName, 171 } 172 if err := tmpl.Execute(file, context); err != nil { 173 panic(err) // bug 174 } 175 } 176 177 // spawn runs the compiled generator using the arguments initialized by Kingpin 178 // when parsing the command line. 179 func (m *Generator) spawn(genbin string) ([]string, error) { 180 var args []string 181 for k, v := range m.Flags { 182 if k == "debug" { 183 continue 184 } 185 args = append(args, fmt.Sprintf("--%s=%s", k, v)) 186 } 187 sort.Strings(args) 188 args = append(args, "--version="+version.String()) 189 args = append(args, m.CustomFlags...) 190 cmd := exec.Command(genbin, args...) 191 out, err := cmd.CombinedOutput() 192 if err != nil { 193 return nil, fmt.Errorf("%s\n%s", err, string(out)) 194 } 195 res := strings.Split(string(out), "\n") 196 for (len(res) > 0) && (res[len(res)-1] == "") { 197 res = res[:len(res)-1] 198 } 199 return res, nil 200 } 201 202 const mainTmpl = ` 203 func main() { 204 // Check if there were errors while running the first DSL pass 205 dslengine.FailOnError(dslengine.Errors) 206 207 // Now run the secondary DSLs 208 dslengine.FailOnError(dslengine.Run()) 209 210 files, err := {{.Genfunc}}() 211 dslengine.FailOnError(err) 212 213 // We're done 214 fmt.Println(strings.Join(files, "\n")) 215 }`