Skip to content

Latest commit

 

History

History
362 lines (286 loc) · 11.2 KB

File metadata and controls

362 lines (286 loc) · 11.2 KB

CI integration

Two lines, on any platform:

curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sh
searchdeadcode . --profile ci

--profile ci is the whole pipeline setup in one flag: exit 1 on findings, no cache file left in the workspace, and .deadcode-baseline.json picked up if your project committed one. It is the equivalent of ./gradlew detekt.

Everything below is the same two lines wrapped in each platform's syntax, plus optional reporting for teams who want more than a red build.

Looking for the editor side instead? The Android Studio / IntelliJ plugin is covered in android-studio.md.

Before the first green build

A codebase that never ran this tool has existing dead code, and failing on all of it teaches the team to ignore the job. Freeze it once, like detekt's baseline:

searchdeadcode . --generate-baseline .deadcode-baseline.json   # commit this

From then on the pipeline only breaks on what a branch adds. --profile ci finds the file by name; you never pass it again.

That one command runs outside the CI profile, so it leaves a .searchdeadcode-cache.json next to your project — useful locally, 221 MB on a 9000-file repository. Add it to .gitignore now rather than discovering it in a git status.

Keeping the baseline honest:

--baseline-prune drop entries whose finding no longer exists, so the file shrinks as you clean
--baseline-stats entries per rule — where the tool cries wolf the most
--baseline-rm <name> remove one entry, to start failing on it again

If your team refuses one more file in the repository, --diff-base origin/main reports only what became dead since that reference instead. It needs the full history: shallow clones are the number one cause of surprises with it (fetch-depth: 0 on GitHub Actions, GIT_DEPTH: 0 on GitLab).

Exit codes

code meaning
0 analysis ran, nothing left after filtering
1 findings remain, and the gate was asked for
2 the tool could not work: unreadable path, corrupt baseline, invalid config
3 --ratchet refused a count increase against the baseline

The 1 / 2 split is what stops a pipeline from reporting "no dead code" when the binary never started. Script against the code, never against the output text.

One deliberate exception: a run carrying --generate-baseline never exits 1. Freezing the debt is an act of acceptance, and failing the step that performs it would break the adoption command this guide opens with.

Two things worth knowing before you wire it up

Run it at the repository root, never per module. Reachability needs to see the whole project: analysed module by module, every symbol used from elsewhere looks dead. This is the structural difference with detekt, which lints file by file and can be split any way you like.

