Skip to content

Commit

Permalink
fix: find Cosmos SDK runtime app registered modules (#3661)
Browse files Browse the repository at this point in the history
* fix: find Cosmos SDK runtime app registered modules

These modules are required to properly generate OpenAPI specs.

* chore: update changelog

* chore: remove empty test definition
  • Loading branch information
jeronimoalbi authored Sep 27, 2023
1 parent f210829 commit fa47220
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [#3610](https://github.com/ignite/cli/pull/3610) Fix overflow issue of cosmos faucet in `pkg/cosmosfaucet/transfer.go` and `pkg/cosmosfaucet/cosmosfaucet.go`
- [#3618](https://github.com/ignite/cli/pull/3618) Fix TS client generation import path issue
- [#3631](https://github.com/ignite/cli/pull/3631) Fix unnecessary vue import in hooks/composables template
- [#3661](https://github.com/ignite/cli/pull/3661) Change `pkg/cosmosanalysis` to find Cosmos SDK runtime app registered modules

## [`v0.27.0`](https://github.com/ignite/cli/releases/tag/v0.27.0)

Expand Down
148 changes: 145 additions & 3 deletions ignite/pkg/cosmosanalysis/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import (
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"

"github.com/pkg/errors"

"github.com/ignite/cli/ignite/pkg/cosmosanalysis"
"github.com/ignite/cli/ignite/pkg/cosmosver"
"github.com/ignite/cli/ignite/pkg/goanalysis"
"github.com/ignite/cli/ignite/pkg/goenv"
"github.com/ignite/cli/ignite/pkg/gomodule"
"github.com/ignite/cli/ignite/pkg/xast"
)

const (
appWiringImport = "cosmossdk.io/depinject"
appWiringCallMethod = "Inject"
appWiringImport = "cosmossdk.io/depinject"
appWiringCallMethod = "Inject"
registerRoutesMethod = "RegisterAPIRoutes"
)

// CheckKeeper checks for the existence of the keeper with the provided name in the app structure.
Expand Down Expand Up @@ -96,6 +101,10 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
return nil, err
}

// The modules registered by Cosmos SDK `rumtime.App` are included
// when the app registers API modules though the `App` instance.
var includeRuntimeModules bool

// Loop on package's files
for _, f := range appPkg.Files {
fileImports := goanalysis.FormatImports(f)
Expand All @@ -118,6 +127,11 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
return xast.ErrStop
}

// Check if Cosmos SDK runtime App is called to register API routes
if !includeRuntimeModules {
includeRuntimeModules = checkRuntimeAppCalled(n)
}

// Find modules in RegisterAPIRoutes declaration
if pkgs := findRegisterAPIRoutesRegistrations(n); pkgs != nil {
for _, p := range pkgs {
Expand All @@ -127,6 +141,7 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
}
modules = append(modules, importModule)
}

return xast.ErrStop
}

Expand All @@ -136,6 +151,19 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
return nil, err
}
}

// Try to find the modules registered in Cosmos SDK `runtime.App`.
// This is required to properly generate OpenAPI specs for these
// modules when `app.App.RegisterAPIRoutes` is called.
if includeRuntimeModules {
runtimeModules, err := findRuntimeRegisteredModules(chainRoot)
if err != nil {
return nil, err
}

modules = append(modules, runtimeModules...)
}

return modules, nil
}

Expand Down Expand Up @@ -270,7 +298,7 @@ func findRegisterAPIRoutesRegistrations(n ast.Node) []string {
return nil
}

