Skip to content

Commit b032d0d

Browse files
zuzuleinenEquanox
andauthored
bob run: start tui before build (#112)
* run: start the tui without waiting for build results * run: add startup fix * run: execute all builds in a pipeline Co-authored-by: equanox <[email protected]>
1 parent df4ab7e commit b032d0d

File tree

4 files changed

+79
-45
lines changed

4 files changed

+79
-45
lines changed

bob/run.go

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/benchkram/bob/bob/bobfile"
1111
"github.com/benchkram/bob/pkg/boberror"
1212
"github.com/benchkram/bob/pkg/ctl"
13+
"github.com/benchkram/bob/pkg/sliceutil"
1314
)
1415

1516
// Examples of possible interactive usecase
@@ -20,8 +21,7 @@ import (
2021
// 2: [Done] plain docker-compose run with dependcies to build-cmds
2122
// containing instructions how to build the container image.
2223
//
23-
// TODO:
24-
// 3: init script requiring a executable to run before
24+
// 3: [Done] init script requiring a executable to run before
2525
// containing a health endpoint (REST?). So the init script can be
2626
// sure about the service to be functional.
2727
//
@@ -31,30 +31,26 @@ import (
3131
//
3232
// Canceling the cmd from the outside must be done through the context.
3333
//
34-
// TODO: Forbid circular dependecys.
35-
func (b *B) Run(ctx context.Context, runName string) (_ ctl.Commander, err error) {
34+
// FIXME: Forbid circular dependecys.
35+
func (b *B) Run(ctx context.Context, runTaskName string) (_ ctl.Commander, err error) {
3636
defer errz.Recover(&err)
3737

3838
aggregate, err := b.Aggregate()
3939
errz.Fatal(err)
4040

4141
b.PrintVersionCompatibility(aggregate)
4242

43-
runTask, ok := aggregate.RTasks[runName]
43+
runTask, ok := aggregate.RTasks[runTaskName]
4444
if !ok {
4545
return nil, ErrRunDoesNotExist
4646
}
4747

4848
// gather interactive tasks
49-
childInteractiveTasks := b.runTasksInPipeline(runName, aggregate)
49+
childInteractiveTasks := runTasksInPipeline(runTaskName, aggregate)
5050
interactiveTasks := []string{runTask.Name()}
5151
interactiveTasks = append(interactiveTasks, childInteractiveTasks...)
5252

5353
// build dependencies & main runTask
54-
for _, task := range interactiveTasks {
55-
err = executeBuildTasksInPipeline(ctx, task, aggregate, b.nix)
56-
errz.Fatal(err)
57-
}
5854

5955
// generate run controls to steer the run cmd.
6056
runCommands := []ctl.Command{}
@@ -67,7 +63,7 @@ func (b *B) Run(ctx context.Context, runName string) (_ ctl.Commander, err error
6763
runCommands = append(runCommands, command)
6864
}
6965

70-
builder := NewBuilder(b, runName, aggregate, executeBuildTasksInPipeline)
66+
builder := NewBuilder(b, runTaskName, aggregate, executeBuildTasksInPipeline)
7167
commander := ctl.NewCommander(ctx, builder, runCommands...)
7268

7369
return commander, nil
@@ -78,10 +74,10 @@ func (b *B) Run(ctx context.Context, runName string) (_ ctl.Commander, err error
7874
//
7975
// It will not error but return a empty error in case the runName
8076
// does not exists.
81-
func (b *B) runTasksInPipeline(runName string, aggregate *bobfile.Bobfile) []string {
77+
func runTasksInPipeline(runTaskName string, aggregate *bobfile.Bobfile) []string {
8278
runTasks := []string{}
8379

84-
run, ok := aggregate.RTasks[runName]
80+
run, ok := aggregate.RTasks[runTaskName]
8581
if !ok {
8682
return nil
8783
}
@@ -93,7 +89,7 @@ func (b *B) runTasksInPipeline(runName string, aggregate *bobfile.Bobfile) []str
9389
runTasks = append(runTasks, task)
9490

9591
// assure all it's dependent runTasks are also added.
96-
childs := b.runTasksInPipeline(task, aggregate)
92+
childs := runTasksInPipeline(task, aggregate)
9793
runTasks = append(runTasks, childs...)
9894
}
9995

@@ -137,40 +133,49 @@ func isRunTask(name string, aggregate *bobfile.Bobfile) bool {
137133
// return ok
138134
// }
139135

140-
// executeBuildTasksInPipeline takes a run task but only executes the dependent build tasks
141-
func executeBuildTasksInPipeline(ctx context.Context, runname string, aggregate *bobfile.Bobfile, nix *NixBuilder) (err error) {
136+
// executeBuildTasksInPipeline takes a run task and starts the required builds.
137+
func executeBuildTasksInPipeline(
138+
ctx context.Context,
139+
runTaskName string,
140+
aggregate *bobfile.Bobfile,
141+
nix *NixBuilder,
142+
) (err error) {
142143
defer errz.Recover(&err)
143144

144-
interactive, ok := aggregate.RTasks[runname]
145+
_, ok := aggregate.RTasks[runTaskName]
145146
if !ok {
146147
return ErrRunDoesNotExist
147148
}
149+
runTasksInPipeline := runTasksInPipeline(runTaskName, aggregate)
148150

149-
// Gather build tasks
150-
buildTasks := []string{}
151-
for _, child := range interactive.DependsOn {
152-
if isRunTask(child, aggregate) {
153-
continue
154-
}
155-
buildTasks = append(buildTasks, child)
151+
// Gather build tasks from run task dependencies.
152+
// This is required to get the top most build tasks and start a build for each.
153+
// Each run task could have could have distinct build pipeline beneth it.
154+
// This implies that multiple unrelated builds could be started
155+
// on a run invocation.
156+
157+
// umbrella run task
158+
buildTasks, err := gatherBuildTasks(runTaskName, aggregate)
159+
errz.Fatal(err)
160+
// child run tasks
161+
for _, runTaskName := range runTasksInPipeline {
162+
childBuildTasks, err := gatherBuildTasks(runTaskName, aggregate)
163+
errz.Fatal(err)
164+
buildTasks = append(buildTasks, childBuildTasks...)
156165
}
166+
buildTasks = sliceutil.Unique(buildTasks)
157167

158168
// Build nix dependencies
159169
if nix != nil {
160170
fmt.Println("Building nix dependencies...")
161171
err = nix.BuildNixDependencies(aggregate, buildTasks)
162172
errz.Fatal(err)
163-
fmt.Println("Succeded building nix dependencies")
173+
fmt.Println("Succeeded building nix dependencies")
164174
}
165175

166-
// Run dependent build tasks
167-
// before starting the run task
168-
for _, child := range interactive.DependsOn {
169-
if isRunTask(child, aggregate) {
170-
continue
171-
}
172-
173-
playbook, err := aggregate.Playbook(child)
176+
// Initiate each build
177+
for _, buildTask := range buildTasks {
178+
playbook, err := aggregate.Playbook(buildTask)
174179
if err != nil {
175180
if errors.Is(err, boberror.ErrTaskDoesNotExist) {
176181
continue
@@ -184,3 +189,21 @@ func executeBuildTasksInPipeline(ctx context.Context, runname string, aggregate
184189

185190
return nil
186191
}
192+
193+
// gatherBuildTasks returns all direct build tasks in the pipeline of a run task.
194+
func gatherBuildTasks(runTaskName string, aggregate *bobfile.Bobfile) ([]string, error) {
195+
runTask, ok := aggregate.RTasks[runTaskName]
196+
if !ok {
197+
return nil, ErrRunDoesNotExist
198+
}
199+
200+
buildTasks := []string{}
201+
for _, child := range runTask.DependsOn {
202+
if isRunTask(child, aggregate) {
203+
continue
204+
}
205+
buildTasks = append(buildTasks, child)
206+
}
207+
208+
return buildTasks, nil
209+
}

pkg/ctl/commander.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type Builder interface {
4646
Build(context.Context) error
4747
}
4848

49-
// NewCommander creates a commander object which can be started and stoped
49+
// NewCommander creates a commander object which can be started and stopped
5050
// until shutdown is called, then it becomes noop.
5151
//
5252
// The commander allows it to control multiple commands while taking
@@ -55,7 +55,7 @@ type Builder interface {
5555
// TODO: Could be benficial for a TUI to directly control the commands.
5656
// That needs somehow blocking of a starting/stopping of the whole commander
5757
// while a child is doing some work. This is currently not implemented.
58-
// It's possible to control the underlying commands directly through
58+
// It is possible to control the underlying commands directly through
5959
// `Subcommands()` but that could probably lead to nasty start/stop loops.
6060
// ___________ ___________ ___________
6161
// | | Command() | | Command() | |
@@ -106,10 +106,6 @@ func NewCommander(ctx context.Context, builder Builder, ctls ...Command) Command
106106
err := c.Stop()
107107
boblog.Log.Error(err, "Error on stopping comander")
108108

109-
// Trigger a rebuild.
110-
err = c.builder.Build(ctx)
111-
errz.Fatal(err)
112-
113109
err = c.Start()
114110
boblog.Log.Error(err, "Error during comander run")
115111

@@ -138,8 +134,13 @@ func (c *commander) Subcommands() []Command {
138134
}
139135

140136
// Start cmds in inverse order.
141-
// Blocks subsquent calls until the first one is completed.
137+
// Blocks subsequent calls until the first one is completed.
142138
func (c *commander) Start() (err error) {
139+
defer errz.Recover(&err)
140+
141+
err = c.builder.Build(c.ctx)
142+
errz.Fatal(err)
143+
143144
return c.start()
144145
}
145146

tui/model.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,12 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
191191
switch msg := msg.(type) {
192192

193193
case tea.KeyMsg:
194-
//for _, r := range msg.Runes {
194+
// for _, r := range msg.Runes {
195195
// print(fmt.Sprintf("%s\n", strconv.QuoteRuneToASCII(r)))
196-
//}
196+
// }
197197

198198
switch {
199-
//case string(msg.Runes[0]) == "[":
199+
// case string(msg.Runes[0]) == "[":
200200
// errz.Log(fmt.Errorf("%#v", msg.Runes[0]))
201201

202202
case key.Matches(msg, m.keys.NextTab):
@@ -387,9 +387,9 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
387387
m.content, updateCmd = m.content.Update(msg)
388388
cmds = append(cmds, updateCmd)
389389

390-
//if m.error != nil {
390+
// if m.error != nil {
391391
// //cmds = append(cmds, stop(m))
392-
//}
392+
// }
393393

394394
return m, tea.Batch(cmds...)
395395
}

tui/tui.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type TUI struct {
2121
}
2222

2323
func New() (*TUI, error) {
24+
2425
evts := make(chan interface{}, 1024)
2526

2627
stdout := os.Stdout
@@ -35,6 +36,14 @@ func New() (*TUI, error) {
3536
os.Stdout = wout
3637
os.Stderr = wout
3738

39+
// FIXME: this is a hack to indicate a line to read to multiScanner().
40+
// If we don't do this, the scanner assumes theres is nothing to do and shuts down.
41+
// The TUI does not start and the program will exit.
42+
//
43+
// This happens only occasionaly on mid-size projects (i've no idea why).
44+
// Though, it works fine with the standard server-db example.
45+
fmt.Fprint(wout, "\n")
46+
3847
buf, err := multiScanner(0, evts, rout)
3948
if err != nil {
4049
errz.Log(err)
@@ -51,6 +60,7 @@ func New() (*TUI, error) {
5160
}
5261

5362
func (t *TUI) Start(cmder ctl.Commander) {
63+
5464
t.started = true
5565

5666
programEvts := make(chan interface{}, 1)

0 commit comments

Comments
 (0)