github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/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 os.Getenv("GOPATH") == "" { 86 return nil, fmt.Errorf("GOPATH not set") 87 } 88 if m.OutDir == "" { 89 return nil, fmt.Errorf("missing output directory flag") 90 } 91 if m.DesignPkgPath == "" { 92 return nil, fmt.Errorf("missing design package flag") 93 } 94 95 // Create output directory 96 if err := os.MkdirAll(m.OutDir, 0755); err != nil { 97 return nil, err 98 } 99 100 // Create temporary workspace used for generation 101 wd, err := os.Getwd() 102 if err != nil { 103 return nil, err 104 } 105 tmpDir, err := ioutil.TempDir(wd, "goagen") 106 if err != nil { 107 if _, ok := err.(*os.PathError); ok { 108 err = fmt.Errorf(`invalid output directory path "%s"`, m.OutDir) 109 } 110 return nil, err 111 } 112 defer func() { 113 if !m.debug { 114 os.RemoveAll(tmpDir) 115 } 116 }() 117 if m.debug { 118 fmt.Printf("** Code generator source dir: %s\n", tmpDir) 119 } 120 121 pkgSourcePath, err := codegen.PackageSourcePath(m.DesignPkgPath) 122 if err != nil { 123 return nil, fmt.Errorf("invalid design package import path: %s", err) 124 } 125 pkgName, err := codegen.PackageName(pkgSourcePath) 126 if err != nil { 127 return nil, err 128 } 129 130 // Generate tool source code. 131 pkgPath := filepath.Join(tmpDir, pkgName) 132 p, err := codegen.PackageFor(pkgPath) 133 if err != nil { 134 return nil, err 135 } 136 m.generateToolSourceCode(p) 137 138 // Compile and run generated tool. 139 if m.debug { 140 fmt.Printf("** Compiling with:\n%s", strings.Join(os.Environ(), "\n")) 141 } 142 genbin, err := p.Compile("goagen") 143 if err != nil { 144 return nil, err 145 } 146 return m.spawn(genbin) 147 } 148 149 func (m *Generator) generateToolSourceCode(pkg *codegen.Package) { 150 file := pkg.CreateSourceFile("main.go") 151 imports := append(m.Imports, 152 codegen.SimpleImport("fmt"), 153 codegen.SimpleImport("strings"), 154 codegen.SimpleImport("github.com/goadesign/goa/dslengine"), 155 codegen.NewImport("_", filepath.ToSlash(m.DesignPkgPath)), 156 ) 157 file.WriteHeader("Code Generator", "main", imports) 158 tmpl, err := template.New("generator").Parse(mainTmpl) 159 if err != nil { 160 panic(err) // bug 161 } 162 pkgName, err := codegen.PackageName(pkg.Abs()) 163 if err != nil { 164 panic(err) 165 } 166 context := map[string]string{ 167 "Genfunc": m.Genfunc, 168 "DesignPackage": m.DesignPkgPath, 169 "PkgName": pkgName, 170 } 171 err = tmpl.Execute(file, context) 172 if 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 }`