diff --git a/.github/actions/build-macos-x86_64/action.yml b/.github/actions/build-macos-x86_64/action.yml index 816ac11f..3315021d 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,10 +86,34 @@ 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 + 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 887b1aa4..7baeb547 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,11 +80,35 @@ 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 + 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 cae846a1..c0ac1510 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,10 +73,34 @@ 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 + 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 460b365d..27c73883 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,11 +68,35 @@ 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 + 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 1a195411..e630046d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,17 +4,38 @@ 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" + + # 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: +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 @@ -148,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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b62d411..433eb998 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,125 +17,146 @@ 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 + # 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 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, 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 - - name: Generate changelog for the current build + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=v$version" >> "$GITHUB_OUTPUT" + + # 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 --bump --use-branch-tags + 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 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 push - - prepare-release: - name: Prepare release - needs: changelog - 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 + git commit -m "chore(release): Update CHANGELOG.md for $RELEASE_TAG" - 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=${{ 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) 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: ${{ 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 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: @@ -147,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 @@ -190,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 @@ -230,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 @@ -268,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 @@ -300,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" @@ -406,19 +401,25 @@ jobs: publish-release: name: Publish GitHub release - needs: publish-central + needs: [prepare-release, 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.prepare-release.outputs.release_tag }} + RELEASE_BODY: ${{ needs.prepare-release.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