name: release on: push: # NOTE: introducing a "tags:" filter makes GitHub treat any Git ref kind that # is NOT filtered as "undefined", and the workflow then stops running for it. # So branches MUST be listed explicitly here; without "branches: ['**']" the # tag filter below would silently disable all branch-push builds. '**' (not # '*') is required to match branch names containing slashes (e.g. releases/x). branches: - '**' # Reviewed-fork-PR tags. A maintainer fetches a fork PR head, reviews the # exact SHA, then pushes one of these tags at that SHA. Because a tag push is # a push event in this (base) repo, the run executes in trusted context WITH # secrets — the only way to run secret-bearing validation on fork-authored # code. '*-reviewed-for-compile' runs a compile-only build with NO secrets # (cheap sanity check); '*-reviewed-for-build' runs the full build with tests # and secrets. Safety rests on the maintainer having read that exact SHA # before pushing the tag. See the "Determine build mode" step below. tags: - '*-reviewed-for-compile' - '*-reviewed-for-build' # paths-ignore is not evaluated for tag pushes (GitHub rule), so it only # scopes branch pushes, exactly as before. paths-ignore: - 'wiki/**' - '.github/workflows/*' - '**/*.md' - 'README.md' - 'docs/**' # Auto-build submitted PRs. A same-repo PR (branch pushed by a committer) runs # the full build with tests and secrets — same trust as a direct branch push. # A fork PR runs compile-only with NO secrets (GitHub withholds secrets from # fork pull_request runs at the event level); the secret-bearing full build of # fork code stays gated behind a maintainer pushing a *-reviewed-for-build tag. # No branches: filter here — for pull_request that would filter by BASE branch; # omitting it targets PRs into any branch. Draft PRs are excluded by the build # job's if:. See the "Determine build mode" step for the fork/same-repo split. pull_request: types: [opened, synchronize, reopened, ready_for_review] paths-ignore: - 'wiki/**' - '.github/workflows/*' - '**/*.md' - 'README.md' - 'docs/**' workflow_dispatch: inputs: skip_tests: description: Skip tests (set to "true" to skip all tests) default: 'false' local_target_platform: description: Build with a local target platform (set to "true" to use local target platform) default: 'false' runner_cpus: description: Empty means ubuntu-latest, usually 4 CPUs with little disk/memory, but if you have runners named ubuntu-latest-Xcpu, it can be 4, 8, 16, and 64 as well, using, e.g., ubuntu-latest-16cpu; default is ubuntu-latest default: '' firefox_version: description: Version to use for Selenium tests; default is 137.0, but could, e.g., be "141.0" or "latest" default: '140.0' jobs: # The "changes" job will cause the "build" job to get skipped for trivial changes changes: permissions: {} runs-on: ubuntu-latest outputs: should_run: ${{ steps.filter.outputs.code }} steps: - uses: actions/checkout@v4 - id: filter uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 with: # Under the default 'some' quantifier a negated rule still counts as a # match (picomatch treats '!README.md' as "anything but README.md"), # so a changed README.md would set code=true. 'some-with-excludes' # (v4+) makes the '!' rules act as real exclusions: code is true only # when a changed file matches the positive '**' rule AND none of the # excludes. predicate-quantifier: 'some-with-excludes' filters: | code: - '**' - '!wiki/**' - '!.github/workflows/*' - '!**/*.md' - '!README.md' - '!docs/**' build: needs: changes if: >- ${{ (needs.changes.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} permissions: contents: write checks: write actions: read runs-on: ${{ github.event.inputs.runner_cpus != '' && format('ubuntu-latest-{0}cpu', github.event.inputs.runner_cpus) || 'ubuntu-latest' }} outputs: skip_tests: ${{ steps.mode.outputs.skip_tests }} steps: - name: Determine build mode id: mode shell: bash # skip_tests=true means "compile only, no tests, no secrets". It is true # for a workflow_dispatch with skip_tests=true, for a push of a # *-reviewed-for-compile tag, OR for a fork-originated pull_request (fork # runs get no secrets, so we compile without tests; the secret-bearing # full build of fork code is gated behind a *-reviewed-for-build tag). # Everything else (branches, same-repo PRs, *-reviewed-for-build tags) # runs the full build with tests and secrets. run: | skip=false if [[ "${{ github.event.inputs.skip_tests }}" == "true" ]]; then skip=true; fi if [[ "${{ github.ref }}" == refs/tags/*-reviewed-for-compile ]]; then skip=true; fi if [[ "${{ github.event_name }}" == "pull_request" \ && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then skip=true fi echo "skip_tests=$skip" >> "$GITHUB_OUTPUT" - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@v1.3.1 with: # this might remove tools that are actually needed, # if set to "true" but frees about 6 GB tool-cache: false # all of these default to true, but feel free to set to # "false" if necessary for your workflow android: false dotnet: true haskell: true large-packages: true docker-images: false swap-storage: true - name: Allocate swap space shell: bash run: | sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=30 sudo chmod 600 /mnt/large-swapfile sudo mkswap /mnt/large-swapfile sudo chown root:root /mnt/large-swapfile sudo swapon /mnt/large-swapfile - name: Create TMP space under /mnt/tmp shell: bash run: | sudo mkdir /mnt/tmp sudo chmod 777 /mnt/tmp - name: Collect Workflow Telemetry uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0 with: comment_on_pr: false - name: Setup firefox id: setup-firefox uses: browser-actions/setup-firefox@5914774dda97099441f02628f8d46411fcfbd208 # v1.7.0 with: firefox-version: ${{ github.event.inputs.firefox_version == '' && '140.0' || github.event.inputs.firefox_version }} - run: | echo Installed firefox versions: ${{ steps.setup-firefox.outputs.firefox-version }} ${{ steps.setup-firefox.outputs.firefox-path }} --version - name: Check out the repository to the runner uses: actions/checkout@v4 with: fetch-depth: '500' - name: Install JDK 8 uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '8' mvn-toolchain-id: 'JavaSE-1.8' - name: Install JDK 17 uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' mvn-toolchain-id: 'JavaSE-17' - name: Install JDK 25 uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '25' mvn-toolchain-id: 'JavaSE-25' - name: Setup Android SDK uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 with: log-accepted-android-sdk-licenses: 'false' cmdline-tools-version: 9123335 # This corresponds to human-friendly version number 8.0 - name: Start MongoDB uses: supercharge/mongodb-github-action@315db7fe45ac2880b7758f1933e6e5d59afd5e94 #v1.12.1 with: mongodb-version: 7.0 - name: Start RabbitMQ uses: Namoshek/rabbitmq-github-action@80a182e44c7f60bc3417a8689d6a24fe9d08b27b #v1.2.0 with: version: '3.8.9' ports: '5672:5672' - shell: bash env: # Or as an environment variable TMP: /mnt/tmp # These five are consumed only at TEST runtime. For a compile-only run # (skip_tests) they resolve to '' so the real secret values are never # placed in the process environment where reviewed-but-fork-authored # code could read them. The full build (skip_tests false) gets them all. AWS_S3_TEST_S3ACCESSID: ${{ steps.mode.outputs.skip_tests == 'true' && '' || secrets.AWS_S3_TEST_S3ACCESSID }} AWS_S3_TEST_S3ACCESSKEY: ${{ steps.mode.outputs.skip_tests == 'true' && '' || secrets.AWS_S3_TEST_S3ACCESSKEY }} GEONAMES_ORG_USERNAMES: ${{ steps.mode.outputs.skip_tests == 'true' && '' || secrets.GEONAMES_ORG_USERNAMES }} GOOGLE_MAPS_AUTHENTICATION_PARAMS: ${{ steps.mode.outputs.skip_tests == 'true' && '' || secrets.GOOGLE_MAPS_AUTHENTICATION_PARAMS }} POLAR_DATA_BASE_URL: ${{ vars.POLAR_DATA_BASE_URL }} POLAR_DATA_BEARER_TOKEN: ${{ steps.mode.outputs.skip_tests == 'true' && '' || secrets.POLAR_DATA_BEARER_TOKEN }} APP_PARAMETERS: "-Daws.region=eu-west-1" JAVA8_HOME: ${{env.JAVA_HOME_8_X64}} JAVA17_HOME: ${{env.JAVA_HOME_17_X64}} run: | ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '4' || github.event.inputs.runner_cpus }} ${{ steps.mode.outputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1 - name: show disk stats if: always() run: df -h - name: Upload build log uses: actions/upload-artifact@v4 if: always() with: name: build.log path: build.log retention-days: 90 - name: Collect Test Reports uses: dorny/test-reporter@a6ddd83ac95ff4586f5d3aceeb314d9a1841db95 # v3.0.0 # Skip for compile-only runs: no tests ran, so there are no TEST-*.xml # and fail-on-error would otherwise error on zero matches. if: ${{ always() && steps.mode.outputs.skip_tests != 'true' }} with: name: Maven Tests path: '**/TEST-*.xml' max-annotations: '1' list-suites: failed list-tests: failed reporter: java-junit fail-on-error: true - name: Build Release if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }} shell: bash run: | ./configuration/buildAndUpdateProduct.sh -u -L ${{ github.event.inputs.skip_tests == 'true' && '-n untested' || '' }} release - name: Determine release name run: | echo "SIMPLE_VERSION_INFO=$( basename dist/* )" >>${GITHUB_ENV} - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: test-results path: | **/surefire-reports/** retention-days: 90 - name: Upload distribution artifact uses: actions/upload-artifact@v4 if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }} with: name: ${{ env.SIMPLE_VERSION_INFO }} path: dist/**/* retention-days: 90 - name: Create Release id: create_release if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }} uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 with: tag_name: ${{ env.SIMPLE_VERSION_INFO }} target_commitish: ${{ github.ref_name }} name: ${{ env.SIMPLE_VERSION_INFO }} draft: false prerelease: false - name: Upload Release Asset tar.gz id: upload-release-asset-tar-gz if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }} uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ./dist/${{ env.SIMPLE_VERSION_INFO }}/${{ env.SIMPLE_VERSION_INFO }}.tar.gz asset_name: ${{ env.SIMPLE_VERSION_INFO }}.tar.gz asset_content_type: application/x-tar - name: Upload Release Asset release_notes.txt id: upload-release-asset-release-notes-txt if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-24' || github.ref == 'refs/heads/docker-25' || startsWith(github.ref, 'refs/heads/releases/') }} uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ./dist/${{ env.SIMPLE_VERSION_INFO }}/release-notes.txt asset_name: release-notes.txt asset_content_type: text/plain - name: Trigger CI job env: CI_JOB_USERNAME: ${{ vars.CI_JOB_USERNAME }} CI_JOB_PASSWORD: ${{ secrets.CI_JOB_PASSWORD }} CI_JOB_TOKEN: ${{ secrets.CI_JOB_TOKEN }} if: ${{ always() && env.CI_JOB_USERNAME != '' && env.CI_JOB_PASSWORD != '' && env.CI_JOB_TOKEN != '' }} shell: bash run: | if [[ "${{ github.ref }}" =~ refs/heads/releases/.* ]]; then JOB=$( echo "${{ github.ref }}" | sed -e 's/^refs\/heads\/releases\///' ) else JOB=$( echo "${{ github.ref }}" | sed -e 's/^refs\/heads\///' ) fi echo "Identified CI job: ${JOB}" curl -u "${{ vars.CI_JOB_USERNAME }}:${{ secrets.CI_JOB_PASSWORD}}" ${{ vars.CI_BASE_URL }}/job/${JOB}/build?token=${{ secrets.CI_JOB_TOKEN }} # build-gate is the REQUIRED status check in branch protection. It must go # green ONLY for a genuine full build (tests actually ran) or a legitimate # trivial-change skip. A compile-only run (skip_tests true, e.g. a # *-reviewed-for-compile tag or a workflow_dispatch with skip_tests=true) must # NOT satisfy it — so those cannot qualify a PR for merge. build-gate: permissions: {} needs: [changes, build] if: always() runs-on: ubuntu-latest steps: - run: | if [[ "${{ needs.build.result }}" == "success" && "${{ needs.build.outputs.skip_tests }}" != "true" ]]; then echo "Full build with tests passed." elif [[ "${{ needs.build.result }}" == "skipped" && "${{ needs.changes.outputs.should_run }}" == "false" ]]; then echo "No relevant changes — skipping is OK." elif [[ "${{ needs.build.result }}" == "success" && "${{ needs.build.outputs.skip_tests }}" == "true" ]]; then echo "Compile-only build (tests skipped) does not satisfy the required check." exit 1 else echo "Build failed or was cancelled." exit 1 fi # compile-gate is an ADVISORY (non-required) check. It reflects only whether # the build job compiled successfully, regardless of whether tests ran — a full # build implies compilation succeeded. It gives *-reviewed-for-compile runs a # distinct green/red on the PR without touching the required build-gate check. compile-gate: permissions: {} needs: [changes, build] if: always() runs-on: ubuntu-latest steps: - run: | if [[ "${{ needs.build.result }}" == "success" ]]; then echo "Compilation succeeded." elif [[ "${{ needs.build.result }}" == "skipped" && "${{ needs.changes.outputs.should_run }}" == "false" ]]; then echo "No relevant changes — nothing to compile." else echo "Compilation failed or was cancelled." exit 1 fi