CI Workflow — Detailed Documentation - VittorioDeMarzi/hero-beans GitHub Wiki
CI Workflow — Detailed Documentation
This document explains every section and step in GitHub Actions workflow, what it does, and why it’s used.
Workflow metadata
name: CI
- What: Human-readable name shown in the Actions tab.
- Why: Makes it easy to identify this pipeline among others (e.g., “CI” vs “Release”).
Triggers
on:
on:
push:
branches: [ "main", "develop" ]
pull_request:
branches: [ "main", "develop" ]
-
What: Defines when the workflow runs.
- push: Runs when commits are pushed directly to
main
ordevelop
(including merges). - pull_request: Runs for PRs whose target branch is
main
ordevelop
.
- push: Runs when commits are pushed directly to
-
Why: Ensures the CI executes both for feature work entering via PR and for direct changes on the primary branches.
Concurrency control
concurrency:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
-
What: Prevents overlapping runs for the same ref (branch/PR).
group
: Groups runs by the current Git ref (e.g., one group per branch).cancel-in-progress
: Cancels any previous in-flight run of the same group when a new one starts.
-
Why: Saves time and runner capacity; only the latest commit on a branch is validated.
Jobs
jobs.build-and-test
jobs:
build-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
-
What:
runs-on: ubuntu-latest
: Uses GitHub’s hosted Ubuntu runner VM.permissions.contents: read
: Minimal token scope so steps can read repository contents; safer default.
-
Why: Ubuntu runners are fast and commonly supported. Tight permissions improve security (principle of least privilege).
Steps (in order)
1) Checkout
- name: Checkout
uses: actions/checkout@v4
- What: Retrieves the repository code into the runner workspace.
- Why: Subsequent steps (Gradle, tests) need the source files.
2) Set up JDK
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- What: Installs Temurin JDK 21
- Why:
- Ensures a consistent Java toolchain (JDK 21) that matches your project.
3) Setup Gradle caching
- name: Setup Gradle caching
uses: gradle/gradle-build-action@v3
with:
gradle-home-cache-cleanup: true
-
What: Adds smarter Gradle caching & build scans integration.
gradle-home-cache-cleanup: true
periodically cleans the cache to prevent unbounded growth.
-
Why: Reduces build time by caching Gradle user home artifacts/metadata beyond dependency jars, and keeps cache size healthy over time.
Note: You’re using both
setup-java
’scache: gradle
and the Gradle Build Action. This is a common pattern; the Gradle action manages caches more comprehensively.
4) Grant execute to gradlew
- name: Grant execute to gradlew
run: chmod +x gradlew
- What: Ensures the Gradle wrapper script is executable on Linux.
- Why: Without this,
./gradlew
might fail with a permission error on fresh runners.
5) Run tests
- name: Run tests
run: ./gradlew test --no-daemon
-
What: Executes your test suite.
--no-daemon
runs Gradle without the background daemon process.
-
Why:
- Validates code correctness on every change.
--no-daemon
avoids leaving background processes in ephemeral CI environments and can produce cleaner logs.
6) Build (assemble)
- name: Build (assemble)
run: ./gradlew assemble --no-daemon
-
What: Runs the
assemble
task to compile and package artifacts without running tests. -
Why: Produces the build outputs (e.g., JAR) quickly after tests already passed.
Tip:
assemble
differs frombuild
—build
runs a broader lifecycle (including tests, checks). Here, tests already ran in the previous step, soassemble
avoids duplicate work.
7) Upload test report (HTML)
- name: Upload test report (HTML)
if: always()
uses: actions/upload-artifact@v4
with:
name: junit-report-html
path: build/reports/tests/test
-
What: Publishes the HTML test report as a downloadable artifact.
if: always()
ensures the report uploads even if tests fail, which is crucial for debugging.name
is the artifact label in the Actions UI.path
points to Gradle’s default test report directory.
-
Why: Keeps test evidence accessible from the workflow run, helping triage failures.
8) Upload app JAR
- name: Upload app JAR
if: ${{ success() && github.event_name == 'push' }}
uses: actions/upload-artifact@v4
with:
# (missing fields)
-
What: Intends to upload the built application artifact (e.g., JAR) only when:
- the workflow succeeded, and
- the event is a push (not a pull request).
-
Why: Avoids publishing artifacts for failing builds or unmerged PRs; suitable for downstream CD steps (e.g., deployment).
Action needed: the
with:
block is incomplete. You typically need:
name
: a clear label (e.g.,app-jar
orhero-beans-jar
)path
: the actual file(s) to upload (e.g.,build/libs/*.jar
)- (optional)
retention-days
: how long GitHub keeps the artifact
Example fields to add (conceptually):
with:
name: app-jar
path: build/libs/*.jar
retention-days: 7
Additional notes & rationale
- Permissions hardening: You set only
contents: read
. If later steps need to create releases or write to the repo, you’ll have to expand permissions. - Test/report separation: Running tests before
assemble
avoids double execution. Uploading reports withif: always()
guarantees visibility into failures. - Push vs PR behavior: Uploading the JAR only on
push
prevents accidental deployment artifacts from unmerged PRs. - Linux specifics: The
chmod +x
step is required on Linux runners but harmless on others; keeping it is a good cross-platform practice.
Summary
This CI pipeline:
-
Triggers on pushes/PRs to
main
/develop
. -
Deduplicates runs per branch with concurrency.
-
Prepares a consistent JDK 21 environment and efficient Gradle caches.
-
Runs tests, assembles the app, and publishes:
- Test reports (always).
- The app JAR (only on successful pushes).
-
Uses minimal permissions for security.
Finalize the last step by specifying the artifact path
(and optionally name
and retention-days
) to complete the workflow.