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 }