This reusable GitHub Actions workflow builds, tests and analyses a Gradle-based Java project.
It is the GitHub Actions counterpart of the buildJavaProject Jenkins pipeline used by the iExec middleware
services, and covers the path from a checkout to a jar handed over to an OCI image build.
It serves both services and pure libraries, so anything only a service needs — the Docker Hub login for Testcontainers, the jar upload — is opt-in and left to the caller.
- ☕ Sets up a JDK and Gradle with dependency caching
- 🧪 Runs the unit tests, and optionally an
itesttask - 🔎 Runs a SonarCloud analysis whose branch and pull request context is auto-detected from the GitHub environment
- 🐳 Optionally logs in to Docker Hub, so Testcontainers-based tests are not hit by anonymous pull rate limits
- 📤 Uploads the built jar as an artifact, ready for
docker-build.ymlto copy into an image - 📊 Always uploads the Gradle HTML test and coverage reports (
build/reports), including on failure
| Name | Description | Required | Default |
|---|---|---|---|
artifact-name |
Name of the uploaded jar artifact | No | "boot-jar" |
artifact-retention-days |
Retention of the uploaded jar artifact, in days | No | 1 |
dockerhub-login |
Log in to Docker Hub before the build. Enable it when the tests pull images, e.g. Testcontainers | No | false |
java-version |
Java version to use | No | "21" |
refresh-dependencies |
Run Gradle with --refresh-dependencies (only useful for -SNAPSHOT or dynamic versions) |
No | false |
run-itest |
Run the itest Gradle task after the unit tests |
No | false |
sonar |
Run the SonarQube/SonarCloud analysis | No | false |
upload-jar |
Upload the built jar as an artifact, so that a later job can build an OCI image from it | No | false |
| Name | Description | Required |
|---|---|---|
dockerhub-username |
Docker Hub username | When dockerhub-login: true |
dockerhub-password |
Docker Hub token | When dockerhub-login: true |
sonar-token |
SonarCloud token | When sonar: true |
| Name | Description |
|---|---|
jar-path |
Path of the Gradle project built jar. Feed it to docker-build.yml as build-args: jar=<path>. Only set when upload-jar: true, empty for pure libraries |
artifact-name |
Name of the uploaded jar artifact, echoed back for convenience |
Building and testing a service, then building an OCI image from the very same jar. upload-jar is opt-in
because the workflow also serves pure libraries, which have no image to build and nothing to hand over:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
build-and-test:
# ⚠️ use tagged version here
uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/java-build.yml@main
with:
java-version: "21"
sonar: true
dockerhub-login: true
# opt in to the jar upload: only projects that ship an OCI image need it
upload-jar: true
secrets:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN_PULL_ONLY }}
sonar-token: ${{ secrets.SONAR_TOKEN }}
build-image:
needs: build-and-test
# ⚠️ use tagged version here
uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/docker-build.yml@main
with:
image-name: docker-regis.iex.ec/my-service
image-tag: dev-${{ github.sha }}
registry: docker-regis.iex.ec
push: true
# the jar built above is downloaded into the build context
artifact-name: ${{ needs.build-and-test.outputs.artifact-name }}
artifact-path: build/libs
build-args: jar=${{ needs.build-and-test.outputs.jar-path }}
secrets:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN_PULL_ONLY }}
username: ${{ secrets.NEXUS_USERNAME }}
password: ${{ secrets.NEXUS_PASSWORD }}- 🔎 The SonarQube Gradle plugin reads the branch, pull request number and repository from the
GITHUB_*environment variables, so none of thesonar.pullrequest.*orsonar.branch.nameproperties need to be passed. Thesonar.projectKeyproperty is derived from SonarQube Gradle plugin and Gradle project properties. Thesonar.organizationproperty (mandatory on SonarQube Cloud) is not auto-detected and derived from the calling repository. The checkout usesfetch-depth: 0because Sonar attributes lines to authors throughgit blame. - 🏷️ The checkout also needs the full history because the iExec
build.gradlefiles derive the project version from the tag pointing atHEAD, appending-NEXT-SNAPSHOTwhen there is none. - 🔄
refresh-dependenciesdefaults tofalse.setup-gradlecaches the Gradle home between runs — writing it from the default branch and restoring it read-only elsewhere — so a build can legitimately resolve against a cache populated days earlier. That only matters for changing modules (-SNAPSHOT) and dynamic versions, which Gradle re-checks at most every 24 hours; enable the input when a project depends on one. For the pinned versions the iExec projects resolve from jitpack, it forces Gradle to revalidate the metadata of every module on every build for no benefit. - 📤 The jar artifact is uploaded flat, so
artifact-path: build/libsindocker-build.ymlrestores it at the path reported by thejar-pathoutput. - 🧩 Pure libraries pass
upload-jar: false: no artifact is uploaded and thejar-pathoutput is never set (it evaluates to an empty string on the caller side, which is safe to ignore). - 🔐 The workflow declares
permissions: contents: read+actions: write: the first is needed bycheckout, the second byupload-artifact. An explicitpermissionsblock resets every other scope tonone, so droppingactions: writesilently breaks the report artifact. - 🌐 The
test-reportsartifact (7-day retention) contains the human-readable HTML reports frombuild/reports(unit tests, JaCoCo coverage). GitHub does not render HTML inline on a PR: open it from PR → Checks → the run → Summary (bottom Artifacts section), download, unzip and open locally.upload-artifactalso exposes anartifact-urloutput if you later want to post a direct download link as a PR comment.
Could not resolve com.github.iExecBlockchainComputing...— the dependency is not on jitpack yet. jitpack builds a version on first request, so a freshly pushed tag can take a few minutes to become resolvable.Cannot perform inline analysis, no branch or pull request found— the checkout is shallow. This workflow setsfetch-depth: 0; a caller wrapping it differently has to do the same.You must define the following mandatory properties ... sonar.organization— the SonarQube Cloud organization could not be resolved. Either the repository owner is not a SonarQube Cloud organization key, or the project lives under a different one. Update the project configuration on SonarQube Cloud.- Testcontainers fails pulling an image — set
dockerhub-login: trueand pass the Docker Hub secrets.