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: implement gno mod init command #955

Merged
merged 25 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
48e6cc8
Initialize `gno mod init` command
harry-hov Jul 6, 2023
c806201
Implement `CreateGnoModFile()`
harry-hov Jul 7, 2023
d429246
Todo: write tests
harry-hov Jul 7, 2023
a80d4a2
fix: error if cannot determine package name
harry-hov Jul 10, 2023
ac64106
Add `gno mod init` tests
harry-hov Jul 10, 2023
fd9cf8d
Test `CreateGnoModFile()`
harry-hov Jul 10, 2023
1dd6df8
Updated go-gno-compatibility.md
harry-hov Jul 10, 2023
6e94287
make fmt
harry-hov Jul 10, 2023
1f5bc54
Merge branch 'master' into hariom/mod-init
harry-hov Jul 10, 2023
b189d2a
Split WriteToPath() args into multiple lines
harry-hov Jul 10, 2023
bc12c9f
Merge branch 'master' into hariom/mod-init
harry-hov Jul 11, 2023
e093de6
Merge branch 'master' into hariom/mod-init
harry-hov Jul 12, 2023
a1c51c1
Merge branch 'master' into hariom/mod-init
harry-hov Jul 20, 2023
5c9af0e
Merge branch 'master' into hariom/mod-init
harry-hov Jul 26, 2023
58f6ffa
fix: switch args `assert.Equal()`
harry-hov Jul 27, 2023
791996d
Use `commands.NewEmptyConfig()` instead of `nil`
harry-hov Jul 27, 2023
fa7fc5a
fix: validate module path
harry-hov Aug 1, 2023
4286d3d
Use less strict `CheckImportPath()`
harry-hov Aug 1, 2023
7807a32
Add test for invalid char in modPath
harry-hov Aug 1, 2023
b95edba
Merge branch 'master' into hariom/mod-init
harry-hov Aug 2, 2023
efc2e5b
Parallelize TestCreateGnoModFile
harry-hov Aug 2, 2023
c1ef18b
fix lint
harry-hov Aug 2, 2023
b61b60d
Merge branch 'master' into hariom/mod-init
harry-hov Aug 4, 2023
371eaef
Merge branch 'master' into hariom/mod-init
harry-hov Aug 7, 2023
fde1acd
Merge branch 'master' into hariom/mod-init
harry-hov Aug 9, 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
36 changes: 35 additions & 1 deletion gnovm/cmd/gno/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func newModCmd(io *commands.IO) *commands.Command {

cmd.AddSubCommands(
newModDownloadCmd(io),
newModInitCmd(),
)

return cmd
Expand All @@ -51,6 +52,20 @@ func newModDownloadCmd(io *commands.IO) *commands.Command {
)
}

func newModInitCmd() *commands.Command {
return commands.NewCommand(
commands.Metadata{
Name: "init",
ShortUsage: "init [module-path]",
ShortHelp: "Initialize gno.mod file in current directory",
},
nil,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: you can use commands.NewEmptyConfig(), as with other commands that don't have a config file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 791996d

func(_ context.Context, args []string) error {
return execModInit(args)
},
)
}

func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&c.remote,
Expand Down Expand Up @@ -111,10 +126,29 @@ func execModDownload(cfg *modDownloadCfg, args []string, io *commands.IO) error
}

// write go.mod file
err = gomod.WriteToPath(path)
err = gomod.WriteToPath(filepath.Join(path, "go.mod"))
if err != nil {
return fmt.Errorf("write go.mod file: %w", err)
}

return nil
}

func execModInit(args []string) error {
if len(args) > 1 {
return flag.ErrHelp
}
var modPath string
if len(args) == 1 {
modPath = args[0]
}
dir, err := os.Getwd()
if err != nil {
return err
}
if err := gnomod.CreateGnoModFile(dir, modPath); err != nil {
return fmt.Errorf("create gno.mod file: %w", err)
}

return nil
}
67 changes: 66 additions & 1 deletion gnovm/cmd/gno/mod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func TestModApp(t *testing.T) {
errShouldBe: "flag: help requested",
},

