name: Release # A release is a `v` tag on main. The tag is the trigger and the # version of record: nothing here reads the branch, so the first job checks that # the commit really is on main and that the tag, `Cargo.toml` and the PKGBUILD # all say the same version — a tag that disagrees would otherwise publish # artifacts named after the manifest, not after the tag. # # `workflow_dispatch` builds everything and publishes nothing, which is how to # rehearse a release without burning a tag. on: push: tags: - 'v*' workflow_dispatch: permissions: contents: read env: CARGO_TERM_COLOR: always jobs: verify: name: Tag and versions runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 with: fetch-depth: 0 - name: The tag is on main if: startsWith(github.ref, 'refs/tags/') run: | git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main git merge-base --is-ancestor "$GITHUB_SHA" origin/main || { echo "::error::$GITHUB_REF_NAME points at a commit that is not on main"; exit 1; } - name: The tag, Cargo.toml and the PKGBUILD agree if: startsWith(github.ref, 'refs/tags/') run: | tag="${GITHUB_REF_NAME#v}" manifest="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n1)" pkgbuild="$(sed -n 's/^pkgver=//p' packaging/PKGBUILD | head -n1)" echo "tag=$tag manifest=$manifest pkgbuild=$pkgbuild" [ "$tag" = "$manifest" ] || { echo "::error::tag $tag != Cargo.toml $manifest"; exit 1; } [ "$tag" = "$pkgbuild" ] || { echo "::error::tag $tag != PKGBUILD $pkgbuild"; exit 1; } # A release must resolve the SDK from crates.io. The build scripts refuse # to run with a patch pointing at a missing sibling checkout, but here the # patch is wrong even when the path exists. - name: No path patch on the SDK run: | ! grep -q '^\[patch\.crates-io\]' Cargo.toml || { echo "::error::Cargo.toml carries [patch.crates-io]; releases build against the registry"; exit 1; } linux: name: Linux artifacts needs: verify runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: # Pinned: the MSRV the workspace declares. toolchain: "1.96" components: rustfmt, clippy - uses: Swatinem/rust-cache@v2.9.1 # rpm for rpmbuild; the .deb goes through dpkg-deb, which is preinstalled. - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y libmpv-dev libwebkit2gtk-4.1-dev rpm - name: Gate run: | cargo fmt --all -- --check cargo clippy --workspace --all-targets -- -D warnings cargo test --workspace --locked - name: Build tarball, .deb and .rpm run: scripts/build.sh tarball deb rpm - uses: actions/upload-artifact@v7 with: name: linux path: | dist/*.tar.gz dist/*.deb dist/*.rpm if-no-files-found: error # Cross-compiled from Linux with mingw-w64, and the .msi built by wixl rather # than WiX — scripts/build-windows.sh, which is how these artifacts have been # produced by hand. A windows runner would need MSVC's mpv.lib, and current # mpv-dev archives ship only the mingw import library. windows: name: Windows artifacts needs: verify runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: toolchain: "1.96" targets: x86_64-pc-windows-gnu - uses: Swatinem/rust-cache@v2.9.1 # mingw-w64 for the cross compile and p7zip for the mpv-dev archive. - name: Install the cross toolchain run: | sudo apt-get update sudo apt-get install -y mingw-w64 zip p7zip-full # Not `apt install wixl`: noble has msitools 0.103, and — # the PATH entry for pstr.exe — arrived in 0.105. An older wixl core dumps # on the .wxs rather than declining it. 0.106 is in 25.10 but its .deb # wants a libxml2 noble does not have, so this builds from source (~2 min). - name: Install wixl >= 0.105 run: scripts/install-wixl.sh - name: Build the portable .zip and the .msi env: GITHUB_TOKEN: ${{ github.token }} run: scripts/build-windows.sh --fetch-mpv - uses: actions/upload-artifact@v7 with: name: windows path: | dist/*.zip dist/*.msi if-no-files-found: error # The signed APK plus the corresponding source of the libmpv it bundles. The # keystore and its passwords come from the ANDROID_RELEASE_* secrets. android: name: Android needs: verify uses: ./.github/workflows/android.yml with: signed_release: true secrets: inherit # The unsigned .ipa: SideStore and AltStore re-sign it with the viewer's own # Apple ID when they install it. ios: name: iOS needs: verify uses: ./.github/workflows/ios.yml publish: name: Publish if: startsWith(github.ref, 'refs/tags/') needs: [linux, windows, android, ios] runs-on: ubuntu-24.04 permissions: contents: write steps: # By name: the Android workflow also uploads the AAB, which is for Play # and does not belong on the release. - uses: actions/download-artifact@v8 with: name: linux path: artifacts - uses: actions/download-artifact@v8 with: name: windows path: artifacts - uses: actions/download-artifact@v8 with: name: android path: artifacts - uses: actions/download-artifact@v8 with: name: ios path: artifacts - name: Checksums run: | cd artifacts sha256sum * > SHA256SUMS cat SHA256SUMS - uses: softprops/action-gh-release@v3 with: files: artifacts/* generate_release_notes: true fail_on_unmatched_files: true