|
| 1 | +# ☕ Java Build Workflow |
| 2 | + |
| 3 | +## 🔍 Overview |
| 4 | + |
| 5 | +This reusable GitHub Actions workflow builds, tests and analyses a Gradle-based Java project. |
| 6 | +It is the GitHub Actions counterpart of the `buildJavaProject` Jenkins pipeline used by the iExec middleware |
| 7 | +services, and covers the path from a checkout to a jar handed over to an OCI image build. |
| 8 | + |
| 9 | +It serves both services and pure libraries, so anything only a service needs — the Docker Hub login for |
| 10 | +Testcontainers, the jar upload — is opt-in and left to the caller. |
| 11 | + |
| 12 | +## ✨ Features |
| 13 | + |
| 14 | +- ☕ Sets up a JDK and Gradle with dependency caching |
| 15 | +- 🧪 Runs the unit tests, and optionally an `itest` task |
| 16 | +- 🔎 Runs a SonarCloud analysis whose branch and pull request context is auto-detected from the GitHub environment |
| 17 | +- 🐳 Optionally logs in to Docker Hub, so Testcontainers-based tests are not hit by anonymous pull rate limits |
| 18 | +- 📤 Uploads the built jar as an artifact, ready for `docker-build.yml` to copy into an image |
| 19 | +- 📊 Always uploads the Gradle HTML test and coverage reports (`build/reports`), including on failure |
| 20 | + |
| 21 | +## ⚙️ Inputs |
| 22 | + |
| 23 | +| Name | Description | Required | Default | |
| 24 | +| ------------------------- | ------------------------------------------------------------------------------------------------ | -------- | ----------------- | |
| 25 | +| `artifact-name` | Name of the uploaded jar artifact | No | `"boot-jar"` | |
| 26 | +| `artifact-retention-days` | Retention of the uploaded jar artifact, in days | No | `1` | |
| 27 | +| `dockerhub-login` | Log in to Docker Hub before the build. Enable it when the tests pull images, e.g. Testcontainers | No | `false` | |
| 28 | +| `java-version` | Java version to use | No | `"21"` | |
| 29 | +| `refresh-dependencies` | Run Gradle with `--refresh-dependencies` (only useful for `-SNAPSHOT` or dynamic versions) | No | `false` | |
| 30 | +| `run-itest` | Run the `itest` Gradle task after the unit tests | No | `false` | |
| 31 | +| `sonar` | Run the SonarQube/SonarCloud analysis | No | `false` | |
| 32 | +| `upload-jar` | Upload the built jar as an artifact, so that a later job can build an OCI image from it | No | `false` | |
| 33 | + |
| 34 | +## 🔐 Secrets |
| 35 | + |
| 36 | +| Name | Description | Required | |
| 37 | +| -------------------- | -------------------------------- | ---------------------------- | |
| 38 | +| `dockerhub-username` | Docker Hub username | When `dockerhub-login: true` | |
| 39 | +| `dockerhub-password` | Docker Hub token | When `dockerhub-login: true` | |
| 40 | +| `sonar-token` | SonarCloud token | When `sonar: true` | |
| 41 | + |
| 42 | +## 📤 Outputs |
| 43 | + |
| 44 | +| Name | Description | |
| 45 | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 46 | +| `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 | |
| 47 | +| `artifact-name` | Name of the uploaded jar artifact, echoed back for convenience | |
| 48 | + |
| 49 | +## 💻 Example Usage |
| 50 | + |
| 51 | +Building and testing a service, then building an OCI image from the very same jar. `upload-jar` is opt-in |
| 52 | +because the workflow also serves pure libraries, which have no image to build and nothing to hand over: |
| 53 | + |
| 54 | +```yaml |
| 55 | +name: CI |
| 56 | + |
| 57 | +on: |
| 58 | + pull_request: |
| 59 | + push: |
| 60 | + branches: [main] |
| 61 | + |
| 62 | +jobs: |
| 63 | + build-and-test: |
| 64 | + # ⚠️ use tagged version here |
| 65 | + uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/java-build.yml@main |
| 66 | + with: |
| 67 | + java-version: "21" |
| 68 | + sonar: true |
| 69 | + dockerhub-login: true |
| 70 | + # opt in to the jar upload: only projects that ship an OCI image need it |
| 71 | + upload-jar: true |
| 72 | + secrets: |
| 73 | + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} |
| 74 | + dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN_PULL_ONLY }} |
| 75 | + sonar-token: ${{ secrets.SONAR_TOKEN }} |
| 76 | + |
| 77 | + build-image: |
| 78 | + needs: build-and-test |
| 79 | + # ⚠️ use tagged version here |
| 80 | + uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/docker-build.yml@main |
| 81 | + with: |
| 82 | + image-name: docker-regis.iex.ec/my-service |
| 83 | + image-tag: dev-${{ github.sha }} |
| 84 | + registry: docker-regis.iex.ec |
| 85 | + push: true |
| 86 | + # the jar built above is downloaded into the build context |
| 87 | + artifact-name: ${{ needs.build-and-test.outputs.artifact-name }} |
| 88 | + artifact-path: build/libs |
| 89 | + build-args: jar=${{ needs.build-and-test.outputs.jar-path }} |
| 90 | + secrets: |
| 91 | + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} |
| 92 | + dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN_PULL_ONLY }} |
| 93 | + username: ${{ secrets.NEXUS_USERNAME }} |
| 94 | + password: ${{ secrets.NEXUS_PASSWORD }} |
| 95 | +``` |
| 96 | +
|
| 97 | +## 📝 Notes |
| 98 | +
|
| 99 | +- 🔎 The SonarQube Gradle plugin reads the branch, pull request number and repository from the `GITHUB_*` environment |
| 100 | + variables, so none of the `sonar.pullrequest.*` or `sonar.branch.name` properties need to be passed. |
| 101 | + The `sonar.projectKey` property is derived from SonarQube Gradle plugin and Gradle project properties. |
| 102 | + The `sonar.organization` property (mandatory on SonarQube Cloud) is not auto-detected and derived from the calling repository. |
| 103 | + The checkout uses `fetch-depth: 0` because Sonar attributes lines to authors through `git blame`. |
| 104 | +- 🏷️ The checkout also needs the full history because the iExec `build.gradle` files derive the project version |
| 105 | + from the tag pointing at `HEAD`, appending `-NEXT-SNAPSHOT` when there is none. |
| 106 | +- 🔄 `refresh-dependencies` defaults to `false`. `setup-gradle` caches the Gradle home between runs — writing it |
| 107 | + from the default branch and restoring it read-only elsewhere — so a build can legitimately resolve against a |
| 108 | + cache populated days earlier. That only matters for changing modules (`-SNAPSHOT`) and dynamic versions, which |
| 109 | + Gradle re-checks at most every 24 hours; enable the input when a project depends on one. For the pinned versions |
| 110 | + the iExec projects resolve from jitpack, it forces Gradle to revalidate the metadata of every module on every |
| 111 | + build for no benefit. |
| 112 | +- 📤 The jar artifact is uploaded flat, so `artifact-path: build/libs` in `docker-build.yml` restores it at the |
| 113 | + path reported by the `jar-path` output. |
| 114 | +- 🧩 Pure libraries pass `upload-jar: false`: no artifact is uploaded and the `jar-path` output is never set |
| 115 | + (it evaluates to an empty string on the caller side, which is safe to ignore). |
| 116 | +- 🔐 The workflow declares `permissions: contents: read` + `actions: write`: the first is needed by `checkout`, |
| 117 | + the second by `upload-artifact`. An explicit `permissions` block resets every other scope to `none`, so dropping |
| 118 | + `actions: write` silently breaks the report artifact. |
| 119 | +- 🌐 The `test-reports` artifact (7-day retention) contains the human-readable HTML reports from `build/reports` |
| 120 | + (unit tests, JaCoCo coverage). GitHub does not render HTML inline on a PR: open it from PR → **Checks** → the run |
| 121 | + → **Summary** (bottom *Artifacts* section), download, unzip and open locally. `upload-artifact` also exposes an |
| 122 | + `artifact-url` output if you later want to post a direct download link as a PR comment. |
| 123 | + |
| 124 | +## 🛠️ Troubleshooting |
| 125 | + |
| 126 | +- **`Could not resolve com.github.iExecBlockchainComputing...`** — the dependency is not on jitpack yet. jitpack |
| 127 | + builds a version on first request, so a freshly pushed tag can take a few minutes to become resolvable. |
| 128 | +- **`Cannot perform inline analysis, no branch or pull request found`** — the checkout is shallow. This workflow |
| 129 | + sets `fetch-depth: 0`; a caller wrapping it differently has to do the same. |
| 130 | +- **`You must define the following mandatory properties ... sonar.organization`** — the SonarQube Cloud organization |
| 131 | + could not be resolved. Either the repository owner is not a SonarQube Cloud organization key, or the project |
| 132 | + lives under a different one. Update the project configuration on SonarQube Cloud. |
| 133 | +- **Testcontainers fails pulling an image** — set `dockerhub-login: true` and pass the Docker Hub secrets. |
0 commit comments