diff --git a/utils/binary-build.ts b/utils/binary-build.ts index 57c2736..c23c40b 100644 --- a/utils/binary-build.ts +++ b/utils/binary-build.ts @@ -1,4 +1,4 @@ -import { basename, dirname } from "jsr:@std/path@^1.0.0"; +import { basename, dirname, resolve } from "jsr:@std/path@^1.0.0"; import type { BinaryBuildSpec } from "./types.ts"; import { DECKER_ROOT } from "./root.ts"; @@ -17,7 +17,8 @@ function outputPath(spec: BinaryBuildSpec): string { } function relDir(spec: BinaryBuildSpec): string { - return `${repoBasename(spec.repo)}-${slug(spec.ref)}`; + if (spec.path) return `local-${slug(resolve(spec.path))}`; + return `${repoBasename(spec.repo!)}-${slug(spec.ref!)}`; } function repoBasename(repo: string): string { @@ -56,7 +57,19 @@ async function run(cmd: string[], opts: { cwd?: string } = {}): Promise { if (proc.code !== 0) throw new Error(`${cmd.join(" ")} exited with code ${proc.code}`); } -async function ensureClone(spec: BinaryBuildSpec): Promise { +// The directory `spec.cmd` runs in: an existing working tree for a path spec, +// otherwise a clone pinned to `spec.ref`. +async function ensureSource(spec: BinaryBuildSpec): Promise { + if (spec.path) { + const dir = resolve(spec.path); + if (!(await dirExists(dir))) { + throw new Error(`binary build source ${dir} does not exist`); + } + return dir; + } + if (!spec.repo || !spec.ref) { + throw new Error("binary build needs either `path` or both `repo` and `ref`"); + } const cloneDir = `${SRC_CACHE}/${repoBasename(spec.repo)}`; await Deno.mkdir(SRC_CACHE, { recursive: true }); if (!(await dirExists(`${cloneDir}/.git`))) { @@ -72,9 +85,9 @@ async function ensureClone(spec: BinaryBuildSpec): Promise { } async function buildOne(spec: BinaryBuildSpec): Promise { - const cloneDir = await ensureClone(spec); - await run(["sh", "-c", spec.cmd], { cwd: cloneDir }); - const artifact = `${cloneDir}/${spec.artifact}`; + const srcDir = await ensureSource(spec); + await run(["sh", "-c", spec.cmd], { cwd: srcDir }); + const artifact = `${srcDir}/${spec.artifact}`; if (!(await exists(artifact))) { throw new Error(`build for ${basename(spec.artifact)} ran but ${spec.artifact} is missing`); } @@ -89,7 +102,9 @@ export async function ensureBinaries( ): Promise { const built: string[] = []; for (const spec of specs.values()) { - if (await exists(outputPath(spec))) continue; + // A pinned ref builds the same bytes every time, so a cached artifact is + // final. A working tree does not, so it is rebuilt on every up. + if (!spec.path && (await exists(outputPath(spec)))) continue; await buildOne(spec); built.push(outputPath(spec)); } diff --git a/utils/emit.ts b/utils/emit.ts index 8250080..5ffa0b0 100644 --- a/utils/emit.ts +++ b/utils/emit.ts @@ -86,6 +86,7 @@ export async function emit( if (existing) { if ( existing.repo !== spec.repo || existing.ref !== spec.ref || + existing.path !== spec.path || existing.cmd !== spec.cmd || existing.artifact !== spec.artifact ) { throw new Error(`binary ${path} produced by conflicting BinaryBuildSpec`); diff --git a/utils/types.ts b/utils/types.ts index 9ff0da2..82cb631 100644 --- a/utils/types.ts +++ b/utils/types.ts @@ -40,12 +40,22 @@ export type ImageBuildSpec = { cmd: string; }; -// A host binary built from a git source, the process-side analogue of -// ImageBuildSpec. `cmd` runs in the clone root; `artifact` is the built -// binary's path within the clone (e.g. "target/release/reth-rbuilder"). +// A host binary built from source, the process-side analogue of ImageBuildSpec. +// `cmd` runs in the source root; `artifact` is the built binary's path within it +// (e.g. "target/release/reth-rbuilder"). +// +// Two source forms: +// • `repo` + `ref` — cloned and pinned under the decker cache. Built once and +// reused, since a pinned ref cannot change. +// • `path` — an existing working tree, built in place. Used when the thing +// being tested is the uncommitted state of a repo, which a pinned ref cannot +// name. Always rebuilt: the tree changes between runs and there is no ref to +// key a cache on. Cheap in practice — the build tool's own incremental cache +// does the real work. export type BinaryBuildSpec = { - repo: string; - ref: string; + repo?: string; + ref?: string; + path?: string; cmd: string; artifact: string; };