The operator's desktop slows while it builds (shanks at 92%, the Go build runtime at 1650% of 24 processors). The build modules declare cpu-cap (default shanks=50,g14=50,ace=75), which the composer turns into the runtime's quota at idle priority; each step's go, node and buildah commands get GOMAXPROCS and UV_THREADPOOL_SIZE for their part of it, and the status tools show the limits and what the kernel holds the runtime to.
111 lines
4.1 KiB
Go
111 lines
4.1 KiB
Go
package main
|
|
|
|
// The run steps shut off (novox/hq ADR 0448; issue 1309, lead decision L2 of R4e): the type-check, the package's test
|
|
// script, tsc and esbuild run the change's code, so each runs through mesh-sdk's buildstep.Isolated, in bubblewrap: no
|
|
// network, none of the account (its home, its ssh key, its registry secret, its bus login, the runtime directory), the
|
|
// workspace read-write, the toolchain read-only, the shared npm cache read-only beneath a throwaway writable layer, or
|
|
// read-write for a trunk build alone (publish). The fetch step (git, npm ci of the module's own packages) holds the key
|
|
// and the network and runs none of the change's code, so it runs as before.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
|
)
|
|
|
|
// BubblewrapLeast is the oldest bubblewrap a run step is shut off with: --overlay-src and --tmp-overlay, which keep the
|
|
// shared cache unchanged by a candidate, came in 0.10.
|
|
const BubblewrapLeast = "0.10"
|
|
|
|
// npmCache is the shared npm cache a package's test script reads.
|
|
func (m *Module) npmCache() string { return filepath.Join(m.Toolchain.Paths.Cache, "npm") }
|
|
|
|
// isolation is how a run step in the workspace root is shut off.
|
|
func (m *Module) isolation(root string, write bool) buildstep.Isolation {
|
|
return buildstep.Isolation{
|
|
Workspace: root,
|
|
ReadOnly: []string{m.toolchainDir()},
|
|
Caches: []string{m.npmCache()},
|
|
WriteCaches: write,
|
|
Env: append([]string{"PATH=" + pathOrDefault(), "LC_ALL=C"}, m.threadEnv()...),
|
|
}
|
|
}
|
|
|
|
// stepRunner is the runner of a run step in the workspace root: buildstep.Isolated over m.Run, every command shut off;
|
|
// write is a trunk build's alone.
|
|
func (m *Module) stepRunner(root string, write bool) (forgekey.Runner, error) {
|
|
// bubblewrap binds only a directory that is there.
|
|
if err := os.MkdirAll(m.npmCache(), 0o700); err != nil {
|
|
return nil, fmt.Errorf("the shared cache %s: %w", m.npmCache(), err)
|
|
}
|
|
return buildstep.Isolated(m.Run, m.isolation(root, write)), nil
|
|
}
|
|
|
|
// ProbeBubblewrap reads the installed bubblewrap's version (`bwrap --version`: "bubblewrap 0.13.0").
|
|
func ProbeBubblewrap(ctx context.Context) (string, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
|
defer cancel()
|
|
r := forgekey.ExecRunnerClean(0)(ctx, []string{"PATH=" + defaultPath}, buildstep.Bubblewrap, "--version")
|
|
if r.Status != 0 || r.Err != "" {
|
|
return "", fmt.Errorf("`%s --version` failed: %s", buildstep.Bubblewrap, firstLine(r.Err+"\n"+r.Stderr))
|
|
}
|
|
return parseBubblewrapVersion(r.Stdout)
|
|
}
|
|
|
|
var bubblewrapVersion = regexp.MustCompile(`^bubblewrap (\d+)\.(\d+)(\.\d+)?\s*$`)
|
|
|
|
func parseBubblewrapVersion(out string) (string, error) {
|
|
f := bubblewrapVersion.FindStringSubmatch(strings.TrimSpace(out))
|
|
if f == nil {
|
|
return "", fmt.Errorf("`%s --version` answered %q", buildstep.Bubblewrap, strings.TrimSpace(out))
|
|
}
|
|
return f[1] + "." + f[2] + f[3], nil
|
|
}
|
|
|
|
// bubblewrapProblem says why the installed bubblewrap cannot shut a run step off, or "".
|
|
func bubblewrapProblem(version string, err error) string {
|
|
if err != nil {
|
|
return "bubblewrap cannot be run, and every build and test step runs in it (ADR 0448): " + err.Error()
|
|
}
|
|
if !atLeast(version, BubblewrapLeast) {
|
|
return fmt.Sprintf("bubblewrap %s installed, and a build or test step needs %s or later (ADR 0448)", version, BubblewrapLeast)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// atLeast says whether the dotted version v is least or later.
|
|
func atLeast(v, least string) bool {
|
|
a, b := strings.Split(v, "."), strings.Split(least, ".")
|
|
for i := range b {
|
|
var x int
|
|
if i < len(a) {
|
|
x, _ = strconv.Atoi(a[i])
|
|
}
|
|
y, _ := strconv.Atoi(b[i])
|
|
if x != y {
|
|
return x > y
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// checkBubblewrap refuses a step when bubblewrap cannot shut it off; nil Bubblewrap probes nothing.
|
|
func (m *Module) checkBubblewrap(ctx context.Context) error {
|
|
if m.Bubblewrap == nil {
|
|
return nil
|
|
}
|
|
v, err := m.Bubblewrap(ctx)
|
|
if why := bubblewrapProblem(v, err); why != "" {
|
|
return &buildstep.Refusal{Text: why, Want: BubblewrapLeast + " or later", Have: v}
|
|
}
|
|
return nil
|
|
}
|