Skip to content

ci: narrow the language matrix to the languages a PR can affect - #649

Draft
malaverdiere wants to merge 4 commits into
mainfrom
marc-andre/lang-579-matrix-changed-languages
Draft

ci: narrow the language matrix to the languages a PR can affect#649
malaverdiere wants to merge 4 commits into
mainfrom
marc-andre/lang-579-matrix-changed-languages

Conversation

@malaverdiere

Copy link
Copy Markdown
Contributor

Implements the review suggestion on #646: "Is there a way to limit the matrix to only run a language if it is changed?"

A typical grammar bump touches one grammar dir, so this takes the matrix from 47 jobs to 1–3.

Why the naive version is unsafe

Grammars read each other, so "changed dir → that language" silently under-tests:

a change here must also test
tree-sitter-c cpp, solidity
tree-sitter-c-sharp c-sharp-pro
tree-sitter-xml html (no semgrep-xml exists)
tree-sitter-hacklang hack (no semgrep-hacklang exists)

lang/scripts/languages-for-paths therefore walks the reference graph transitively, reading edges out of the grammars themselves (tree-sitter-<x> tokens in grammar.js / prep) so the graph cannot drift from the grammars it describes.

Fail-safe in every direction

  • Any changed path outside the grammar dirs (core/, lang/Makefile, lang/scripts/, .github/, …) selects every language.
  • An empty or unreadable diff selects every language.
  • push: main and workflow_dispatch always select every language, so main is never partially verified.
  • Over-selection is tolerated (a tree-sitter-rust mention in a comment costs one extra job); under-selection is what the graph walk exists to prevent.

This PR's own diff touches .github/ and lang/scripts/, so it selects all 47 — visible in the Enumerate languages job log.

Branch protection

The required Test languages check accepts a skipped matrix only when the selection was genuinely empty ([]). A matrix skipped for any other reason still fails the check, so it cannot silently pass.

Tests

lang/test_languages_for_paths.py (6 cases) covers the cross-grammar edges, the no-language-of-its-own dirs, shared paths, mixed diffs, and the empty-diff fallback. Wired into make test-python.

@malaverdiere
malaverdiere requested a review from a team as a code owner July 30, 2026 16:23
@malaverdiere
malaverdiere requested review from rhwlo and removed request for a team July 30, 2026 16:23
@malaverdiere
malaverdiere force-pushed the marc-andre/lang-579-ci-matrix-setup-cost branch from 656eba5 to fc5fdff Compare July 30, 2026 17:12
@malaverdiere
malaverdiere force-pushed the marc-andre/lang-579-matrix-changed-languages branch from 344a687 to 39de6d8 Compare July 30, 2026 17:13
@malaverdiere
malaverdiere changed the base branch from marc-andre/lang-579-ci-matrix-setup-cost to marc-andre/lang-579-goal-5-make-the-ci-tests-pass July 30, 2026 17:38
@malaverdiere
malaverdiere force-pushed the marc-andre/lang-579-matrix-changed-languages branch from 39de6d8 to 36bc636 Compare July 30, 2026 17:39
@malaverdiere
malaverdiere force-pushed the marc-andre/lang-579-matrix-changed-languages branch from 36bc636 to dbaae2c Compare July 30, 2026 18:33
@malaverdiere
malaverdiere marked this pull request as draft July 30, 2026 18:34
Base automatically changed from marc-andre/lang-579-goal-5-make-the-ci-tests-pass to main July 30, 2026 19:49
malaverdiere and others added 3 commits July 30, 2026 14:06
Review suggestion on #646: only run a language if it changed. A typical grammar
bump touches one grammar dir, so this takes the matrix from 47 jobs to 1-3.

The naive version of this is unsafe, because grammars read each other. Bumping
tree-sitter-c must also test cpp *and* solidity; tree-sitter-c-sharp must also
test c-sharp-pro; tree-sitter-xml and tree-sitter-hacklang have no language of
their own and are only reachable through html and hack. lang/scripts/
languages-for-paths therefore walks the reference graph transitively, reading
the edges out of the grammars themselves ("tree-sitter-<x>" tokens in grammar.js
and prep) so the graph cannot drift from the grammars it describes.

Fail-safe in every direction:
- any changed path outside the grammar dirs (core/, lang/Makefile, lang/scripts,
  .github/, ...) selects every language;
- an empty or unreadable diff selects every language;
- push to main and workflow_dispatch always select every language, so main is
  never partially verified;
- over-selection is tolerated (a "tree-sitter-rust" mention in a comment costs
  one extra job), under-selection is what the graph walk exists to prevent.

The required "Test languages" check now accepts a skipped matrix only when the
selection was genuinely empty, so a matrix that is skipped for any other reason
cannot silently pass branch protection.

Co-Authored-By: Claude <noreply@anthropic.com>
'github.event_name == '"'"'pull_request'"'"' && 0 || 1' evaluates to 1: 0 is falsy in a
GitHub Actions expression, so the ternary falls through to the fallback. The
enumerate job therefore had a depth-1 clone and could not resolve the base:

  fatal: Invalid symmetric difference expression 656eba5...HEAD

Use fetch-depth: 0 unconditionally, and treat an unusable base as "select
everything" rather than a failure.

The required check behaved correctly meanwhile: enumerate failing left the
matrix skipped with an empty selection, which the gate rejected instead of
passing.

Co-Authored-By: Claude <noreply@anthropic.com>
CI caught the exact under-selection this design exists to prevent. The
reference graph is read out of the upstream grammars, so with submodules
uninitialised the edges simply are not there:

  assert {'c', 'cpp'} >= {'c', 'cpp', 'solidity'}

tree-sitter-solidity is what records that solidity reads tree-sitter-c, so
without it checked out a tree-sitter-c bump would silently skip solidity.

- languages-for-paths now refuses to narrow when any tree-sitter-* dir has no
  content, and says which one. Dotfiles do not count as content: a deinitialised
  submodule can retain a .git file and would otherwise look populated.
- The enumerate job checks the grammars out so narrowing actually applies. Done
  by hand rather than via checkout's submodules: input, because gosu and
  requirements use SSH URLs that need the HTTPS rewrite first.
- Tests now assert both modes: the graph edges when submodules are present, and
  the fall-back-to-everything behaviour when they are not.

Co-Authored-By: Claude <noreply@anthropic.com>
@malaverdiere
malaverdiere force-pushed the marc-andre/lang-579-matrix-changed-languages branch from dbaae2c to ea5f10c Compare July 30, 2026 20:06
Scan only live .js/prep refs (not comments), resolve prep's
tree-sitter-"$name", and alias go-mod/gomod so every wrapper stays
reachable. Fail closed when a grammar-dir diff resolves to no languages,
and lock the JS→TS / C→cpp invariants in tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant