Open riton opened 1 year ago
I've found a fix for my problem by calling rootCmd.SetArgs([]string{builderCmd.Parent().Use, builderCmd.Use})
just before builderCmd.Execute()
Here is my current solution:
func buildAllRun(cmd *cobra.Command, args []string) error {
// Get all build commands, except the 'all' command to avoid infinite recursion
[... previous code left unchanged ...]
for _, builderCmd := range buildSoftwareCmds {
[... previous code left unchanged ...]
//
// SetArgs() on the rootCmd to avoid infinite recursion
//
rootCmd.SetArgs([]string{builderCmd.Parent().Use, builderCmd.Use})
if err := builderCmd.Execute(); err != nil {
return errors.Wrapf(err, "running command %s", builderCmd.Use)
}
}
return nil
}
Everything is now working as I expect:
$ go run . build all
[rootCmd] PersistentPreRun()
[buildCmd] PersistentPreRun()
[buildAllRun] discovered cmd.Use = "tool1"
[buildAllRun] discovered cmd.Use = "tool2"
[buildAllRun] executing command = "tool1"
[rootCmd] PersistentPreRun()
[buildCmd] PersistentPreRun()
buildTool1 called
[buildAllRun] executing command = "tool2"
[rootCmd] PersistentPreRun()
[buildCmd] PersistentPreRun()
buildTool2 called
Is this the recommended way to achieve such behavior ?
Thanks in advance, Regards
Damn, I've just discovered that #1168 existed which was exactly what I'm trying to do.
Problem, even after reading this issue, I'm not really sure If my solution is the recommended way to achieve such behavior...
The Cobra project currently lacks enough contributors to adequately respond to all issues. This bot triages issues and PRs according to the following rules:
I'm thinking the problem is that you're calling builderCmd.Execute()
instead of builderCmd.Run()
, @riton. Take a look at these lines:
// Regardless of what command execute is called on, run on Root only
if c.HasParent() {
return c.Root().ExecuteC()
}
These are present in the Execute
function, which I believe should only be called on the root command, and it's probably the cause of the recursion. builderCmd.Run()
should work fine
These are present in the
Execute
function, which I believe should only be called on the root command, and it's probably the cause of the recursion.builderCmd.Run()
should work fine
It seems we ended up doing the same kind of investigation.
As mentioned here https://github.com/spf13/cobra/issues/379, the issue of calling Run()
or RunE
directly is that you skip all possible hooks that might be in use for the command, sush as PostRun()
or Args()
.
The workaround solution, if you only really want to call the subcommand programatically and not via the CLI, is to remove them from the root (basically making them not a real CLI command). This allows you to call .Execute()
safely but at a cost.
I've found a fix for my problem by calling
rootCmd.SetArgs([]string{builderCmd.Parent().Use, builderCmd.Use})
just beforebuilderCmd.Execute()
Here is my current solution:
func buildAllRun(cmd *cobra.Command, args []string) error { // Get all build commands, except the 'all' command to avoid infinite recursion [... previous code left unchanged ...] for _, builderCmd := range buildSoftwareCmds { [... previous code left unchanged ...] // // SetArgs() on the rootCmd to avoid infinite recursion // rootCmd.SetArgs([]string{builderCmd.Parent().Use, builderCmd.Use}) if err := builderCmd.Execute(); err != nil { return errors.Wrapf(err, "running command %s", builderCmd.Use) } } return nil }
Everything is now working as I expect:
$ go run . build all [rootCmd] PersistentPreRun() [buildCmd] PersistentPreRun() [buildAllRun] discovered cmd.Use = "tool1" [buildAllRun] discovered cmd.Use = "tool2" [buildAllRun] executing command = "tool1" [rootCmd] PersistentPreRun() [buildCmd] PersistentPreRun() buildTool1 called [buildAllRun] executing command = "tool2" [rootCmd] PersistentPreRun() [buildCmd] PersistentPreRun() buildTool2 called
Is this the recommended way to achieve such behavior ?
Thanks in advance, Regards
I think this might be the only way currently to achieve this. It doesn't look particularly great but it gets the job done and it means you have will access to the hooks from the command you're trying to call. Great work finding this!
Context
I'm trying to create a build helper for multiple tools. The command hierarchy would be such as:
I would like to have a special subcommand of the
build
command, namedall
. Ideally, thisall
subcommand would discover all the subcommands of thebuild
command and execute them. Like ifmycmd build tool1 && mycmd build tool2
were called.Software versions used
``` module github.com/riton/cobra-traverse-and-exec-child-cmd go 1.19 require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.6.1 ) require ( github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/spf13/pflag v1.0.5 // indirect ) ```go.mod
What I've tried
Most of the code bellow was generated using
cobra-cli
.All of the other commands code
```go package cmd import ( "fmt" "os" "github.com/spf13/cobra" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "cobra-traverse-and-exec-child-cmd", PersistentPreRun: func(cmd *cobra.Command, args []string) { fmt.Println("[rootCmd] PersistentPreRun()") }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } } ```cmd/root.go
```go package cmd import ( "fmt" "github.com/spf13/cobra" ) // buildCmd represents the build command var buildCmd = &cobra.Command{ Use: "build", PersistentPreRun: func(cmd *cobra.Command, args []string) { rootCmd.PersistentPreRun(rootCmd, args) fmt.Println("[buildCmd] PersistentPreRun()") }, } func init() { rootCmd.AddCommand(buildCmd) } ```cmd/build.go
```go package cmd import ( "fmt" "github.com/spf13/cobra" ) // buildTool1Cmd represents the buildTool1 command var buildTool1Cmd = &cobra.Command{ Use: "tool1", Run: func(cmd *cobra.Command, args []string) { fmt.Println("build_tool_1 called") }, } func init() { buildCmd.AddCommand(buildTool1Cmd) } ```cmd/build_tool1.go
```go package cmd import ( "fmt" "github.com/spf13/cobra" ) // buildTool2cmd represents the buildTool2 command var buildTool2Cmd = &cobra.Command{ Use: "tool2", Run: func(cmd *cobra.Command, args []string) { fmt.Println("build_tool_2 called") }, } func init() { buildCmd.AddCommand(buildTool2Cmd) } ```cmd/build_tool2.go
Attempt to discover and execute the sub commands
And here is what I've tried to use to discover and execute all of the
buildTool1Cmd
andbuildTool2Cmd
:Filename:
cmd/build_all_tools.go
Sub command discovery seem to work. The
buildSoftwareCmds
array in the code above successfully containsbuildTool1Cmd
andbuildTool2Cmd
command definitions.As a side note: I'm not directly calling the
Run()
orRunE()
functions on the subcommands here to honor thePersistenPreRun()
logic that was positioned on the parents.Problem
The problem is with my call to the
Execute()
function on any of those commands. It drops me in an infinite recursion...Here is a sample of the execution output:
=> kill the command using
Ctrl-C
Any help on how to solve this specific problem would be really appreciated :pray:
Thanks in advance
Regards
Rémi