From 93985202c7ce6b9fa8625aa0b79b48e17bb6c6ac Mon Sep 17 00:00:00 2001 From: maxjivi05 Date: Sat, 24 Oct 2026 15:21:26 -0400 Subject: [PATCH] Build DD-Turnip weekly and hold scheduled builds as prereleases until promoted Scheduled runs go back to Wednesdays with a minor bump and publish only a prerelease here, which DroidDeck never sees. promote.yml publishes an approved prerelease to Drivers-CI and marks it latest. A newer build with the same version replaces an unpromoted prerelease. --- .github/workflows/promote.yml | 45 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 27 +++++++++++++++++---- README.md | 20 +++++++++++----- scripts/release_version.py | 8 +++++-- scripts/turnip_changed.py | 4 ++-- tests/test_release_version.py | 11 ++++++++- 6 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/promote.yml diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml new file mode 100644 index 0000000..046fc73 --- /dev/null +++ b/.github/workflows/promote.yml @@ -0,0 +1,45 @@ +name: DD-Turnip promote + +on: + workflow_dispatch: + inputs: + tag: + description: Prerelease to publish, e.g. DD-Turnip-v0.4.0 + required: true + type: string + +concurrency: + group: dd-turnip-release + cancel-in-progress: false + +permissions: + contents: write + +jobs: + promote: + runs-on: ubuntu-24.04 + steps: + - name: Publish ${{ inputs.tag }} + env: + GH_TOKEN: ${{ github.token }} + CI_TOKEN: ${{ secrets.DRIVERS_CI_TOKEN }} + TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "$TAG" =~ ^DD-Turnip-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::$TAG is not a DD-Turnip release tag" + exit 1 + fi + if [[ -z "$CI_TOKEN" ]]; then + echo "::error::DRIVERS_CI_TOKEN is not set, so $TAG cannot be published to Droid-Deck/Drivers-CI" + exit 1 + fi + gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json name,body > release.json + jq -r .body release.json > notes.md + gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir dist \ + --pattern "$TAG.zip" --pattern "$TAG.zip.sha256" --pattern "$TAG-source.tar.xz" + (cd dist && sha256sum -c "$TAG.zip.sha256") + GH_TOKEN="$CI_TOKEN" gh release view "$TAG" --repo Droid-Deck/Drivers-CI >/dev/null 2>&1 \ + || GH_TOKEN="$CI_TOKEN" gh release create "$TAG" "dist/$TAG.zip" "dist/$TAG.zip.sha256" "dist/$TAG-source.tar.xz" \ + --repo Droid-Deck/Drivers-CI --target main --title "$(jq -r .name release.json)" --notes-file notes.md --latest + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --prerelease=false --latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e17546d..27a7068 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,13 +2,20 @@ name: DD-Turnip release on: schedule: - - cron: '0 12 * * *' + - cron: '0 12 * * 3' workflow_dispatch: inputs: hotfix: description: Hotfix (bump the patch version instead of the minor version) type: boolean default: false + prerelease: + description: Prerelease (stays off Drivers-CI until promoted with promote.yml) + type: boolean + default: false + +env: + PRERELEASE: ${{ github.event_name == 'schedule' || inputs.prerelease }} concurrency: group: dd-turnip-release @@ -36,7 +43,7 @@ jobs: id: version env: GH_TOKEN: ${{ github.token }} - HOTFIX: ${{ github.event_name == 'schedule' || inputs.hotfix }} + HOTFIX: ${{ github.event_name == 'workflow_dispatch' && inputs.hotfix }} run: | set -euo pipefail gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/releases?per_page=100" > releases.json @@ -46,7 +53,9 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ steps.version.outputs.tag }} run: | - if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --silent 2>/dev/null \ + if [[ "$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isPrerelease --jq .isPrerelease 2>/dev/null)" == true ]]; then + echo "$TAG is an unpromoted prerelease and will be replaced" + elif gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --silent 2>/dev/null \ || gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then echo "::error::$TAG already exists" exit 1 @@ -153,10 +162,20 @@ jobs: TAG: ${{ needs.prepare.outputs.tag }} VERSION: ${{ needs.prepare.outputs.version }} run: | + set -euo pipefail + if [[ "$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isPrerelease --jq .isPrerelease 2>/dev/null)" == true ]]; then + gh release delete "$TAG" --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes + fi + if [[ "$PRERELEASE" == true ]]; then + flags=(--prerelease --latest=false) + else + flags=(--latest) + fi gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 \ || gh release create "$TAG" "dist/$TAG.zip" "dist/$TAG.zip.sha256" "dist/$TAG-source.tar.xz" --repo "$GITHUB_REPOSITORY" \ - --target "$GITHUB_SHA" --title "DD-Turnip v$VERSION" --notes-file notes.md --latest + --target "$GITHUB_SHA" --title "DD-Turnip v$VERSION" --notes-file notes.md "${flags[@]}" - name: Publish to Drivers-CI + if: env.PRERELEASE != 'true' env: GH_TOKEN: ${{ secrets.DRIVERS_CI_TOKEN }} TAG: ${{ needs.prepare.outputs.tag }} diff --git a/README.md b/README.md index a5e029e..f7f7bb0 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,10 @@ Tags are `DD-Turnip-vX.Y.Z`. The next version comes from the highest published ( | Trigger | Rule | Examples | |---|---|---| | No release yet | `0.1.0` | → `0.1.0` | -| Manual run without Hotfix | minor + 1, patch reset; minor 9 rolls into the next major | `0.1.0` → `0.2.0`, `0.1.5` → `0.2.0`, `0.9.0` → `1.0.0`, `1.9.3` → `2.0.0` | -| Daily schedule, or manual run with Hotfix | patch + 1 | `0.1.0` → `0.1.1` → `0.1.2`, `0.9.0` → `0.9.1` | +| Weekly schedule, or manual run without Hotfix | minor + 1, patch reset; minor 9 rolls into the next major | `0.1.0` → `0.2.0`, `0.1.5` → `0.2.0`, `0.9.0` → `1.0.0`, `1.9.3` → `2.0.0` | +| Manual run with Hotfix | patch + 1 | `0.1.0` → `0.1.1` → `0.1.2`, `0.9.0` → `0.9.1` | + +An unpromoted prerelease does not count, so the next build with the same version replaces it. ## Release notes @@ -97,20 +99,26 @@ Tags are `DD-Turnip-vX.Y.Z`. The next version comes from the highest published ( ## Workflows -- `release.yml` runs every day at 12:00 UTC and on manual dispatch (`Hotfix` checkbox). A scheduled run only - builds when Mesa main has Turnip, Vulkan runtime or gralloc commits since the last release, or this repository - changed (`scripts/turnip_changed.py`). When a scheduled run fails, usually because a patch no longer applies, it - opens an issue (or comments on the open one), and the next scheduled release closes it. It resolves the +- `release.yml` runs every Wednesday at 12:00 UTC and on manual dispatch (`Hotfix` and `Prerelease` checkboxes). + A scheduled run only builds when Mesa main has Turnip, Vulkan runtime or gralloc commits since the last release + or prerelease, or this repository changed (`scripts/turnip_changed.py`). When a scheduled run fails, usually + because a patch no longer applies, it opens an issue (or comments on the open one), and the next scheduled + build closes it. Scheduled runs and `Prerelease` runs publish a prerelease here only, which DroidDeck never + sees; `promote.yml` publishes an approved prerelease to Drivers-CI and marks it latest. It resolves the version and the Mesa main commit, builds Android and Linux in parallel from that commit, packages and verifies the bundle, and publishes the release here and to the public [Droid-Deck/Drivers-CI](https://github.com/Droid-Deck/Drivers-CI), which carries only the release assets (bundle, `.sha256`, source archive) and is where DroidDeck downloads drivers from. Publishing there needs the `DRIVERS_CI_TOKEN` secret: a fine-grained token with Contents read and write on Drivers-CI. +- `promote.yml` runs on manual dispatch with the prerelease tag, copies its assets and notes to Drivers-CI and + marks it the latest release in both repositories. - `check.yml` runs the version tests and applies the patch set to Mesa main on every push and pull request. ```sh gh workflow run release.yml gh workflow run release.yml -f hotfix=true +gh workflow run release.yml -f prerelease=true +gh workflow run promote.yml -f tag=DD-Turnip-v0.4.0 ``` ## Local build diff --git a/scripts/release_version.py b/scripts/release_version.py index 5dce18c..654b260 100644 --- a/scripts/release_version.py +++ b/scripts/release_version.py @@ -13,9 +13,13 @@ def parse(tag): return tuple(map(int, match.groups())) if match else None +def newest_release(releases): + built = [r for r in releases if not r['draft'] and parse(r['tag_name'])] + return max(built, key=lambda r: parse(r['tag_name']), default=None) + + def latest_release(releases): - published = [r for r in releases if not r['draft'] and not r['prerelease'] and parse(r['tag_name'])] - return max(published, key=lambda r: parse(r['tag_name']), default=None) + return newest_release([r for r in releases if not r['prerelease']]) def next_version(current, hotfix): diff --git a/scripts/turnip_changed.py b/scripts/turnip_changed.py index d71d5ed..c32b54b 100755 --- a/scripts/turnip_changed.py +++ b/scripts/turnip_changed.py @@ -6,7 +6,7 @@ import tempfile from datetime import datetime, timedelta from release_notes import TURNIP_PATHS, fetch_history, log, previous_mesa_commit -from release_version import latest_release +from release_version import newest_release def turnip_commits(release, commit): @@ -44,7 +44,7 @@ if __name__ == '__main__': parser.add_argument('--recipe', required=True) args = parser.parse_args() with open(args.releases) as f: - release = latest_release([r for page in json.load(f) for r in page]) + release = newest_release([r for page in json.load(f) for r in page]) build, reason = decide(release, args.mesa_commit, args.recipe) print(f'changed={"true" if build else "false"}') print(f'reason={reason}') diff --git a/tests/test_release_version.py b/tests/test_release_version.py index 55f75a9..9d240ef 100644 --- a/tests/test_release_version.py +++ b/tests/test_release_version.py @@ -4,7 +4,7 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'scripts')) -from release_version import latest_release, next_version, parse, version_string +from release_version import latest_release, newest_release, next_version, parse, version_string def release(tag, draft=False, prerelease=False): @@ -58,6 +58,15 @@ class ReleaseVersionTest(unittest.TestCase): self.assertEqual(latest['tag_name'], 'DD-Turnip-v0.1.10') self.assertIsNone(latest_release([release('DD-Turnip-v0.1.0', draft=True)])) + def test_newest_includes_prereleases_but_not_drafts(self): + releases = [ + release('DD-Turnip-v0.3.0'), + release('DD-Turnip-v0.4.0', prerelease=True), + release('DD-Turnip-v0.5.0', draft=True), + ] + self.assertEqual(newest_release(releases)['tag_name'], 'DD-Turnip-v0.4.0') + self.assertEqual(latest_release(releases)['tag_name'], 'DD-Turnip-v0.3.0') + if __name__ == '__main__': unittest.main()