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 or develop (including merges).
    • pull_request: Runs for PRs whose target branch is main or develop.
  • 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’s cache: 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 from buildbuild runs a broader lifecycle (including tests, checks). Here, tests already ran in the previous step, so assemble 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 or hero-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 with if: 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:

  1. Triggers on pushes/PRs to main/develop.

  2. Deduplicates runs per branch with concurrency.

  3. Prepares a consistent JDK 21 environment and efficient Gradle caches.

  4. Runs tests, assembles the app, and publishes:

    • Test reports (always).
    • The app JAR (only on successful pushes).
  5. Uses minimal permissions for security.

Finalize the last step by specifying the artifact path (and optionally name and retention-days) to complete the workflow.