diff --git a/ignite/cmd/tools.go b/ignite/cmd/tools.go index 606dd4cfeb..b8ca0077c4 100644 --- a/ignite/cmd/tools.go +++ b/ignite/cmd/tools.go @@ -74,7 +74,7 @@ func toolsProtocProxy(cmd *cobra.Command, args []string) error { } defer cleanup() - return toolsProxy(cmd.Context(), append(command.Command, args...)) + return toolsProxy(cmd.Context(), append(command.Command(), args...)) } func toolsProxy(ctx context.Context, command []string) error { diff --git a/ignite/pkg/cosmosgen/generate_javascript.go b/ignite/pkg/cosmosgen/generate_javascript.go index 476783e843..941a5bda3d 100644 --- a/ignite/pkg/cosmosgen/generate_javascript.go +++ b/ignite/pkg/cosmosgen/generate_javascript.go @@ -57,11 +57,19 @@ func (g *generator) generateJS() error { } func (g *jsGenerator) generateModules() error { - tsprotoPluginPath, cleanup, err := tsproto.BinaryPath() + protocCmd, cleanupProtoc, err := protoc.Command() if err != nil { return err } - defer cleanup() + + defer cleanupProtoc() + + tsprotoPluginPath, cleanupPlugin, err := tsproto.BinaryPath() + if err != nil { + return err + } + + defer cleanupPlugin() gg := &errgroup.Group{} @@ -81,7 +89,8 @@ func (g *jsGenerator) generateModules() error { return nil } - if err := g.generateModule(g.g.ctx, tsprotoPluginPath, sourcePath, m); err != nil { + err = g.generateModule(g.g.ctx, protocCmd, tsprotoPluginPath, sourcePath, m) + if err != nil { return err } @@ -102,7 +111,12 @@ func (g *jsGenerator) generateModules() error { } // generateModule generates generates JS code for a module. -func (g *jsGenerator) generateModule(ctx context.Context, tsprotoPluginPath, appPath string, m module.Module) error { +func (g *jsGenerator) generateModule( + ctx context.Context, + cmd protoc.Cmd, + tsprotoPluginPath, appPath string, + m module.Module, +) error { var ( out = g.g.o.jsOut(m) storeDirPath = filepath.Dir(out) @@ -127,6 +141,7 @@ func (g *jsGenerator) generateModule(ctx context.Context, tsprotoPluginPath, app tsOut, protoc.Plugin(tsprotoPluginPath, "--ts_proto_opt=snakeToCamel=false"), protoc.Env("NODE_OPTIONS="), // unset nodejs options to avoid unexpected issues with vercel "pkg" + protoc.WithCommand(cmd), ) if err != nil { return err @@ -145,6 +160,7 @@ func (g *jsGenerator) generateModule(ctx context.Context, tsprotoPluginPath, app m.Pkg.Path, includePaths, jsOpenAPIOut, + protoc.WithCommand(cmd), ) if err != nil { return err diff --git a/ignite/pkg/nodetime/programs/ts-proto/tsproto.go b/ignite/pkg/nodetime/programs/ts-proto/tsproto.go index 36b3fc8bab..fd90252989 100644 --- a/ignite/pkg/nodetime/programs/ts-proto/tsproto.go +++ b/ignite/pkg/nodetime/programs/ts-proto/tsproto.go @@ -10,7 +10,10 @@ import ( "github.com/ignite/cli/ignite/pkg/nodetime" ) -const pluginName = "protoc-gen-ts_proto" +const ( + pluginName = "protoc-gen-ts_proto" + scriptTemplate = "#!/bin/bash\n%s $@\n" +) // BinaryPath returns the path to the binary of the ts-proto plugin so it can be passed to // protoc via --plugin option. @@ -19,22 +22,44 @@ const pluginName = "protoc-gen-ts_proto" // will be protoc-gen-ts_proto. // see why: https://github.com/stephenh/ts-proto/blob/7f76c05/README.markdown#quickstart. func BinaryPath() (path string, cleanup func(), err error) { - var command []string + // Create binary for the TypeScript protobuf generator + command, cleanupBin, err := nodetime.Command(nodetime.CommandTSProto) + if err != nil { + return + } - command, cleanup, err = nodetime.Command(nodetime.CommandTSProto) + defer func() { + if err != nil { + cleanupBin() + } + }() + + // Create a random directory for the script that runs the TypeScript protobuf generator. + // This is required to avoid potential flaky integration tests caused by one concurrent + // test overwriting the generator script while it is being run in a separate test process. + tmpDir, err := os.MkdirTemp("", "ts_proto_plugin") if err != nil { return } - tmpdir := os.TempDir() - path = filepath.Join(tmpdir, pluginName) + cleanupScriptDir := func() { os.RemoveAll(tmpDir) } + + defer func() { + if err != nil { + cleanupScriptDir() + } + }() - // comforting protoc by giving protoc-gen-ts_proto name to the plugin's binary. - script := fmt.Sprintf(`#!/bin/bash -%s "$@" -`, strings.Join(command, " ")) + cleanup = func() { + cleanupBin() + cleanupScriptDir() + } + // Wrap the TypeScript protobuf generator in a script with a fixed name + // located in a random temporary directory. + script := fmt.Sprintf(scriptTemplate, strings.Join(command, " ")) + path = filepath.Join(tmpDir, pluginName) err = os.WriteFile(path, []byte(script), 0o755) - return + return path, cleanup, err } diff --git a/ignite/pkg/protoc/protoc.go b/ignite/pkg/protoc/protoc.go index c93b1030ca..c884e15883 100644 --- a/ignite/pkg/protoc/protoc.go +++ b/ignite/pkg/protoc/protoc.go @@ -24,6 +24,7 @@ type configs struct { isGeneratedDepsEnabled bool pluginOptions []string env []string + command Cmd } // Plugin configures a plugin for code generation. @@ -49,9 +50,29 @@ func Env(v ...string) Option { } } +// WithCommand assigns a protoc command to use for code generation. +// This allows to use a single protoc binary in multiple code generation calls. +// Otherwise `Generate` creates a new protoc binary each time it is called. +func WithCommand(command Cmd) Option { + return func(c *configs) { + c.command = command + } +} + +// Cmd contains the information necessary to execute the protoc command. type Cmd struct { - Command []string - Included []string + command []string + includes []string +} + +// Command returns the strings to execute the `protoc` command. +func (c Cmd) Command() []string { + return c.command +} + +// Includes returns the proto files import paths. +func (c Cmd) Includes() []string { + return c.includes } // Command sets the protoc binary up and returns the command needed to execute c. @@ -73,8 +94,8 @@ func Command() (command Cmd, cleanup func(), err error) { } command = Cmd{ - Command: []string{path, "-I", include}, - Included: []string{include}, + command: []string{path, "-I", include}, + includes: []string{include}, } return command, cleanup, nil @@ -88,13 +109,21 @@ func Generate(ctx context.Context, outDir, protoPath string, includePaths, proto o(&c) } - cmd, cleanup, err := Command() - if err != nil { - return err - } - defer cleanup() + // init the string to run the protoc command and the proto files import path + command := c.command.Command() + includes := c.command.Includes() + + if command == nil { + cmd, cleanup, err := Command() + if err != nil { + return err + } - command := cmd.Command + defer cleanup() + + command = cmd.Command() + includes = cmd.Includes() + } // add plugin if set. if c.pluginPath != "" { @@ -116,7 +145,7 @@ func Generate(ctx context.Context, outDir, protoPath string, includePaths, proto } // find out the list of proto files to generate code for and perform code generation. - files, err := discoverFiles(ctx, c, protoPath, append(cmd.Included, existentIncludePaths...), protoanalysis.NewCache()) + files, err := discoverFiles(ctx, c, protoPath, append(includes, existentIncludePaths...), protoanalysis.NewCache()) if err != nil { return err }