CI integration
Recipes for wiring bca into a build pipeline. The
bca check command already ships every output
shape a modern CI needs (Checkstyle, SARIF, GitLab Code Climate JSON,
clang/GCC warning lines, MSVC warning lines), plus
bca report markdown
for humans. This page is a consolidated map from the user's goal to
the right combination of subcommand, flags, and platform glue.
Picking outputs
The matrix below maps each common goal to the bca invocation that
feeds the corresponding CI surface. Linked sections below have the
runnable example.
| Goal | Command + flags |
|---|---|
| Hard gate on threshold regressions | bca check (thresholds from the auto-discovered bca.toml) |
| Ratchet thresholds on an existing codebase | bca check --baseline .bca-baseline.toml (‡) |
| Inline PR annotations (GitHub) | bca check … --report-format clang-warning --no-fail + GCC problem matcher |
| Code Scanning alerts (GitHub) | bca check … --report-format sarif --no-fail + github/codeql-action/upload-sarif |
| Merge-request widget (GitLab Code Quality) | bca check … --report-format code-climate --no-fail |
| Jenkins / SonarQube ingestion | bca check … --report-format checkstyle |
| Human-readable PR/MR comment or downloadable | bca report -O markdown --top 20 --strip-prefix "$PWD/" |
| Machine-readable artifact for dashboards | bca metrics --format json --output-dir ./out |
(‡) Recommended adoption path when introducing thresholds on a codebase with existing offenders. See the Baselines recipe for the bootstrap-refresh-retire workflow.
The full reference for bca check's output formats, exit codes
(0 clean, 2 violation, 1 tool error), and threshold config lives
in the Check command page. For the Markdown
report shape, see the Report command page and
the Quality reports recipe.
GitHub Actions
Live worked example
big-code-analysis runs the recipes below against its own source on
every push and PR. The workflow source —
.github/workflows/pages.yml —
exercises the threshold gate, the baseline ratchet, both report
formats, and a SARIF upload to GitHub Code Scanning end-to-end against
the workspace itself. (The SARIF upload runs on same-repo pushes and
PRs only; fork PRs skip it because the upload needs a write-scoped
token, exactly as the clippy SARIF job does.) The output sits on
GitHub Pages alongside this book:
- HTML hotspot report: https://dekobon.github.io/big-code-analysis/reports/index.html
- Markdown PR/MR comment: https://dekobon.github.io/big-code-analysis/reports/report.md
Copy snippets below straight into your own workflow; the bca version
quoted is the latest published release at the time of writing.
The in-tree workflow installs
bcaby building it from the current checkout rather than downloading a pinned release — this avoids the CLI-artifact schema-skew failure mode described under Installingbcafrom a GitHub Release below for repos whose.bca-baseline.tomlis always written by the samebcathat gates it. Downstream adopters tracking a stable release line should stick with the pinned-tarball pattern; only switch to "build from checkout" if you, too, are mutating CLI artifact schemas in lockstep with the binary.
Threshold gate, SARIF, and clang-warning matcher
The three pre-existing recipes — hard threshold gate, SARIF upload to
Code Scanning, and clang-warning + GCC problem matcher for inline PR
annotations — live in the
Check command page.
Use the link rather than re-implementing them here.
Installing bca from a GitHub Release (recommended)
The fastest, most reproducible install path is the prebuilt tarball
from this repository's GitHub Releases.
It is a single curl | sha256sum | tar, requires no Rust toolchain,
and produces byte-identical binaries across runs. Pair it with
actions/cache keyed by version
so a green-path rerun skips the download entirely:
CLI-artifact schema compatibility. The
BCA_VERSIONyou pin here must support the schema version of every CLI artifact your repo commits — most importantly.bca-baseline.toml(carries its ownversionfield) and thebca.tomlmanifest. A baseline file written by a newerbca(carrying a newer schema version) is not loadable by an olderbcaand the gate will fail withbaseline version N is not supported by this bca. When trackingmainor regenerating baselines locally with a newerbca, either re-pin to a release that covers the new schema or switch to acargo install --gitbuild ofbcapointed at the same commit your baseline was written from (see thecargo installalternative below). The compatibility contract is recorded in STABILITY.md.
env:
BCA_VERSION: "2.0.0"
BCA_TARGET: "x86_64-unknown-linux-gnu"
# sha256 of big-code-analysis-${BCA_VERSION}-${BCA_TARGET}.tar.gz from the
# release's SHA256SUMS file. Bump together with BCA_VERSION.
BCA_SHA256: "a205fff13108d0f8c679a062e352ba8468109c4adfdd8c9e3567cf5fcc99c3d5"
steps:
# Cache key MUST include BCA_SHA256 (and BCA_TARGET). Without the
# sha256 in the key, rotating the published checksum without bumping
# the version returns a stale binary on cache hit and silently
# bypasses the `sha256sum --check` in the install step (which is
# gated on cache miss). Including BCA_TARGET matters when the same
# workflow runs against multiple `runs-on`.
- name: Cache bca binary
id: bca-cache
uses: actions/cache@v5
with:
path: ~/.local/bin/bca
key: bca-${{ runner.os }}-${{ env.BCA_TARGET }}-${{ env.BCA_VERSION }}-${{ env.BCA_SHA256 }}
- name: Install bca from GitHub Releases
if: steps.bca-cache.outputs.cache-hit != 'true'
run: |
set -euo pipefail
stage="big-code-analysis-${BCA_VERSION}-${BCA_TARGET}"
tarball="${stage}.tar.gz"
url="https://github.com/dekobon/big-code-analysis/releases/download/v${BCA_VERSION}/${tarball}"
mkdir -p "$HOME/.local/bin"
curl -fsSL --proto '=https' --tlsv1.2 -o "/tmp/${tarball}" "$url"
echo "${BCA_SHA256} /tmp/${tarball}" | sha256sum --check --strict -
tar -xzf "/tmp/${tarball}" -C /tmp
install -m 0755 "/tmp/${stage}/bca" "$HOME/.local/bin/bca"
rm -rf "/tmp/${tarball}" "/tmp/${stage}"
- name: Prepend ~/.local/bin to PATH
run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
Available BCA_TARGET values (pick the one that matches runs-on):
x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl,
aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl,
aarch64-apple-darwin, x86_64-pc-windows-msvc,
aarch64-pc-windows-msvc. Windows assets use .zip instead of
.tar.gz; the bca-web binary ships alongside bca in the same
archive.
Alternative: cargo install via prebuilt-aware actions
When you cannot reach github.com from a runner (air-gapped, custom
mirror) but can reach crates.io, the following two actions fall back
transparently to cargo install when no prebuilt is published — at
the cost of compile time on the cold path. Both pin to the same
crates.io release as the GitHub Releases assets, so the
CLI-artifact schema compatibility
warning applies here unchanged.
If you specifically need a bca ahead of the latest crates.io
release (e.g., your .bca-baseline.toml is committed at a newer
schema than any published bca understands), swap the
tool: big-code-analysis-cli@<version> or --version form for
cargo install --git https://github.com/dekobon/big-code-analysis --rev <SHA> --locked big-code-analysis-cli against the exact commit
the baseline was generated from. This is what the in-tree
pages.yml
workflow does (against the local checkout via --path) — it is a
deliberate workaround for bca's own repo, not a recommended
default for downstream adopters.
# Option 1: taiki-e/install-action
- name: Install bca
uses: taiki-e/install-action@v2
with:
tool: big-code-analysis-cli@2.0.0
# Option 2: cargo-binstall
- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@main
- name: Install bca
run: cargo binstall --no-confirm big-code-analysis-cli --version 2.0.0
If either action falls back to compilation, cache the cargo registry + the installed binary so the second run is fast:
- name: Cache cargo registry and bca binary
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/bca
# crates.io publishes immutable releases, so a `<version>` key is
# sufficient here — there is no sha256 to rotate. (The GitHub
# Releases install path above is different: republished release
# assets share a version, so its cache key must include the sha256.)
key: bca-${{ runner.os }}-2.0.0
Pin to a specific version (matching a published
big-code-analysis-cli release on crates.io) so reports stay
reproducible across runs. A floating install surfaces
metric-counting changes as "mysterious CI flakes" on Mondays.
Posting the Markdown report as a PR comment
bca report markdown is purpose-built for PR/MR comments: a stable
header structure, one row per hot spot, and short paths once you pass
--strip-prefix. Pair it with
marocchino/sticky-pull-request-comment
so each push updates a single comment instead of stacking new ones:
name: bca-pr-report
on:
pull_request:
branches: [main]
jobs:
report:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Install bca
uses: taiki-e/install-action@v2
with:
tool: big-code-analysis-cli@2.0.0
- name: Generate report
run: |
bca \
report -O markdown \
--paths "$PWD" \
--top 20 \
--strip-prefix "$PWD/" \
--output report.md
- name: Post or update PR comment
uses: marocchino/sticky-pull-request-comment@v2
with:
path: report.md
header: bca-quality-report
The same Markdown file is suitable for upload as a build artifact
(actions/upload-artifact@v7) if you want it downloadable from the
workflow run page in addition to the PR comment.
Baseline / ratchet pattern
bca check --baseline is the native ratchet: record today's offenders
in a committed TOML file, fail only on regressions and new offenders,
and shrink the file over time. Bootstrap once, commit, then point CI
at it:
# Once, on a developer machine. Commit both files.
bca check --paths src/ \
--write-baseline .bca-baseline.toml
git add bca.toml .bca-baseline.toml
This snippet bootstraps from src/ only — appropriate for a
single-crate library. For a multi-crate workspace, see the
live worked example: its .github/workflows/pages.yml
scans the entire repo with --exclude-from .bcaignore, a checked-in
deny-set covering vendored grammars, generated trees, and tests.
Share the exclude list across workflow, recipe, and bootstrap. Put the deny-set in a single file at the repo root (a
.bcaignoreby convention, mirroring.gitignore/.dockerignore) and point everybcainvocation at it with--exclude-from .bcaignore. Patterns from--exclude-fromare unioned with any inline--exclude <GLOB>flags into one deny-set — keep--excludefor one-off ad-hoc excludes. Blank lines and#-prefixed comment lines in the file are skipped. Patterns follow the same./-prefix convention as--excludearguments (the walker's emitted form). Pair edits to.bcaignorewith a--write-baselinerefresh — the baseline keys are sensitive to which files the walker visits.
- name: Threshold check with baseline
run: |
bca check --paths src/ \
--baseline .bca-baseline.toml
A regressed function (current value > baseline value) still fails.
A new offender not in the baseline still fails. An improved function
passes silently and stays in the baseline until the next
--write-baseline refresh.
Each surviving violation in the stderr stream is prefixed with a tag so a developer can tell at a glance whether they are looking at a brand-new offender or a known one that has worsened:
[new]— no baseline entry for this function / metric.[regr +N%]— current value exceeds the recorded baseline byNpercent. Special forms:[regr from 0]when the baseline value was zero,[regr +>9999%]when the regression exceeds 100× the baseline,[regr NaN]when the current value is NaN.
After the per-violation lines the stderr stream emits a per-file
rollup footer with the format <path>: <count> violations (worst: <metric> = <value> vs limit <limit> at L<start>), sorted by
violation count descending. This is intended to be the first thing a
reader looks at: which file has the most problems, and which metric
is the loudest in that file. Pass --no-summary to suppress the
footer for downstream tooling that grep-pipes the stderr stream.
Actionable failure output
The four sub-sections below turn bca check's failure output from
"a wall of offender lines" into a stack of CI-aware presentations:
which files in this PR tripped a threshold (--since /
--changed-only), inline file-diff annotations
(--github-annotations), a rendered step-summary digest
($GITHUB_STEP_SUMMARY), and a copy-paste-safe remediation block.
Each is independent; mix and match per CI surface. A combined worked
example is at the end of this group.
Diff-aware mode (--since / --changed-only)
On a PR or push, the developer's first question is usually which of my files in this change tripped a threshold — not the whole-tree offender list. Two flags answer that:
--since <ref>partitions the per-file footer into a "Files in this range:" section (offenders in files touched between<ref>andHEAD) followed by "Other offenders:" (everything else). Per-violation lines are unchanged so existing grep-anchored tooling keeps working.--changed-onlydrops violations from files outside the touched set entirely. Use it for PR gates that should be terse.
- uses: actions/checkout@v4
with:
# `--since origin/<base>` resolves a merge-base. The default
# `fetch-depth: 1` checkout makes that ref unreachable; `0`
# pulls the full history so the diff base resolves.
fetch-depth: 0
- name: Threshold check with diff-aware footer
run: |
bca check --paths . --exclude-from .bcaignore \
--baseline .bca-baseline.toml \
--since "origin/${{ github.base_ref }}"
When --since is omitted, bca auto-detects the diff base from the
environment in this precedence:
BCA_DIFF_BASE— the explicit-override hatch. Use this from a local shell or non-GHA CI runner to mimic the auto-detection.GITHUB_BASE_REF— set by GitHub Actions onpull_requestevents. Expanded toorigin/<value>; the runner is responsible for the correspondinggit fetch.GITHUB_EVENT_BEFORE— set by GitHub Actions onpushevents to the SHA at HEAD before the push. The all-zeroes SHA (force push, brand-new branch) is treated as no signal.
Auto-detection failing — git missing, ref unresolvable, not a git
checkout — is non-fatal without --changed-only: bca prints a
warning and falls back to today's whole-tree footer. With
--changed-only, the same failure is fatal so a misconfigured CI
does not silently green-light by suppressing every violation.
The "Files in this range:" banner names the resolved base and the signal that produced it, so a CI-log reader can verify the gate latched onto the expected ref:
Files in this range (diff base: origin/main via GITHUB_BASE_REF):
./src/a.rs: 1 violation (worst: cyclomatic = 11 vs limit 2 at L1)
Other offenders:
./src/b.rs: 1 violation (worst: cyclomatic = 11 vs limit 2 at L1)
This is distinct from bca diff-baseline, which
diffs baseline files between two on-disk paths and reports
added / removed / worsened / improved entries. --since diffs
source files between two git refs.
GitHub Actions inline annotations (--github-annotations)
The GHA UI renders ::error file=…,line=…,title=…::msg workflow
commands as inline annotations on the file-diff view — much more
discoverable than scrolling the raw job log. bca check emits one
per violation per the tri-state --github-annotations <auto|always|never>: auto (the default) enables when
$GITHUB_ACTIONS == "true" (set by every GHA workflow step),
always forces them on, never suppresses them even inside a step
(handy when a workflow runs bca check twice and wants annotations
from only one run). A bare --github-annotations means always.
The annotations ride on top of the existing per-violation human
stream — both are emitted. To avoid exhausting GitHub's
10-error-per-step UI quota, annotations are capped at 10 per metric;
overflow rolls up to a single ::error::N more <metric> violations not shown line so the count stays visible.
- name: Threshold check with inline annotations
run: |
bca check --paths . --exclude-from .bcaignore \
--baseline .bca-baseline.toml
# No `--github-annotations` flag needed — auto-enabled in GHA.
Pair this with --since (above) so the annotations point at the
files in the PR, not the entire offender list.
Step-summary markdown digest ($GITHUB_STEP_SUMMARY)
GitHub Actions exposes $GITHUB_STEP_SUMMARY — a path to a markdown
file that, when populated, renders as the step's summary view in the
job UI. bca check appends a digest containing the per-file rollup
table, a per-metric count breakdown, and the top-10 offenders by
value / limit ratio whenever that env var is set, or when
--summary-file <path> is passed explicitly. --summary-file never
suppresses the digest even when $GITHUB_STEP_SUMMARY is set.
The digest is bracketed by HTML-comment markers (<!-- bca-step-summary-begin -->
/ <!-- bca-step-summary-end -->) so a retried step replaces (not
stacks) the previous block — three retries converge to exactly one
up-to-date digest. Content outside the markers (e.g. summaries
written by other tools earlier in the same step) is preserved.
- name: Threshold check with step-summary digest
run: |
bca check --paths . --exclude-from .bcaignore \
--baseline .bca-baseline.toml
# No flag needed — `$GITHUB_STEP_SUMMARY` is set automatically in GHA.
Local users can pipe the digest into any markdown file with
--summary-file <path>. Empty input (clean run) still writes a
"✓ No threshold violations." block so the step summary positively
confirms the gate ran.
Remediation footer (always on)
When the gate finds violations, bca check emits a trailing
--- next steps --- block on stderr (and inside the step-summary
digest from above):
--- next steps ---
* Detailed reports: bca-reports artifact at https://github.com/<owner>/<repo>/actions/runs/<run-id>
* To refresh baseline: bca check --paths . --exclude-from .bcaignore --write-baseline .bca-baseline.toml
* Adoption guide: https://dekobon.github.io/big-code-analysis/recipes/baselines.html
The refresh invocation mirrors the gate's resolved --paths,
--exclude, --exclude-from, --config, and --baseline so a
first-time reader of a failing CI log can copy-paste it verbatim.
The artifact URL is derived from $GITHUB_REPOSITORY and
$GITHUB_RUN_ID when both are present (always true in GHA); local
runs — where there is no upload to point at — instead suggest
running bca report to see the detailed view locally.
Suppress the block with --no-remediation for downstream tooling
that grep-pipes stderr.
Refresh after focused refactors:
bca check --paths src/ \
--write-baseline .bca-baseline.toml
git diff .bca-baseline.toml # expect a shrinking file
Two --write-baseline runs over an unchanged tree produce
byte-identical output, so spurious diffs only appear when offenders
actually changed. See the Baselines recipe for the
full adoption flow, PR-review heuristics, and the suppression
composition rules.
Putting it all together
The four flags above compose. For a PR-gate workflow, the recommended invocation is:
- uses: actions/checkout@v4
with:
# `--since origin/<base>` resolves a merge-base. Default
# `fetch-depth: 1` makes that ref unreachable; `0` pulls the
# full history so the diff resolves.
fetch-depth: 0
- name: Threshold gate (diff-aware + GHA UX)
run: |
bca check --paths . --exclude-from .bcaignore \
--baseline .bca-baseline.toml \
--since "origin/${{ github.base_ref }}"
# No `--github-annotations` or `--summary-file` flag needed:
# both auto-enable from `$GITHUB_ACTIONS == "true"` and
# `$GITHUB_STEP_SUMMARY`. The trailing remediation block is also
# auto-emitted.
What this gives you on a failing PR:
- Per-violation stderr lines — same shape as the legacy gate, so existing grep tooling keeps working.
- Per-file rollup footer with
Files in this range:(touched in the PR) listed beforeOther offenders:— the developer sees their own contributions first. - Inline GHA annotations on the file-diff view, capped at 10 per metric with an overflow rollup.
- Step-summary panel with a rendered markdown digest (per-file rollup, per-metric breakdown, top-10 offenders by ratio).
- Trailing remediation block naming the artifact, printing the
exact
--write-baselinerefresh invocation, and linking to the Baselines recipe.
Knobs:
| Flag | Effect | Default |
|---|---|---|
--since <ref> | Partition footer; auto-detect from env if omitted | Off, auto-detect via BCA_DIFF_BASE / GITHUB_BASE_REF / GITHUB_EVENT_BEFORE |
--changed-only | Drop violations outside the diff entirely | Off |
--github-annotations <auto|always|never> | Emit ::error file=…::msg workflow commands (bare flag = always) | auto enables when $GITHUB_ACTIONS == "true"; never opts out |
--summary-file <path|auto|never> | Append markdown digest; never opts out | auto detects $GITHUB_STEP_SUMMARY |
--no-remediation | Suppress the trailing --- next steps --- block | Block emitted on failure |
Local users running bca check outside GHA see no change in
default behaviour: none of the four features auto-enable without
an env signal. To preview the GHA experience locally:
GITHUB_ACTIONS=true GITHUB_STEP_SUMMARY=/tmp/bca-summary.md \
BCA_DIFF_BASE=main \
bca check --paths . --exclude-from .bcaignore \
--baseline .bca-baseline.toml
cat /tmp/bca-summary.md
For a non-GHA CI (GitLab, Buildkite, Jenkins), set the env vars your runner exposes (or pass the flags explicitly) and the same output paths fire.
Offender-count delta against merge base (stopgap)
For teams who cannot commit a baseline file (e.g. policy reasons), a
coarser approximation counts <error> elements in two Checkstyle
documents — one on the merge base, one on the PR head — and fails
when the count grows:
- name: Compute offender deltas vs. merge base
run: |
set -euo pipefail
BASE="$(git merge-base origin/main HEAD)"
git worktree add /tmp/base "$BASE"
bca check --paths /tmp/base \
--report-format checkstyle \
--output /tmp/base.xml \
--no-fail
BASE_COUNT=$(grep -c "<error" /tmp/base.xml || true)
bca check --paths "$PWD" \
--report-format checkstyle \
--output /tmp/head.xml \
--no-fail
HEAD_COUNT=$(grep -c "<error" /tmp/head.xml || true)
echo "Offenders: base=$BASE_COUNT head=$HEAD_COUNT"
if [ "$HEAD_COUNT" -gt "$BASE_COUNT" ]; then
echo "::error::Offender count grew from $BASE_COUNT to $HEAD_COUNT"
exit 1
fi
This counts violations, not their identity: renaming an offender does not register as a regression, and improving one offender while regressing another nets to zero. The native baseline flow above is strictly more precise and is the recommended approach.
Self-scan threshold gate (local mirror of the CI gate)
CI's threshold gate fires only after push, which is too late if a
refactor silently nudged a metric past its limit. The
big-code-analysis repo's
Makefile
exposes four targets that mirror the CI gate (the
Threshold gate step in .github/workflows/pages.yml)
locally and add a second tier at 95% of every limit so encroachment
is caught a commit or two before the hard gate trips:
make self-scan # hard gate, 100% of bca.toml thresholds
make self-scan-headroom # soft gate, default 95% (BCA_HEADROOM)
make self-scan-write-baseline # refresh baseline at hard thresholds
make self-scan-write-baseline-headroom # refresh baseline at soft thresholds
Path selection, the .bcaignore deny-set, the per-function
thresholds, the cyclomatic ? policy, and the baseline file all
live in the repo-root bca.toml manifest, which bca discovers
automatically. The hard tier is exactly what CI runs; expanded, it
is a bare check (no path / threshold / baseline flags — the manifest
supplies them):
cargo run --quiet --release -p big-code-analysis-cli -- check
Both tiers consume the same bca.toml thresholds and the same
.bca-baseline.toml; the soft tier just runs the hard recipe
with every threshold value multiplied by BCA_HEADROOM. Both
exit 0 clean, 2 on any threshold violation, 1 on tool
error — the soft tier is a real gate, not advisory, so do not
wrap make self-scan-headroom in || true. The two gate targets
(self-scan, self-scan-headroom) are wired into make pre-commit, make ci, and .pre-commit-config.yaml; those chains
run the hard tier before the soft tier, so a true regression always
reports before near-limit headroom. The two write-baseline
targets are side-effecting and deliberately not wired in.
BCA_HEADROOM=0.90 make self-scan-headroom widens the band;
BCA_HEADROOM=0.99 tightens it to the last 1%. When the soft
tier fires, absorb the offender into the baseline with
make self-scan-write-baseline-headroom (which records every
offender at the scaled thresholds — strictly a superset of the
hard-tier offenders).
The pattern (hard tier mirroring CI + soft tier as early-warning
band, both ratcheted by the same baseline) is project-agnostic —
the Local threshold gates recipe documents the
underlying principles, drop-in Makefile / just / package.json
skeletons, and the helper script that scales thresholds, so you
can adopt the same workflow in your own repo. The generic recipe
uses the same BCA_* env-var names as the Makefile above, so
overrides like BCA_HEADROOM=0.90 work identically across both.
GitLab CI
Full .gitlab-ci.yml example
The job below installs bca, runs the threshold check producing
Code Climate JSON (for the MR Code Quality widget), Checkstyle XML,
and a Markdown report, then uploads them as artifacts.
The same CLI-artifact schema-compatibility note from the GitHub Actions section applies here — the
BCA_VERSIONpin must cover the schema version of every CLI artifact you commit.
stages:
- quality
variables:
BCA_VERSION: "2.0.0" # pin a published big-code-analysis-cli release
BCA_TARGET: "x86_64-unknown-linux-gnu"
# sha256 of big-code-analysis-${BCA_VERSION}-${BCA_TARGET}.tar.gz from
# the release's SHA256SUMS file. Bump together with BCA_VERSION.
BCA_SHA256: "a205fff13108d0f8c679a062e352ba8468109c4adfdd8c9e3567cf5fcc99c3d5"
bca-quality:
stage: quality
image: debian:stable-slim
cache:
# Same key shape as the GitHub Actions snippet — bumping
# BCA_VERSION invalidates the cache automatically.
key: "bca-$BCA_VERSION"
paths:
- .cache/bca/
before_script:
- apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl tar
- |
set -euo pipefail
install -d "$CI_PROJECT_DIR/.cache/bca" "$HOME/.local/bin"
if [ ! -x "$CI_PROJECT_DIR/.cache/bca/bca" ]; then
stage="big-code-analysis-${BCA_VERSION}-${BCA_TARGET}"
tarball="${stage}.tar.gz"
url="https://github.com/dekobon/big-code-analysis/releases/download/v${BCA_VERSION}/${tarball}"
curl -fsSL --proto '=https' --tlsv1.2 -o "/tmp/${tarball}" "$url"
echo "${BCA_SHA256} /tmp/${tarball}" | sha256sum --check --strict -
tar -xzf "/tmp/${tarball}" -C /tmp
install -m 0755 "/tmp/${stage}/bca" "$CI_PROJECT_DIR/.cache/bca/bca"
rm -rf "/tmp/${tarball}" "/tmp/${stage}"
fi
install -m 0755 "$CI_PROJECT_DIR/.cache/bca/bca" "$HOME/.local/bin/bca"
export PATH="$HOME/.local/bin:$PATH"
script:
- bca
check
--paths "$PWD"
--report-format code-climate
--output gl-code-quality-report.json
--no-fail
- bca
check
--paths "$PWD"
--report-format checkstyle
--output bca-checkstyle.xml
--no-fail
- bca
report -O markdown
--paths "$PWD"
--top 20
--strip-prefix "$PWD/"
--output bca-report.md
# The threshold gate runs separately so the artifacts above still
# publish on failure. Exit 2 = at least one threshold exceeded.
- bca check --paths "$PWD"
artifacts:
when: always
reports:
codequality: gl-code-quality-report.json
paths:
- gl-code-quality-report.json
- bca-checkstyle.xml
- bca-report.md
A few notes about the example:
- The first two
bca check … --no-failinvocations collect offenders for the artifacts; the finalbca check(no--no-fail) is the pass/fail gate. All three runs use the same threshold config so the artifacts always match the gate decision. artifacts:when: alwaysensures every artifact is downloadable even on a red pipeline — which is exactly when you want them most.artifacts:reports:codequalitywires the Code Climate JSON directly into GitLab's MR Code Quality widget — see the Code Quality widget section below for the field-by-field semantics.
GitLab Code Quality widget
GitLab's first-class Code Quality experience (inline complaints on
the MR diff, summary on the MR overview page) consumes
Code Climate JSON.
bca check emits this natively via --report-format code-climate,
so the integration is a one-liner:
code_quality:
stage: quality
script:
- bca check --paths "$CI_PROJECT_DIR"
--report-format code-climate
--output gl-code-quality-report.json
--no-fail
artifacts:
when: always
reports:
codequality: gl-code-quality-report.json
paths:
- gl-code-quality-report.json
Severity bands are derived from how far each metric exceeds its
configured threshold (value / limit ratio, inverted for the
maintainability-index family where lower is worse): ≤ 1.5× →
minor, ≤ 2× → major, ≤ 4× → critical, > 4× →
blocker. The widget deduplicates findings by fingerprint; bca
hashes path \0 function \0 metric (no line, no value) so a
violation surviving an upstream line-drift edit still collapses
into the same widget entry across pipeline runs.
Sanity-check a generated report locally:
jq 'all(.[]; has("description") and has("check_name")
and has("fingerprint") and has("severity")
and has("location"))' gl-code-quality-report.json
# → true
jq '[.[] | .severity] | unique' gl-code-quality-report.json
# → a subset of ["info","minor","major","critical","blocker"]
MR-only comment with the Markdown report
To attach the Markdown report as an MR note (the GitLab analogue of the GitHub PR comment recipe), use the project access token and the Notes API:
bca-mr-comment:
stage: quality
image: alpine:3
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
needs: ["bca-quality"]
before_script:
- apk add --no-cache curl jq
script:
- |
BODY=$(jq -Rs '.' < bca-report.md)
curl --fail --silent --show-error \
--request POST \
--header "PRIVATE-TOKEN: $CI_BCA_BOT_TOKEN" \
--header "Content-Type: application/json" \
--data "{\"body\": $BODY}" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
CI_BCA_BOT_TOKEN is a project access token with api scope. The
job depends on bca-quality so the Markdown artifact is in place
before it runs.
Jenkins / SonarQube
Both Jenkins (via the Warnings Next Generation plugin) and SonarQube (via its Generic Issue importer) consume Checkstyle 4.3 XML directly. The same invocation feeds both:
bca check --paths src/ \
--report-format checkstyle \
--output report.checkstyle.xml
Wire report.checkstyle.xml into your existing Jenkins
Record Issues / SonarQube External Issues step. The Checkstyle
writer emits an empty (well-formed) document when there are no
offenders, so neither tool needs special-casing for a clean run. See
the Check command page
for the writer's schema details.
Generic CI guidance
Applies regardless of provider:
- Pin
bcato a specific version. Bothcargo install --versionandcargo binstall --versionaccept the published crate version ofbig-code-analysis-cli. A floating install surfaces metric-counting changes as "mysterious CI flakes" on Mondays. Pin to a version whose CLI-artifact schemas (baseline, thresholds) match the files your repo commits — see the schema-compatibility note in the install section. --jobsdefaults to the effective CPU count. The flag honorsavailable_parallelism()— cgroup-/cpuset-/quota-aware on Linux, OS CPU count on macOS/Windows — so CI runners no longer need to thread--jobs "$(nproc)"through every recipe.--jobs 1remains a debugging knob, not a default.- Always pass
--strip-prefix "$PWD/"tobca report markdownso the path column is identical across runners with different workspace paths. Without it the diff between two reports is dominated by/home/runner/work/...vs./builds/group/project/...noise. - Store
bca.tomlat the repo root, alongsideCargo.toml/pyproject.toml/package.json.bcadiscovers it automatically, so a barebca checkreads the committed thresholds, paths, and baseline. Treat it as source: review threshold relaxations in code review. - Exit-code contract.
bca checkexits0clean,2on any threshold violation,1on tool error (bad config, unknown metric, unreadable path). Reserving1for tool errors lets CI distinguish "a function got too complex" from "the analyzer crashed". Pass--exit-codes=tiered(or set[check] exit_codes = "tiered"inbca.toml) to split the violation case by severity:2new offenders only,3regressions only,4both,5a--tier=softviolation that also breaches the hard limit. The tiered codes are opt-in; the default stays0/1/2. Every fail-state remains non-zero, soexit != 0 → failwrappers keep working — only tooling that tests$? -eq 2explicitly needs to widen to2-5. - Honor in-source suppression markers, audit with
--no-suppress. The defaultbca checkhonorsbca: suppress/bca: suppress-filemarkers; passing--no-suppressignores them so auditors see the raw offender list.