Pregenerate: add a concept of tasks that can be waited upon.

Not yet 100% sure if all tasks should be made waitable or not - we're
only gonna need it on the Perlasm tasks (and there only for the Linux
ones).

The goal is to use this for symbol prefixing the asm symbols: one task
depends on all the Perlasm outputs, and will then read those as well as
any non-Perlasm asm files, and generate the list of asm symbols for
prefixing. By making this a task, it can run in parallel with other
things, including the upcoming Clang AST based public symbol extraction.

Bug: 42220000
Change-Id: I8ce81394822f38dd3b3114ba99626f616a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/87027
Reviewed-by: Xiangfei Ding <xfding@google.com>
Commit-Queue: Rudolf Polzer <rpolzer@google.com>
diff --git a/util/pregenerate/build.go b/util/pregenerate/build.go
index 743b84b..6bae080 100644
--- a/util/pregenerate/build.go
+++ b/util/pregenerate/build.go
@@ -105,7 +105,7 @@
 		}
 		dst = path.Join("gen", name, dst+fileSuffix)
 		args = append(slices.Clone(args), p.Args...)
-		addTask(list, &PerlasmTask{Src: p.Src, Dst: dst, Args: args})
+		addTask(list, WrapWaitable(&PerlasmTask{Src: p.Src, Dst: dst, Args: args}))
 	}
 
 	for _, p := range in.PerlasmAarch64 {
diff --git a/util/pregenerate/task.go b/util/pregenerate/task.go
index 4d170a0..3a565e4 100644
--- a/util/pregenerate/task.go
+++ b/util/pregenerate/task.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"fmt"
 	"os"
 	"os/exec"
 	"path"
@@ -33,14 +34,54 @@
 	Run() ([]byte, error)
 }
 
+type WaitableTask interface {
+	Task
+
+	// Wait waits for the task to finish, and returns its status.
+	Wait() error
+}
+
+// WaitableTaskImpl is an implementation of a waitable task.
+type WaitableTaskImpl struct {
+	Task
+	FinishedC chan struct{}
+	Err       error
+}
+
+func WrapWaitable(t Task) WaitableTask {
+	return &WaitableTaskImpl{Task: t, FinishedC: make(chan struct{})}
+}
+
+// Run performs the task, taking care of infrastructure so it can be waited on.
+func (t *WaitableTaskImpl) Run() (out []byte, err error) {
+	defer func() {
+		if p := recover(); p != nil {
+			err = fmt.Errorf("panic caught: %v", p)
+		}
+		t.Err = err
+		close(t.FinishedC)
+	}()
+	return t.Task.Run()
+}
+
+// Wait waits for the task to finish, and returns its status.
+func (t *WaitableTaskImpl) Wait() error {
+	<-t.FinishedC
+	return t.Err
+}
+
 type SimpleTask struct {
 	Dst     string
 	RunFunc func() ([]byte, error)
 }
 
-func (t *SimpleTask) Destination() string  { return t.Dst }
+// Destination returns where this task will write to.
+func (t *SimpleTask) Destination() string { return t.Dst }
+
+// Run performs the task.
 func (t *SimpleTask) Run() ([]byte, error) { return t.RunFunc() }
 
+// NewSimpleTask creates a new task based on a lambda for what it does.
 func NewSimpleTask(dst string, runFunc func() ([]byte, error)) *SimpleTask {
 	return &SimpleTask{Dst: dst, RunFunc: runFunc}
 }