Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support gno.mod #479

Merged
merged 32 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fe73da9
Initial Implementation
harry-hov Jan 21, 2023
c499f4a
Read gno.mod
harry-hov Jan 22, 2023
71462ac
Improve mod cmd
harry-hov Jan 22, 2023
5dc9de8
Minor refactoring
harry-hov Jan 22, 2023
ba3347b
write go.mod
harry-hov Jan 24, 2023
cb72782
Replace req modules and write module go.mod
harry-hov Jan 25, 2023
944a35b
Write precompiled files instead of .gno
harry-hov Jan 25, 2023
6e7adc6
Make necessary changes before writing go.mod
harry-hov Jan 25, 2023
eb7c918
pkgs/gnomod -> pkgs/gnolang/gnomod
harry-hov Jan 30, 2023
c692f37
Make `WriteGoMod()` a method
harry-hov Jan 30, 2023
4b3e698
Make `FetchModPackages()` a method
harry-hov Jan 30, 2023
f52b4eb
s/QueryChain/queryChain; unexport
harry-hov Jan 30, 2023
c0d1cfe
Remove redundant `nil` check
harry-hov Jan 30, 2023
b050f96
Use standard logger
harry-hov Jan 31, 2023
06f8210
Use better errors
harry-hov Jan 31, 2023
f247f31
Use `GetPrecompileFilenameAndTags()`
harry-hov Jan 31, 2023
ce3d9d1
make `queryPathFile` const
harry-hov Jan 31, 2023
a4ca78a
s/source/remote
harry-hov Jan 31, 2023
b9f8f00
Fix `GetGnoModPath()`
harry-hov Jan 31, 2023
2bb60d6
Move `IsModFileExists()` to cmd uilts
harry-hov Jan 31, 2023
c1b5130
Get rid of `ReadModFile()`
harry-hov Jan 31, 2023
78929b3
Add mod cmd tests
harry-hov Feb 2, 2023
422437b
Add method to validate gno.mod
harry-hov Feb 10, 2023
77f3935
Add tests
harry-hov Feb 10, 2023
adce8c7
Merge branch 'master' into hariom/gno.mod
harry-hov Feb 10, 2023
3fe14da
Merge branch 'master' into hariom/gno.mod
harry-hov Feb 12, 2023
b2f93c8
Set GOPATH in GitHub Actions
harry-hov Feb 12, 2023
442e7f7
Fix lint errors
harry-hov Feb 12, 2023
27389a9
remove gno.mod from p/demo/acl
harry-hov Feb 13, 2023
2224da8
s/absModPath/path
harry-hov Feb 15, 2023
aaa6930
Make version mandatory for `require` modules
harry-hov Feb 15, 2023
5b93aa9
Merge branch 'master' into hariom/gno.mod
harry-hov Feb 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ jobs:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v3
- name: test
run: make ${{ matrix.args }}
run: |
export GOPATH=$HOME/go
make ${{ matrix.args }}
6 changes: 6 additions & 0 deletions cmd/gnodev/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ var mainApps AppList = []AppItem{
Desc: "build a gno package",
Defaults: defaultBuildOptions,
},
{
App: modApp,
Name: "mod",
Desc: "manage gno.mod",
Defaults: defaultModFlags,
},
{
App: precompileApp,
Name: "precompile",
Expand Down
38 changes: 36 additions & 2 deletions cmd/gnodev/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"testing"

Expand All @@ -12,7 +14,9 @@ import (

func TestMain(t *testing.T) {
tc := []struct {
args []string
args []string
testDir string
simulateExternalRepo bool

// for the following FooContain+FooBe expected couples, if both are empty,
// then the test suite will require that the "got" is not empty.
Expand All @@ -30,13 +34,15 @@ func TestMain(t *testing.T) {
{args: []string{"test"}, errShouldBe: "invalid args", stderrShouldBe: "Usage: test [test flags] [packages]\n"},
{args: []string{"build"}, errShouldBe: "invalid args", stderrShouldBe: "Usage: build [build flags] [packages]\n"},
{args: []string{"precompile"}, errShouldBe: "invalid args", stderrShouldBe: "Usage: precompile [precompile flags] [packages]\n"},
{args: []string{"mod"}, errShouldBe: "invalid command", stderrShouldBe: "Usage: mod [flags] <command>\n"},
// {args: []string{"repl"}},

// --help
{args: []string{"build", "--help"}, stdoutShouldContain: "# buildOptions options\n-"},
{args: []string{"test", "--help"}, stdoutShouldContain: "# testOptions options\n-"},
{args: []string{"precompile", "--help"}, stdoutShouldContain: "# precompileFlags options\n-"},
{args: []string{"repl", "--help"}, stdoutShouldContain: "# replOptions options\n-"},
{args: []string{"mod", "--help"}, stdoutShouldContain: "# modFlags options\n-"},

// custom
{args: []string{"test", "../../examples/gno.land/p/demo/rand"}, stderrShouldContain: "ok ./../../examples/gno.land/p/demo/rand \t"},
Expand Down Expand Up @@ -85,16 +91,28 @@ func TestMain(t *testing.T) {
{args: []string{"test", "../../examples/gno.land/p/demo/ufmt", "--verbose", "--run", "Sprintf/hello"}, stderrShouldContain: "ok ./../../examples/gno.land/p/demo/ufmt"},
{args: []string{"test", "../../examples/gno.land/p/demo/ufmt", "--verbose", "--timeout", "100000000000" /* 100s */}, stderrShouldContain: "ok ./../../examples/gno.land/p/demo/ufmt"},
// {args: []string{"test", "../../examples/gno.land/p/demo/ufmt", "--verbose", "--timeout", "10000" /* 10µs */}, recoverShouldContain: "test timed out after"}, // FIXME: should be testable

// test gno.mod
{args: []string{"mod", "download"}, testDir: "../../tests/integ/empty-dir", simulateExternalRepo: true, errShouldBe: "mod download: gno.mod not found"},
{args: []string{"mod", "download"}, testDir: "../../tests/integ/empty-gnomod", simulateExternalRepo: true, errShouldBe: "mod download: validate: requires module"},
{args: []string{"mod", "download"}, testDir: "../../tests/integ/invalid-module-name", simulateExternalRepo: true, errShouldContain: "usage: module module/path"},
{args: []string{"mod", "download"}, testDir: "../../tests/integ/minimalist-gnomod", simulateExternalRepo: true},
{args: []string{"mod", "download"}, testDir: "../../tests/integ/require-remote-module", simulateExternalRepo: true},
{args: []string{"mod", "download"}, testDir: "../../tests/integ/require-invalid-module", simulateExternalRepo: true, errShouldContain: "mod download: fetch: writepackage: querychain:"},
}

workingDir, err := os.Getwd()
require.Nil(t, err)

for _, test := range tc {
errShouldBeEmpty := test.errShouldContain == "" && test.errShouldBe == ""
stdoutShouldBeEmpty := test.stdoutShouldContain == "" && test.stdoutShouldBe == ""
stderrShouldBeEmpty := test.stderrShouldContain == "" && test.stderrShouldBe == ""
recoverShouldBeEmpty := test.recoverShouldContain == "" && test.recoverShouldBe == ""

testName := strings.Join(test.args, " ")
testName = strings.ReplaceAll(testName, "/", "~")
testName = strings.ReplaceAll(testName+test.testDir, "/", "~")

t.Run(testName, func(t *testing.T) {
cmd := command.NewMockCommand()
mockOut := bytes.NewBufferString("")
Expand Down Expand Up @@ -152,6 +170,22 @@ func TestMain(t *testing.T) {
require.True(t, recoverShouldBeEmpty, "should not panic")
}
}()

if test.simulateExternalRepo {
// create external dir
tmpDir, cleanUpFn := createTmpDir(t)
defer cleanUpFn()

// copy to external dir
absTestDir, err := filepath.Abs(test.testDir)
require.Nil(t, err)
require.Nil(t, copyDir(absTestDir, tmpDir))

// cd to tmp directory
os.Chdir(tmpDir)
defer os.Chdir(workingDir)
}

err := runMain(cmd, exec, test.args)

if errShouldBeEmpty {
Expand Down
84 changes: 84 additions & 0 deletions cmd/gnodev/mod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"fmt"
"os"
"path/filepath"

"github.com/gnolang/gno/pkgs/command"
"github.com/gnolang/gno/pkgs/errors"
"github.com/gnolang/gno/pkgs/gnolang/gnomod"
)

type modFlags struct {
Verbose bool `flag:"verbose" help:"verbose"`
}

var defaultModFlags = modFlags{
Verbose: false,
}
moul marked this conversation as resolved.
Show resolved Hide resolved

func modApp(cmd *command.Command, args []string, iopts interface{}) error {
opts := iopts.(modFlags)

if len(args) != 1 {
cmd.ErrPrintfln("Usage: mod [flags] <command>")
return errors.New("invalid command")
}

switch args[0] {
case "download":
if err := runModDownload(&opts); err != nil {
return fmt.Errorf("mod download: %w", err)
}
default:
return fmt.Errorf("invalid command: %s", args[0])
}

return nil
}

func runModDownload(opts *modFlags) error {
path, err := os.Getwd()
if err != nil {
return err
}
modPath := filepath.Join(path, "gno.mod")
if !isFileExist(modPath) {
return errors.New("gno.mod not found")
}

// read gno.mod
data, err := os.ReadFile(modPath)
if err != nil {
return fmt.Errorf("readfile %q: %w", modPath, err)
}

// parse gno.mod
gnoMod, err := gnomod.Parse(modPath, data)
if err != nil {
return fmt.Errorf("parse: %w", err)
}

// validate gno.mod
if err := gnoMod.Validate(); err != nil {
return fmt.Errorf("validate: %w", err)
}

// fetch dependencies
if err := gnoMod.FetchDeps(); err != nil {
return fmt.Errorf("fetch: %w", err)
}

if err := gnomod.Sanitize(gnoMod); err != nil {
return fmt.Errorf("sanitize: %w", err)
}

// write go.mod file
err = gnoMod.WriteToPath(path)
if err != nil {
return fmt.Errorf("write go.mod file: %w", err)
moul marked this conversation as resolved.
Show resolved Hide resolved
}

return nil
}
24 changes: 24 additions & 0 deletions cmd/gnodev/testutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"os"
"testing"
)

func createTmpDir(t *testing.T) (string, func()) {
t.Helper()

tmpDir, err := os.MkdirTemp("", "gno-mod-test")
if err != nil {
t.Error("Failed to create tmp dir for mod:", err)
}

cleanUpFn := func() {
err := os.RemoveAll(tmpDir)
if err != nil {
t.Logf("Failed to clean up test %s: %v", t.Name(), err)
}
}

return tmpDir, cleanUpFn
}
75 changes: 75 additions & 0 deletions cmd/gnodev/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"go/ast"
"io"
"io/fs"
"log"
"os"
Expand All @@ -19,6 +20,11 @@ func isGnoFile(f fs.DirEntry) bool {
return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".gno") && !f.IsDir()
}

func isFileExist(absModPath string) bool {
_, err := os.Stat(absModPath)
moul marked this conversation as resolved.
Show resolved Hide resolved
return err == nil
}

func gnoFilesFromArgs(args []string) ([]string, error) {
paths := []string{}
for _, arg := range args {
Expand Down Expand Up @@ -159,3 +165,72 @@ func WriteDirFile(pathWithName string, data []byte) error {

return os.WriteFile(pathWithName, data, 0o644)
}

// copyDir copies the dir from src to dst, the paths have to be
// absolute to ensure consistent behavior.
func copyDir(src, dst string) error {
if !filepath.IsAbs(src) || !filepath.IsAbs(dst) {
return fmt.Errorf("src or dst path not abosulte, src: %s dst: %s", src, dst)
}

entries, err := os.ReadDir(src)
if err != nil {
return fmt.Errorf("cannot read dir: %s", src)
}

if err := os.MkdirAll(dst, 0755); err != nil {
return fmt.Errorf("failed to create directory: '%s', error: '%w'", dst, err)
}

for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())

if entry.Type().IsDir() {
copyDir(srcPath, dstPath)
} else if entry.Type().IsRegular() {
copyFile(srcPath, dstPath)
}
}

return nil
}

// copyFile copies the file from src to dst, the paths have
// to be absolute to ensure consistent behavior.
func copyFile(src, dst string) error {
if !filepath.IsAbs(src) || !filepath.IsAbs(dst) {
return fmt.Errorf("src or dst path not abosulte, src: %s dst: %s", src, dst)
}

// verify if it's regular flile
srcStat, err := os.Stat(src)
if err != nil {
return fmt.Errorf("cannot copy file: %w", err)
}
if !srcStat.Mode().IsRegular() {
return fmt.Errorf("%s not a regular file", src)
}

// create dst file
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()

// open src file
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()

// copy srcFile -> dstFile
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
}

return nil
}
26 changes: 26 additions & 0 deletions pkgs/gnolang/gnomod/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gnomod

import (
"fmt"

abci "github.com/gnolang/gno/pkgs/bft/abci/types"
"github.com/gnolang/gno/pkgs/bft/rpc/client"
)

func queryChain(qpath string, data []byte) (res *abci.ResponseQuery, err error) {
opts2 := client.ABCIQueryOptions{
// Height: height, XXX
// Prove: false, XXX
}
cli := client.NewHTTP(remote, "/websocket")
qres, err := cli.ABCIQueryWithOptions(qpath, data, opts2)
if err != nil {
return nil, err
}
if qres.Response.Error != nil {
fmt.Printf("Log: %s\n", qres.Response.Log)
return nil, qres.Response.Error
}

return &qres.Response, nil
}
Loading