How do you configure detekt reports (SARIF, XML, HTML) and integrate detekt into CI so it gates merges and surfaces findings in code review?
answer
- reports { html/xml/txt/md/sarif .required.set(true) }
- SARIF = standard JSON; GitHub upload-sarif -> PR annotations
- Multi-module: ReportMergeTask to combine SARIF
- Gate: task fails on maxIssues; make it a required check
- if: always() to upload SARIF even when build fails
basics
~20 sdetekt can output reports in several formats. SARIF is a standard JSON format that GitHub understands, so you upload it to show findings as annotations on pull requests. In CI you run the detekt task and fail the build on findings.
solid answer
~40 sdetekt emits reports configured in the `reports {}` block of each `Detekt` task: `html`, `xml` (Checkstyle), `txt`, `md`, and `sarif`. **SARIF** (Static Analysis Results Interchange Format) is a vendor-neutral JSON schema; GitHub's code-scanning ingests it via the `github/codeql-action/upload-sarif` action, rendering findings as PR **annotations** and Security alerts. For multi-module builds you merge per-module SARIF with `ReportMergeTask`. CI integration: run `./gradlew detekt` (or typed `detektMain`) as a required check; the task **fails the build** when findings exceed `maxIssues`, so the merge is blocked. Use the **baseline** to avoid drowning in legacy debt while still failing on new issues. Set `reports.sarif.required.set(true)` and upload `build/reports/detekt/detekt.sarif`. Pair with `--build-upon-default-config` and pin the detekt version for reproducible CI.
code
kotlin · 9 linestasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
reports {
sarif.required.set(true)
html.required.set(true)
xml.required.set(true)
}
}
// CI: ./gradlew detekt --continue
// github/codeql-action/upload-sarif with sarif_file: build/reports/detekt/detekt.sarif (if: always())go deeper
Knows detekt produces reports (e.g. HTML) and runs in CI.
Configures the reports block, explains SARIF + GitHub upload, and the failing-task gate.
Adds multi-module ReportMergeTask, baseline pairing, typed runs, and required-check setup.
Designs the org-wide gate policy: reproducibility, debt strategy, where typed runs live, and review ergonomics.
## Report formats Each `Detekt` task has a `reports {}` block. Available formats: - **html** — human-readable, good for browsing locally. - **xml** — Checkstyle-compatible; many CI dashboards parse it. - **txt** — plain console-like list. - **md** — Markdown. - **sarif** — the strategic one for modern CI. ```kotlin import io.gitlab.arturbosch.detekt.Detekt tasks.withType<Detekt>().configureEach { reports { html.required.set(true) xml.required.set(true) sarif.required.set(true) txt.required.set(false) } } ``` Outputs land in `build/reports/detekt/`. ## What SARIF is and why it matters **SARIF** = *Static Analysis Results Interchange Format*, a standardized **JSON** schema (an OASIS standard) for analyzer output. Because it's standardized, tools like **GitHub code scanning** can ingest *any* SARIF-producing analyzer and render findings uniformly — as inline **PR annotations**, in the Security tab, and with dedup across runs. Upload step (GitHub Actions): ```yaml - name: Run detekt run: ./gradlew detekt --continue # --continue so all modules report - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: build/reports/detekt/detekt.sarif ``` `if: always()` ensures the SARIF uploads even when detekt failed the build, so reviewers still see annotations. ## Multi-module merge In a multi-project build each module writes its own SARIF. Merge them with detekt's `ReportMergeTask` so you upload one file: ```kotlin val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) { output.set(rootProject.layout.buildDirectory.file("reports/detekt/merged.sarif")) } tasks.withType<Detekt>().configureEach { finalizedBy(reportMerge) } reportMerge { input.from(tasks.withType<Detekt>().map { it.sarifReportFile }) } ``` ## Gating merges The core gate: detekt's task **fails** when findings exceed `build.maxIssues` in `detekt.yml` (commonly `0`). Make the detekt job a **required status check** on the protected branch, so a PR can't merge while it's red. To keep the gate practical on legacy code, pair it with a **baseline** (forgive existing, fail on new) and run typed `detektMain` if you rely on `@RequiresTypeResolution` rules. ## CI hygiene - **Pin the detekt version** (and config) for reproducibility. - Use `--continue` so all modules run before failing. - Cache Gradle for speed. - Optionally publish HTML as a build artifact for deep dives. ## Mental model Reports = how findings are *expressed*; SARIF = the *lingua franca* CI tools read; the failing task + required check = the *gate*; the baseline = the *grace period* for old debt.
- Why upload SARIF with `if: always()` in CI?So findings still appear as PR annotations even when detekt failed the job; otherwise a red build would skip the upload and hide the results.
- How do you avoid uploading many separate SARIF files in a multi-module build?Use detekt's ReportMergeTask to merge per-module SARIF into one file, then upload that.
saying these in an interview costs you the question
- Not knowing SARIF is a standard format consumable by GitHub code scanning
- Uploading SARIF only on success, hiding findings when the build fails
- Thinking reports themselves gate the build (the failing task does)
- Ignoring multi-module merge and uploading nothing or duplicates
- Not pinning the detekt version, causing nondeterministic CI