-
Notifications
You must be signed in to change notification settings - Fork 555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: PoC for passing data to plugins #3498
Closed
Closed
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
0da6600
feat: Pass data to plugin
clockworkgr f51152f
feat: ensure go.mod points to the updated cli/manifest
clockworkgr 7a63ab7
feat: Add module analysis
clockworkgr 85e27d2
feat: Plugin go.mod to correct commit
clockworkgr 8401d74
fix: add additional includes
clockworkgr 0108ea9
feat: Correctly resolve protos in indirect deps
clockworkgr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
package common | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/ignite/cli/ignite/pkg/cache" | ||
"github.com/ignite/cli/ignite/pkg/cmdrunner" | ||
"github.com/ignite/cli/ignite/pkg/cmdrunner/step" | ||
"github.com/ignite/cli/ignite/pkg/cosmosanalysis/module" | ||
"github.com/ignite/cli/ignite/pkg/cosmosgen" | ||
"github.com/ignite/cli/ignite/pkg/gomodule" | ||
"github.com/ignite/cli/ignite/pkg/xfilepath" | ||
"github.com/ignite/cli/ignite/services/chain" | ||
"github.com/pkg/errors" | ||
gomod "golang.org/x/mod/module" | ||
) | ||
|
||
const ( | ||
defaultSDKImport = "github.com/cosmos/cosmos-sdk" | ||
moduleCacheNamespace = "analyze.setup.module" | ||
) | ||
|
||
var protocGlobalInclude = xfilepath.List( | ||
xfilepath.JoinFromHome(xfilepath.Path("local/include")), | ||
xfilepath.JoinFromHome(xfilepath.Path(".local/include")), | ||
) | ||
|
||
type ModulesInPath struct { | ||
Path string | ||
Modules []module.Module | ||
} | ||
type AllModules struct { | ||
ModulePaths []ModulesInPath | ||
Includes []string | ||
} | ||
type Analyzer struct { | ||
ctx context.Context | ||
appPath string | ||
protoDir string | ||
sdkImport string | ||
appModules []module.Module | ||
cacheStorage cache.Storage | ||
deps []gomod.Version | ||
includeDirs []string | ||
thirdModules map[string][]module.Module // app dependency-modules pair. | ||
} | ||
|
||
func GetModuleList(ctx context.Context, c *chain.Chain) (map[string]string, error) { | ||
|
||
conf, err := c.Config() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cacheStorage, err := cache.NewStorage(filepath.Join(c.AppPath(), "analyzer_cache.db")) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := cosmosgen.InstallDepTools(ctx, c.AppPath()); err != nil { | ||
return nil, err | ||
} | ||
|
||
var errb bytes.Buffer | ||
if err := cmdrunner. | ||
New( | ||
cmdrunner.DefaultStderr(&errb), | ||
cmdrunner.DefaultWorkdir(c.AppPath()), | ||
).Run(ctx, step.New(step.Exec("go", "mod", "download"))); err != nil { | ||
return nil, errors.Wrap(err, errb.String()) | ||
} | ||
|
||
modFile, err := gomodule.ParseAt(c.AppPath()) | ||
g := &Analyzer{ | ||
ctx: ctx, | ||
appPath: c.AppPath(), | ||
protoDir: conf.Build.Proto.Path, | ||
includeDirs: conf.Build.Proto.ThirdPartyPaths, | ||
thirdModules: make(map[string][]module.Module), | ||
cacheStorage: cacheStorage, | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
g.sdkImport = defaultSDKImport | ||
|
||
// Check if the Cosmos SDK import path points to a different path | ||
// and if so change the default one to the new location. | ||
for _, r := range modFile.Replace { | ||
if r.Old.Path == defaultSDKImport { | ||
g.sdkImport = r.New.Path | ||
break | ||
} | ||
} | ||
|
||
// Read the dependencies defined in the `go.mod` file | ||
g.deps, err = gomodule.ResolveDependencies(modFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
includePaths, err := g.resolveInclude(c.AppPath()) | ||
|
||
// Discover any custom modules defined by the user's app | ||
g.appModules, err = g.discoverModules(g.appPath, g.protoDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Go through the Go dependencies of the user's app within go.mod, some of them might be hosting Cosmos SDK modules | ||
// that could be in use by user's blockchain. | ||
// | ||
// Cosmos SDK is a dependency of all blockchains, so it's absolute that we'll be discovering all modules of the | ||
// SDK as well during this process. | ||
// | ||
// Even if a dependency contains some SDK modules, not all of these modules could be used by user's blockchain. | ||
// this is fine, we can still generate TS clients for those non modules, it is up to user to use (import in typescript) | ||
// not use generated modules. | ||
// | ||
// TODO: we can still implement some sort of smart filtering to detect non used modules by the user's blockchain | ||
// at some point, it is a nice to have. | ||
moduleCache := cache.New[ModulesInPath](g.cacheStorage, moduleCacheNamespace) | ||
for _, dep := range g.deps { | ||
// Try to get the cached list of modules for the current dependency package | ||
cacheKey := cache.Key(dep.Path, dep.Version) | ||
modulesInPath, err := moduleCache.Get(cacheKey) | ||
if err != nil && !errors.Is(err, cache.ErrorNotFound) { | ||
return nil, err | ||
} | ||
|
||
// Discover the modules of the dependency package when they are not cached | ||
if errors.Is(err, cache.ErrorNotFound) { | ||
// Get the absolute path to the package's directory | ||
path, err := gomodule.LocatePath(g.ctx, g.cacheStorage, c.AppPath(), dep) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Discover any modules defined by the package | ||
modules, err := g.discoverModules(path, "") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
modulesInPath = ModulesInPath{ | ||
Path: path, | ||
Modules: modules, | ||
} | ||
|
||
if err := moduleCache.Put(cacheKey, modulesInPath); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
g.thirdModules[modulesInPath.Path] = append(g.thirdModules[modulesInPath.Path], modulesInPath.Modules...) | ||
} | ||
|
||
var modulelist []ModulesInPath | ||
modulelist = append(modulelist, ModulesInPath{Path: c.AppPath(), Modules: g.appModules}) | ||
for sourcePath, modules := range g.thirdModules { | ||
modulelist = append(modulelist, ModulesInPath{Path: sourcePath, Modules: modules}) | ||
} | ||
var allModules = &AllModules{ | ||
ModulePaths: modulelist, | ||
Includes: includePaths, | ||
} | ||
ret := make(map[string]string) | ||
jsonm, _ := json.Marshal(allModules) | ||
ret["ModuleAnalysis"] = string(jsonm) | ||
return ret, nil | ||
} | ||
|
||
func (g *Analyzer) resolveDependencyInclude() ([]string, error) { | ||
// Init paths with the global include paths for protoc | ||
paths, err := protocGlobalInclude() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Relative paths to proto directories | ||
protoDirs := append([]string{g.protoDir}, g.includeDirs...) | ||
|
||
// Create a list of proto import paths for the dependencies. | ||
// These paths will be available to be imported from the chain app's proto files. | ||
for rootPath, m := range g.thirdModules { | ||
// Skip modules without proto files | ||
if m == nil { | ||
continue | ||
} | ||
|
||
// Check each one of the possible proto directory names for the | ||
// current module and append them only when the directory exists. | ||
for _, d := range protoDirs { | ||
p := filepath.Join(rootPath, d) | ||
f, err := os.Stat(p) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
|
||
return nil, err | ||
} | ||
|
||
if f.IsDir() { | ||
paths = append(paths, p) | ||
} | ||
} | ||
} | ||
|
||
return paths, nil | ||
} | ||
|
||
func (g *Analyzer) resolveInclude(path string) (paths []string, err error) { | ||
// Append chain app's proto paths | ||
paths = append(paths, filepath.Join(path, g.protoDir)) | ||
for _, p := range g.includeDirs { | ||
paths = append(paths, filepath.Join(path, p)) | ||
} | ||
|
||
// Append paths for dependencies that have protocol buffer files | ||
includePaths, err := g.resolveDependencyInclude() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
paths = append(paths, includePaths...) | ||
|
||
return paths, nil | ||
} | ||
|
||
func (g *Analyzer) discoverModules(path, protoDir string) ([]module.Module, error) { | ||
var filteredModules []module.Module | ||
|
||
modules, err := module.Discover(g.ctx, g.appPath, path, protoDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
protoPath := filepath.Join(path, g.protoDir) | ||
|
||
for _, m := range modules { | ||
if !strings.HasPrefix(m.Pkg.Path, protoPath) { | ||
continue | ||
} | ||
|
||
filteredModules = append(filteredModules, m) | ||
} | ||
|
||
return filteredModules, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The package name should be more specific, maybe it could be named as
codeanalisys
,appanalisys
orcodeinspect
. Its purpose could be to provide a simpler or unified API to inspect/analize blockchain apps using the other specific analysis libraries withinpkg
.It would be ideal to have it very well documented if the point is to make it easier for plugin writers to understand how to inspect the app code.