Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
413 lines
9.3 KiB
Go
413 lines
9.3 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/ignite/cli/v29/ignite/pkg/cosmosanalysis"
|
|
"github.com/ignite/cli/v29/ignite/pkg/cosmosver"
|
|
"github.com/ignite/cli/v29/ignite/pkg/errors"
|
|
"github.com/ignite/cli/v29/ignite/pkg/goanalysis"
|
|
"github.com/ignite/cli/v29/ignite/pkg/gomodule"
|
|
"github.com/ignite/cli/v29/ignite/pkg/xast"
|
|
)
|
|
|
|
const registerRoutesMethod = "RegisterAPIRoutes"
|
|
|
|
// CheckKeeper checks for the existence of the keeper with the provided name in the app structure.
|
|
func CheckKeeper(path, keeperName string) error {
|
|
// find app type
|
|
appImpl, err := cosmosanalysis.FindEmbed(path, cosmosanalysis.AppEmbeddedTypes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(appImpl) != 1 {
|
|
return errors.Errorf("app.go should contain a single app (got %d)", len(appImpl))
|
|
}
|
|
appTypeName := appImpl[0]
|
|
|
|
// Inspect the module for app struct
|
|
var found bool
|
|
fileSet := token.NewFileSet()
|
|
pkgs, err := parser.ParseDir(fileSet, path, nil, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, pkg := range pkgs {
|
|
for _, f := range pkg.Files {
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
// look for struct methods.
|
|
appType, ok := n.(*ast.TypeSpec)
|
|
if !ok || appType.Name.Name != appTypeName {
|
|
return true
|
|
}
|
|
|
|
appStruct, ok := appType.Type.(*ast.StructType)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// Search for the keeper specific field
|
|
for _, field := range appStruct.Fields.List {
|
|
for _, fieldName := range field.Names {
|
|
if fieldName.Name == keeperName {
|
|
found = true
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
})
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return errors.Errorf("app doesn't contain %s", keeperName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindRegisteredModules returns all registered modules into the chain root.
|
|
func FindRegisteredModules(chainRoot string) ([]string, error) {
|
|
// Assumption: modules are registered in the app package
|
|
appFilePath, err := cosmosanalysis.FindAppFilePath(chainRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The directory where the app file is located.
|
|
// This is required to resolve references within the app package.
|
|
appDir := filepath.Dir(appFilePath)
|
|
|
|
appPkg, _, err := xast.ParseDir(appDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Search the app for the imported SDK modules
|
|
var discovered []string
|
|
for _, f := range appPkg.Files {
|
|
discovered = append(discovered, goanalysis.FindBlankImports(f)...)
|
|
fileImports := goanalysis.FormatImports(f)
|
|
d, err := DiscoverModules(f, chainRoot, fileImports)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
discovered = append(discovered, d...)
|
|
}
|
|
|
|
// Discover IBC wired modules
|
|
// TODO: This can be removed once IBC modules use dependency injection
|
|
ibcPath := filepath.Join(chainRoot, "app", "ibc.go")
|
|
if _, err := os.Stat(ibcPath); err == nil {
|
|
m, err := discoverIBCModules(ibcPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
discovered = append(discovered, m...)
|
|
}
|
|
|
|
return removeDuplicateEntries(discovered), nil
|
|
}
|
|
|
|
// DiscoverModules find a map of import modules based on the configured app.
|
|
func DiscoverModules(file *ast.File, chainRoot string, fileImports map[string]string) ([]string, error) {
|
|
// find app type
|
|
appImpl := cosmosanalysis.FindEmbedInFile(file, cosmosanalysis.AppEmbeddedTypes)
|
|
appTypeName := "App"
|
|
switch {
|
|
case len(appImpl) > 1:
|
|
return nil, errors.Errorf("app.go should contain only a single app (got %d)", len(appImpl))
|
|
case len(appImpl) == 1:
|
|
appTypeName = appImpl[0]
|
|
}
|
|
|
|
var discovered []string
|
|
for _, decl := range file.Decls {
|
|
switch x := decl.(type) {
|
|
case *ast.GenDecl:
|
|
discovered = append(discovered, discoverKeeperModules(x, appTypeName, fileImports)...)
|
|
case *ast.FuncDecl:
|
|
// The modules registered by Cosmos SDK `rumtime.App` are included
|
|
// when the app registers API modules though the `App` instance.
|
|
if isRuntimeAppCalled(x) {
|
|
m, err := discoverRuntimeAppModules(chainRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
discovered = append(discovered, m...)
|
|
}
|
|
}
|
|
}
|
|
return removeDuplicateEntries(discovered), nil
|
|
}
|
|
|
|
func removeDuplicateEntries(entries []string) (res []string) {
|
|
seen := make(map[string]struct{})
|
|
for _, e := range entries {
|
|
if _, ok := seen[e]; ok {
|
|
continue
|
|
}
|
|
|
|
seen[e] = struct{}{}
|
|
res = append(res, e)
|
|
}
|
|
return
|
|
}
|
|
|
|
func discoverKeeperModules(d *ast.GenDecl, appTypeName string, imports map[string]string) []string {
|
|
var modules []string
|
|
for _, spec := range d.Specs {
|
|
typeSpec, ok := spec.(*ast.TypeSpec)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if typeSpec.Name.Name != appTypeName {
|
|
continue
|
|
}
|
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, field := range structType.Fields.List {
|
|
f := field.Type
|
|
CheckSpec:
|
|
switch spec := f.(type) {
|
|
case *ast.StarExpr:
|
|
f, ok = spec.X.(*ast.SelectorExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
goto CheckSpec
|
|
case *ast.SelectorExpr:
|
|
if !strings.HasSuffix(spec.Sel.Name, "Keeper") {
|
|
continue
|
|
}
|
|
ident, ok := spec.X.(*ast.Ident)
|
|
if !ok {
|
|
continue
|
|
}
|
|
fileImport, ok := imports[ident.Name]
|
|
if !ok {
|
|
continue
|
|
}
|
|
modules = append(modules, removeKeeperPkgPath(fileImport))
|
|
}
|
|
}
|
|
}
|
|
return modules
|
|
}
|
|
|
|
func discoverRuntimeAppModules(chainRoot string) ([]string, error) {
|
|
// Resolve the absolute path to the Cosmos SDK module
|
|
cosmosPath, err := resolveCosmosPackagePath(chainRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var modules []string
|
|
|
|
// When runtime package doesn't exists it means is an older Cosmos SDK version,
|
|
// so all the module API registrations are defined within user's app.
|
|
path := filepath.Join(cosmosPath, "runtime", "app.go")
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
return modules, nil
|
|
}
|
|
|
|
f, _, err := xast.ParseFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
imports := goanalysis.FormatImports(f)
|
|
err = xast.Inspect(f, func(n ast.Node) error {
|
|
if pkgs := findRegisterAPIRoutesRegistrations(n); pkgs != nil {
|
|
for _, p := range pkgs {
|
|
if m := imports[p]; m != "" {
|
|
modules = append(modules, m)
|
|
}
|
|
}
|
|
return xast.ErrStop
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return modules, nil
|
|
}
|
|
|
|
func discoverIBCModules(ibcPath string) ([]string, error) {
|
|
f, _, err := xast.ParseFile(ibcPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
names []string
|
|
imports = goanalysis.FormatImports(f)
|
|
)
|
|
err = xast.Inspect(f, func(n ast.Node) error {
|
|
fn, _ := n.(*ast.FuncDecl)
|
|
if fn == nil {
|
|
return nil
|
|
}
|
|
|
|
if fn.Name.Name != "RegisterIBC" && fn.Name.Name != "AddIBCModuleManager" {
|
|
return nil
|
|
}
|
|
|
|
for _, stmt := range fn.Body.List {
|
|
x, _ := stmt.(*ast.AssignStmt)
|
|
if x == nil {
|
|
continue
|
|
}
|
|
|
|
if len(x.Rhs) == 0 {
|
|
continue
|
|
}
|
|
|
|
c, _ := x.Rhs[0].(*ast.CompositeLit)
|
|
if c == nil {
|
|
continue
|
|
}
|
|
|
|
s, _ := c.Type.(*ast.SelectorExpr)
|
|
if s == nil || s.Sel.Name != "AppModule" {
|
|
continue
|
|
}
|
|
|
|
if m, _ := s.X.(*ast.Ident); m != nil {
|
|
names = append(names, m.Name)
|
|
}
|
|
}
|
|
|
|
return xast.ErrStop
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var modules []string
|
|
for _, n := range names {
|
|
modules = append(modules, imports[n])
|
|
}
|
|
return modules, nil
|
|
}
|
|
|
|
func resolveCosmosPackagePath(chainRoot string) (string, error) {
|
|
modFile, err := gomodule.ParseAt(chainRoot)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
deps, err := gomodule.ResolveDependencies(modFile, false)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var pkg string
|
|
for _, dep := range deps {
|
|
// dependencies are resolved, so we need to check for possible SDK forks
|
|
if cosmosver.CosmosSDKModulePathPattern.MatchString(dep.Path) {
|
|
pkg = dep.String()
|
|
break
|
|
}
|
|
}
|
|
|
|
if pkg == "" {
|
|
return "", errors.New("cosmos-sdk package version not found")
|
|
}
|
|
|
|
m, err := gomodule.FindModule(context.Background(), chainRoot, pkg)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return m.Dir, nil
|
|
}
|
|
|
|
func findRegisterAPIRoutesRegistrations(n ast.Node) []string {
|
|
funcLitType, ok := n.(*ast.FuncDecl)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if funcLitType.Name.Name != registerRoutesMethod {
|
|
return nil
|
|
}
|
|
|
|
var packagesRegistered []string
|
|
for _, stmt := range funcLitType.Body.List {
|
|
exprStmt, ok := stmt.(*ast.ExprStmt)
|
|
if !ok {
|
|
continue
|
|
}
|
|
exprCall, ok := exprStmt.X.(*ast.CallExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
exprFun, ok := exprCall.Fun.(*ast.SelectorExpr)
|
|
if !ok || exprFun.Sel.Name != "RegisterGRPCGatewayRoutes" {
|
|
continue
|
|
}
|
|
identType, ok := exprFun.X.(*ast.Ident)
|
|
if !ok {
|
|
continue
|
|
}
|
|
pkgName := identType.Name
|
|
if pkgName == "" {
|
|
continue
|
|
}
|
|
packagesRegistered = append(packagesRegistered, identType.Name)
|
|
}
|
|
return packagesRegistered
|
|
}
|
|
|
|
func removeKeeperPkgPath(pkg string) string {
|
|
path := strings.TrimSuffix(pkg, "/keeper")
|
|
path = strings.TrimSuffix(path, "/controller")
|
|
return strings.TrimSuffix(path, "/host")
|
|
}
|
|
|
|
func isRuntimeAppCalled(fn *ast.FuncDecl) bool {
|
|
if fn.Name.Name != registerRoutesMethod {
|
|
return false
|
|
}
|
|
|
|
for _, stmt := range fn.Body.List {
|
|
exprStmt, ok := stmt.(*ast.ExprStmt)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
exprCall, ok := exprStmt.X.(*ast.CallExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
exprFun, ok := exprCall.Fun.(*ast.SelectorExpr)
|
|
if !ok || exprFun.Sel.Name != registerRoutesMethod {
|
|
continue
|
|
}
|
|
|
|
exprSel, ok := exprFun.X.(*ast.SelectorExpr)
|
|
if !ok || exprSel.Sel.Name != "App" {
|
|
continue
|
|
}
|
|
|
|
identType, ok := exprSel.X.(*ast.Ident)
|
|
if !ok || identType.Name != "app" {
|
|
continue
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|