Skip to content

Commit

Permalink
improve build
Browse files Browse the repository at this point in the history
  • Loading branch information
thehowl committed Feb 27, 2024
1 parent e47ff20 commit 2156609
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 82 deletions.
2 changes: 2 additions & 0 deletions examples/gno.land/r/demo/art/gnoface/gno.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Draft

module gno.land/r/demo/art/gnoface

require (
Expand Down
2 changes: 2 additions & 0 deletions examples/gno.land/r/x/manfred_outfmt/gno.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Draft

module gno.land/r/x/manfred_outfmt

require (
Expand Down
4 changes: 2 additions & 2 deletions gnovm/cmd/gno/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {
transpileOpts := newTranspileOptions(&transpileCfg{
output: tempdirRoot,
})
err := transpilePkg(importPath(pkg.Dir), transpileOpts)
err := transpilePkg(pkg.Dir, transpileOpts)
if err != nil {
io.ErrPrintln(err)
io.ErrPrintln("FAIL")
Expand All @@ -230,7 +230,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {
if verbose {
io.ErrPrintfln("=== BUILD %s", pkg.Dir)
}
tempDir, err := ResolvePath(tempdirRoot, importPath(pkg.Dir))
tempDir, err := ResolvePath(tempdirRoot, pkg.Dir)
if err != nil {
return errors.New("cannot resolve build dir")
}
Expand Down
121 changes: 76 additions & 45 deletions gnovm/cmd/gno/transpile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,37 @@ import (
"errors"
"flag"
"fmt"
"go/ast"
"go/scanner"
"log"
"os"
"path/filepath"
"slices"
"strconv"
"strings"

"github.com/gnolang/gno/gnovm/pkg/gnoenv"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/gnovm/pkg/transpiler"
"github.com/gnolang/gno/tm2/pkg/commands"
)

type importPath string

type transpileCfg struct {
verbose bool
rootDir string
skipImports bool
gobuild bool
goBinary string
gofmtBinary string
output string
}

type transpileOptions struct {
cfg *transpileCfg
// transpiled is the set of packages already
// transpiled from .gno to .go.
transpiled map[importPath]struct{}
transpiled map[string]struct{}
// skipped packages (gno mod marks them as draft)
skipped []string
}

var defaultTranspileCfg = &transpileCfg{
Expand All @@ -40,19 +44,22 @@ var defaultTranspileCfg = &transpileCfg{
}

func newTranspileOptions(cfg *transpileCfg) *transpileOptions {
return &transpileOptions{cfg, map[importPath]struct{}{}}
return &transpileOptions{
cfg: cfg,
transpiled: map[string]struct{}{},
}
}

func (p *transpileOptions) getFlags() *transpileCfg {
return p.cfg
}

func (p *transpileOptions) isTranspiled(pkg importPath) bool {
func (p *transpileOptions) isTranspiled(pkg string) bool {
_, transpiled := p.transpiled[pkg]
return transpiled
}

func (p *transpileOptions) markAsTranspiled(pkg importPath) {
func (p *transpileOptions) markAsTranspiled(pkg string) {
p.transpiled[pkg] = struct{}{}
}

Expand Down Expand Up @@ -108,13 +115,6 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) {
"go binary to use for building",
)

fs.StringVar(
&c.gofmtBinary,
"go-fmt-binary",
"gofmt",
"gofmt binary to use for syntax checking",
)

fs.StringVar(
&c.output,
"output",
Expand All @@ -133,32 +133,43 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error {
cfg.rootDir = gnoenv.RootDir()
}

// transpile .gno files.
paths, err := gnoFilesFromArgs(args)
// transpile .gno packages and files.
paths, err := gnoPackagesFromArgs(args)
if err != nil {
return fmt.Errorf("list paths: %w", err)
}

opts := newTranspileOptions(cfg)
var errlist scanner.ErrorList
for _, filepath := range paths {
if err := transpileFile(filepath, opts); err != nil {
for _, path := range paths {
st, err := os.Stat(path)
if err != nil {
return err
}
if st.IsDir() {
err = transpilePkg(path, opts)
} else {
if opts.cfg.verbose {
fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(path))
}

err = transpileFile(path, opts)
}
if err != nil {
var fileErrlist scanner.ErrorList
if !errors.As(err, &fileErrlist) {
// Not an scanner.ErrorList: return immediately.
return fmt.Errorf("%s: transpile: %w", filepath, err)
return fmt.Errorf("%s: transpile: %w", path, err)
}
errlist = append(errlist, fileErrlist...)
}
}

if errlist.Len() == 0 && cfg.gobuild {
paths, err := gnoPackagesFromArgs(args)
if err != nil {
return fmt.Errorf("list packages: %w", err)
}

for _, pkgPath := range paths {
if slices.Contains(opts.skipped, pkgPath) {
continue
}
err := goBuildFileOrPkg(pkgPath, cfg)
if err != nil {
var fileErrlist scanner.ErrorList
Expand All @@ -180,27 +191,39 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error {
return nil
}

func transpilePkg(pkgPath importPath, opts *transpileOptions) error {
if opts.isTranspiled(pkgPath) {
// transpilePkg transpiles all non-test files at the given location.
// Additionally, it checks the gno.mod in said location, and skips it if it is
// a draft module
func transpilePkg(dirPath string, opts *transpileOptions) error {
if opts.isTranspiled(dirPath) {
return nil
}
opts.markAsTranspiled(pkgPath)
opts.markAsTranspiled(dirPath)

// resolve dir
dir := filepath.Join(opts.cfg.rootDir, string(pkgPath))
if _, err := os.Stat(dir); err != nil {
gmod, err := gnomod.ParseAt(dirPath)
if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) {
return err
}
if err == nil && gmod.Draft {
if opts.cfg.verbose {
fmt.Fprintf(os.Stderr, "%s (skipped, gno.mod marks module as draft)\n", filepath.Clean(dirPath))
}
opts.skipped = append(opts.skipped, dirPath)
return nil
}

// XXX(morgan): Currently avoiding test files as they contain imports like "fmt".
// The transpiler doesn't currently support "test stdlibs", and even if it
// did all packages like "fmt" would have to exist as standard libraries to work.
// Easier to skip for now.
files, err := listNonTestFiles(dir)
files, err := listNonTestFiles(dirPath)
if err != nil {
log.Fatal(err)
}

if opts.cfg.verbose {
fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(dirPath))
}
for _, file := range files {
if err = transpileFile(file, opts); err != nil {
return fmt.Errorf("%s: %w", file, err)
Expand All @@ -212,14 +235,6 @@ func transpilePkg(pkgPath importPath, opts *transpileOptions) error {

func transpileFile(srcPath string, opts *transpileOptions) error {
flags := opts.getFlags()
gofmt := flags.gofmtBinary
if gofmt == "" {
gofmt = "gofmt"
}

if flags.verbose {
fmt.Fprintf(os.Stderr, "%s\n", srcPath)
}

// parse .gno.
source, err := os.ReadFile(srcPath)
Expand All @@ -239,7 +254,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error {
// resolve target path
var targetPath string
if flags.output != "." {
path, err := ResolvePath(flags.output, importPath(filepath.Dir(srcPath)))
path, err := ResolvePath(flags.output, filepath.Dir(srcPath))
if err != nil {
return fmt.Errorf("resolve output path: %w", err)
}
Expand All @@ -255,10 +270,10 @@ func transpileFile(srcPath string, opts *transpileOptions) error {
}

// transpile imported packages, if `SkipImports` sets to false
if !flags.skipImports {
importPaths := getPathsFromImportSpec(transpileRes.Imports)
// NOTE: importPaths are relative to root dir
for _, path := range importPaths {
if !flags.skipImports &&
!strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") {
dirPaths := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports)
for _, path := range dirPaths {
if err := transpilePkg(path, opts); err != nil {
return err
}
Expand All @@ -273,8 +288,24 @@ func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error {
goBinary := cfg.goBinary

if verbose {
fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg)
fmt.Fprintf(os.Stderr, "%s [build]\n", fileOrPkg)
}

return transpiler.TranspileBuildPackage(fileOrPkg, goBinary)
}

// getPathsFromImportSpec returns the directory paths where the code for each
// importSpec is stored (assuming they start with [transpiler.ImportPrefix]).
func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string) {
for _, i := range importSpec {
path, err := strconv.Unquote(i.Path.Value)
if err != nil {
continue
}
if strings.HasPrefix(path, transpiler.ImportPrefix) {
res := strings.TrimPrefix(path, transpiler.ImportPrefix)
dirs = append(dirs, rootDir+strings.Replace(res, "/", string(filepath.Separator), -1))
}
}
return
}
56 changes: 26 additions & 30 deletions gnovm/cmd/gno/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ package main

import (
"fmt"
"go/ast"
"io"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/gnolang/gno/gnovm/pkg/gnoenv"
"github.com/gnolang/gno/gnovm/pkg/transpiler"
)

func isGnoFile(f fs.DirEntry) bool {
Expand Down Expand Up @@ -198,36 +194,36 @@ func makeTestGoMod(path string, packageName string, goversion string) error {
return os.WriteFile(path, []byte(content), 0o644)
}

// getPathsFromImportSpec derive and returns ImportPaths
// without ImportPrefix from *ast.ImportSpec
func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importPath) {
for _, i := range importSpec {
path := i.Path.Value[1 : len(i.Path.Value)-1] // trim leading and trailing `"`
if strings.HasPrefix(path, transpiler.ImportPrefix) {
res := strings.TrimPrefix(path, transpiler.ImportPrefix)
importPaths = append(importPaths, importPath("."+res))
}
}
return
}

// ResolvePath joins the output dir with relative pkg path
// e.g
// Output Dir: Temp/gno-transpile
// Pkg Path: ../example/gno.land/p/pkg
// Returns -> Temp/gno-transpile/example/gno.land/p/pkg
func ResolvePath(output string, path importPath) (string, error) {
absOutput, err := filepath.Abs(output)
if err != nil {
return "", err
// ResolvePath determines the path where to place output files.
// dstPath is the desired output path by the gno program. output is the output
// directory provided by the user.
//
// If dstPath is absolute, it will be simply joined together with output.
// If dstPath is local, the output will be joined together with the relative
// path to reach dstPath.
// If dstPath is relative non-local path (ie. contains ../), the dstPath will
// be made absolute and joined with output
//
// Working directory: /home/gno
// ResolvePath("transpile-result", "./examples/test/test1.gno.gen.go")
// -> transpile-result/examples/test/test1.gen.go
// ResolvePath("/transpile-result", "./examples/test/test1.gno.gen.go")
// -> /transpile-result/examples/test/test1.gen.go
// ResolvePath("/transpile-result", "/home/gno/examples/test/test1.gno.gen.go")
// -> /transpile-result/home/gno/examples/test/test1.gen.go
// ResolvePath("result", "../jae/hello")
// -> result/home/jae/hello
func ResolvePath(output string, dstPath string) (string, error) {
if filepath.IsAbs(dstPath) ||
filepath.IsLocal(dstPath) {
return filepath.Join(output, dstPath), nil
}
absPkgPath, err := filepath.Abs(string(path))
// Make dstPath absolute and join it with output.
absDst, err := filepath.Abs(dstPath)
if err != nil {
return "", err
}
pkgPath := strings.TrimPrefix(absPkgPath, gnoenv.RootDir())

return filepath.Join(absOutput, pkgPath), nil
return filepath.Join(output, absDst), nil
}

// WriteDirFile write file to the path and also create
Expand Down
4 changes: 1 addition & 3 deletions gnovm/pkg/transpiler/transpiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,7 @@ func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (*ast.F
}

transp := TranspileImportPath(importPath)
if !astutil.RewriteImport(fset, f, importPath, transp) {
errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, transp))
}
importSpec.Path.Value = strconv.Quote(transp)
}
}

Expand Down
6 changes: 4 additions & 2 deletions gnovm/stdlibs/io/io.gno
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,10 @@ func (s *SectionReader) Read(p []byte) (n int, err error) {
return
}

var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")
var (
errWhence = errors.New("Seek: invalid whence")
errOffset = errors.New("Seek: invalid offset")
)

func (s *SectionReader) Seek(offset int64, whence int) (int64, error) {
switch whence {
Expand Down
1 change: 1 addition & 0 deletions gnovm/stdlibs/std/native.gno
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func GetOrigPkgAddr() Address {
func GetCallerAt(n int) Address {
return Address(callerAt(n))
}

func DerivePkgAddr(pkgPath string) Address {
return Address(derivePkgAddr(pkgPath))
}
Expand Down

0 comments on commit 2156609

Please sign in to comment.