fix(ci): rebuild backends when shared build inputs change (#10975)

The backend matrix path filter only matched files under a backend's own
directory, so a change to shared build infrastructure rebuilt nothing at
all: an empty matrix, every job green, and the change reaching no image.

PR #10946 fixed scripts/build/package-gpu-libs.sh shipping a partial
4-of-8 cuDNN library set, which mixed versions with the venv's pip cuDNN
and produced CUDNN_STATUS_SUBLIBRARY_VERSION_MISMATCH at inference time.
It merged 1h48m after the weekly full-matrix cron had already run, so no
backend image ever received the fix and nothing signalled that it had
been un-shipped.

Add a SHARED_BUILD_INPUTS table mapping each shared path to the narrowest
set of matrix entries it can honestly invalidate, plus a generic rule for
backend/Dockerfile.<x> (which each entry already names). A full matrix is
417 Linux + 56 Darwin builds, so package-gpu-libs.sh now rebuilds the 176
Python entries rather than everything. Unclassified files under
scripts/build/ fall back to a full rebuild deliberately: over-building is
recoverable, silently shipping nothing is not.

Extract the filtering logic to scripts/lib/backend-filter.mjs so it can be
unit-tested without bun, js-yaml or a GitHub API round-trip, and run those
tests from the existing lint workflow via `make test-ci-scripts`.


Assisted-by: Claude Code:claude-opus-4-8[1m] [Read] [Edit] [Bash]

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
mudler's LocalAI [bot]
2026-07-20 13:48:12 +02:00
committed by GitHub
parent 465d488c90
commit 6e52d0c2ef
8 changed files with 529 additions and 135 deletions

View File

@@ -2,6 +2,11 @@ import fs from "fs";
import * as yaml from "js-yaml";
import { Octokit } from "@octokit/core";
import {
getAllBackendPaths,
filterMatrix,
} from "./lib/backend-filter.mjs";
// Matrix data lives in a small data-only YAML so both backend.yml (master push)
// and backend_pr.yml (pull_request) can use a dynamic `matrix: ${{ fromJson(...) }}`
// for the live job, while this script remains the single source of truth for
@@ -13,125 +18,7 @@ const includesDarwin = matrixYml.includeDarwin;
const eventPath = process.env.GITHUB_EVENT_PATH;
const event = JSON.parse(fs.readFileSync(eventPath, "utf8"));
// Infer backend path
function inferBackendPath(item) {
if (item.dockerfile.endsWith("python")) {
return `backend/python/${item.backend}/`;
}
// parakeet-cpp is a Go backend (Dockerfile.golang) wrapping the parakeet.cpp
// ggml port via purego. It lives in backend/go/parakeet-cpp/; this explicit
// branch (placed before the generic golang one, which would also resolve it
// correctly) documents the mapping and guards against a future
// dockerfile-suffix change.
if (item.backend === "parakeet-cpp") {
return `backend/go/parakeet-cpp/`;
}
// ced is a Go backend (Dockerfile.golang) wrapping the ced.cpp ggml port via
// purego, living in backend/go/ced/. Same explicit-branch rationale as
// parakeet-cpp above: the generic golang fallthrough would also resolve it,
// but this documents the mapping and guards a future dockerfile-suffix change.
if (item.backend === "ced") {
return `backend/go/ced/`;
}
// moss-transcribe-cpp is a Go backend (Dockerfile.golang) wrapping the
// moss-transcribe.cpp ggml port via purego, living in
// backend/go/moss-transcribe-cpp/. Same explicit-branch rationale as
// parakeet-cpp / ced: the generic golang fallthrough would also resolve it,
// but this documents the mapping and guards a future dockerfile-suffix change.
if (item.backend === "moss-transcribe-cpp") {
return `backend/go/moss-transcribe-cpp/`;
}
// moss-tts-cpp is a Go backend (Dockerfile.golang) wrapping the moss-tts.cpp
// ggml port via purego, living in backend/go/moss-tts-cpp/. Same
// explicit-branch rationale as parakeet-cpp / ced / moss-transcribe-cpp: the
// generic golang fallthrough would also resolve it, but this documents the
// mapping and guards a future dockerfile-suffix change.
if (item.backend === "moss-tts-cpp") {
return `backend/go/moss-tts-cpp/`;
}
if (item.dockerfile.endsWith("golang")) {
return `backend/go/${item.backend}/`;
}
if (item.dockerfile.endsWith("rust")) {
return `backend/rust/${item.backend}/`;
}
if (item.dockerfile.endsWith("ik-llama-cpp")) {
return `backend/cpp/ik-llama-cpp/`;
}
if (item.dockerfile.endsWith("turboquant")) {
// turboquant is a llama.cpp fork that reuses backend/cpp/llama-cpp sources
// via a thin wrapper Makefile. Changes to either dir should retrigger it.
return `backend/cpp/turboquant/`;
}
if (item.dockerfile.endsWith("bonsai")) {
// bonsai is a llama.cpp fork that reuses backend/cpp/llama-cpp sources
// via a thin wrapper Makefile. Changes to either dir should retrigger it.
return `backend/cpp/bonsai/`;
}
if (item.dockerfile.endsWith("privacy-filter")) {
return `backend/cpp/privacy-filter/`;
}
if (item.dockerfile.endsWith("ds4")) {
return `backend/cpp/ds4/`;
}
if (item.dockerfile.endsWith("llama-cpp")) {
return `backend/cpp/llama-cpp/`;
}
return null;
}
function inferBackendPathDarwin(item) {
// llama-cpp on Darwin builds from the C++ sources, not a backend/go/llama-cpp
// tree (which doesn't exist). The Darwin job is matrix-driven with lang=go
// for runner/toolchain selection, but the source path is C++.
if (item.backend === "llama-cpp") {
return `backend/cpp/llama-cpp/`;
}
// ds4 is C++ too (built via `make backends/ds4-darwin`); the matrix entry
// carries lang=go for runner/toolchain selection, but the source is C++.
if (item.backend === "ds4") {
return `backend/cpp/ds4/`;
}
// privacy-filter is C++ too (built via `make backends/privacy-filter-darwin`);
// same lang=go-for-runner convention, source under backend/cpp.
if (item.backend === "privacy-filter") {
return `backend/cpp/privacy-filter/`;
}
if (!item.lang) {
return `backend/python/${item.backend}/`;
}
return `backend/${item.lang}/${item.backend}/`;
}
// Build a deduplicated map of backend name -> path prefix from all matrix entries
function getAllBackendPaths() {
const paths = new Map();
for (const item of includes) {
const p = inferBackendPath(item);
if (p && !paths.has(item.backend)) {
paths.set(item.backend, p);
}
}
for (const item of includesDarwin) {
const p = inferBackendPathDarwin(item);
if (p && !paths.has(item.backend)) {
paths.set(item.backend, p);
}
}
return paths;
}
const allBackendPaths = getAllBackendPaths();
function backendChanged(backend, pathPrefix, changedFiles) {
if (changedFiles.some(file => file.startsWith(pathPrefix))) return true;
// Fork backends reuse backend/cpp/llama-cpp sources via thin wrappers;
// changes to either directory must retrigger their pipelines.
return (backend === "turboquant" || backend === "bonsai") &&
changedFiles.some(file => file.startsWith("backend/cpp/llama-cpp/"));
}
const allBackendPaths = getAllBackendPaths(includes, includesDarwin);
const token = process.env.GITHUB_TOKEN;
const octokit = new Octokit({ auth: token });
@@ -316,15 +203,10 @@ function emitFullMatrix() {
function emitFilteredMatrix(changedFiles) {
console.log("Changed files:", changedFiles);
const filtered = includes.filter(item => {
const backendPath = inferBackendPath(item);
if (!backendPath) return false;
return backendChanged(item.backend, backendPath, changedFiles);
});
const filteredDarwin = includesDarwin.filter(item => {
const backendPath = inferBackendPathDarwin(item);
return changedFiles.some(file => file.startsWith(backendPath));
const { filtered, filteredDarwin, changedBackends } = filterMatrix({
includes,
includesDarwin,
changedFiles,
});
console.log("Filtered files:", filtered);
@@ -350,8 +232,8 @@ function emitFilteredMatrix(changedFiles) {
emitSinglearchShards(singlearch);
// Per-backend boolean outputs
for (const [backend, pathPrefix] of allBackendPaths) {
const changed = backendChanged(backend, pathPrefix, changedFiles);
for (const backend of allBackendPaths.keys()) {
const changed = changedBackends.has(backend);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${backend}=${changed ? 'true' : 'false'}\n`);
}
}

View File

@@ -0,0 +1,287 @@
// Pure matrix-filtering logic for scripts/changed-backends.js.
//
// Split out of that script so it can be unit-tested: changed-backends.js reads
// GITHUB_EVENT_PATH and talks to the GitHub API at import time, and pulls in
// js-yaml + @octokit/core, none of which a test of "which backends does this
// diff invalidate?" should need. Everything here is dependency-free and
// side-effect-free.
// Infer backend path
export function inferBackendPath(item) {
if (item.dockerfile.endsWith("python")) {
return `backend/python/${item.backend}/`;
}
// parakeet-cpp is a Go backend (Dockerfile.golang) wrapping the parakeet.cpp
// ggml port via purego. It lives in backend/go/parakeet-cpp/; this explicit
// branch (placed before the generic golang one, which would also resolve it
// correctly) documents the mapping and guards against a future
// dockerfile-suffix change.
if (item.backend === "parakeet-cpp") {
return `backend/go/parakeet-cpp/`;
}
// ced is a Go backend (Dockerfile.golang) wrapping the ced.cpp ggml port via
// purego, living in backend/go/ced/. Same explicit-branch rationale as
// parakeet-cpp above: the generic golang fallthrough would also resolve it,
// but this documents the mapping and guards a future dockerfile-suffix change.
if (item.backend === "ced") {
return `backend/go/ced/`;
}
// moss-transcribe-cpp is a Go backend (Dockerfile.golang) wrapping the
// moss-transcribe.cpp ggml port via purego, living in
// backend/go/moss-transcribe-cpp/. Same explicit-branch rationale as
// parakeet-cpp / ced: the generic golang fallthrough would also resolve it,
// but this documents the mapping and guards a future dockerfile-suffix change.
if (item.backend === "moss-transcribe-cpp") {
return `backend/go/moss-transcribe-cpp/`;
}
// moss-tts-cpp is a Go backend (Dockerfile.golang) wrapping the moss-tts.cpp
// ggml port via purego, living in backend/go/moss-tts-cpp/. Same
// explicit-branch rationale as parakeet-cpp / ced / moss-transcribe-cpp: the
// generic golang fallthrough would also resolve it, but this documents the
// mapping and guards a future dockerfile-suffix change.
if (item.backend === "moss-tts-cpp") {
return `backend/go/moss-tts-cpp/`;
}
if (item.dockerfile.endsWith("golang")) {
return `backend/go/${item.backend}/`;
}
if (item.dockerfile.endsWith("rust")) {
return `backend/rust/${item.backend}/`;
}
if (item.dockerfile.endsWith("ik-llama-cpp")) {
return `backend/cpp/ik-llama-cpp/`;
}
if (item.dockerfile.endsWith("turboquant")) {
// turboquant is a llama.cpp fork that reuses backend/cpp/llama-cpp sources
// via a thin wrapper Makefile. Changes to either dir should retrigger it.
return `backend/cpp/turboquant/`;
}
if (item.dockerfile.endsWith("bonsai")) {
// bonsai is a llama.cpp fork that reuses backend/cpp/llama-cpp sources
// via a thin wrapper Makefile. Changes to either dir should retrigger it.
return `backend/cpp/bonsai/`;
}
if (item.dockerfile.endsWith("privacy-filter")) {
return `backend/cpp/privacy-filter/`;
}
if (item.dockerfile.endsWith("ds4")) {
return `backend/cpp/ds4/`;
}
if (item.dockerfile.endsWith("llama-cpp")) {
return `backend/cpp/llama-cpp/`;
}
return null;
}
export function inferBackendPathDarwin(item) {
// llama-cpp on Darwin builds from the C++ sources, not a backend/go/llama-cpp
// tree (which doesn't exist). The Darwin job is matrix-driven with lang=go
// for runner/toolchain selection, but the source path is C++.
if (item.backend === "llama-cpp") {
return `backend/cpp/llama-cpp/`;
}
// ds4 is C++ too (built via `make backends/ds4-darwin`); the matrix entry
// carries lang=go for runner/toolchain selection, but the source is C++.
if (item.backend === "ds4") {
return `backend/cpp/ds4/`;
}
// privacy-filter is C++ too (built via `make backends/privacy-filter-darwin`);
// same lang=go-for-runner convention, source under backend/cpp.
if (item.backend === "privacy-filter") {
return `backend/cpp/privacy-filter/`;
}
if (!item.lang) {
return `backend/python/${item.backend}/`;
}
return `backend/${item.lang}/${item.backend}/`;
}
// Build a deduplicated map of backend name -> path prefix from all matrix entries
export function getAllBackendPaths(includes, includesDarwin) {
const paths = new Map();
for (const item of includes) {
const p = inferBackendPath(item);
if (p && !paths.has(item.backend)) {
paths.set(item.backend, p);
}
}
for (const item of includesDarwin) {
const p = inferBackendPathDarwin(item);
if (p && !paths.has(item.backend)) {
paths.set(item.backend, p);
}
}
return paths;
}
export function backendChanged(backend, pathPrefix, changedFiles) {
if (changedFiles.some(file => file.startsWith(pathPrefix))) return true;
// Fork backends reuse backend/cpp/llama-cpp sources via thin wrappers;
// changes to either directory must retrigger their pipelines.
return (backend === "turboquant" || backend === "bonsai") &&
changedFiles.some(file => file.startsWith("backend/cpp/llama-cpp/"));
}
// Darwin entries carry `lang` only for runner/toolchain selection; an entry
// without it is a Python backend (see .github/backend-matrix.yml).
const isDarwinPython = item => !item.lang;
// backend_build_darwin.yml routes llama-cpp, ds4 and privacy-filter to their
// own bespoke make targets; every other lang=go entry goes through
// `make build-darwin-go-backend` -> scripts/build/golang-darwin.sh.
const DARWIN_BESPOKE_BUILDERS = new Set(["llama-cpp", "ds4", "privacy-filter"]);
const isDarwinGenericGo = item =>
!!item.lang && !DARWIN_BESPOKE_BUILDERS.has(item.backend);
const isLinuxPython = item => item.dockerfile.endsWith("python");
const never = () => false;
const always = () => true;
// Shared build inputs: files that end up in, or decide the contents of, images
// belonging to backends whose own directory they do not live under. The
// per-backend prefix match in filterMatrix() structurally cannot see these, so
// before this table a change to any of them rebuilt *nothing at all*.
//
// That is not hypothetical. PR #10946 fixed scripts/build/package-gpu-libs.sh
// shipping a partial 4-of-8 cuDNN library set (which mixed versions with the
// venv's pip cuDNN and produced CUDNN_STATUS_SUBLIBRARY_VERSION_MISMATCH at
// inference time). It merged 1h48m after the weekly full-matrix cron had
// already run, so no backend image ever received the fix and nothing signalled
// that it had been un-shipped.
//
// Each rule names the narrowest set of matrix entries it can honestly
// invalidate, because a full backend matrix is a large CI spend. Rules are
// first-match-wins per changed file, so specific entries must precede the
// scripts/build/ catch-all at the bottom.
export const SHARED_BUILD_INPUTS = [
{
// Every language consumes backend.proto: Dockerfile.python COPYs it, Go
// backends regenerate their stubs from it via `make protogen-go`, the C++
// CMakeLists compile backend.pb.cc from it, and the Rust crate Makefile
// copies it in before build.rs runs.
matches: file => file === "backend/backend.proto",
linux: always,
darwin: always,
},
{
// COPY'd into every Python image (Dockerfile.python) and into every Darwin
// Python build (scripts/build/python-darwin.sh).
matches: file => file.startsWith("backend/python/common/"),
linux: isLinuxPython,
darwin: isDarwinPython,
},
{
// The reusable build workflows own build-args, packaging and push for
// their whole OS family, so a change there can alter what lands in every
// image that family builds.
matches: file => file === ".github/workflows/backend_build.yml",
linux: always,
darwin: never,
},
{
matches: file => file === ".github/workflows/backend_build_darwin.yml",
linux: never,
darwin: always,
},
{
// Stages the CUDA/ROCm runtime libraries into every Python image's lib/.
// COPY'd and run by Dockerfile.python only. This is the #10946 case.
matches: file => file === "scripts/build/package-gpu-libs.sh",
linux: isLinuxPython,
darwin: never,
},
{
matches: file => file === "scripts/build/python-darwin.sh",
linux: never,
darwin: isDarwinPython,
},
{
matches: file => file === "scripts/build/golang-darwin.sh",
linux: never,
darwin: isDarwinGenericGo,
},
{
matches: file => file === "scripts/build/llama-cpp-darwin.sh",
linux: never,
darwin: item => item.backend === "llama-cpp",
},
{
matches: file => file === "scripts/build/ds4-darwin.sh",
linux: never,
darwin: item => item.backend === "ds4",
},
{
matches: file => file === "scripts/build/privacy-filter-darwin.sh",
linux: never,
darwin: item => item.backend === "privacy-filter",
},
{
// Catch-all, deliberately last and deliberately broad: anything else under
// scripts/build/ is an unclassified image-packaging input. Rebuild
// everything until someone narrows it with a rule above. Erring toward an
// expensive full matrix beats the failure mode that hid #10946 — a new
// shared script silently shipping to nothing. `*_test.sh` are the tests
// for those scripts and never reach an image.
matches: file =>
file.startsWith("scripts/build/") && !file.endsWith("_test.sh"),
linux: always,
darwin: always,
},
];
// The matrix stores dockerfiles as "./backend/Dockerfile.python"; changed-file
// lists are repo-relative.
function dockerfilePath(item) {
return (item.dockerfile || "").replace(/^\.\//, "");
}
// A backend/Dockerfile.<x> edit invalidates exactly the Linux entries built
// from it. These files sit outside every backend directory, so the prefix match
// never sees them — same blind spot as SHARED_BUILD_INPUTS, but expressible
// precisely because each entry already names its dockerfile.
function dockerfileChanged(item, changedFiles) {
const df = dockerfilePath(item);
return df !== "" && changedFiles.includes(df);
}
function matchedSharedRules(changedFiles) {
const rules = [];
for (const file of changedFiles) {
const rule = SHARED_BUILD_INPUTS.find(r => r.matches(file));
if (rule && !rules.includes(rule)) rules.push(rule);
}
return rules;
}
// Filter both matrices against a changed-file list. Returns the surviving
// entries plus the set of backend names considered changed, which drives the
// per-backend boolean outputs consumed by test-extra.yml.
export function filterMatrix({ includes, includesDarwin, changedFiles }) {
const sharedRules = matchedSharedRules(changedFiles);
const filtered = includes.filter(item => {
const backendPath = inferBackendPath(item);
if (!backendPath) return false;
if (backendChanged(item.backend, backendPath, changedFiles)) return true;
if (dockerfileChanged(item, changedFiles)) return true;
return sharedRules.some(rule => rule.linux(item));
});
const filteredDarwin = includesDarwin.filter(item => {
const backendPath = inferBackendPathDarwin(item);
if (changedFiles.some(file => file.startsWith(backendPath))) return true;
return sharedRules.some(rule => rule.darwin(item));
});
const changedBackends = new Set();
for (const item of filtered) changedBackends.add(item.backend);
for (const item of filteredDarwin) changedBackends.add(item.backend);
for (const [backend, pathPrefix] of getAllBackendPaths(includes, includesDarwin)) {
if (backendChanged(backend, pathPrefix, changedFiles)) changedBackends.add(backend);
}
return { filtered, filteredDarwin, changedBackends };
}

View File

@@ -0,0 +1,189 @@
// Unit tests for the backend matrix path filter (scripts/lib/backend-filter.mjs).
//
// Run with `make test-ci-scripts` (plain `node --test`, no dependencies), which
// is what .github/workflows/lint.yml runs. The production entrypoint
// scripts/changed-backends.js needs bun + js-yaml + @octokit/core; the logic
// under test here needs none of that.
import test from "node:test";
import assert from "node:assert/strict";
import { filterMatrix } from "./backend-filter.mjs";
// A representative slice of .github/backend-matrix.yml: one entry per build
// path that the filter treats differently. Kept inline rather than parsed from
// the real YAML so these tests stay dependency-free and stable when backends
// are added or removed.
const includes = [
{
backend: "diffusers",
dockerfile: "./backend/Dockerfile.python",
"tag-suffix": "-nvidia-cuda-13-diffusers",
},
{
backend: "vllm",
dockerfile: "./backend/Dockerfile.python",
"tag-suffix": "-nvidia-cuda-13-vllm",
},
{
backend: "ced",
dockerfile: "./backend/Dockerfile.golang",
"tag-suffix": "-ced",
},
{
backend: "kokoros",
dockerfile: "./backend/Dockerfile.rust",
"tag-suffix": "-kokoros",
},
{
backend: "llama-cpp",
dockerfile: "./backend/Dockerfile.llama-cpp",
"tag-suffix": "-llama-cpp",
},
{
backend: "turboquant",
dockerfile: "./backend/Dockerfile.turboquant",
"tag-suffix": "-turboquant",
},
];
const includesDarwin = [
{ backend: "diffusers", "tag-suffix": "-metal-darwin-arm64-diffusers" },
{ backend: "mlx", "tag-suffix": "-metal-darwin-arm64-mlx" },
{ backend: "whisper", lang: "go", "tag-suffix": "-metal-darwin-arm64-whisper" },
{ backend: "llama-cpp", lang: "go", "tag-suffix": "-metal-darwin-arm64-llama-cpp" },
{ backend: "ds4", lang: "go", "tag-suffix": "-metal-darwin-arm64-ds4" },
];
const run = changedFiles =>
filterMatrix({ includes, includesDarwin, changedFiles });
const names = entries => entries.map(e => e.backend).sort();
test("a change to only package-gpu-libs.sh rebuilds every Python image", () => {
// The PR #10946 regression: this script is COPY'd and run by
// Dockerfile.python for every Python backend, but lives under scripts/, so
// the per-backend prefix match produced an empty matrix and the cuDNN
// packaging fix shipped to nothing.
const { filtered, filteredDarwin, changedBackends } = run([
"scripts/build/package-gpu-libs.sh",
]);
assert.notEqual(filtered.length, 0, "expected a non-empty Linux matrix");
assert.deepEqual(names(filtered), ["diffusers", "vllm"]);
assert.ok(changedBackends.has("vllm"));
// Darwin Python builds never invoke it (see scripts/build/python-darwin.sh).
assert.deepEqual(filteredDarwin, []);
});
test("docs- and README-only changes never produce a matrix", () => {
const { filtered, filteredDarwin, changedBackends } = run([
"README.md",
"docs/content/docs/overview.md",
]);
assert.deepEqual(filtered, []);
assert.deepEqual(filteredDarwin, []);
assert.equal(changedBackends.size, 0);
});
test("a backend's own directory still triggers only that backend", () => {
const { filtered, changedBackends } = run(["backend/python/vllm/backend.py"]);
assert.deepEqual(names(filtered), ["vllm"]);
assert.deepEqual([...changedBackends], ["vllm"]);
});
test("Dockerfile.python rebuilds Python images and nothing else", () => {
const { filtered, filteredDarwin } = run(["backend/Dockerfile.python"]);
assert.deepEqual(names(filtered), ["diffusers", "vllm"]);
// Darwin doesn't build from any Dockerfile.
assert.deepEqual(filteredDarwin, []);
});
test("Dockerfile.golang rebuilds Go images and nothing else", () => {
const { filtered } = run(["backend/Dockerfile.golang"]);
assert.deepEqual(names(filtered), ["ced"]);
});
test("a per-family Dockerfile rebuilds only its own family", () => {
const { filtered } = run(["backend/Dockerfile.llama-cpp"]);
assert.deepEqual(names(filtered), ["llama-cpp"]);
});
test("backend.proto rebuilds every backend on every OS", () => {
const { filtered, filteredDarwin } = run(["backend/backend.proto"]);
assert.equal(filtered.length, includes.length);
assert.equal(filteredDarwin.length, includesDarwin.length);
});
test("backend/python/common rebuilds Python images on Linux and Darwin", () => {
const { filtered, filteredDarwin } = run([
"backend/python/common/libbackend.sh",
]);
assert.deepEqual(names(filtered), ["diffusers", "vllm"]);
assert.deepEqual(names(filteredDarwin), ["diffusers", "mlx"]);
});
test("the reusable Linux build workflow rebuilds Linux only", () => {
const { filtered, filteredDarwin } = run([
".github/workflows/backend_build.yml",
]);
assert.equal(filtered.length, includes.length);
assert.deepEqual(filteredDarwin, []);
});
test("the reusable Darwin build workflow rebuilds Darwin only", () => {
const { filtered, filteredDarwin } = run([
".github/workflows/backend_build_darwin.yml",
]);
assert.deepEqual(filtered, []);
assert.equal(filteredDarwin.length, includesDarwin.length);
});
test("golang-darwin.sh skips backends with bespoke Darwin build scripts", () => {
const { filtered, filteredDarwin } = run([
"scripts/build/golang-darwin.sh",
]);
assert.deepEqual(filtered, []);
assert.deepEqual(names(filteredDarwin), ["whisper"]);
});
test("a bespoke Darwin build script rebuilds only its own backend", () => {
const { filteredDarwin } = run(["scripts/build/ds4-darwin.sh"]);
assert.deepEqual(names(filteredDarwin), ["ds4"]);
});
test("an unclassified scripts/build/ file conservatively rebuilds everything", () => {
const { filtered, filteredDarwin } = run([
"scripts/build/package-something-new.sh",
]);
assert.equal(filtered.length, includes.length);
assert.equal(filteredDarwin.length, includesDarwin.length);
});
test("tests for the packaging scripts do not rebuild anything", () => {
const { filtered, filteredDarwin } = run([
"scripts/build/package-gpu-libs_test.sh",
]);
assert.deepEqual(filtered, []);
assert.deepEqual(filteredDarwin, []);
});
test("turboquant still retriggers on llama-cpp source changes", () => {
const { filtered } = run(["backend/cpp/llama-cpp/grpc-server.cpp"]);
assert.deepEqual(names(filtered), ["llama-cpp", "turboquant"]);
});