Skip to content

Commit

Permalink
feat: implement gno mod init command (#955)
Browse files Browse the repository at this point in the history
Implement `gno mod init` command.

Usage: 
```sh
$ gno mod init <module-path>
```
If no `<module-path>` is given, tries to determine package name from
existing `*.gno` files.

<details><summary>Checklists...</summary>

## Contributors Checklist

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](../.benchmarks/README.md).

## Maintainers Checklist

- [x] Checked that the author followed the guidelines in
`CONTRIBUTING.md`
- [x] Checked the conventional-commit (especially PR title and verb,
presence of `BREAKING CHANGE:` in the body)
- [x] Ensured that this PR is not a significant change or confirmed that
the review/consideration process was appropriate for the change
</details>
  • Loading branch information
harry-hov committed Aug 9, 2023
1 parent 9045a8e commit a612e57
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 8 deletions.
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",
},
commands.NewEmptyConfig(),
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,
},
{
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
13 changes: 7 additions & 6 deletions gnovm/pkg/gnomod/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {
if err != nil {
return err
}
err = goMod.WriteToPath(PackageDir(path, mod))
pkgPath := PackageDir(path, mod)
goModFilePath := filepath.Join(pkgPath, "go.mod")
err = goMod.WriteToPath(goModFilePath)
if err != nil {
return err
}
Expand All @@ -119,10 +121,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 +152,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
64 changes: 64 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,68 @@ 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") {
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)
}
if err := module.CheckImportPath(modPath); err != nil {
return err
}

modFile := &File{
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
Loading

0 comments on commit a612e57

Please sign in to comment.