diff --git a/cmd/webrpc-gen/main.go b/cmd/webrpc-gen/main.go index 2ad1bc55..d3e22504 100644 --- a/cmd/webrpc-gen/main.go +++ b/cmd/webrpc-gen/main.go @@ -17,11 +17,12 @@ var flags = flag.NewFlagSet("webrpc-gen", flag.ExitOnError) func main() { versionFlag := flags.Bool("version", false, "print webrpc version and exit") schemaFlag := flags.String("schema", "", "webrpc schema file (required)") - targetFlag := flags.String("target", "", fmt.Sprintf("target generator (required), ie. golang@v0.7.0")) + targetFlag := flags.String("target", "", "target generator (required), ie. golang or golang@v0.7.0") outFlag := flags.String("out", "", "generated output file, default: stdout") fmtFlag := flags.Bool("fmt", true, "format generated code") refreshCacheFlag := flags.Bool("refreshCache", false, "refresh webrpc cache") testFlag := flags.Bool("test", false, "test schema parsing (skips code-gen)") + silentFlag := flags.Bool("silent", false, "silence gen summary") // Collect CLI -flags and custom template -options. cliFlags, templateOpts, err := collectCliArgs(flags, os.Args[1:]) @@ -39,7 +40,7 @@ func main() { fmt.Fprintf(os.Stderr, "\nTarget generator usage:\n") templateHelp, err := gen.Generate(&schema.WebRPCSchema{}, *targetFlag, &gen.Config{TemplateOptions: templateOpts}) if err != nil { - fmt.Fprintln(os.Stderr, templateHelp) + fmt.Fprintln(os.Stderr, templateHelp.Code) } else { fmt.Fprintf(os.Stderr, "failed to render -help: %v\n", err) } @@ -90,7 +91,7 @@ func main() { TemplateOptions: templateOpts, } - protoGen, err := gen.Generate(schema, *targetFlag, config) + genOutput, err := gen.Generate(schema, *targetFlag, config) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) @@ -98,35 +99,29 @@ func main() { // Write output to stdout if *outFlag == "" || *outFlag == "stdout" { - fmt.Println(protoGen) + fmt.Println(genOutput.Code) os.Exit(0) } // Write output to a file - outfile := *outFlag - cwd, err := os.Getwd() + err = writeOutfile(*outFlag, []byte(genOutput.Code)) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } - if outfile[0:1] != "/" { - outfile = filepath.Join(cwd, outfile) - } - outdir := filepath.Dir(outfile) - if _, err := os.Stat(outdir); os.IsNotExist(err) { - err := os.MkdirAll(outdir, 0755) - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } + // Print gen report + if *silentFlag { + os.Exit(0) } - err = os.WriteFile(outfile, []byte(protoGen), 0644) - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } + fmt.Println("=========================") + fmt.Println("webrpc generated summary:") + fmt.Println("=========================") + fmt.Println("- schema:", *schemaFlag) + fmt.Println("- target:", genOutput.TmplVersion) + fmt.Println("- template source:", genOutput.TmplDir) + fmt.Println("- output file:", *outFlag) } func collectCliArgs(flags *flag.FlagSet, args []string) (cliFlags []string, templateOpts map[string]interface{}, err error) { @@ -165,3 +160,28 @@ func collectCliArgs(flags *flag.FlagSet, args []string) (cliFlags []string, temp return } + +func writeOutfile(outfile string, protoGen []byte) error { + cwd, err := os.Getwd() + if err != nil { + return err + } + if outfile[0:1] != "/" { + outfile = filepath.Join(cwd, outfile) + } + + outdir := filepath.Dir(outfile) + if _, err := os.Stat(outdir); os.IsNotExist(err) { + err := os.MkdirAll(outdir, 0755) + if err != nil { + return err + } + } + + err = os.WriteFile(outfile, []byte(protoGen), 0644) + if err != nil { + return err + } + + return nil +} diff --git a/gen/gen.go b/gen/gen.go index 0a896a54..322a2e68 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -16,18 +16,26 @@ type Config struct { TemplateOptions map[string]interface{} } -func Generate(proto *schema.WebRPCSchema, target string, config *Config) (string, error) { +type GenOutput struct { + Code string + *TemplateSource +} + +func Generate(proto *schema.WebRPCSchema, target string, config *Config) (*GenOutput, error) { + genOutput := &GenOutput{} + target = getOldTarget(target) - tmpl, err := loadTemplates(proto, target, config) + tmpl, tmplSource, err := loadTemplates(proto, target, config) if err != nil { - return "", err + return genOutput, err } + genOutput.TemplateSource = tmplSource // Generate deterministic schema hash of the proto file schemaHash, err := proto.SchemaHash() if err != nil { - return "", err + return genOutput, err } // Template vars @@ -54,14 +62,21 @@ func Generate(proto *schema.WebRPCSchema, target string, config *Config) (string var b bytes.Buffer err = tmpl.ExecuteTemplate(&b, "main", vars) if err != nil { - return "", err + return genOutput, err } if config.Format && isGolangTarget(target) { - return formatGoSource(b.Bytes()) + genCode, err := formatGoSource(b.Bytes()) + if err != nil { + return genOutput, err + } + genOutput.Code = genCode + return genOutput, nil } - return b.String(), nil + genOutput.Code = b.String() + + return genOutput, nil } func getWebrpcGenCommand() string { diff --git a/gen/template_source.go b/gen/template_source.go index c0c79fe7..6c6ca3ba 100644 --- a/gen/template_source.go +++ b/gen/template_source.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "hash/fnv" - "io/ioutil" "log" "net/http" "os" @@ -21,12 +20,16 @@ import ( "github.com/webrpc/webrpc/schema" ) -func loadTemplates(proto *schema.WebRPCSchema, target string, config *Config) (*template.Template, error) { - s, err := newTemplateSource(proto, target, config) +func loadTemplates(proto *schema.WebRPCSchema, target string, config *Config) (*template.Template, *TemplateSource, error) { + s, err := NewTemplateSource(proto, target, config) if err != nil { - return nil, err + return nil, nil, err + } + tmpl, err := s.loadTemplates() + if err != nil { + return nil, nil, err } - return s.loadTemplates() + return tmpl, s, nil } // period of time before we attempt to refetch from git source again. @@ -34,18 +37,23 @@ func loadTemplates(proto *schema.WebRPCSchema, target string, config *Config) (* const ( templateCacheTime = 1 * time.Hour templateCacheTimestampFilename = ".webrpc-gen-timestamp" + templateCacheInfoFilename = ".webrpc-gen-info" ) -type templateSource struct { +type TemplateSource struct { tmpl *template.Template proto *schema.WebRPCSchema target string config *Config + + IsLocal bool + TmplDir string + TmplVersion string // git url and hash, or the local dir same as TmplDir } -func newTemplateSource(proto *schema.WebRPCSchema, target string, config *Config) (*templateSource, error) { +func NewTemplateSource(proto *schema.WebRPCSchema, target string, config *Config) (*TemplateSource, error) { tmpl := template.New(target).Funcs(templateFuncMap(proto, config.TemplateOptions)) - return &templateSource{ + return &TemplateSource{ tmpl: tmpl, proto: proto, target: target, @@ -53,16 +61,20 @@ func newTemplateSource(proto *schema.WebRPCSchema, target string, config *Config }, nil } -func (s *templateSource) loadTemplates() (*template.Template, error) { +func (s *TemplateSource) loadTemplates() (*template.Template, error) { if isLocalDir(s.target) { // from local directory + s.IsLocal = true tmpl, err := s.tmpl.ParseGlob(filepath.Join(s.target, "/*.tmpl")) if err != nil { return nil, fmt.Errorf("failed to load templates from %s: %w", s.target, err) } + s.TmplDir = s.target + s.TmplVersion = s.target return tmpl, err } else { // from remote git or cache source + s.IsLocal = false s.target = s.inferRemoteTarget(s.target) tmpl, err := s.loadRemote() if err != nil { @@ -72,17 +84,20 @@ func (s *templateSource) loadTemplates() (*template.Template, error) { } } -func (s *templateSource) loadRemote() (*template.Template, error) { +func (s *TemplateSource) loadRemote() (*template.Template, error) { var sourceFS http.FileSystem cacheDir, cacheFS, cacheAvailable, cacheTS, err := s.openCacheDir() if err != nil { return nil, err } + s.IsLocal = false + s.TmplDir = cacheDir // cache is new, so lets fetch from git if !cacheAvailable || s.config.RefreshCache || time.Now().Unix()-cacheTS > int64(templateCacheTime.Seconds()) { sourceFS, err = gitfs.New(context.Background(), s.target) //, gitfs.OptPrefetch(true), gitfs.OptGlob("/*.tmpl")) + s.TmplVersion = s.target if err != nil { // error occured reading from git, if cache is available, use that instead @@ -94,7 +109,7 @@ func (s *templateSource) loadRemote() (*template.Template, error) { } else { // using git remote source -- lets cache the files too - err := s.syncTemplates(sourceFS, cacheFS, cacheDir) + err := s.syncTemplates(s.target, sourceFS, cacheFS, cacheDir) if err != nil { // in case of error, just print a warning and carry on log.Println("[warning] error syncing git templates to local cache:", err.Error()) @@ -108,6 +123,12 @@ func (s *templateSource) loadRemote() (*template.Template, error) { sourceFS = cacheFS } + // read template version info + if cacheAvailable && s.TmplVersion == "" { + tmplVersion, _ := os.ReadFile(filepath.Join(cacheDir, templateCacheInfoFilename)) + s.TmplVersion = strings.TrimSpace(string(tmplVersion)) + } + // parse the template files from the source tmpl, err := vfstemplate.ParseGlob(sourceFS, s.tmpl, "/*.tmpl") if err != nil { @@ -117,7 +138,7 @@ func (s *templateSource) loadRemote() (*template.Template, error) { return tmpl, nil } -func (s *templateSource) syncTemplates(remoteFS, cacheFS http.FileSystem, cacheDir string) error { +func (s *TemplateSource) syncTemplates(target string, remoteFS, cacheFS http.FileSystem, cacheDir string) error { filenames, err := vfspath.Glob(remoteFS, "/*.tmpl") if err != nil { return err @@ -129,7 +150,7 @@ func (s *templateSource) syncTemplates(remoteFS, cacheFS http.FileSystem, cacheD return err } - err = ioutil.WriteFile(filepath.Join(cacheDir, filename), data, 0755) + err = os.WriteFile(filepath.Join(cacheDir, filename), data, 0755) if err != nil { return err } @@ -138,7 +159,12 @@ func (s *templateSource) syncTemplates(remoteFS, cacheFS http.FileSystem, cacheD now := time.Now().Unix() data := []byte(fmt.Sprintf("%d", now)) - err = ioutil.WriteFile(filepath.Join(cacheDir, templateCacheTimestampFilename), data, 0755) + err = os.WriteFile(filepath.Join(cacheDir, templateCacheTimestampFilename), data, 0755) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(cacheDir, templateCacheInfoFilename), []byte(strings.TrimSpace(target)), 0755) if err != nil { return err } @@ -146,7 +172,7 @@ func (s *templateSource) syncTemplates(remoteFS, cacheFS http.FileSystem, cacheD return nil } -func (s *templateSource) openCacheDir() (string, http.FileSystem, bool, int64, error) { +func (s *TemplateSource) openCacheDir() (string, http.FileSystem, bool, int64, error) { cacheDir, _ := s.getTmpCacheDir() if cacheDir == "" { // unable to find OS temp dir, but we don't error -- although @@ -185,7 +211,7 @@ func (s *templateSource) openCacheDir() (string, http.FileSystem, bool, int64, e return cacheDir, cacheFS, available, ts, nil } -func (s *templateSource) getTmpCacheDir() (string, error) { +func (s *TemplateSource) getTmpCacheDir() (string, error) { dir := os.TempDir() if dir == "" { return "", fmt.Errorf("unable to determine OS temp dir") @@ -201,7 +227,7 @@ func (s *templateSource) getTmpCacheDir() (string, error) { return filepath.Join(dir, "webrpc-cache", fmt.Sprintf("%d-%s", hash.Sum32(), name)), nil } -func (s *templateSource) inferRemoteTarget(target string) string { +func (s *TemplateSource) inferRemoteTarget(target string) string { // extra check to ensure its not a local dir if isLocalDir(target) { return target