github.com/blp1526/goa@v1.4.0/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, err := pkg.CreateSourceFile("main.go") 151 if err != nil { 152 panic(err) // bug 153 } 154 defer file.Close() 155 imports := append(m.Imports, 156 codegen.SimpleImport("fmt"), 157 codegen.SimpleImport("strings"), 158 codegen.SimpleImport("github.com/goadesign/goa/dslengine"), 159 codegen.NewImport("_", filepath.ToSlash(m.DesignPkgPath)), 160 ) 161 file.WriteHeader("Code Generator", "main", imports) 162 tmpl, err := template.New("generator").Parse(mainTmpl) 163 if err != nil { 164 panic(err) // bug 165 } 166 pkgName, err := codegen.PackageName(pkg.Abs()) 167 if err != nil { 168 panic(err) 169 } 170 context := map[string]string{ 171 "Genfunc": m.Genfunc, 172 "DesignPackage": m.DesignPkgPath, 173 "PkgName": pkgName, 174 } 175 if err := tmpl.Execute(file, context); err != nil { 176 panic(err) // bug 177 } 178 } 179 180 // spawn runs the compiled generator using the arguments initialized by Kingpin 181 // when parsing the command line. 182 func (m *Generator) spawn(genbin string) ([]string, error) { 183 var args []string 184 for k, v := range m.Flags { 185 if k == "debug" { 186 continue 187 } 188 args = append(args, fmt.Sprintf("--%s=%s", k, v)) 189 } 190 sort.Strings(args) 191 args = append(args, "--version="+version.String()) 192 args = append(args, m.CustomFlags...) 193 cmd := exec.Command(genbin, args...) 194 out, err := cmd.CombinedOutput() 195 if err != nil { 196 return nil, fmt.Errorf("%s\n%s", err, string(out)) 197 } 198 res := strings.Split(string(out), "\n") 199 for (len(res) > 0) && (res[len(res)-1] == "") { 200 res = res[:len(res)-1] 201 } 202 return res, nil 203 } 204 205 const mainTmpl = ` 206 func main() { 207 // Check if there were errors while running the first DSL pass 208 dslengine.FailOnError(dslengine.Errors) 209 210 // Now run the secondary DSLs 211 dslengine.FailOnError(dslengine.Run()) 212 213 files, err := {{.Genfunc}}() 214 dslengine.FailOnError(err) 215 216 // We're done 217 fmt.Println(strings.Join(files, "\n")) 218 }`