mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-07 21:43:39 -04:00
fix(ci): only rebuild the full backend matrix on breaking backend.proto edits (#11192)
backend/backend.proto is consumed by every language, so its SHARED_BUILD_INPUTS rule could only ever be always/always: 417 Linux plus 56 Darwin builds. It fires on ~1.3% of commits (10 of 767 over six months), which made it the single largest CI cost driver in the repo. On 2026-07-29 the queue reached 2178 jobs against 8 concurrent runners. Four runs totalling 935 of those jobs were triggered by nothing but a proto edit. The largest, 378 jobs on master, came from PR #11158, whose entire proto diff was six lines adding `bool cache_prompt = 8;` to one message. No backend that does not read that field behaves any differently for it. Make the rule content-aware. changed-backends.js resolves backend.proto at the base revision (the contents-API pattern already used for backend-matrix.yml) and hands both texts to protoChangeIsAdditive(), which compares them structurally so a comment reflow, reindent or field reorder does not read as a change. An additive-only edit (new field with an unused number, new message, new enum value, new RPC) suppresses the rule and rebuilds nothing; a removed, renumbered, retyped or renamed field, a dropped RPC or a changed option still rebuilds everything, as does an unresolvable base revision. Every other matched rule is untouched, so a PR that edits the proto and scripts/build/ is still a full rebuild, and the weekly full-matrix cron remains the backstop for stale wheels. Verified against all ten proto commits of the preceding six months: the nine with a resolvable parent all classify as additive, and controls covering a retyped-and-renumbered field, a deleted RPC, identical revisions and a reindent-plus-comment-reflow all classify correctly. Assisted-by: Claude:opus-5 [claude-code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
1 parent
89ee62b2af
commit
8089b2bf09
4 files changed
+339
-5
No files matched your search
@@ -6,6 +6,7 @@ import {
|
||||
getAllBackendPaths,
|
||||
filterMatrix,
|
||||
BACKEND_MATRIX_FILE,
|
||||
BACKEND_PROTO_FILE,
|
||||
} from "./lib/backend-filter.mjs";
|
||||
|
||||
// Matrix data lives in a small data-only YAML so both backend.yml (master push)
|
||||
@@ -116,6 +117,42 @@ async function getPreviousMatrix(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// backend.proto at the base revision plus the checked-out copy, so filterMatrix
|
||||
// can tell an additive edit (a new field, message or RPC, which invalidates no
|
||||
// existing image) from a breaking one. Returning null means "rebuild
|
||||
// everything", the same posture as an unresolvable matrix diff.
|
||||
//
|
||||
// Only called when the changed-file list names the proto, so the common path
|
||||
// costs no extra API request.
|
||||
async function getProtoRevisions(event) {
|
||||
const ref = event.pull_request ? event.pull_request.base.sha : event.before;
|
||||
if (!ref || /^0+$/.test(ref)) return null;
|
||||
const owner = event.repository.owner.login;
|
||||
const repo = event.repository.name;
|
||||
try {
|
||||
const res = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
|
||||
owner,
|
||||
repo,
|
||||
path: BACKEND_PROTO_FILE,
|
||||
ref,
|
||||
mediaType: { format: 'raw' },
|
||||
});
|
||||
const previous = typeof res.data === 'string'
|
||||
? res.data
|
||||
: Buffer.from(res.data.content, 'base64').toString('utf8');
|
||||
return {
|
||||
previous,
|
||||
current: fs.readFileSync(BACKEND_PROTO_FILE, "utf8"),
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`could not read ${BACKEND_PROTO_FILE} at ${ref}, falling back to run-all:`,
|
||||
err.message
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Group matrix entries by tag-suffix and emit a merge-matrix entry per group.
|
||||
// Both multi-leg groups (per-arch fan-out) and singletons get one entry each:
|
||||
// the build job pushes by digest only with no tags applied, so every backend
|
||||
@@ -240,7 +277,7 @@ function emitFullMatrix() {
|
||||
}
|
||||
}
|
||||
|
||||
function emitFilteredMatrix(changedFiles, previousMatrix) {
|
||||
function emitFilteredMatrix(changedFiles, previousMatrix, protoRevisions) {
|
||||
console.log("Changed files:", changedFiles);
|
||||
|
||||
const { filtered, filteredDarwin, changedBackends } = filterMatrix({
|
||||
@@ -248,6 +285,7 @@ function emitFilteredMatrix(changedFiles, previousMatrix) {
|
||||
includesDarwin,
|
||||
changedFiles,
|
||||
previousMatrix,
|
||||
protoRevisions,
|
||||
});
|
||||
|
||||
console.log("Filtered files:", filtered);
|
||||
@@ -306,5 +344,9 @@ function emitFilteredMatrix(changedFiles, previousMatrix) {
|
||||
? await getPreviousMatrix(event)
|
||||
: null;
|
||||
|
||||
emitFilteredMatrix(changedFiles, previousMatrix);
|
||||
const protoRevisions = changedFiles.includes(BACKEND_PROTO_FILE)
|
||||
? await getProtoRevisions(event)
|
||||
: null;
|
||||
|
||||
emitFilteredMatrix(changedFiles, previousMatrix, protoRevisions);
|
||||
})();
|
||||
Reference in new issue
Block a user