mukan-ignite/ignite/pkg/cosmosanalysis/app/app.go
Mukan Erkin Törük 26b204bd04
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
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

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
}