The job needs no JDK, no Gradle, no build. **/build/** and **/generated/** are excluded by default, so a fresh checkout gives the same answer as your machine after a full build. A small container is enough — do not put this on your expensive Android executor, and do not make it wait for a build.

One constraint on that container: the Linux binary links against glibc, so a musl image (alpine) will not start it — the loader is simply absent, and the error says "not found" about a file that visibly exists. The examples below use buildpack-deps:curl, a slim Debian with curl preinstalled; any glibc image works. On Alpine specifically, apk add gcompat usually suffices, but that is Alpine's compatibility shim, not something this project tests.

Do not cache anything. The incremental cache halves the run (330 s → 163 s on a 9000-file project) but weighs 221 MB. Shipping that to and from a CI cache costs more than it saves, and it lands in your workspace. --profile ci already turns it off; if you run without the profile, add --incremental=false and put .searchdeadcode-cache.json in your .gitignore.


Per platform

Ordered by real-world adoption.

GitHub Actions

The published action installs, caches and runs in one step:

# .github/workflows/dead-code.yml
name: Dead code
on: [push, pull_request]

jobs:
  dead-code:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: KevinDoremy/SearchDeadCode@v0
        with:
          args: '--profile ci'
Input Description Default
path Path to analyze .
version Version to install, or latest latest
format terminal, compact, json, sarif, html, markdown, reviewdog, csv, gitlab, checkstyle terminal
output Output file -
args Extra CLI arguments -
fail-on-findings Fail the workflow on findings false
min-confidence low, medium, high, confirmed medium

@v0 follows every 0.x release. Pin @v0.17.0 if you would rather upgrade by hand.

Findings in the Security tab

jobs:
  dead-code:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v7
      - uses: KevinDoremy/SearchDeadCode@v0
        with:
          format: 'sarif'
          output: 'deadcode.sarif'
          args: '--profile ci'

The action uploads the SARIF itself; results land in Security → Code scanning and annotate the pull request diff. The permissions block is not optional: the upload needs security-events: write, and on repositories where the default token is read-only it fails silently without it — listing a permission turns every unlisted one off, so contents: read must be spelled out too or checkout stops working.

Jenkins

stage('Dead code') {
  steps {
    sh '''
      curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh \
        | SDC_INSTALL_DIR="$WORKSPACE/bin" sh
      "$WORKSPACE/bin/searchdeadcode" . --profile ci \
        --format checkstyle --output deadcode.xml
    '''
  }
  post {
    always {
      recordIssues tools: [checkStyle(pattern: 'deadcode.xml', name: 'Dead code')]
    }
  }
}

recordIssues comes from the Warnings Next Generation plugin, which reads Checkstyle natively — the same format detekt publishes. You get the findings list, the new/fixed/outstanding split and trend charts.

GitLab CI

One include, the job comes ready:

include:
  - remote: https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/v0.18.0/ci-templates/searchdeadcode.gitlab-ci.yml

Or spelled out, if you would rather own the job:

dead-code:
  stage: test
  image: buildpack-deps:curl
  before_script:
    - curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sh
  script:
    - searchdeadcode . --profile ci --format gitlab --output gl-code-quality.json
  artifacts:
    reports:
      codequality: gl-code-quality.json
    when: always

The Code Quality report renders inline in the merge request widget, so a reviewer sees the findings next to the diff.

CircleCI

The orb carries the whole job:

version: 2.1
orbs:
  deadcode: kevindoremy/searchdeadcode@0
workflows:
  quality:
    jobs:
      - deadcode/scan

@0 floats within the 0.x series, like the action's @v0. Orb versions track the analyzer's: pinning @0.18.0 gives you a job that installs analyzer 0.18.0, forever — published orb versions are immutable and their default is pinned to themselves. deadcode/scan takes version (override the analyzer), path, args and resource_class (default small). Or spelled out, without the orb:

jobs:
  dead_code:
    docker:
      - image: cimg/base:current
    steps:
      - checkout
      - run:
          name: Install SearchDeadCode
          command: curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sudo sh
      - run:
          name: Dead code
          command: searchdeadcode . --profile ci

A plain cimg/base image, not your Android executor: this job needs no toolchain and no cache, so it is cheap and runs in parallel with the rest.

Azure Pipelines

- job: DeadCode
  pool:
    vmImage: ubuntu-latest
  steps:
    - checkout: self
    - script: |
        curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sudo sh
        searchdeadcode . --profile ci
      displayName: Dead code

Bitbucket Pipelines

pipelines:
  default:
    - step:
        name: Dead code
        image: buildpack-deps:curl
        script:
          - curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sh
          - searchdeadcode . --profile ci

TeamCity

A command line build step:

curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh \
  | SDC_INSTALL_DIR="%teamcity.build.checkoutDir%/bin" sh
"%teamcity.build.checkoutDir%/bin/searchdeadcode" . --profile ci \
  --format checkstyle --output deadcode.xml

Then add an XML Report Processing build feature with the Checkstyle type on deadcode.xml.

Buildkite

steps:
  - label: "Dead code"
    command:
      - curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sudo sh
      - searchdeadcode . --profile ci

Woodpecker / Drone

steps:
  dead-code:
    image: buildpack-deps:curl
    commands:
      - curl -fsSL https://raw.githubusercontent.com/KevinDoremy/SearchDeadCode/main/install.sh | sh
      - searchdeadcode . --profile ci

Inline comments on the pull request

A red build tells the author something is wrong; an inline comment tells them where. reviewdog does that on GitHub, GitLab, Bitbucket, CircleCI and Jenkins, and SearchDeadCode speaks its format:

searchdeadcode . --profile ci --fail-on-findings=false --format reviewdog \
  | reviewdog -f=rdjsonl -name=deadcode -reporter=github-pr-review

--fail-on-findings=false hands the gating to reviewdog, which decides from its own -fail-level. Swap the reporter for gitlab-mr-discussion or bitbucket-code-report as needed.

Pre-commit hook

To catch it before the push rather than after:

searchdeadcode --install-hook

or, with pre-commit, this repository is a native hook source:

repos:
  - repo: https://github.com/KevinDoremy/SearchDeadCode
    rev: v0.18.0
    hooks:
      - id: searchdeadcode        # diff mode, sub-second
      # - id: searchdeadcode-full # the whole-project gate, for pre-push

Diff mode, not the full --profile ci run: a commit hook fires on every commit, and analysing only the changed files is what keeps it under a second. It is the same command --install-hook writes. The binary must be on PATH (install.sh, brew, or cargo) — the hook deliberately does not compile the tool on contributors' machines.

Installing another way

Homebrew, for developer machines:

brew tap KevinDoremy/tap && brew install searchdeadcode

cargo install searchdeadcode also works, but it compiles the tool from source every time — minutes per build. Keep it for architectures with no published binary.