diff --git a/ignite/pkg/cosmosgen/generate.go b/ignite/pkg/cosmosgen/generate.go index 3a1212d856..59bf2bd713 100644 --- a/ignite/pkg/cosmosgen/generate.go +++ b/ignite/pkg/cosmosgen/generate.go @@ -87,7 +87,7 @@ func (g *generator) setup(ctx context.Context) (err error) { New( cmdrunner.DefaultStderr(&errb), cmdrunner.DefaultWorkdir(g.appPath), - ).Run(ctx, step.New(step.Exec("go", "mod", "download"))); err != nil { + ).Run(ctx, step.New(step.Exec("go", "mod", "download"))); err != nil { // TODO: use gocmd.ModDownload return errors.Wrap(err, errb.String()) } diff --git a/ignite/services/injector/commands.go b/ignite/services/injector/commands.go new file mode 100644 index 0000000000..881634748d --- /dev/null +++ b/ignite/services/injector/commands.go @@ -0,0 +1,10 @@ +package injector + +import "context" + +type Command struct { +} + +func (i *injector) AddCommand(ctx context.Context, c *Command) error { + panic("unimplemented") +} diff --git a/ignite/services/injector/helpers.go b/ignite/services/injector/helpers.go new file mode 100644 index 0000000000..3d477ea3cb --- /dev/null +++ b/ignite/services/injector/helpers.go @@ -0,0 +1,31 @@ +package injector + +import ( + "path/filepath" + + "github.com/ignite/cli/v29/ignite/templates/module" +) + +const ( + PathAppConfigGo = module.PathAppConfigGo + PathAppGo = module.PathAppGo + PathCommands = "cmd/commands.go" +) + +func (i *injector) appPath() string { + return filepath.Join(i.chain.AppPath(), PathAppGo) +} + +func (i *injector) appConfigPath() string { + return filepath.Join(i.chain.AppPath(), PathAppConfigGo) +} + +func (i *injector) commandsPath() (string, error) { + appPath := i.chain.AppPath() + binaryName, err := i.chain.Binary() + if err != nil { + return "", err + } + + return filepath.Join(appPath, "cmd", binaryName, PathCommands), nil +} diff --git a/ignite/services/injector/injector.go b/ignite/services/injector/injector.go new file mode 100644 index 0000000000..652a4f55f9 --- /dev/null +++ b/ignite/services/injector/injector.go @@ -0,0 +1,66 @@ +package injector + +import ( + "context" + + "github.com/gobuffalo/genny/v2" + "github.com/gobuffalo/plush/v4" + + "github.com/ignite/cli/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/ignite/pkg/gocmd" + "github.com/ignite/cli/v29/ignite/pkg/xgenny" + "github.com/ignite/cli/v29/ignite/services/chain" + "github.com/ignite/cli/v29/ignite/templates/field/plushhelpers" +) + +// Injector allows to add modules, commands and other changes to the chain, using a convinient API. +// Once the wanted changes are added, the Inject method should be called to apply them. +type Injector interface { + AddModule(ctx context.Context, m *Module) error + AddNonDepinjectModule(ctx context.Context, m *Module) error + AddCommand(ctx context.Context, c *Command) error + + Inject(ctx context.Context) error +} + +// TODO: somewhere else, injector for modules to replace placeholders + +type injector struct { + chain *chain.Chain + generator *genny.Generator +} + +// NewInject creates an Injector. +func NewInjector(c *chain.Chain) Injector { + g := genny.New() + ctx := plush.NewContext() + plushhelpers.ExtendPlushContext(ctx) + g.Transformer(xgenny.Transformer(ctx)) + + return &injector{ + chain: c, + generator: g, + } +} + +// Inject runs the injector, adding all the changes to the chain. +func (i *injector) Inject(ctx context.Context) error { + appPath := i.chain.AppPath() + + runner := xgenny.NewRunner(ctx, appPath) + if err := runner.Run(i.generator); err != nil { + return errors.Errorf("failed to execute generator: %w", err) + } + + if err := gocmd.ModTidy(ctx, appPath); err != nil { + return err + } + + if err := gocmd.Fmt(ctx, appPath); err != nil { + return err + } + + _ = gocmd.GoImports(ctx, appPath) // goimports installation could fail, so ignore the error + + return nil +} diff --git a/ignite/services/injector/module.go b/ignite/services/injector/module.go new file mode 100644 index 0000000000..22f4df0ca3 --- /dev/null +++ b/ignite/services/injector/module.go @@ -0,0 +1,129 @@ +package injector + +import ( + "context" + "fmt" + "go/ast" + "io" + "path/filepath" + "strings" + + "github.com/gobuffalo/genny/v2" + "github.com/ignite/cli/v29/ignite/pkg/cmdrunner/exec" + "github.com/ignite/cli/v29/ignite/pkg/cmdrunner/step" + "github.com/ignite/cli/v29/ignite/pkg/gocmd" + "github.com/ignite/cli/v29/ignite/pkg/goenv" + "github.com/ignite/cli/v29/ignite/pkg/xast" +) + +// Modules should follow the recommended modules structure. +// See https://docs.cosmos.network/main/build/building-modules/structure#structure +type Module struct { + Name string // bank + Import string // cosmossdk.io/bank + Permissions string // TODO +} + +type moduleConfig struct { + typePkg string + keeperPkg string + modulePkg string + apiPkg string + + hasGenesis bool // TODO + hasPreBlocker bool + hasBeginBlocker bool + hasEndBlocker bool +} + +// downloadModule downloads the module and returns its path +func (i *injector) downloadModule(ctx context.Context, m *Module) (string, error) { + // TODO: allow to check local modules that aren't their own go modules + // inspect if module is in the chain. + + if err := gocmd.ModDownload(ctx, m.Import, false, exec.StepOption(step.Stdout(io.Discard))); err != nil { + return "", fmt.Errorf("failed to download module: %w", err) + } + + return filepath.Join(append([]string{goenv.GoModCache()}, strings.Split(m.Import, "/")...)...), nil +} + +// AddModule adds a new module to the chain. +func (i *injector) AddModule(ctx context.Context, m *Module) error { + mc := moduleConfig{ + typePkg: fmt.Sprintf("%s/types", m.Import), + keeperPkg: fmt.Sprintf("%s/keeper", m.Import), + modulePkg: fmt.Sprintf("%s/module", m.Import), + apiPkg: fmt.Sprintf("%s/api", m.Import), + } + + modulePath, err := i.downloadModule(ctx, m) + if err != nil { + return err + } + + modulePkg, _, err := xast.ParseDir(modulePath) + if err != nil { + return err + } + + for _, f := range modulePkg.Files { + ast.Inspect(f, func(x ast.Node) bool { + // TODO find ModuleName and set moduleTypePackage + // TODO check depinject config and find api module package + // TODO check abci.go or module.go and find abci implementations + return true + }) + } + + appConfigFn := func(r *genny.Runner) error { + path := i.appConfigPath() + + f, err := r.Disk.Find(path) + if err != nil { + return err + } + + content, err := xast.AppendImports( + f.String(), + xast.WithLastNamedImport(fmt.Sprintf("%stypes", m.Name), mc.typePkg), + ) + if err != nil { + return err + } + + return r.File(genny.NewFileS(path, content)) + } + + appFn := func(r *genny.Runner) error { + path := i.appPath() + + f, err := r.Disk.Find(path) + if err != nil { + return err + } + + content, err := xast.AppendImports( + f.String(), + xast.WithLastNamedImport(fmt.Sprintf("%skeeper", m.Name), mc.keeperPkg), + xast.WithLastNamedImport("_", mc.modulePkg), + ) + if err != nil { + return err + } + + return r.File(genny.NewFileS(path, content)) + } + + i.generator.RunFn(appConfigFn) + i.generator.RunFn(appFn) + + return nil +} + +func (i *injector) AddNonDepinjectModule(ctx context.Context, m *Module) error { + // append app.go or create custom.go + // do not break ibc.go + + panic("not implemented") +}