JaCoCo Coverage Badges for Multi-Module Projects in GitHub Actions
Tags: java, github, showdev, kotlin
Published: 2023-05-25 20:39:07

The [jacoco-badge-generator](https://github.com/cicirello/jacoco-badge-generator) is a GitHub Action, which can also be used locally as a command-line utility. Its functionality includes parsing JaCoCo test coverage csv files, generating coverage badges that can be displayed within the READMEs of GitHub repositories, generating a JSON summary file with the coverage percentages, and can optionally be used for pull-request coverage checks (e.g., validating if coverage is above a target minimum and/or ensuring that coverage did not decrease). It is customizable in a variety of ways (see its [GitHub repository](https://github.com/cicirello/jacoco-badge-generator) and/or the other posts in this series for details).

I just release version v2.9.0, which enhanced the existing functionality associated with using the [jacoco-badge-generator](https://github.com/cicirello/jacoco-badge-generator) GitHub Action with multi-module projects. Specifically, prior to this release, for a multi-module project, the paths to all of the JaCoCo csv reports had to be listed in the inputs to the action. Now, as of v2.9.0, you can use a glob pattern to specify the paths to the JaCoCo csv reports. This can actually now also work for the more common single module project, but the glob functionality is likely most useful in the multi-module case. Note that the CLI mode already implicitly supported globs since your shell will expand any globs you specify on the command line. But as a GitHub Action this previously was not the case as GitHub Actions doesn't expand globs in the inputs to an Action. The [jacoco-badge-generator](https://github.com/cicirello/jacoco-badge-generator) v2.9.0 now handles glob expansion internally.

This post focuses on workflows for the multi-module case, including introducing the new approach to using a glob to specify the JaCoCo reports. The README in the GitHub repository, as well as other posts in this series here on DEV, cover other functionality along with additional sample workflows. 

__Table of Contents:__ The remainder of this post is organized as follows:
* [Preliminaries](#preliminaries)
  * [Running JaCoCo with Maven](#running-jacoco-with-maven)
  * [Running JaCoCo with Gradle](#running-jacoco-with-gradle)
* [Workflow Specifying Paths to JaCoCo Reports Without Globs](#workflow-specifying-paths-to-jacoco-reports-without-globs)
* [Workflow Specifying Paths to JaCoCo Reports With Globs](#workflow-specifying-paths-to-jacoco-reports-with-globs)
* [Workflow Generating Separate Badges for Each Module](#workflow-generating-separate-badges-for-each-module)
* [Multi-Module Examples with the CLI Utility](#multimodule-examples-with-the-cli-utility)
* [More Information](#more-information)
* [Where You Can Find Me](#where-you-can-find-me)

## Preliminaries

Before you can use the jacoco-badge-generator, your workflow must generate a JaCoCo csv coverage report. Here are a couple examples of how to do this for different build tools.

### Running JaCoCo with Maven

The example workflows in this post assume that you are using Maven to build and test a Java project, and that you have the jacoco-maven-plugin configured in your pom.xml in the test phase with something along the lines of the following:

```XML
<build>
  <plugins>
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>0.8.10</version>
      <executions>
        <execution>
          <goals>
            <goal>prepare-agent</goal>
          </goals>
        </execution>
        <execution>
          <id>generate-code-coverage-report</id>
          <phase>test</phase>
          <goals>
            <goal>report</goal>
          </goals>
        </execution>
      </executions>
      </plugin>
  </plugins>
</build>
```

### Running JaCoCo with Gradle

If you use gradle as your build tool, then you can configure JaCoCo to generate the csv report in `build.gradle.kts` with:

```Kotlin
plugins {
    jacoco
}

tasks.jacocoTestReport {
    reports {
        csv.isEnabled = true
    }
}
```

Or the equivalent in `build.gradle`:

```Gradle
plugins {
    id 'jacoco'
}

jacocoTestReport {
    reports {
        csv.enabled true
    }
}
```

Note that the workflows in the remainder of this post assume that Maven is in use, including Maven's default location and filename for the coverage report. You will need to alter the `jacoco-csv-file` input if you are using Gradle to the appropriate location and filename.


## Workflow Specifying Paths to JaCoCo Reports Without Globs

This example workflow generates both badges (instructions coverage percentage and branches coverage percentage) for a multi-module project. The badges that are generated are computed over all modules. To do so, simply pass the paths to all of the JaCoCo reports that you want to include via the jacoco-csv-file input. The `>` is just Yaml's way of writing a string across multiple lines. You can also just list all on a single space-separated line, but your workflow file will be easier to read if you put them one per line. In this example, there are three subprojects: `module1`, `module2`, and `module3`. This workflow assumes that you are using Maven as your build tool, and that you have configured JaCoCo to run during the test phase.

```YAML
name: build

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Set up the Java JDK
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'adopt'

    - name: Build with Maven
      run: mvn -B test

    - name: Generate JaCoCo Badge
      id: jacoco
      uses: cicirello/jacoco-badge-generator@v2
      with:
        generate-branches-badge: true
        jacoco-csv-file: >
          module1/target/site/jacoco/jacoco.csv
          module2/target/site/jacoco/jacoco.csv
          module3/target/site/jacoco/jacoco.csv

    - name: Log coverage percentage
      run: |
        echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
        echo "branch coverage = ${{ steps.jacoco.outputs.branches }}"

    - name: Commit the badge (if it changed)
      run: |
        if [[ `git status --porcelain` ]]; then
          git config --global user.name 'YOUR NAME HERE'
          git config --global user.email 'YOUR-GITHUB-USERID@users.noreply.github.com'
          git add -A
          git commit -m "Autogenerated JaCoCo coverage badge"
          git push
        fi
```

## Workflow Specifying Paths to JaCoCo Reports With Globs

This next example workflow is just like the previous, except that now a glob is used to to find all JaCoCo reports named `jacoco.csv` regardless of where they appear within your project. This will actually also work if you have a single module project, with a single `jacoco.csv` but without the need to specify its path. We'll pass the glob pattern to the `jacoco-csv-file` input with the following: `jacoco-csv-file: "**/jacoco.csv"`. Note that the quotes around the pattern are necessary. Without the quotes, the wildcard characters will lead to an "invalid workflow" error from GitHub Actions. We're essentially passing the glob as a string to the action, and the action then processes the glob internally.

```YAML
name: build

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Set up the Java JDK
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'adopt'

    - name: Build with Maven
      run: mvn -B test

    - name: Generate JaCoCo Badge
      id: jacoco
      uses: cicirello/jacoco-badge-generator@v2
      with:
        generate-branches-badge: true
        jacoco-csv-file: "**/jacoco.csv"

    - name: Log coverage percentage
      run: |
        echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
        echo "branch coverage = ${{ steps.jacoco.outputs.branches }}"

    - name: Commit the badge (if it changed)
      run: |
        if [[ `git status --porcelain` ]]; then
          git config --global user.name 'YOUR NAME HERE'
          git config --global user.email 'YOUR-GITHUB-USERID@users.noreply.github.com'
          git add -A
          git commit -m "Autogenerated JaCoCo coverage badge"
          git push
        fi
```

## Workflow Generating Separate Badges for Each Module

If you prefer to generate separate coverage badges for each of the modules of a multi-module project, then just include multiple steps of the jacoco-badge-generator in your workflow, such as in this example. Be sure to use the inputs to specify names for the badge files, otherwise with the defaults the subsequent steps will overwrite the previous. This example assumes that there are two modules. You also will likely want to use the `coverage-label` and `branches-label` inputs to change the text on the left side of the badges if you are displaying badges for multiple modules in the README of the same repository. This example demonstrates that as well.

```YAML
name: build

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Set up the Java JDK
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'adopt'

    - name: Build with Maven
      run: mvn -B test

    - name: Generate JaCoCo Badges for Module 1
      id: jacocoMod1
      uses: cicirello/jacoco-badge-generator@v2
      with:
        generate-branches-badge: true
        jacoco-csv-file: module1/target/site/jacoco/jacoco.csv
        coverage-badge-filename: jacoco1.svg
        branches-badge-filename: branches1.svg
        coverage-label: coverage (module 1)
        branches-label: branches (module 1)

    - name: Generate JaCoCo Badges for Module 2
      id: jacocoMod2
      uses: cicirello/jacoco-badge-generator@v2
      with:
        generate-branches-badge: true
        jacoco-csv-file: module2/target/site/jacoco/jacoco.csv
        coverage-badge-filename: jacoco2.svg
        branches-badge-filename: branches2.svg
        coverage-label: coverage (module 2)
        branches-label: branches (module 2)

    - name: Commit the badges (if they changed)
      run: |
        if [[ `git status --porcelain` ]]; then
          git config --global user.name 'YOUR NAME HERE'
          git config --global user.email 'YOUR-GITHUB-USERID@users.noreply.github.com'
          git add -A
          git commit -m "Autogenerated JaCoCo coverage badge"
          git push
        fi
```

## Multi-Module Examples with the CLI Utility

For those using the `jacoco-badge-generator` as a CLI tool, the following commands will accomplish the equivalent of the above three workflows. First, to install the CLI tool with pip from PyPI.

__On Linux and MacOS:__

```Shell
python3 -m pip install jacoco-badge-generator
```

__On Windows:__

```Shell
py -m pip install jacoco-badge-generator
```

Note that all examples below assume Linux (e.g., Python command is `python3`). If on Windows, just change `python3` to `py` in all of the examples below.

__Three Modules Without Globs:__

Generate both instructions coverage and branches coverage badges for combination of three modules:

```Shell
python3 -m jacoco_badge_generator --generate-branches-badge true --jacoco-csv-file module1/target/site/jacoco/jacoco.csv module2/target/site/jacoco/jacoco.csv module3/target/site/jacoco/jacoco.csv
```

__Three Modules With Globs:__

Generate both instructions coverage and branches coverage badges for combination of three modules:

```Shell
python3 -m jacoco_badge_generator --generate-branches-badge true --jacoco-csv-file **/jacoco.csv
```

__Two Modules with Separate Badges for Each:__

```Shell
python3 -m jacoco_badge_generator --generate-branches-badge true --jacoco-csv-file module1/target/site/jacoco/jacoco.csv --coverage-badge-filename jacoco1.svg --branches-badge-filename branches1.svg --coverage-label "coverage (module 1)" --branches-label "branches (module 1)"
python3 -m jacoco_badge_generator --generate-branches-badge true --jacoco-csv-file module2/target/site/jacoco/jacoco.csv --coverage-badge-filename jacoco2.svg --branches-badge-filename branches2.svg --coverage-label "coverage (module 2)" --branches-label "branches (module 2)"
```

## More Information

For more information, see the GitHub Repository:

{% github cicirello/jacoco-badge-generator %}

You can also check out a website about this action as well as other GitHub Actions that I maintain:

{% embed https://actions.cicirello.org/ %}


## Where You Can Find Me

Follow me [here on DEV](https://dev.to/cicirello) and on [GitHub](https://github.com/cicirello):

{% user cicirello %}

Or visit my website:

{% embed https://www.cicirello.org/ %}