github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/cmd/gno/transpile.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "flag" 7 "fmt" 8 "go/scanner" 9 "log" 10 "os" 11 "path/filepath" 12 13 "github.com/gnolang/gno/gnovm/pkg/transpiler" 14 "github.com/gnolang/gno/tm2/pkg/commands" 15 ) 16 17 type importPath string 18 19 type transpileCfg struct { 20 verbose bool 21 skipFmt bool 22 skipImports bool 23 gobuild bool 24 goBinary string 25 gofmtBinary string 26 output string 27 } 28 29 type transpileOptions struct { 30 cfg *transpileCfg 31 // transpiled is the set of packages already 32 // transpiled from .gno to .go. 33 transpiled map[importPath]struct{} 34 } 35 36 var defaultTranspileCfg = &transpileCfg{ 37 verbose: false, 38 goBinary: "go", 39 } 40 41 func newTranspileOptions(cfg *transpileCfg) *transpileOptions { 42 return &transpileOptions{cfg, map[importPath]struct{}{}} 43 } 44 45 func (p *transpileOptions) getFlags() *transpileCfg { 46 return p.cfg 47 } 48 49 func (p *transpileOptions) isTranspiled(pkg importPath) bool { 50 _, transpiled := p.transpiled[pkg] 51 return transpiled 52 } 53 54 func (p *transpileOptions) markAsTranspiled(pkg importPath) { 55 p.transpiled[pkg] = struct{}{} 56 } 57 58 func newTranspileCmd(io commands.IO) *commands.Command { 59 cfg := &transpileCfg{} 60 61 return commands.NewCommand( 62 commands.Metadata{ 63 Name: "transpile", 64 ShortUsage: "transpile [flags] <package> [<package>...]", 65 ShortHelp: "transpiles .gno files to .go", 66 }, 67 cfg, 68 func(_ context.Context, args []string) error { 69 return execTranspile(cfg, args, io) 70 }, 71 ) 72 } 73 74 func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { 75 fs.BoolVar( 76 &c.verbose, 77 "v", 78 false, 79 "verbose output when running", 80 ) 81 82 fs.BoolVar( 83 &c.skipFmt, 84 "skip-fmt", 85 false, 86 "do not check syntax of generated .go files", 87 ) 88 89 fs.BoolVar( 90 &c.skipImports, 91 "skip-imports", 92 false, 93 "do not transpile imports recursively", 94 ) 95 96 fs.BoolVar( 97 &c.gobuild, 98 "gobuild", 99 false, 100 "run go build on generated go files, ignoring test files", 101 ) 102 103 fs.StringVar( 104 &c.goBinary, 105 "go-binary", 106 "go", 107 "go binary to use for building", 108 ) 109 110 fs.StringVar( 111 &c.gofmtBinary, 112 "go-fmt-binary", 113 "gofmt", 114 "gofmt binary to use for syntax checking", 115 ) 116 117 fs.StringVar( 118 &c.output, 119 "output", 120 ".", 121 "output directory", 122 ) 123 } 124 125 func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { 126 if len(args) < 1 { 127 return flag.ErrHelp 128 } 129 130 // transpile .gno files. 131 paths, err := gnoFilesFromArgs(args) 132 if err != nil { 133 return fmt.Errorf("list paths: %w", err) 134 } 135 136 opts := newTranspileOptions(cfg) 137 var errlist scanner.ErrorList 138 for _, filepath := range paths { 139 if err := transpileFile(filepath, opts); err != nil { 140 var fileErrlist scanner.ErrorList 141 if !errors.As(err, &fileErrlist) { 142 // Not an scanner.ErrorList: return immediately. 143 return fmt.Errorf("%s: transpile: %w", filepath, err) 144 } 145 errlist = append(errlist, fileErrlist...) 146 } 147 } 148 149 if errlist.Len() == 0 && cfg.gobuild { 150 paths, err := gnoPackagesFromArgs(args) 151 if err != nil { 152 return fmt.Errorf("list packages: %w", err) 153 } 154 155 for _, pkgPath := range paths { 156 err := goBuildFileOrPkg(pkgPath, cfg) 157 if err != nil { 158 var fileErrlist scanner.ErrorList 159 if !errors.As(err, &fileErrlist) { 160 // Not an scanner.ErrorList: return immediately. 161 return fmt.Errorf("%s: build: %w", pkgPath, err) 162 } 163 errlist = append(errlist, fileErrlist...) 164 } 165 } 166 } 167 168 if errlist.Len() > 0 { 169 for _, err := range errlist { 170 io.ErrPrintfln(err.Error()) 171 } 172 return fmt.Errorf("%d transpile error(s)", errlist.Len()) 173 } 174 return nil 175 } 176 177 func transpilePkg(pkgPath importPath, opts *transpileOptions) error { 178 if opts.isTranspiled(pkgPath) { 179 return nil 180 } 181 opts.markAsTranspiled(pkgPath) 182 183 files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) 184 if err != nil { 185 log.Fatal(err) 186 } 187 188 for _, file := range files { 189 if err = transpileFile(file, opts); err != nil { 190 return fmt.Errorf("%s: %w", file, err) 191 } 192 } 193 194 return nil 195 } 196 197 func transpileFile(srcPath string, opts *transpileOptions) error { 198 flags := opts.getFlags() 199 gofmt := flags.gofmtBinary 200 if gofmt == "" { 201 gofmt = "gofmt" 202 } 203 204 if flags.verbose { 205 fmt.Fprintf(os.Stderr, "%s\n", srcPath) 206 } 207 208 // parse .gno. 209 source, err := os.ReadFile(srcPath) 210 if err != nil { 211 return fmt.Errorf("read: %w", err) 212 } 213 214 // compute attributes based on filename. 215 targetFilename, tags := transpiler.GetTranspileFilenameAndTags(srcPath) 216 217 // preprocess. 218 transpileRes, err := transpiler.Transpile(string(source), tags, srcPath) 219 if err != nil { 220 return fmt.Errorf("transpile: %w", err) 221 } 222 223 // resolve target path 224 var targetPath string 225 if flags.output != "." { 226 path, err := ResolvePath(flags.output, importPath(filepath.Dir(srcPath))) 227 if err != nil { 228 return fmt.Errorf("resolve output path: %w", err) 229 } 230 targetPath = filepath.Join(path, targetFilename) 231 } else { 232 targetPath = filepath.Join(filepath.Dir(srcPath), targetFilename) 233 } 234 235 // write .go file. 236 err = WriteDirFile(targetPath, []byte(transpileRes.Translated)) 237 if err != nil { 238 return fmt.Errorf("write .go file: %w", err) 239 } 240 241 // check .go fmt, if `SkipFmt` sets to false. 242 if !flags.skipFmt { 243 err = transpiler.TranspileVerifyFile(targetPath, gofmt) 244 if err != nil { 245 return fmt.Errorf("check .go file: %w", err) 246 } 247 } 248 249 // transpile imported packages, if `SkipImports` sets to false 250 if !flags.skipImports { 251 importPaths := getPathsFromImportSpec(transpileRes.Imports) 252 for _, path := range importPaths { 253 transpilePkg(path, opts) 254 } 255 } 256 257 return nil 258 } 259 260 func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { 261 verbose := cfg.verbose 262 goBinary := cfg.goBinary 263 264 if verbose { 265 fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg) 266 } 267 268 return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) 269 }