From 039076ca1b6ef380b79ab9c684f940d6da8262e4 Mon Sep 17 00:00:00 2001 From: Alex Andres Date: Mon, 28 Sep 2026 22:39:46 +0200 Subject: [PATCH 1/4] ci: harden the release workflow Pass the release notes and the development version to the scripts through the environment instead of expanding them into the script text. The notes are commit subjects, so a quote or a backtick in one broke the release step and ran as shell code. Take the release version from the pom, which is what release:prepare tags, instead of letting git-cliff bump it from the commit types. The GitHub release now always matches the Maven tag, and --verify-tag makes a mismatch fail instead of creating an unbuilt tag on main. Grant write access only to the jobs that push, and keep two releases from running at once. --- .github/workflows/release.yml | 56 ++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b62d411..300da10c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,10 +17,21 @@ env: WEBRTC_CHECKOUT_FOLDER: webrtc WEBRTC_INSTALL_FOLDER: webrtc/build +# Only the jobs that push commits, tags or the GitHub release get write access. +permissions: + contents: read + +# Two releases at once would race on the pushed commits and tags. +concurrency: + group: release + cancel-in-progress: false + jobs: changelog: name: Generate changelog runs-on: ubuntu-latest + permissions: + contents: write outputs: release_body: ${{ steps.git-cliff.outputs.content }} release_version: ${{ steps.git-cliff.outputs.version }} @@ -36,31 +47,46 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + # The version is the one release:prepare will tag: the pom's version without + # -SNAPSHOT. git-cliff is told it rather than bumping from the commit types, so + # that the changelog, the Maven tag and the GitHub release always agree. + - name: Determine the release version + id: release-version + run: | + version=$(sed -n '0,//s|.*\([^<]*\).*|\1|p' pom.xml) + version="${version%-SNAPSHOT}" + echo "Release version: $version" + echo "tag=v$version" >> "$GITHUB_OUTPUT" + - name: Generate changelog for the current build uses: orhun/git-cliff-action@v4 id: git-cliff with: config: .github/changelog/cliff-release.toml - args: -v -u --strip footer --bump --use-branch-tags + args: -v -u --strip footer --tag ${{ steps.release-version.outputs.tag }} --use-branch-tags - name: Update CHANGELOG.md uses: orhun/git-cliff-action@v4 with: config: .github/changelog/cliff.toml - args: -v --bump --use-branch-tags + args: -v --tag ${{ steps.release-version.outputs.tag }} --use-branch-tags env: OUTPUT: CHANGELOG.md - name: Commit CHANGELOG.md if: ${{ !inputs.dryRun }} + env: + RELEASE_TAG: ${{ steps.release-version.outputs.tag }} run: | git add CHANGELOG.md - git commit -m "chore(release): Update CHANGELOG.md for ${{ steps.git-cliff.outputs.version }}" + git commit -m "chore(release): Update CHANGELOG.md for $RELEASE_TAG" git push prepare-release: name: Prepare release needs: changelog + permissions: + contents: write outputs: release_body: ${{ needs.changelog.outputs.release_body }} release_version: ${{ needs.changelog.outputs.release_version }} @@ -106,10 +132,12 @@ jobs: # A dry run transforms the poms and writes release.properties, but creates no # commit and no tag, so there is nothing to amend or push below. - name: Prepare release + env: + DEVELOPMENT_VERSION: ${{ inputs.developmentVersion }} run: | mvn release:prepare -DskipTests -DpushChanges=false \ -DdryRun=${{ inputs.dryRun }} \ - -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} + -DdevelopmentVersion="$DEVELOPMENT_VERSION" # Get the release version from the release.properties file RELEASE_VERSION=$(grep "project.rel.dev.onvoid.webrtc\\\:webrtc-java=" release.properties 2>/dev/null | cut -d'=' -f2 || true) @@ -118,11 +146,13 @@ jobs: - name: Push release commit and tag if: ${{ !inputs.dryRun }} + env: + DEVELOPMENT_VERSION: ${{ inputs.developmentVersion }} run: | # Update versions.ts file - echo "Updating versions.ts with release version: $RELEASE_VERSION and development version: ${{ github.event.inputs.developmentVersion }}" + echo "Updating versions.ts with release version: $RELEASE_VERSION and development version: $DEVELOPMENT_VERSION" sed -i "s/VERSION: '.*'/VERSION: '$RELEASE_VERSION'/g" docs/.vitepress/versions.ts - sed -i "s/VERSION_SNAPSHOT: '.*'/VERSION_SNAPSHOT: '${{ github.event.inputs.developmentVersion }}'/g" docs/.vitepress/versions.ts + sed -i "s/VERSION_SNAPSHOT: '.*'/VERSION_SNAPSHOT: '$DEVELOPMENT_VERSION'/g" docs/.vitepress/versions.ts # Add the updated file to the existing commit git add docs/.vitepress/versions.ts @@ -409,16 +439,22 @@ jobs: needs: publish-central if: ${{ !inputs.dryRun }} runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v6 + # The notes are commit subjects, so they go through the environment and a file + # rather than into the script, where a quote or a backtick would be run. + # --verify-tag fails instead of creating a tag that release:prepare did not. - name: Publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ needs.publish-central.outputs.release_version }} + RELEASE_BODY: ${{ needs.publish-central.outputs.release_body }} run: | - version="${{ needs.publish-central.outputs.release_version }}" - body="${{ needs.publish-central.outputs.release_body }}" - - gh release create $version -d -t "Release $version" -n "$body" \ No newline at end of file + printf '%s\n' "$RELEASE_BODY" > release-notes.md + gh release create "$RELEASE_VERSION" --draft --verify-tag \ + -t "Release $RELEASE_VERSION" -F release-notes.md \ No newline at end of file From a41e8b9a743783c0dd1b165f7368b8f832714e87 Mon Sep 17 00:00:00 2001 From: Alex Andres Date: Mon, 28 Sep 2026 22:39:53 +0200 Subject: [PATCH 2/4] ci: keep the native build caches warm Build on every push to main that can change a build, not only on source changes. A PR can restore caches only from its own ref and from main, so when main skipped a build change, every following PR compiled WebRTC from scratch under a key that already existed on another PR's ref. Pom, CMake and submodule changes now also deploy a snapshot. Save the WebRTC and FFmpeg caches right after the build instead of in the post step of actions/cache, which saves only when the whole job succeeds; a failing test threw the finished native build away. Cancel a PR's running build when a newer push supersedes it, and give the workflow a read-only token. --- .github/actions/build-macos-x86_64/action.yml | 28 ++++++++++++++++--- .github/actions/build/action.yml | 28 ++++++++++++++++--- .../actions/release-macos-x86_64/action.yml | 28 ++++++++++++++++--- .github/actions/release/action.yml | 28 ++++++++++++++++--- .github/workflows/build.yml | 22 ++++++++++++--- 5 files changed, 114 insertions(+), 20 deletions(-) diff --git a/.github/actions/build-macos-x86_64/action.yml b/.github/actions/build-macos-x86_64/action.yml index 816ac11f..90a175c8 100644 --- a/.github/actions/build-macos-x86_64/action.yml +++ b/.github/actions/build-macos-x86_64/action.yml @@ -27,8 +27,13 @@ runs: run: echo "WEBRTC_CACHE_BRANCH=$(sed -n 's|.*branch-heads/\([^<]*\).*|\1|p' webrtc-jni/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up WebRTC cache - uses: actions/cache@v6 + # Restored here, saved right after the build below: the post step of + # actions/cache only saves when the whole job succeeds, so a failing test + # would otherwise throw away a WebRTC build that took the better part of an + # hour. + - id: webrtc-cache + name: Restore WebRTC cache + uses: actions/cache/restore@v6 with: path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} key: webrtc-${{ env.WEBRTC_CACHE_BRANCH }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-jni/src/main/cpp/dependencies/webrtc/CMakeLists.txt') }}-${{ hashFiles('webrtc-jni/pom.xml') }} @@ -38,8 +43,9 @@ runs: run: echo "FFMPEG_VERSION=$(sed -n 's|.*\([^<]*\).*|\1|p' webrtc-java-media/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up FFmpeg cache - uses: actions/cache@v6 + - id: ffmpeg-cache + name: Restore FFmpeg cache + uses: actions/cache/restore@v6 with: path: ~/ffmpeg key: ffmpeg-${{ env.FFMPEG_VERSION }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-java-media/src/main/cpp/dependencies/ffmpeg/CMakeLists.txt') }}-${{ hashFiles('webrtc-java-media/pom.xml') }} @@ -80,6 +86,20 @@ runs: run: mvn package -DskipTests -Pmacos-cross-x86_64 shell: bash + - name: Save WebRTC cache + if: steps.webrtc-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} + key: ${{ steps.webrtc-cache.outputs.cache-primary-key }} + + - name: Save FFmpeg cache + if: steps.ffmpeg-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/ffmpeg + key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} + - name: Test run: JAVA_HOME="${{ steps.jdk-x64.outputs.path }}" mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 shell: bash diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 887b1aa4..ec6e7055 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -27,8 +27,13 @@ runs: run: echo "WEBRTC_CACHE_BRANCH=$(sed -n 's|.*branch-heads/\([^<]*\).*|\1|p' webrtc-jni/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up WebRTC cache - uses: actions/cache@v6 + # Restored here, saved right after the build below: the post step of + # actions/cache only saves when the whole job succeeds, so a failing test + # would otherwise throw away a WebRTC build that took the better part of an + # hour. + - id: webrtc-cache + name: Restore WebRTC cache + uses: actions/cache/restore@v6 with: path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} key: webrtc-${{ env.WEBRTC_CACHE_BRANCH }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-jni/src/main/cpp/dependencies/webrtc/CMakeLists.txt') }}-${{ hashFiles('webrtc-jni/pom.xml') }} @@ -38,8 +43,9 @@ runs: run: echo "FFMPEG_VERSION=$(sed -n 's|.*\([^<]*\).*|\1|p' webrtc-java-media/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up FFmpeg cache - uses: actions/cache@v6 + - id: ffmpeg-cache + name: Restore FFmpeg cache + uses: actions/cache/restore@v6 with: path: ~/ffmpeg key: ffmpeg-${{ env.FFMPEG_VERSION }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-java-media/src/main/cpp/dependencies/ffmpeg/CMakeLists.txt') }}-${{ hashFiles('webrtc-java-media/pom.xml') }} @@ -74,6 +80,20 @@ runs: fi shell: bash + - name: Save WebRTC cache + if: steps.webrtc-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} + key: ${{ steps.webrtc-cache.outputs.cache-primary-key }} + + - name: Save FFmpeg cache + if: steps.ffmpeg-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/ffmpeg + key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} + - name: Test if: ${{ inputs.platform-name != 'linux_arm' && inputs.platform-name != 'linux_arm64' && inputs.platform-name != 'windows_arm64' }} run: mvn -B jar:jar surefire:test diff --git a/.github/actions/release-macos-x86_64/action.yml b/.github/actions/release-macos-x86_64/action.yml index cae846a1..34051575 100644 --- a/.github/actions/release-macos-x86_64/action.yml +++ b/.github/actions/release-macos-x86_64/action.yml @@ -19,8 +19,13 @@ runs: run: echo "WEBRTC_CACHE_BRANCH=$(sed -n 's|.*branch-heads/\([^<]*\).*|\1|p' webrtc-jni/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up WebRTC cache - uses: actions/cache@v6 + # Restored here, saved right after the build below: the post step of + # actions/cache only saves when the whole job succeeds, so a failing test + # would otherwise throw away a WebRTC build that took the better part of an + # hour. + - id: webrtc-cache + name: Restore WebRTC cache + uses: actions/cache/restore@v6 with: path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} key: webrtc-${{ env.WEBRTC_CACHE_BRANCH }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-jni/src/main/cpp/dependencies/webrtc/CMakeLists.txt') }}-${{ hashFiles('webrtc-jni/pom.xml') }} @@ -30,8 +35,9 @@ runs: run: echo "FFMPEG_VERSION=$(sed -n 's|.*\([^<]*\).*|\1|p' webrtc-java-media/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up FFmpeg cache - uses: actions/cache@v6 + - id: ffmpeg-cache + name: Restore FFmpeg cache + uses: actions/cache/restore@v6 with: path: ~/ffmpeg key: ffmpeg-${{ env.FFMPEG_VERSION }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-java-media/src/main/cpp/dependencies/ffmpeg/CMakeLists.txt') }}-${{ hashFiles('webrtc-java-media/pom.xml') }} @@ -67,6 +73,20 @@ runs: run: mvn package -DskipTests -Pmacos-cross-x86_64 shell: bash + - name: Save WebRTC cache + if: steps.webrtc-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} + key: ${{ steps.webrtc-cache.outputs.cache-primary-key }} + + - name: Save FFmpeg cache + if: steps.ffmpeg-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/ffmpeg + key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} + - name: Test run: JAVA_HOME="${{ steps.jdk-x64.outputs.path }}" mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 shell: bash diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml index 460b365d..c1a14dae 100644 --- a/.github/actions/release/action.yml +++ b/.github/actions/release/action.yml @@ -18,8 +18,13 @@ runs: run: echo "WEBRTC_CACHE_BRANCH=$(sed -n 's|.*branch-heads/\([^<]*\).*|\1|p' webrtc-jni/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up WebRTC cache - uses: actions/cache@v6 + # Restored here, saved right after the build below: the post step of + # actions/cache only saves when the whole job succeeds, so a failing test + # would otherwise throw away a WebRTC build that took the better part of an + # hour. + - id: webrtc-cache + name: Restore WebRTC cache + uses: actions/cache/restore@v6 with: path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} key: webrtc-${{ env.WEBRTC_CACHE_BRANCH }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-jni/src/main/cpp/dependencies/webrtc/CMakeLists.txt') }}-${{ hashFiles('webrtc-jni/pom.xml') }} @@ -29,8 +34,9 @@ runs: run: echo "FFMPEG_VERSION=$(sed -n 's|.*\([^<]*\).*|\1|p' webrtc-java-media/pom.xml)" >> "$GITHUB_ENV" shell: bash - - name: Set up FFmpeg cache - uses: actions/cache@v6 + - id: ffmpeg-cache + name: Restore FFmpeg cache + uses: actions/cache/restore@v6 with: path: ~/ffmpeg key: ffmpeg-${{ env.FFMPEG_VERSION }}-${{ inputs.platform-name }}-${{ hashFiles('webrtc-java-media/src/main/cpp/dependencies/ffmpeg/CMakeLists.txt') }}-${{ hashFiles('webrtc-java-media/pom.xml') }} @@ -62,6 +68,20 @@ runs: fi shell: bash + - name: Save WebRTC cache + if: steps.webrtc-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/${{ env.WEBRTC_INSTALL_FOLDER }} + key: ${{ steps.webrtc-cache.outputs.cache-primary-key }} + + - name: Save FFmpeg cache + if: steps.ffmpeg-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: ~/ffmpeg + key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} + - name: Test if: ${{ inputs.platform-name != 'linux_arm' && inputs.platform-name != 'linux_arm64' && inputs.platform-name != 'windows_arm64' }} run: mvn -B jar:jar surefire:test diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a195411..350cb732 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,10 +4,15 @@ on: push: branches: - main - paths: - - "**.cpp" - - "**.h" - - "**.java" + # Everything that can change a build runs here: poms, CMake files, the FFmpeg + # submodule and this directory too, not only sources. A PR cannot read the + # caches of another PR, only those of main, so a build change that main does + # not rebuild leaves every following PR to build WebRTC from scratch. + paths-ignore: + - "docs/**" + - "**.md" + - ".github/ISSUE_TEMPLATE/**" + - ".github/workflows/pages.yml" pull_request: branches: @@ -15,6 +20,15 @@ on: workflow_dispatch: +permissions: + contents: read + +# A new push to a PR supersedes its running build. On main every run is kept, +# since each one deploys a snapshot and refreshes the caches. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: WEBRTC_CHECKOUT_FOLDER: webrtc WEBRTC_INSTALL_FOLDER: webrtc/build From 35dea11da3516d69a7bd14483fa27f7da2f30c5e Mon Sep 17 00:00:00 2001 From: Alex Andres Date: Mon, 28 Sep 2026 23:13:48 +0200 Subject: [PATCH 3/4] ci: push a release only once it is prepared Fold the changelog job into prepare-release and commit CHANGELOG.md locally, before release:prepare. The changelog commit, the release commit, the tag and the development commit now go out in one atomic push at the end, so a failure on the way leaves main untouched instead of carrying a changelog for a release that never happened. Check the input before anything else: developmentVersion must be a SNAPSHOT that differs from the release version, the release tag must not exist yet, and release:prepare must choose the version the changelog was written for. Hand the pushed tag to the platform and publish jobs as an output instead of having each of them guess the newest tag in the repository. Run release:prepare with "clean validate" as its preparation goals. The default "clean verify" compiled WebRTC and FFmpeg on an uncached runner, about 35 of the 52 minutes of the last release, only for the platform jobs to build the tagged tree again. --- .github/workflows/release.yml | 175 ++++++++++++++-------------------- 1 file changed, 70 insertions(+), 105 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 300da10c..433eb998 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,43 +27,68 @@ concurrency: cancel-in-progress: false jobs: - changelog: - name: Generate changelog - runs-on: ubuntu-latest + # Nothing reaches main before release:prepare has succeeded: the changelog + # commit is made locally, release:prepare adds its two commits and the tag on + # top, and all of it is pushed in one atomic push at the end. A failure on the + # way leaves main untouched, so the release can simply be dispatched again. + prepare-release: + name: Prepare release + runs-on: ubuntu-22.04 permissions: contents: write outputs: + release_tag: ${{ steps.release-version.outputs.tag }} release_body: ${{ steps.git-cliff.outputs.content }} - release_version: ${{ steps.git-cliff.outputs.version }} - steps: - - name: Checkout + # The full history, for git-cliff. + - name: Checkout code uses: actions/checkout@v6 with: + ref: main fetch-depth: 0 - name: Git config run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" # The version is the one release:prepare will tag: the pom's version without # -SNAPSHOT. git-cliff is told it rather than bumping from the commit types, so # that the changelog, the Maven tag and the GitHub release always agree. - - name: Determine the release version + - name: Determine and check the versions id: release-version + env: + DEVELOPMENT_VERSION: ${{ inputs.developmentVersion }} run: | version=$(sed -n '0,//s|.*\([^<]*\).*|\1|p' pom.xml) version="${version%-SNAPSHOT}" - echo "Release version: $version" + echo "Release version: $version, next development version: $DEVELOPMENT_VERSION" + + if ! [[ "$DEVELOPMENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT$ ]]; then + echo "::error::developmentVersion must look like 1.2.3-SNAPSHOT, got '$DEVELOPMENT_VERSION'" + exit 1 + fi + if [ "${DEVELOPMENT_VERSION%-SNAPSHOT}" == "$version" ]; then + echo "::error::developmentVersion must differ from the release version $version" + exit 1 + fi + if git rev-parse -q --verify "refs/tags/v$version" >/dev/null; then + echo "::error::Tag v$version already exists" + exit 1 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=v$version" >> "$GITHUB_OUTPUT" - - name: Generate changelog for the current build + # Written outside the checkout, which release:prepare requires to be clean. + - name: Generate the release notes uses: orhun/git-cliff-action@v4 id: git-cliff with: config: .github/changelog/cliff-release.toml args: -v -u --strip footer --tag ${{ steps.release-version.outputs.tag }} --use-branch-tags + env: + OUTPUT: ${{ runner.temp }}/release-notes.md - name: Update CHANGELOG.md uses: orhun/git-cliff-action@v4 @@ -74,80 +99,49 @@ jobs: OUTPUT: CHANGELOG.md - name: Commit CHANGELOG.md - if: ${{ !inputs.dryRun }} env: RELEASE_TAG: ${{ steps.release-version.outputs.tag }} run: | git add CHANGELOG.md git commit -m "chore(release): Update CHANGELOG.md for $RELEASE_TAG" - git push - - prepare-release: - name: Prepare release - needs: changelog - permissions: - contents: write - outputs: - release_body: ${{ needs.changelog.outputs.release_body }} - release_version: ${{ needs.changelog.outputs.release_version }} - strategy: - fail-fast: false - matrix: - platform: - - name: linux_x86-64 - runs-on: ubuntu-22.04 - runs-on: ${{ matrix.platform.runs-on }} - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - ref: main - - run: | - git config --global user.name "${{ github.actor }}" - git config --global user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - - - id: prepare - name: Prepare release build - uses: ./.github/actions/prepare-linux - name: Set up Java uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '17' distribution: 'temurin' - server-id: central - server-username: MAVEN_USERNAME - server-password: MAVEN_TOKEN - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - gpg-passphrase: MAVEN_GPG_PASSPHRASE - - # release:prepare runs "clean verify" over the whole reactor, and the media - # module in it builds FFmpeg from its third-party submodule. - - name: Update development version - uses: actions/checkout@v6 - with: - ref: main - submodules: true + # preparationGoals is "clean validate" instead of the default "clean verify": + # verify would compile WebRTC and FFmpeg here, on a runner without their + # caches, only for the platform jobs below to build the tagged tree again. + # # A dry run transforms the poms and writes release.properties, but creates no # commit and no tag, so there is nothing to amend or push below. - name: Prepare release env: DEVELOPMENT_VERSION: ${{ inputs.developmentVersion }} + EXPECTED_VERSION: ${{ steps.release-version.outputs.version }} run: | - mvn release:prepare -DskipTests -DpushChanges=false \ + mvn -B release:prepare -DpushChanges=false \ + -DpreparationGoals="clean validate" \ -DdryRun=${{ inputs.dryRun }} \ -DdevelopmentVersion="$DEVELOPMENT_VERSION" # Get the release version from the release.properties file RELEASE_VERSION=$(grep "project.rel.dev.onvoid.webrtc\\\:webrtc-java=" release.properties 2>/dev/null | cut -d'=' -f2 || true) echo "Extracted release version from release.properties: ${RELEASE_VERSION:-}" + + if [ "$RELEASE_VERSION" != "$EXPECTED_VERSION" ]; then + echo "::error::release:prepare chose version '$RELEASE_VERSION', expected '$EXPECTED_VERSION'" + exit 1 + fi echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$GITHUB_ENV" - - name: Push release commit and tag + - name: Push release commits and tag if: ${{ !inputs.dryRun }} env: DEVELOPMENT_VERSION: ${{ inputs.developmentVersion }} + RELEASE_TAG: ${{ steps.release-version.outputs.tag }} run: | # Update versions.ts file echo "Updating versions.ts with release version: $RELEASE_VERSION and development version: $DEVELOPMENT_VERSION" @@ -158,14 +152,11 @@ jobs: git add docs/.vitepress/versions.ts git commit --amend --no-edit - git push - git push --tags + # The branch and the tag land together or not at all. + git push --atomic origin HEAD:main "refs/tags/$RELEASE_TAG" build-windows: needs: prepare-release - outputs: - release_body: ${{ needs.prepare-release.outputs.release_body }} - release_version: ${{ needs.prepare-release.outputs.release_version }} strategy: fail-fast: false matrix: @@ -177,23 +168,15 @@ jobs: java: [17] runs-on: ${{ matrix.platform.runs-on }} steps: + # The tag prepare-release pushed. A dry run pushes none and builds the + # dispatched commit instead. - name: Checkout code uses: actions/checkout@v6 with: - fetch-tags: true + ref: ${{ !inputs.dryRun && needs.prepare-release.outputs.release_tag || '' }} # The media module builds FFmpeg from its third-party submodule. submodules: true - - name: Get tag from current branch - id: tag-selector - if: ${{ !inputs.dryRun }} - shell: bash - run: | - git fetch -a - tag=$(git describe --tags `git rev-list --tags --max-count=1`) - echo "tag=$tag" >> "$GITHUB_OUTPUT" - git checkout $tag - - id: prepare name: Prepare release build uses: ./.github/actions/prepare-windows @@ -220,22 +203,15 @@ jobs: java: [17] runs-on: ${{ matrix.platform.runs-on }} steps: + # The tag prepare-release pushed. A dry run pushes none and builds the + # dispatched commit instead. - name: Checkout code uses: actions/checkout@v6 with: - fetch-tags: true + ref: ${{ !inputs.dryRun && needs.prepare-release.outputs.release_tag || '' }} # The media module builds FFmpeg from its third-party submodule. submodules: true - - name: Get tag from current branch - id: tag-selector - if: ${{ !inputs.dryRun }} - run: | - git fetch -a - tag=$(git describe --tags `git rev-list --tags --max-count=1`) - echo "tag=$tag" >> "$GITHUB_OUTPUT" - git checkout $tag - - id: prepare-build name: Prepare release build uses: ./.github/actions/prepare-linux @@ -260,22 +236,15 @@ jobs: java: [17] runs-on: ${{ matrix.platform.runs-on }} steps: + # The tag prepare-release pushed. A dry run pushes none and builds the + # dispatched commit instead. - name: Checkout code uses: actions/checkout@v6 with: - fetch-tags: true + ref: ${{ !inputs.dryRun && needs.prepare-release.outputs.release_tag || '' }} # The media module builds FFmpeg from its third-party submodule. submodules: true - - name: Get tag from current branch - id: tag-selector - if: ${{ !inputs.dryRun }} - run: | - git fetch -a - tag=$(git describe --tags `git rev-list --tags --max-count=1`) - echo "tag=$tag" >> "$GITHUB_OUTPUT" - git checkout $tag - - id: prepare-build name: Prepare release build uses: ./.github/actions/prepare-macos @@ -298,17 +267,14 @@ jobs: publish-central: name: Publish to Maven Central - needs: [build-windows, build-linux, build-macos] - outputs: - release_body: ${{ needs.build-windows.outputs.release_body }} - release_version: ${{ needs.build-windows.outputs.release_version }} + needs: [prepare-release, build-windows, build-linux, build-macos] runs-on: ubuntu-22.04 steps: - name: Checkout code uses: actions/checkout@v6 with: - fetch-tags: true + ref: ${{ !inputs.dryRun && needs.prepare-release.outputs.release_tag || '' }} - name: Set up Maven cache uses: actions/cache@v6 @@ -330,14 +296,13 @@ jobs: - name: Determine the version to publish id: release-version + env: + RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }} run: | if [ "${{ inputs.dryRun }}" == "true" ]; then version=$(mvn -B -q -N help:evaluate -Dexpression=project.version -DforceStdout) else - git fetch -a - tag=$(git describe --tags `git rev-list --tags --max-count=1`) - git checkout "$tag" - version="${tag#v}" + version="${RELEASE_TAG#v}" fi echo "Version to publish: $version" @@ -436,7 +401,7 @@ jobs: publish-release: name: Publish GitHub release - needs: publish-central + needs: [prepare-release, publish-central] if: ${{ !inputs.dryRun }} runs-on: ubuntu-latest permissions: @@ -452,8 +417,8 @@ jobs: - name: Publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_VERSION: ${{ needs.publish-central.outputs.release_version }} - RELEASE_BODY: ${{ needs.publish-central.outputs.release_body }} + RELEASE_VERSION: ${{ needs.prepare-release.outputs.release_tag }} + RELEASE_BODY: ${{ needs.prepare-release.outputs.release_body }} run: | printf '%s\n' "$RELEASE_BODY" > release-notes.md gh release create "$RELEASE_VERSION" --draft --verify-tag \ From ecee5e3d1e8d49fe9c7f21c749ffdd38feced90a Mon Sep 17 00:00:00 2001 From: Alex Andres Date: Mon, 28 Sep 2026 23:13:49 +0200 Subject: [PATCH 4/4] ci: skip docs-only native builds and make test failures debuggable Leave docs-only PRs out of the native build and build the docs site on PRs that touch it instead, without deploying, so a broken VitePress build shows up before the merge. Cache the Chromium Clang package and stop listing every file it extracts, keep Homebrew from updating itself before each install, and drop the two disabled steps of the Windows preparation. Give every test run a 30 minute Surefire timeout, and test-natives a job timeout, so a hung test no longer holds a runner for six hours. Composite action steps have no timeout-minutes, and macOS has no timeout command. Upload the Surefire reports when a job fails. --- .github/actions/build-macos-x86_64/action.yml | 12 +++++- .github/actions/build/action.yml | 12 +++++- .github/actions/prepare-linux/action.yml | 37 +++++++++++++------ .github/actions/prepare-macos/action.yml | 4 ++ .github/actions/prepare-windows/action.yml | 29 --------------- .../actions/release-macos-x86_64/action.yml | 12 +++++- .github/actions/release/action.yml | 12 +++++- .github/actions/test-natives/action.yml | 12 +++++- .github/workflows/build.yml | 8 ++++ .github/workflows/pages.yml | 14 ++++++- 10 files changed, 105 insertions(+), 47 deletions(-) diff --git a/.github/actions/build-macos-x86_64/action.yml b/.github/actions/build-macos-x86_64/action.yml index 90a175c8..3315021d 100644 --- a/.github/actions/build-macos-x86_64/action.yml +++ b/.github/actions/build-macos-x86_64/action.yml @@ -101,9 +101,19 @@ runs: key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} - name: Test - run: JAVA_HOME="${{ steps.jdk-x64.outputs.path }}" mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 + run: JAVA_HOME="${{ steps.jdk-x64.outputs.path }}" mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 -Dsurefire.timeout=1800 shell: bash + # What failed, for runners that cannot be reproduced locally. + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-reports-${{ inputs.platform-name }} + path: '**/target/surefire-reports/' + if-no-files-found: ignore + retention-days: 7 + - name: Deploy if: ${{ github.event_name != 'pull_request' && github.repository == 'devopvoid/webrtc-java' }} env: diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index ec6e7055..7baeb547 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -96,9 +96,19 @@ runs: - name: Test if: ${{ inputs.platform-name != 'linux_arm' && inputs.platform-name != 'linux_arm64' && inputs.platform-name != 'windows_arm64' }} - run: mvn -B jar:jar surefire:test + run: mvn -B jar:jar surefire:test -Dsurefire.timeout=1800 shell: bash + # What failed, for runners that cannot be reproduced locally. + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-reports-${{ inputs.platform-name }} + path: '**/target/surefire-reports/' + if-no-files-found: ignore + retention-days: 7 + - name: Deploy if: ${{ github.event_name != 'pull_request' && github.repository == 'devopvoid/webrtc-java' }} env: diff --git a/.github/actions/prepare-linux/action.yml b/.github/actions/prepare-linux/action.yml index 9fc88bfc..4a36699b 100644 --- a/.github/actions/prepare-linux/action.yml +++ b/.github/actions/prepare-linux/action.yml @@ -30,20 +30,35 @@ runs: - name: Install required packages run: | - sudo apt update + sudo apt-get update -q # nasm is what FFmpeg assembles its x86 code with. - sudo apt install -y binutils cmake git locales lsb-release nasm ninja-build pipewire pipewire-pulse pkg-config python3 python3-setuptools rsync unzip wget xz-utils + sudo apt-get install -y -q binutils cmake git locales lsb-release nasm ninja-build pipewire pipewire-pulse pkg-config python3 python3-setuptools rsync unzip wget xz-utils + shell: bash + + # Chromium Clang to be used with the clang toolchain file. The JNI code + # compiles against the libc++ headers of the WebRTC build, which only + # support the Clang that WebRTC branch ships with, so this must be the + # package pinned by CLANG_REVISION and CLANG_SUB_REVISION in + # tools/clang/scripts/update.py of the webrtc.branch set in + # webrtc-jni/pom.xml. Update it whenever that branch changes. + - name: Select the Chromium Clang package + run: echo "CLANG_PACKAGE=clang-llvmorg-23-init-19482-g53d18800-1.tar.xz" >> "$GITHUB_ENV" + shell: bash - # Chromium Clang to be used with the clang toolchain file. The JNI code - # compiles against the libc++ headers of the WebRTC build, which only - # support the Clang that WebRTC branch ships with, so this must be the - # package pinned by CLANG_REVISION and CLANG_SUB_REVISION in - # tools/clang/scripts/update.py of the webrtc.branch set in - # webrtc-jni/pom.xml. Update it whenever that branch changes. - CLANG_PACKAGE=clang-llvmorg-23-init-19482-g53d18800-1.tar.xz + - name: Cache the Chromium Clang package + uses: actions/cache@v6 + with: + path: ~/.cache/chromium-clang + key: chromium-clang-${{ env.CLANG_PACKAGE }} + + - name: Install Chromium Clang + run: | + mkdir -p ~/.cache/chromium-clang + if [ ! -f ~/.cache/chromium-clang/$CLANG_PACKAGE ]; then + wget -q -O ~/.cache/chromium-clang/$CLANG_PACKAGE https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/$CLANG_PACKAGE + fi sudo mkdir -p /opt/clang - wget https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/$CLANG_PACKAGE - sudo tar -xvf $CLANG_PACKAGE -C /opt/clang + sudo tar -xf ~/.cache/chromium-clang/$CLANG_PACKAGE -C /opt/clang shell: bash - name: Install required packages for x86-64 diff --git a/.github/actions/prepare-macos/action.yml b/.github/actions/prepare-macos/action.yml index 5ef69300..cc07285e 100644 --- a/.github/actions/prepare-macos/action.yml +++ b/.github/actions/prepare-macos/action.yml @@ -5,7 +5,11 @@ description: 'Installs required packages for macOS builds.' runs: using: "composite" steps: + # No Homebrew self-update: it takes minutes and neither package needs it. - name: Install required packages + env: + HOMEBREW_NO_AUTO_UPDATE: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' run: | # Required on macos-14 brew install python-setuptools diff --git a/.github/actions/prepare-windows/action.yml b/.github/actions/prepare-windows/action.yml index 692cf2bf..98e9c1f7 100644 --- a/.github/actions/prepare-windows/action.yml +++ b/.github/actions/prepare-windows/action.yml @@ -5,35 +5,6 @@ description: 'Frees up disk space and installs required packages for Windows bui runs: using: "composite" steps: - - name: Set up Python 3.11 - if: false - uses: actions/setup-python@v7 - with: - python-version: "3.11" - - - name: Disk cleanup - if: false - run: | - Get-PSDrive - # Docker Images - docker rmi $(docker images -q -a) - # Android SDK - if ($Env:ANDROID_HOME) { - Remove-Item -Recurse -Force $Env:ANDROID_HOME -ErrorAction Ignore - } - if ($Env:ANDROID_NDK_HOME) { - Remove-Item -Recurse -Force $Env:ANDROID_NDK_HOME -ErrorAction Ignore - } - # JVM - if ($Env:JAVA_HOME_11_X64) { - Remove-Item -Recurse -Force $Env:JAVA_HOME_11_X64 -ErrorAction Ignore - } - if ($Env:JAVA_HOME_8_X64) { - Remove-Item -Recurse -Force $Env:JAVA_HOME_8_X64 -ErrorAction Ignore - } - Get-PSDrive - shell: powershell - - name: Install required packages run: | choco install ninja diff --git a/.github/actions/release-macos-x86_64/action.yml b/.github/actions/release-macos-x86_64/action.yml index 34051575..c0ac1510 100644 --- a/.github/actions/release-macos-x86_64/action.yml +++ b/.github/actions/release-macos-x86_64/action.yml @@ -88,9 +88,19 @@ runs: key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} - name: Test - run: JAVA_HOME="${{ steps.jdk-x64.outputs.path }}" mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 + run: JAVA_HOME="${{ steps.jdk-x64.outputs.path }}" mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 -Dsurefire.timeout=1800 shell: bash + # What failed, for runners that cannot be reproduced locally. + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-reports-${{ inputs.platform-name }} + path: '**/target/surefire-reports/' + if-no-files-found: ignore + retention-days: 7 + # Maven Central publishes a version as one atomic bundle, so this job must not # deploy. The native library jar is handed to the aggregating publish-central # job instead, which attaches every platform to a single deployment. diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml index c1a14dae..27c73883 100644 --- a/.github/actions/release/action.yml +++ b/.github/actions/release/action.yml @@ -84,9 +84,19 @@ runs: - name: Test if: ${{ inputs.platform-name != 'linux_arm' && inputs.platform-name != 'linux_arm64' && inputs.platform-name != 'windows_arm64' }} - run: mvn -B jar:jar surefire:test + run: mvn -B jar:jar surefire:test -Dsurefire.timeout=1800 shell: bash + # What failed, for runners that cannot be reproduced locally. + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-reports-${{ inputs.platform-name }} + path: '**/target/surefire-reports/' + if-no-files-found: ignore + retention-days: 7 + # Maven Central publishes a version as one atomic bundle, so this job must not # deploy. The native library jar is handed to the aggregating publish-central # job instead, which attaches every platform to a single deployment. diff --git a/.github/actions/test-natives/action.yml b/.github/actions/test-natives/action.yml index 6a09796d..26c1d035 100644 --- a/.github/actions/test-natives/action.yml +++ b/.github/actions/test-natives/action.yml @@ -121,5 +121,15 @@ runs: # The native build is off: these natives were cross compiled by another # job, and this runner is here to run them, not to build them again. - name: Test - run: mvn -B -pl webrtc,webrtc-java-media test -DskipNativeBuild -P${{ inputs.profile }} + run: mvn -B -pl webrtc,webrtc-java-media test -DskipNativeBuild -P${{ inputs.profile }} -Dsurefire.timeout=1800 shell: bash + + # What failed, for runners that cannot be reproduced locally. + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-reports-native-${{ inputs.platform-name }} + path: '**/target/surefire-reports/' + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 350cb732..e630046d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,9 +14,16 @@ on: - ".github/ISSUE_TEMPLATE/**" - ".github/workflows/pages.yml" + # A change that only touches docs cannot affect the native build. The docs + # site has its own check in pages.yml. pull_request: branches: - main + paths-ignore: + - "docs/**" + - "**.md" + - ".github/ISSUE_TEMPLATE/**" + - ".github/workflows/pages.yml" workflow_dispatch: @@ -162,6 +169,7 @@ jobs: test-natives: needs: [build-windows, build-linux] + timeout-minutes: 30 strategy: fail-fast: false matrix: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index e4a23ee7..efb5db32 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -6,6 +6,13 @@ on: paths: - 'docs/**' + # PRs only build the site, so that a broken build shows up before the merge. + pull_request: + branches: [main] + paths: + - 'docs/**' + - '.github/workflows/pages.yml' + workflow_dispatch: permissions: @@ -14,8 +21,8 @@ permissions: id-token: write concurrency: - group: pages - cancel-in-progress: false + group: pages-${{ github.event_name == 'pull_request' && github.ref || 'deploy' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build: @@ -32,6 +39,7 @@ jobs: cache-dependency-path: docs/package-lock.json - name: Setup Pages + if: github.event_name != 'pull_request' uses: actions/configure-pages@v6 - name: Install dependencies @@ -45,11 +53,13 @@ jobs: npm run build - name: Upload artifact + if: github.event_name != 'pull_request' uses: actions/upload-pages-artifact@v5 with: path: docs/.vitepress/dist deploy: + if: github.event_name != 'pull_request' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }}