A build says what it does on the bus, as it happens (novox/hq ADR 0157)
The build-machine seat emits `started` and `log.<build id>` beside `built`. Every line the builder speaks — each step, each command with its duration, and on failure the command's own output — goes to stderr as before and onto the bus under the build's id, one subject per build, kept a week in EVENTS with every other event. `builds --log <id>` reads it back from the stream with a consumer that is gone when the reading is done, on the command line and as the controller's seat verb; `builds` lists each build's id and `build` says the id it asked with. Lines are core publishes with a sequence number, so a build is not slowed by an ack per line and a gap is visible; `started` and `built` are awaited into the stream. The seat protocol widens additively at the controller's next start; the holder's grant follows on the broker node's next composition.
This commit is contained in:
@@ -719,6 +719,21 @@ func short(commit string) string {
|
||||
return commit
|
||||
}
|
||||
|
||||
// Said is where the lines Command speaks go, beside the build's own Log: what runs, how long it
|
||||
// took, and that it failed. Nil prints them to stderr, as a build machine with nobody listening
|
||||
// should. The machine sets it per build so every line reaches the bus too (novox/hq ADR 0157) —
|
||||
// the step is "run", and the message is the line as it has always been printed.
|
||||
var Said Log
|
||||
|
||||
func tell(step, format string, args ...any) {
|
||||
message := fmt.Sprintf(format, args...)
|
||||
if Said == nil {
|
||||
fmt.Fprintf(os.Stderr, " %s\n", message)
|
||||
return
|
||||
}
|
||||
Said(step, message)
|
||||
}
|
||||
|
||||
// Command is a Runner that actually runs things.
|
||||
func Command(ctx context.Context, dir, name string, args ...string) (string, error) {
|
||||
// **Every command is echoed before it runs**, with where. On a build that hangs, the last line
|
||||
@@ -726,16 +741,23 @@ func Command(ctx context.Context, dir, name string, args ...string) (string, err
|
||||
// nothing" and "git clone is waiting on a network that will not answer". Silent on success is
|
||||
// what made an empty workspace unreadable.
|
||||
started := timeNow()
|
||||
fmt.Fprintf(os.Stderr, " $ (%s) %s %s\n", short(filepath.Base(dir)), name, strings.Join(args, " "))
|
||||
tell("run", "$ (%s) %s %s", short(filepath.Base(dir)), name, strings.Join(args, " "))
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Dir = dir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, " ! %s %s failed after %s\n", name, args[0], since(started))
|
||||
tell("run", "! %s %s failed after %s", name, args[0], since(started))
|
||||
// The command's own output is part of what a reader needs — the compiler's error, the
|
||||
// clone's refusal — and a line per output line keeps it readable on the bus.
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
if line != "" {
|
||||
tell("output", "%s", line)
|
||||
}
|
||||
}
|
||||
return string(out), fmt.Errorf("%s %s: %w\n%s",
|
||||
name, strings.Join(args, " "), err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, " ✓ %s %s (%s)\n", name, firstArg(args), since(started))
|
||||
tell("run", "✓ %s %s (%s)", name, firstArg(args), since(started))
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user