Skip to content

Commit

Permalink
feat: add logic to load and sort gno pkgs
Browse files Browse the repository at this point in the history
  • Loading branch information
harry-hov committed Apr 19, 2023
1 parent c53c5ad commit 2f6e8e6
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 0 deletions.
108 changes: 108 additions & 0 deletions gno.land/cmd/gnoland/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"context"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/tm2/pkg/amino"
abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
"github.com/gnolang/gno/tm2/pkg/bft/config"
Expand All @@ -35,6 +37,12 @@ type gnolandCfg struct {
rootDir string
}

type pkg struct {
name string
path string
requires []string
}

func main() {
cfg := &gnolandCfg{}

Expand Down Expand Up @@ -314,3 +322,103 @@ func loadGenesisBalances(path string) []string {
}
return balances
}

// listGnoPkgs lists all gno packages in the given root directory.
func listGnoPkgs(root string) ([]pkg, error) {
var pkgs []pkg

err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if d.IsDir() {
goModPath := filepath.Join(path, "gno.mod")
data, err := os.ReadFile(goModPath)
if err == nil {
gnoMod, err := gnomod.Parse(goModPath, data)
if err != nil {
return fmt.Errorf("parse: %w", err)
}
gnoMod.Sanitize()
if err := gnoMod.Validate(); err != nil {
return fmt.Errorf("validate: %w", err)
}
pkgs = append(pkgs, pkg{
name: gnoMod.Module.Mod.Path,
path: path,
requires: func() []string {
var reqs []string
for _, req := range gnoMod.Require {
reqs = append(reqs, req.Mod.Path)
}
return reqs
}(),
})
return fs.SkipDir
} else if !os.IsNotExist(err) {
return err
}
}

return nil
})
if err != nil {
return nil, err
}

return pkgs, nil
}

// sortPkgs sorts the given packages by their dependencies.
func sortPkgs(pkgs []pkg) error {
visited := make(map[string]bool)
onStack := make(map[string]bool)
sortedPkgs := make([]pkg, 0, len(pkgs))

var visit func(pkg pkg) error
visit = func(pkg pkg) error {
if onStack[pkg.name] {
return fmt.Errorf("cycle detected: %s", pkg.name)
}
if visited[pkg.name] {
return nil
}

visited[pkg.name] = true
onStack[pkg.name] = true

// Visit package's dependencies
for _, req := range pkg.requires {
found := false
for _, p := range pkgs {
if p.name == req {
err := visit(p)
if err != nil {
return err
}
found = true
break
}
}
if !found {
return fmt.Errorf("missing dependency '%s' for package '%s'", req, pkg.name)
}
}

onStack[pkg.name] = false
sortedPkgs = append(sortedPkgs, pkg)
return nil
}

// Visit all packages
for _, p := range pkgs {
err := visit(p)
if err != nil {
return err
}
}

copy(pkgs, sortedPkgs)
return nil
}
56 changes: 56 additions & 0 deletions gno.land/cmd/gnoland/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

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

Expand Down Expand Up @@ -51,4 +52,59 @@ func TestInitialize(t *testing.T) {
}
}

func TestSortPkgs(t *testing.T) {
for _, tc := range []struct {
desc string
in []pkg
expected []string
shouldErr bool
}{
{
desc: "empty_input",
in: []pkg{},
expected: make([]string, 0),
}, {
desc: "no_dependencies",
in: []pkg{
{name: "pkg1", path: "/path/to/pkg1", requires: []string{}},
{name: "pkg2", path: "/path/to/pkg2", requires: []string{}},
{name: "pkg3", path: "/path/to/pkg3", requires: []string{}},
},
}, {
desc: "circular_dependencies",
in: []pkg{
{name: "pkg1", path: "/path/to/pkg1", requires: []string{"pkg2"}},
{name: "pkg2", path: "/path/to/pkg2", requires: []string{"pkg1"}},
},
shouldErr: true,
}, {
desc: "missing_dependencies",
in: []pkg{
{name: "pkg1", path: "/path/to/pkg1", requires: []string{"pkg2"}},
},
shouldErr: true,
}, {
desc: "valid_dependencies",
in: []pkg{
{name: "pkg1", path: "/path/to/pkg1", requires: []string{"pkg2"}},
{name: "pkg2", path: "/path/to/pkg2", requires: []string{"pkg3"}},
{name: "pkg3", path: "/path/to/pkg3", requires: []string{}},
},
expected: []string{"pkg3", "pkg2", "pkg1"},
},
} {
t.Run(tc.desc, func(t *testing.T) {
err := sortPkgs(tc.in)
if tc.shouldErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
for i := range tc.expected {
assert.Equal(t, tc.expected[i], tc.in[i].name)
}
}
})
}
}

// TODO: test various configuration files?

0 comments on commit 2f6e8e6

Please sign in to comment.