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  }`