if funcLitType.Name.Name != "RegisterAPIRoutes" {
if funcLitType.Name.Name != registerRoutesMethod {
return nil
}

Expand Down Expand Up @@ -306,3 +334,117 @@ func findRegisterAPIRoutesRegistrations(n ast.Node) []string {

return packagesRegistered
}

func checkRuntimeAppCalled(n ast.Node) bool {
funcLitType, ok := n.(*ast.FuncDecl)
if !ok {
return false
}

if funcLitType.Name.Name != registerRoutesMethod {
return false
}

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 != 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
}

func findRuntimeRegisteredModules(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 resolveCosmosPackagePath(chainRoot string) (string, error) {
modFile, err := gomodule.ParseAt(chainRoot)
if err != nil {
return "", err
}

deps, err := gomodule.ResolveDependencies(modFile)
if err != nil {
return "", err
}

var pkg string
for _, dep := range deps {
if dep.Path == cosmosver.CosmosModulePath {
pkg = dep.String()
break
}
}

if pkg == "" {
return "", errors.New("Cosmos SDK package version not found")
}

// Check path of the package directory within Go's module cache
path := filepath.Join(goenv.GoModCache(), pkg)
info, err := os.Stat(path)
if os.IsNotExist(err) || !info.IsDir() {
return "", errors.New("local path to Cosmos SDK package not found")
}
return path, nil
}
11 changes: 11 additions & 0 deletions ignite/pkg/cosmosanalysis/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ func TestFindRegisteredModules(t *testing.T) {
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice",
),
},
{
name: "with runtime api routes",
path: "testdata/modules/runtime_api_routes",
expectedModules: append(
basicModules,
"github.com/cosmos/cosmos-sdk/x/auth/tx",
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice",
"github.com/username/test/x/foo",
"github.com/cosmos/cosmos-sdk/client/grpc/node",
),
},
{
name: "same file function",
path: "testdata/modules/file_function",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package app

import (
"cosmossdk.io/api/tendermint/abci"
"cosmossdk.io/client/v2/autocli"
"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server/config"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/gov"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
"github.com/cosmos/cosmos-sdk/x/staking"
foomodule "github.com/username/test/x/foo"
)

// App modules are defined as NewBasicManager arguments
var ModuleBasics = module.NewBasicManager(
auth.AppModuleBasic{},
bank.AppModuleBasic{},
staking.AppModuleBasic{},
gov.NewAppModuleBasic([]govclient.ProposalHandler{
paramsclient.ProposalHandler,
}),
foomodule.AppModuleBasic{},
)

type Foo struct {
*runtime.App
}

func (Foo) Name() string { return "foo" }
func (Foo) InterfaceRegistry() codectypes.InterfaceRegistry { return nil }
func (Foo) TxConfig() client.TxConfig { return nil }
func (Foo) AutoCliOpts() autocli.AppOptions { return autocli.AppOptions{} }

func (Foo) BeginBlocker(sdk.Context, abci.RequestBeginBlock) abci.ResponseBeginBlock {
return abci.ResponseBeginBlock{}
}

func (Foo) EndBlocker(sdk.Context, abci.RequestEndBlock) abci.ResponseEndBlock {
return abci.ResponseEndBlock{}
}

func (app *Foo) RegisterAPIRoutes(s *api.Server, cfg config.APIConfig) {
// This module should be discovered
foomodule.RegisterGRPCGatewayRoutes(s.ClientCtx, s.GRPCGatewayRouter)
// Runtime app modules for the current Cosmos SDK should be discovered too
app.App.RegisterAPIRoutes(apiSvr, apiConfig)
}

func (Foo) GetKey(storeKey string) *storetypes.KVStoreKey { return nil }

func (Foo) TxConfig() client.TxConfig { return nil }
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module app

go 1.20

require (
cosmossdk.io/api v0.3.1
cosmossdk.io/core v0.5.1
cosmossdk.io/depinject v1.0.0-alpha.3
cosmossdk.io/errors v1.0.0-beta.7
cosmossdk.io/math v1.0.1
github.com/bufbuild/buf v1.23.1
github.com/cometbft/cometbft v0.37.2
github.com/cometbft/cometbft-db v0.8.0
github.com/cosmos/cosmos-proto v1.0.0-beta.2
github.com/cosmos/cosmos-sdk v0.47.3
github.com/cosmos/gogoproto v1.4.10
github.com/cosmos/ibc-go/v7 v7.2.0
github.com/golang/protobuf v1.5.3
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.55.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
google.golang.org/protobuf v1.31.0
)

replace (
// use cosmos fork of keyring
github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
// replace broken goleveldb
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
)
15 changes: 15 additions & 0 deletions ignite/pkg/goenv/goenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ const (

// GOPATH is the env var for GOPATH.
GOPATH = "GOPATH"

// GOMODCACHE is the env var for GOMODCACHE.
GOMODCACHE = "GOMODCACHE"
)

const (
binDir = "bin"
modDir = "pkg/mod"
)

// Bin returns the path of where Go binaries are installed.
Expand All @@ -40,3 +44,14 @@ func Path() string {
func ConfigurePath() error {
return os.Setenv("PATH", Path())
}

// GoModCache returns the path to Go's module cache.
func GoModCache() string {
if path := os.Getenv(GOMODCACHE); path != "" {
return path
}
if path := os.Getenv(GOPATH); path != "" {
return filepath.Join(path, modDir)
}
return filepath.Join(build.Default.GOPATH, modDir)
}
Loading

0 comments on commit fa47220

Please sign in to comment.