diff --git a/ignite/cmd/chain.go b/ignite/cmd/chain.go index cff8281e7d..5a884c7a26 100644 --- a/ignite/cmd/chain.go +++ b/ignite/cmd/chain.go @@ -18,13 +18,13 @@ import ( "github.com/ignite/cli/ignite/pkg/cosmosgen" "github.com/ignite/cli/ignite/pkg/goanalysis" "github.com/ignite/cli/ignite/pkg/xast" + "github.com/ignite/cli/ignite/services/doctor" ) const ( msgMigration = "Migrating blockchain config file from v%d to v%d..." msgMigrationPrefix = "Your blockchain config version is v%d and the latest is v%d." msgMigrationPrompt = "Would you like to upgrade your config file to v%d" - toolsFile = "tools/tools.go" ) // NewChain returns a command that groups sub commands related to compiling, serving @@ -116,7 +116,11 @@ func toolsMigrationPreRunHandler(cmd *cobra.Command, session *cliui.Session) (er session.StartSpinner("Checking missing tools...") appPath := flagGetPath(cmd) - toolsFilename := filepath.Join(appPath, toolsFile) + toolsFilename := filepath.Join(appPath, doctor.ToolsFile) + if _, err := os.Stat(toolsFilename); os.IsNotExist(err) { + return errors.New("the dependency tools file is missing, run `ignite doctor` and try again") + } + f, _, err := xast.ParseFile(toolsFilename) if err != nil { return err diff --git a/ignite/cmd/generate.go b/ignite/cmd/generate.go index 1d66720e5a..4214fe4d8c 100644 --- a/ignite/cmd/generate.go +++ b/ignite/cmd/generate.go @@ -1,6 +1,10 @@ package ignitecmd -import "github.com/spf13/cobra" +import ( + "github.com/spf13/cobra" + + "github.com/ignite/cli/ignite/pkg/cliui" +) // NewGenerate returns a command that groups code generation related sub commands. func NewGenerate() *cobra.Command { @@ -15,8 +19,9 @@ functionality, for example, generating an OpenAPI spec. Produced source code can be regenerated by running a command again and is not meant to be edited by hand. `, - Aliases: []string{"g"}, - Args: cobra.ExactArgs(1), + Aliases: []string{"g"}, + Args: cobra.ExactArgs(1), + PersistentPreRunE: generatePreRunHandler, } flagSetPath(c) @@ -30,3 +35,10 @@ meant to be edited by hand. return c } + +func generatePreRunHandler(cmd *cobra.Command, _ []string) error { + session := cliui.New() + defer session.End() + + return toolsMigrationPreRunHandler(cmd, session) +} diff --git a/ignite/pkg/cosmosgen/install.go b/ignite/pkg/cosmosgen/install.go index 0c459c2ebb..90a21004c2 100644 --- a/ignite/pkg/cosmosgen/install.go +++ b/ignite/pkg/cosmosgen/install.go @@ -32,7 +32,7 @@ func InstallDepTools(ctx context.Context, appPath string) error { } err := gocmd.Install(ctx, appPath, DepTools()) if gocmd.IsInstallError(err) { - return errors.New("unable to install dependency tools, try to run `ignite doctor` and try again") + return errors.New("unable to install dependency tools, run `ignite doctor` and try again") } return err } diff --git a/ignite/services/doctor/doctor.go b/ignite/services/doctor/doctor.go index 199e75b299..51f791236a 100644 --- a/ignite/services/doctor/doctor.go +++ b/ignite/services/doctor/doctor.go @@ -14,10 +14,17 @@ import ( "github.com/ignite/cli/ignite/pkg/cliui/icons" "github.com/ignite/cli/ignite/pkg/cosmosgen" "github.com/ignite/cli/ignite/pkg/events" + "github.com/ignite/cli/ignite/pkg/goanalysis" "github.com/ignite/cli/ignite/pkg/gomodulepath" + "github.com/ignite/cli/ignite/pkg/xast" "github.com/ignite/cli/ignite/templates/app" ) +const ( + // ToolsFile defines the app relative path to the Go tools file. + ToolsFile = "tools/tools.go" +) + // DONTCOVER: Doctor read and write the filesystem intensively, so it's better // to rely on integration tests only. See integration/doctor package. type Doctor struct { @@ -54,6 +61,7 @@ func (d *Doctor) MigrateConfig(_ context.Context) error { if err != nil { return errf(err) } + f, err := os.Open(configPath) if err != nil { return errf(err) @@ -64,21 +72,31 @@ func (d *Doctor) MigrateConfig(_ context.Context) error { if err != nil { return errf(err) } + + status := "OK" + if version != chainconfig.LatestVersion { + f.Seek(0, 0) + // migrate config file // Convert the current config to the latest version and update the YAML file var buf bytes.Buffer - f.Seek(0, 0) if err := chainconfig.MigrateLatest(f, &buf); err != nil { return errf(err) } + if err := os.WriteFile(configPath, buf.Bytes(), 0o755); err != nil { return errf(fmt.Errorf("config file migration failed: %w", err)) } - d.ev.Send(fmt.Sprintf("config file %s", colors.Success("migrated")), - events.Icon(icons.OK), events.ProgressFinish()) + + status = "migrated" } - d.ev.Send("config file OK", events.Icon(icons.OK), events.ProgressFinish()) + + d.ev.Send( + fmt.Sprintf("config file %s", colors.Success(status)), + events.Icon(icons.OK), + events.ProgressFinish(), + ) return nil } @@ -93,53 +111,119 @@ func (d *Doctor) FixDependencyTools(ctx context.Context) error { d.ev.Send("Checking dependency tools:", events.ProgressFinish()) - const toolsGoFile = "tools/tools.go" - _, err := os.Stat(toolsGoFile) + _, err := os.Stat(ToolsFile) switch { case err == nil: - // tools.go exists - d.ev.Send(fmt.Sprintf("%s exists", toolsGoFile), events.Icon(icons.OK), - events.ProgressFinish()) - // TODO ensure tools.go has the required dependencies + d.ev.Send( + fmt.Sprintf("%s %s", ToolsFile, colors.Success("exists")), + events.Icon(icons.OK), + events.ProgressUpdate(), + ) - case os.IsNotExist(err): - // create tools.go - pathInfo, err := gomodulepath.ParseAt(".") - if err != nil { - return errf(err) - } - g, err := app.NewGenerator(&app.Options{ - ModulePath: pathInfo.RawPath, - AppName: pathInfo.Package, - BinaryNamePrefix: pathInfo.Root, - IncludePrefixes: []string{toolsGoFile}, - }) + updated, err := d.ensureDependencyImports(ToolsFile) if err != nil { return errf(err) } - // run generator - runner := genny.WetRunner(ctx) - if err := runner.With(g); err != nil { - return errf(err) - } - if err := runner.Run(); err != nil { - return errf(err) + + status := "OK" + if updated { + status = "updated" } - d.ev.Send(fmt.Sprintf("%s %s", toolsGoFile, colors.Success("created")), - events.Icon(icons.OK), events.ProgressFinish()) - d.ev.Send("Installing dependency tools", events.ProgressStart()) - if err := cosmosgen.InstallDepTools(ctx, "."); err != nil { + d.ev.Send( + fmt.Sprintf("tools file %s", colors.Success(status)), + events.Icon(icons.OK), + events.ProgressFinish(), + ) + + case os.IsNotExist(err): + if err := d.createToolsFile(ctx, ToolsFile); err != nil { return errf(err) } - for _, dep := range cosmosgen.DepTools() { - d.ev.Send(fmt.Sprintf("%s %s", path.Base(dep), colors.Success("installed")), - events.Icon(icons.OK), events.ProgressFinish()) - } default: return errf(err) } + + return nil +} + +func (d Doctor) createToolsFile(ctx context.Context, toolsFilename string) error { + pathInfo, err := gomodulepath.ParseAt(".") + if err != nil { + return err + } + + g, err := app.NewGenerator(&app.Options{ + ModulePath: pathInfo.RawPath, + AppName: pathInfo.Package, + BinaryNamePrefix: pathInfo.Root, + IncludePrefixes: []string{toolsFilename}, + }) + if err != nil { + return err + } + + runner := genny.WetRunner(ctx) + if err := runner.With(g); err != nil { + return err + } + + if err := runner.Run(); err != nil { + return err + } + + d.ev.Send( + fmt.Sprintf("%s %s", toolsFilename, colors.Success("created")), + events.Icon(icons.OK), + events.ProgressFinish(), + ) + + d.ev.Send("Installing dependency tools", events.ProgressStart()) + if err := cosmosgen.InstallDepTools(ctx, "."); err != nil { + return err + } + + for _, dep := range cosmosgen.DepTools() { + d.ev.Send( + fmt.Sprintf("%s %s", path.Base(dep), colors.Success("installed")), + events.Icon(icons.OK), + events.ProgressFinish(), + ) + } + return nil } + +func (d Doctor) ensureDependencyImports(toolsFilename string) (bool, error) { + d.ev.Send("Ensuring required tools imports", events.ProgressStart()) + + f, _, err := xast.ParseFile(toolsFilename) + if err != nil { + return false, err + } + + var ( + buf bytes.Buffer + missing = cosmosgen.MissingTools(f) + unused = cosmosgen.UnusedTools(f) + ) + + // Check if the tools file should be fixed + if len(missing) == 0 && len(unused) == 0 { + return false, nil + } + + err = goanalysis.UpdateInitImports(f, &buf, missing, unused) + if err != nil { + return false, err + } + + err = os.WriteFile(toolsFilename, buf.Bytes(), 0o644) + if err != nil { + return false, err + } + + return true, nil +}