Skip to content

Commit a63cd31

Browse files
authored
fix: update dependencies + signal context + exec-stage improvements (#67)
- Update flake. `nixpkgs`. - Add `exec-stage` and aliasing commands to simplify adding commands like `just quitsh build/test/lint`. - Add `rootargs.ErrorSummary` to print all ocurred errors at the end, by default now disabled to reduce logging. - Add a global variable `exec.GlobalContext` which can be deliberatily set over `cli.WithSignalContext(true)` which enables then all instantiations of `exec.CmdContext` to listen on cancelation by SIGINT and SIGTERM. - Not having a global is still possible with ` cli.WithSignalContext(false)` and just using `cli.Ctx()` and passing that downstream to any usage of `exec.CmdContext`. This is however hard to change in already existing code, thus the global variable.
1 parent 0a7dc2f commit a63cd31

23 files changed

Lines changed: 1079 additions & 94 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ cli, err := cli.New(
171171
},
172172
),
173173
)
174+
175+
cli.Run()
174176
```
175177

176178
You can now add runners and your own commands depending on the needs of your

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/sdsc-ordes/quitsh
22

3-
go 1.24
3+
go 1.25
44

55
require (
66
deedles.dev/xiter v0.2.1

pkg/cli/cli-impl.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cli
22

33
import (
4+
"context"
5+
46
"github.com/r3labs/diff"
57
"github.com/sdsc-ordes/quitsh/pkg/build"
68
"github.com/sdsc-ordes/quitsh/pkg/ci"
@@ -23,6 +25,10 @@ func (c *cliApp) Config() config.IConfig {
2325
return c.config
2426
}
2527

28+
func (c *cliApp) Ctx() context.Context {
29+
return c.context
30+
}
31+
2632
func (c *cliApp) ToolchainDispatcher() toolchain.IDispatcher {
2733
return c.toolchainDispatcher
2834
}
@@ -71,7 +77,47 @@ func (c *cliApp) Run() error {
7177
}
7278
}
7379

74-
return c.rootCmd.Execute()
80+
e := c.rootCmd.Execute()
81+
if e != nil {
82+
if c.rootArgs.ErrorSummary {
83+
log.ErrorE(e, "Errors occurred.")
84+
} else {
85+
log.Error("Errors occurred, see above.")
86+
}
87+
}
88+
89+
return e
90+
}
91+
92+
func (c *cliApp) Shutdown() error {
93+
if c.shutdown != nil {
94+
return c.shutdown()
95+
}
96+
97+
return nil
98+
}
99+
100+
// AddShutdown adds another shutdown task to the
101+
// shutdown function chain.
102+
func (c *cliApp) AddShutdown(f func() error) {
103+
if f == nil {
104+
return
105+
}
106+
107+
// Wrap shutdown function.
108+
oldFunc := c.shutdown
109+
c.shutdown = func() error {
110+
e := f()
111+
if e != nil {
112+
return e
113+
}
114+
115+
if oldFunc != nil {
116+
return oldFunc()
117+
}
118+
119+
return nil
120+
}
75121
}
76122

77123
func (c *cliApp) FindComponents(

pkg/cli/cli.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cli
22

33
import (
4+
"context"
5+
46
"github.com/sdsc-ordes/quitsh/pkg/build"
57
"github.com/sdsc-ordes/quitsh/pkg/ci"
68
rootcmd "github.com/sdsc-ordes/quitsh/pkg/cli/cmd/root"
@@ -16,6 +18,11 @@ import (
1618
)
1719

1820
type ICLI interface {
21+
// The root context to be used for `exec.CmdContext` or
22+
// other operations which need cancelation.
23+
// See [WithContext] and [WithSignalContext].
24+
Ctx() context.Context
25+
1926
// The root directory from where certain operations are done,
2027
// i.e finding components etc.
2128
RootDir() string
@@ -49,16 +56,21 @@ type ICLI interface {
4956

5057
// Run will run the CLI.
5158
Run() error
59+
60+
// Shutdown will shutdown the CLI.
61+
// This function should be called in a `defer`
62+
// to correctly cleanup resources.
63+
Shutdown() error
5264
}
5365

5466
// New creates a new `quitsh` CLI application.
5567
// The root arguments `args` generally point into the `config`.
5668
// The CLI instance needs the full `config`
5769
// because it will marshall/unmarshall it
5870
// from disk by the `rootCmd`.
59-
6071
func New(args *rootcmd.Args, config config.IConfig, opts ...Option) (ICLI, error) {
6172
app := &cliApp{
73+
context: context.Background(),
6274
rootArgs: args,
6375
config: config,
6476
}
@@ -85,6 +97,7 @@ func New(args *rootcmd.Args, config config.IConfig, opts ...Option) (ICLI, error
8597
}
8698

8799
type cliApp struct {
100+
context context.Context
88101
rootCmd *cobra.Command
89102

90103
rootDirResolved bool
@@ -103,4 +116,6 @@ type cliApp struct {
103116

104117
factory factory.IFactory
105118
toolchainDispatcher toolchain.IDispatcher
119+
120+
shutdown func() error
106121
}

pkg/cli/cmd/exec-stage/exec.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package execstage
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/sdsc-ordes/quitsh/pkg/cli"
7+
"github.com/sdsc-ordes/quitsh/pkg/cli/general"
8+
"github.com/sdsc-ordes/quitsh/pkg/component/stage"
9+
"github.com/sdsc-ordes/quitsh/pkg/dag"
10+
"github.com/sdsc-ordes/quitsh/pkg/errors"
11+
"github.com/sdsc-ordes/quitsh/pkg/toolchain"
12+
13+
"github.com/spf13/cobra"
14+
)
15+
16+
type (
17+
Option func(*opts)
18+
19+
opts struct {
20+
name string
21+
modify func(cmd *cobra.Command)
22+
}
23+
)
24+
25+
// AddCmdGeneral adds the general `exec-stage` command to execute all targets
26+
// in given stages.
27+
func AddCmdGeneral(
28+
cli cli.ICLI,
29+
parent *cobra.Command,
30+
execArgs *dag.ExecArgs,
31+
) {
32+
var compArgs general.ComponentArgs
33+
cmd := &cobra.Command{
34+
Use: "exec-stage [stage...]",
35+
Short: "Execute all targets in a stage.",
36+
RunE: func(_ *cobra.Command, stages []string) error {
37+
for _, s := range stages {
38+
e := ExecuteStage(cli, &compArgs, stage.Stage(s), execArgs)
39+
if e != nil {
40+
return e
41+
}
42+
}
43+
44+
return nil
45+
},
46+
}
47+
general.AddFlagsExecArgs(cmd, execArgs)
48+
general.AddFlagsComponentArgs(cmd, &compArgs)
49+
50+
parent.AddCommand(cmd)
51+
}
52+
53+
// AddCmdAlias adds a generalized command similar to `AddCmdGeneral` but
54+
// with name either from [WithName] or defaulted to `stage`.
55+
// Its possible to modify the command with [WithModifications].
56+
func AddCmdAlias(
57+
cli cli.ICLI,
58+
parent *cobra.Command,
59+
stage stage.Stage,
60+
execArgs *dag.ExecArgs,
61+
opt ...Option,
62+
) {
63+
var o opts
64+
o.Apply(opt...)
65+
66+
if o.name == "" {
67+
o.name = stage.String()
68+
}
69+
70+
var compArgs general.ComponentArgs
71+
72+
cmd := &cobra.Command{
73+
Use: o.name,
74+
Short: fmt.Sprintf("Execute all targets in stage %v.", stage),
75+
RunE: func(_ *cobra.Command, _args []string) error {
76+
return ExecuteStage(cli, &compArgs, stage, execArgs)
77+
},
78+
}
79+
80+
general.AddFlagsExecArgs(cmd, execArgs)
81+
general.AddFlagsComponentArgs(cmd, &compArgs)
82+
83+
if o.modify != nil {
84+
o.modify(cmd)
85+
}
86+
87+
parent.AddCommand(cmd)
88+
}
89+
90+
// ExecuteStage executes all targets found with `compArgs` which belong to stage `stage`.
91+
func ExecuteStage(
92+
cl cli.ICLI,
93+
compArgs *general.ComponentArgs,
94+
stage stage.Stage,
95+
execArgs *dag.ExecArgs,
96+
) error {
97+
comps, all, rootDir, err := cl.FindComponents(compArgs)
98+
if err != nil {
99+
return err
100+
}
101+
102+
targets, prios, err := dag.DefineExecutionOrder(
103+
all, rootDir,
104+
dag.WithTargetsByStageFromComponents(comps, stage),
105+
)
106+
if err != nil {
107+
return err
108+
} else if len(targets) == 0 {
109+
return errors.New("no targets selected")
110+
}
111+
112+
var dispatcher toolchain.IDispatcher
113+
if !cl.RootArgs().SkipToolchainDispatch {
114+
dispatcher = cl.ToolchainDispatcher()
115+
}
116+
117+
return dag.Execute(
118+
targets,
119+
prios,
120+
cl.RunnerFactory(),
121+
dispatcher,
122+
cl.Config(),
123+
rootDir,
124+
cl.RootArgs().Parallel,
125+
dag.WithTags(execArgs.Tags...),
126+
)
127+
}
128+
129+
// Apply applies all options.
130+
func (c *opts) Apply(options ...Option) {
131+
for _, f := range options {
132+
f(c)
133+
}
134+
}
135+
136+
// WithName sets the command name to use.
137+
func WithName(name string) Option {
138+
return func(o *opts) {
139+
o.name = name
140+
}
141+
}
142+
143+
// WithModifications sets a modification function to change the command.
144+
func WithModifications(mod func(cmd *cobra.Command)) Option {
145+
return func(o *opts) {
146+
o.modify = mod
147+
}
148+
}

pkg/cli/cmd/root/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ type (
6565

6666
// Enable running targets in parallel.
6767
Parallel bool `yaml:"parallel"`
68+
69+
// Error summary of all errors at the end.
70+
ErrorSummary bool `yaml:"errorSummary"`
6871
}
6972

7073
Settings struct {

pkg/cli/general/general.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package general
33
import (
44
"github.com/sdsc-ordes/quitsh/pkg/component"
55
"github.com/sdsc-ordes/quitsh/pkg/component/query"
6+
"github.com/sdsc-ordes/quitsh/pkg/dag"
67
"github.com/sdsc-ordes/quitsh/pkg/errors"
78
fs "github.com/sdsc-ordes/quitsh/pkg/filesystem"
89
"github.com/sdsc-ordes/quitsh/pkg/log"
10+
"github.com/spf13/cobra"
911
)
1012

1113
// ComponentArgs are arguments for the CLI commands.
@@ -19,6 +21,27 @@ type ComponentArgs struct {
1921
ComponentDir string
2022
}
2123

24+
// AddFlagsComponentArgs adds the flags to command `cmd`
25+
// for an instance of [ComponentArgs].
26+
func AddFlagsComponentArgs(cmd *cobra.Command, compArgs *ComponentArgs) {
27+
cmd.Flags().
28+
StringArrayVarP(&compArgs.ComponentPatterns,
29+
"components", "c", nil, "Components matched by these patterns are built.")
30+
cmd.Flags().
31+
StringVar(&compArgs.ComponentDir,
32+
"component-dir", "", "Directory pointing to a component to build, instead of giving them by patterns.")
33+
34+
cmd.MarkFlagsMutuallyExclusive("components", "component-dir")
35+
cmd.MarkFlagsOneRequired("components", "component-dir")
36+
}
37+
38+
// AddFlagsExecArgs adds all `execArgs` arguments to the command.
39+
func AddFlagsExecArgs(cmd *cobra.Command, execArgs *dag.ExecArgs) {
40+
cmd.Flags().StringArrayVar(&execArgs.Tags, "tag", execArgs.Tags,
41+
"The executable tags which will get matched against the "+
42+
"`include.tagExpr` on a step to include/exclude steps.")
43+
}
44+
2245
// FindComponents dispatches to the query function to find all components and
2346
// returns them.
2447
func FindComponents(

0 commit comments

Comments
 (0)