// test gno.mod
// test gno.mod download
{
args: []string{"mod", "download"},
testDir: "../../tests/integ/empty-dir",
Expand Down Expand Up @@ -72,6 +72,71 @@ func TestModApp(t *testing.T) {
simulateExternalRepo: true,
errShouldContain: "fetch: writepackage: querychain",
},

// test gno.mod init with no module name
{
args: []string{"mod", "init"},
testDir: "../../tests/integ/valid1",
simulateExternalRepo: true,
harry-hov marked this conversation as resolved.
Show resolved Hide resolved
},
{
args: []string{"mod", "init"},
testDir: "../../tests/integ/empty-dir",
simulateExternalRepo: true,
errShouldBe: "create gno.mod file: cannot determine package name",
},
{
args: []string{"mod", "init"},
testDir: "../../tests/integ/empty-gno1",
simulateExternalRepo: true,
recoverShouldContain: "expected 'package', found 'EOF'",
},
{
args: []string{"mod", "init"},
testDir: "../../tests/integ/empty-gno2",
simulateExternalRepo: true,
recoverShouldContain: "expected 'package', found 'EOF'",
},
{
args: []string{"mod", "init"},
testDir: "../../tests/integ/empty-gno3",
simulateExternalRepo: true,
recoverShouldContain: "expected 'package', found 'EOF'",
},
{
args: []string{"mod", "init"},
testDir: "../../tests/integ/empty-gnomod",
simulateExternalRepo: true,
errShouldBe: "create gno.mod file: gno.mod file already exists",
},

// test gno.mod init with module name
{
args: []string{"mod", "init", "gno.land/p/demo/foo"},
testDir: "../../tests/integ/empty-dir",
simulateExternalRepo: true,
},
{
args: []string{"mod", "init", "gno.land/p/demo/foo"},
testDir: "../../tests/integ/empty-gno1",
simulateExternalRepo: true,
},
{
args: []string{"mod", "init", "gno.land/p/demo/foo"},
testDir: "../../tests/integ/empty-gno2",
simulateExternalRepo: true,
},
{
args: []string{"mod", "init", "gno.land/p/demo/foo"},
testDir: "../../tests/integ/empty-gno3",
simulateExternalRepo: true,
},
{
args: []string{"mod", "init", "gno.land/p/demo/foo"},
testDir: "../../tests/integ/empty-gnomod",
simulateExternalRepo: true,
errShouldBe: "create gno.mod file: gno.mod file already exists",
},
}
testMainCaseRun(t, tc)
}
1 change: 1 addition & 0 deletions gnovm/docs/go-gno-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ Additional native types:
| go list | | |
| go mod | | |
| + go mod download | gno mod download | same behavior |
| + go mod init | gno mod init | same behavior |
| | gno precompile | |
| go work | | |
| | gno repl | |
Expand Down
11 changes: 5 additions & 6 deletions gnovm/pkg/gnomod/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {
if err != nil {
return err
}
err = goMod.WriteToPath(PackageDir(path, mod))
err = goMod.WriteToPath(filepath.Join(PackageDir(path, mod), "go.mod"))
harry-hov marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
Expand All @@ -119,10 +119,10 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {
return nil
}

// WriteToPath writes go.mod file in the given absolute path
// WriteToPath writes file to the given absolute file path
// TODO: Find better way to do this. Try to use `modfile`
// package to manage this.
func (f *File) WriteToPath(absPath string) error {
func (f *File) WriteToPath(absFilePath string) error {
if f.Module == nil {
return errors.New("writing go.mod: module not found")
}
Expand Down Expand Up @@ -150,10 +150,9 @@ func (f *File) WriteToPath(absPath string) error {
data += ")\n"
}

modPath := filepath.Join(absPath, "go.mod")
err := os.WriteFile(modPath, []byte(data), 0o644)
err := os.WriteFile(absFilePath, []byte(data), 0o644)
if err != nil {
return fmt.Errorf("writefile %q: %w", modPath, err)
return fmt.Errorf("writefile %q: %w", absFilePath, err)
}

return nil
Expand Down
61 changes: 61 additions & 0 deletions gnovm/pkg/gnomod/gnomod.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gnomod

import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -163,6 +165,65 @@ func GnoToGoMod(f File) (*File, error) {
return &f, nil
}

func CreateGnoModFile(rootDir, modPath string) error {
if !filepath.IsAbs(rootDir) {
return fmt.Errorf("dir %q is not absolute", rootDir)
}

modFilePath := filepath.Join(rootDir, "gno.mod")
if _, err := os.Stat(modFilePath); err == nil {
return errors.New("gno.mod file already exists")
}

if modPath == "" {
// Check .gno files for package name
// and use it as modPath
files, err := ioutil.ReadDir(rootDir)
if err != nil {
fmt.Errorf("read dir %q: %w", rootDir, err)
}

var pkgName gnolang.Name
for _, file := range files {
if file.IsDir() || !strings.HasSuffix(file.Name(), ".gno") || strings.HasSuffix(file.Name(), "_filetest.gno") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create helpers for this in another PR. Also discussed with @tbruyelle in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Will open PR for that soon.

continue
}

fpath := filepath.Join(rootDir, file.Name())
bz, err := os.ReadFile(fpath)
if err != nil {
return fmt.Errorf("read file %q: %w", fpath, err)
}

pn := gnolang.PackageNameFromFileBody(file.Name(), string(bz))
if strings.HasSuffix(string(pkgName), "_test") {
pkgName = pkgName[:len(pkgName)-len("_test")]
}
if pkgName == "" {
pkgName = pn
}
if pkgName != pn {
return fmt.Errorf("package name mismatch: [%q] and [%q]", pkgName, pn)
}
}
if pkgName == "" {
return errors.New("cannot determine package name")
}
modPath = string(pkgName)
Comment on lines +209 to +212
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this situation when pkgName == """?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Should be fixed by: fa7fc5a

}

modFile := &File{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not create a helper for this initialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I agree there is a helper needed for File initialization.
I plan to refactor code and define methods for File. like for this specific case modFile.AddModuleStmt().
Will do it in the another PR.

Module: &modfile.Module{
Mod: module.Version{
Path: modPath,
},
},
}
modFile.WriteToPath(filepath.Join(rootDir, "gno.mod"))

return nil
}

func isReplaced(mod module.Version, repl []*modfile.Replace) (module.Version, bool) {
for _, r := range repl {
hasNoVersion := r.Old.Path == mod.Path && r.Old.Version == ""
Expand Down
136 changes: 136 additions & 0 deletions gnovm/pkg/gnomod/gnomod_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package gnomod

import (
"os"
"path/filepath"
"testing"

"github.com/gnolang/gno/tm2/pkg/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCreateGnoModFile(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you paralellize this test with t.Parallel()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done efc2e5b

for _, tc := range []struct {
desc string
in []struct{ filename, content string }
inModPath string
out string
errShouldContain string
}{
{
desc: "empty directory",
inModPath: "gno.land/p/demo/foo",
out: "module gno.land/p/demo/foo\n",
},
{
desc: "empty directory (without modPath)",
errShouldContain: "cannot determine package name",
},
{
desc: "valid package",
in: []struct{ filename, content string }{
{
"foo.gno",
`package foo`,
},
},
inModPath: "gno.land/p/demo/foo",
out: "module gno.land/p/demo/foo\n",
},
{
desc: "valid package (without modPath)",
in: []struct{ filename, content string }{
{
"foo.gno",
`package foo`,
},
},
out: "module foo\n",
},
{
desc: "ambigious package names",
in: []struct{ filename, content string }{
{
"foo.gno",
`package foo`,
},
{
"bar.gno",
`package bar`,
},
},
inModPath: "gno.land/p/demo/foo",
out: "module gno.land/p/demo/foo\n",
},
{
desc: "ambigious package names (without modPath)",
in: []struct{ filename, content string }{
{
"foo.gno",
`package foo`,
},
{
"bar.gno",
`package bar`,
},
},
errShouldContain: "package name mismatch:",
},
{
desc: "valid package with gno.mod file",
in: []struct{ filename, content string }{
{
"foo.gno",
`package foo`,
},
{
"gno.mod",
`module gno.land/p/demo/foo`,
},
},
inModPath: "gno.land/p/demo/foo",
errShouldContain: "gno.mod file already exists",
},
{
desc: "valid package with gno.mod file (without modPath)",
in: []struct{ filename, content string }{
{
"foo.gno",
`package foo`,
},
{
"gno.mod",
`module gno.land/p/demo/foo`,
},
},
errShouldContain: "gno.mod file already exists",
},
} {
t.Run(tc.desc, func(t *testing.T) {
// Create test dir
dirPath, cleanUpFn := testutils.NewTestCaseDir(t)
require.NotNil(t, dirPath)
defer cleanUpFn()

// Create files
for _, f := range tc.in {
err := os.WriteFile(filepath.Join(dirPath, f.filename), []byte(f.content), 0o644)
require.NoError(t, err)
}

err := CreateGnoModFile(dirPath, tc.inModPath)
if tc.errShouldContain != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.errShouldContain)
return
}
assert.NoError(t, err)

// Verify gno.mod file
bz, err := os.ReadFile(filepath.Join(dirPath, "gno.mod"))
assert.NoError(t, err)
assert.Equal(t, string(bz), tc.out)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second param for assert.Equal is the expected, not the actual (so the params should be switched)

Copy link
Contributor Author

@harry-hov harry-hov Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opps.

Fixed here: 58f6ffa, 7807a32

})
}
}