Activate AMF intra refresh (GDR) - fixing xbox moonlight artefacts #277
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: CI | |
| permissions: {} | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - '**' | |
| tags: | |
| - '*' | |
| pull_request: | |
| branches: | |
| - vibe | |
| - main | |
| - master | |
| concurrency: | |
| group: "${{ github.workflow }}-${{ github.ref }}" | |
| cancel-in-progress: true | |
| jobs: | |
| release-candidate: | |
| name: Release candidate | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_commit: ${{ steps.release-candidate.outputs.release_commit }} | |
| release_version: ${{ steps.release-candidate.outputs.release_version }} | |
| should_release: ${{ steps.release-candidate.outputs.should_release }} | |
| tag_name: ${{ steps.release-candidate.outputs.tag_name }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve release candidate | |
| id: release-candidate | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| python <<'PY' | |
| import os | |
| import re | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| VERSION_RE = re.compile(r"^v?([0-9]+\.[0-9]+\.[0-9]+)([-.][0-9A-Za-z.-]+)?$") | |
| REPO = os.environ["GITHUB_REPOSITORY"] | |
| def run(*args, check=True): | |
| return subprocess.run(args, check=check, text=True, capture_output=True).stdout.strip() | |
| def output(**values): | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh: | |
| for key, value in values.items(): | |
| print(f"{key}={value}", file=fh) | |
| def skip(reason): | |
| print(f"::notice::{reason}") | |
| output(should_release="false", tag_name="", release_version="", release_commit="") | |
| sys.exit(0) | |
| def version_key(tag): | |
| match = VERSION_RE.match(tag) | |
| if not match: | |
| return None | |
| major, minor, patch = (int(part) for part in match.group(1).split(".")) | |
| suffix = (match.group(2) or "").lstrip("-.").lower() | |
| suffix_parts = re.split(r"[-.]", suffix) if suffix else [] | |
| suffix_num = next((int(part) for part in suffix_parts if part.isdigit()), 0) | |
| if not suffix or "stable" in suffix_parts: | |
| suffix_rank = 40 | |
| elif "rc" in suffix_parts: | |
| suffix_rank = 30 | |
| elif "beta" in suffix_parts: | |
| suffix_rank = 20 | |
| elif "alpha" in suffix_parts: | |
| suffix_rank = 10 | |
| else: | |
| suffix_rank = 5 | |
| return (major, minor, patch, suffix_rank, suffix_num, suffix) | |
| def is_prerelease_key(key): | |
| return key[3] < 40 | |
| def release_notes_for(tag, commit): | |
| match = VERSION_RE.match(tag) | |
| if not match: | |
| return None | |
| release_version = tag[1:] if tag.startswith("v") else tag | |
| for candidate in ( | |
| Path("release_notes") / f"{release_version}.md", | |
| Path("release_notes") / f"{tag}.md", | |
| ): | |
| notes_path = candidate.as_posix() | |
| if subprocess.run( | |
| ["git", "cat-file", "-e", f"{commit}:{notes_path}"], | |
| text=True, | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ).returncode == 0: | |
| return notes_path | |
| return None | |
| def canonical_release_tag(tag): | |
| return tag if tag.startswith("v") else f"v{tag}" | |
| event_name = os.environ["EVENT_NAME"] | |
| if event_name not in {"push", "workflow_dispatch"}: | |
| skip("Release gate only applies to push and manual workflow_dispatch events.") | |
| run("git", "fetch", "--force", "--tags") | |
| release_tags = set(filter(None, run( | |
| "gh", "api", "--paginate", f"repos/{REPO}/releases", "--jq", ".[].tag_name" | |
| ).splitlines())) | |
| release_versions = [version_key(tag) for tag in release_tags] | |
| release_versions = [key for key in release_versions if key is not None] | |
| candidate_tags = run("git", "tag", "--list").splitlines() | |
| valid_candidates = [] | |
| for tag in candidate_tags: | |
| key = version_key(tag) | |
| if key is None: | |
| continue | |
| release_tag = canonical_release_tag(tag) | |
| if tag in release_tags or release_tag in release_tags: | |
| print(f"Skipping {tag}: release already exists as {release_tag}.") | |
| continue | |
| comparable_releases = [ | |
| release_key for release_key in release_versions | |
| if is_prerelease_key(release_key) == is_prerelease_key(key) | |
| ] | |
| current_release_version = max(comparable_releases) if comparable_releases else None | |
| if current_release_version is not None and key <= current_release_version: | |
| print(f"Skipping {tag}: not newer than the current release.") | |
| continue | |
| release_commit = run("git", "rev-list", "-n", "1", tag) | |
| if event_name == "push": | |
| head_commit = run("git", "rev-parse", "HEAD") | |
| if release_commit != head_commit: | |
| print(f"Skipping {tag}: tag commit {release_commit} is not the pushed HEAD.") | |
| continue | |
| if subprocess.run( | |
| ["git", "merge-base", "--is-ancestor", release_commit, "HEAD"], | |
| text=True, | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ).returncode != 0: | |
| print(f"Skipping {tag}: tag commit {release_commit} is not reachable from this run ref.") | |
| continue | |
| notes_file = release_notes_for(tag, release_commit) | |
| if notes_file is None: | |
| print(f"Skipping {tag}: no exact matching release_notes file in the tag commit.") | |
| continue | |
| valid_candidates.append((key, release_tag, notes_file, release_commit)) | |
| if not valid_candidates: | |
| skip("No unreleased newer tag with exact matching release notes was found.") | |
| _, tag_name, notes_file, release_commit = max(valid_candidates, key=lambda item: item[0]) | |
| release_version = tag_name[1:] if tag_name.startswith("v") else tag_name | |
| print(f"Release candidate: {tag_name} ({release_commit}) using {notes_file}") | |
| output( | |
| should_release="true", | |
| tag_name=tag_name, | |
| release_version=release_version, | |
| release_commit=release_commit, | |
| ) | |
| PY | |
| build-windows: | |
| name: Windows | |
| needs: | |
| - release-candidate | |
| if: github.event_name != 'push' || needs.release-candidate.outputs.should_release == 'true' | |
| permissions: | |
| actions: read | |
| contents: read | |
| uses: ./.github/workflows/ci-windows.yml | |
| with: | |
| release_commit: ${{ needs.release-candidate.outputs.release_commit || github.sha }} | |
| release_version: ${{ needs.release-candidate.outputs.release_version || '0.0.0' }} | |
| release_tag: ${{ needs.release-candidate.outputs.tag_name }} | |
| symbol_product_name: Vibeshine | |
| publish_symbols: ${{ needs.release-candidate.outputs.should_release == 'true' }} | |
| symbol_release_draft: true | |
| symbol_release_prefix: shine | |
| require_signpath_signing: ${{ needs.release-candidate.outputs.should_release == 'true' }} | |
| signpath_signing_policy_slug: ${{ needs.release-candidate.outputs.should_release == 'true' && 'release-signing' || 'test-signing' }} | |
| # A release-signing request requires manual approval. Leave 15 minutes of | |
| # the hosted runner's six-hour limit for job setup and artifact handling. | |
| signpath_wait_for_completion_timeout_in_seconds: ${{ needs.release-candidate.outputs.should_release == 'true' && 20700 || 1800 }} | |
| require_truehdr_runtime: ${{ needs.release-candidate.outputs.should_release == 'true' }} | |
| secrets: | |
| symbol_token: ${{ secrets.SYMBOL_TOKEN }} | |
| signpath_api_token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| truehdr_runtime_token: ${{ secrets.TRUEHDR_RUNTIME_TOKEN }} | |
| release: | |
| name: Release | |
| if: needs.release-candidate.outputs.should_release == 'true' | |
| needs: | |
| - release-candidate | |
| - build-windows | |
| permissions: | |
| actions: read | |
| contents: write | |
| issues: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ needs.release-candidate.outputs.release_commit }} | |
| - name: Resolve release metadata | |
| id: release-metadata | |
| env: | |
| TAG_NAME: ${{ needs.release-candidate.outputs.tag_name }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${TAG_NAME}" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)([-.][0-9A-Za-z.-]+)?$ ]]; then | |
| base_version="${BASH_REMATCH[1]}" | |
| release_version="${TAG_NAME#v}" | |
| else | |
| echo "::error::Unsupported release tag '${TAG_NAME}'. Use a version tag like 1.15.4, v1.15.4, or 1.15.4-beta.1." | |
| exit 1 | |
| fi | |
| tag_lower="$(printf '%s' "${release_version}" | tr '[:upper:]' '[:lower:]')" | |
| prerelease=false | |
| channel=stable | |
| if [[ "${tag_lower}" == *alpha* ]]; then | |
| prerelease=true | |
| channel=alpha | |
| elif [[ "${tag_lower}" == *beta* ]]; then | |
| prerelease=true | |
| channel=beta | |
| elif [[ "${tag_lower}" == *rc* ]]; then | |
| prerelease=true | |
| channel=rc | |
| elif [[ "${tag_lower}" == *stable* ]]; then | |
| prerelease=false | |
| channel=stable | |
| fi | |
| if [[ "${prerelease}" == "true" ]]; then | |
| make_latest=false | |
| else | |
| make_latest=true | |
| fi | |
| product_name="Vibeshine" | |
| notes_file="" | |
| for candidate in \ | |
| "release_notes/${release_version}.md" \ | |
| "release_notes/${TAG_NAME}.md"; do | |
| if [[ -f "${candidate}" ]]; then | |
| notes_file="${candidate}" | |
| break | |
| fi | |
| done | |
| if [[ -z "${notes_file}" ]]; then | |
| echo "::error::Release notes file not found. Expected exact release notes at release_notes/${release_version}.md or release_notes/${TAG_NAME}.md" | |
| exit 1 | |
| fi | |
| release_body_file="${RUNNER_TEMP}/release-notes-${release_version}.md" | |
| python .github/scripts/compose_release_notes.py \ | |
| --release-version "${release_version}" \ | |
| --notes-dir release_notes \ | |
| --product-name "${product_name}" \ | |
| --output "${release_body_file}" | |
| echo "Using stable-line release notes: ${release_body_file}" | |
| echo "base_version=${base_version}" >> "${GITHUB_OUTPUT}" | |
| echo "channel=${channel}" >> "${GITHUB_OUTPUT}" | |
| echo "make_latest=${make_latest}" >> "${GITHUB_OUTPUT}" | |
| echo "notes_file=${release_body_file}" >> "${GITHUB_OUTPUT}" | |
| echo "prerelease=${prerelease}" >> "${GITHUB_OUTPUT}" | |
| echo "product_name=${product_name}" >> "${GITHUB_OUTPUT}" | |
| echo "release_version=${release_version}" >> "${GITHUB_OUTPUT}" | |
| - name: Download Windows artifacts | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: build-Windows | |
| path: artifacts | |
| - name: Debug artifacts | |
| run: ls -l artifacts | |
| - name: Create or update GitHub release | |
| id: create-release | |
| env: | |
| BASE_VERSION: ${{ steps.release-metadata.outputs.base_version }} | |
| CHANNEL: ${{ steps.release-metadata.outputs.channel }} | |
| GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} | |
| MAKE_LATEST: ${{ steps.release-metadata.outputs.make_latest }} | |
| NOTES_FILE: ${{ steps.release-metadata.outputs.notes_file }} | |
| PRERELEASE: ${{ steps.release-metadata.outputs.prerelease }} | |
| PRODUCT_NAME: ${{ steps.release-metadata.outputs.product_name }} | |
| RELEASE_COMMIT: ${{ needs.release-candidate.outputs.release_commit }} | |
| RELEASE_VERSION: ${{ steps.release-metadata.outputs.release_version }} | |
| TAG_NAME: ${{ needs.release-candidate.outputs.tag_name }} | |
| run: | | |
| set -euo pipefail | |
| release_title="v${RELEASE_VERSION}" | |
| # Keep the installer release asset aligned with the full tag version. | |
| # This preserves prerelease/patch-channel suffixes such as | |
| # -alpha.1, -beta.2, or -stable.3, while plain stable tags fall back | |
| # to just the numeric version (for example, v1.2.3). | |
| versioned_setup="artifacts/${PRODUCT_NAME}Setup-v${RELEASE_VERSION}.exe" | |
| mapfile -t setup_assets < <(find artifacts -maxdepth 1 -type f -name "${PRODUCT_NAME}Setup*.exe" | sort) | |
| if (( ${#setup_assets[@]} != 1 )); then | |
| echo "::error::Expected exactly one ${PRODUCT_NAME} setup artifact, found ${#setup_assets[@]}." | |
| find artifacts -maxdepth 2 -type f -print >&2 || true | |
| exit 1 | |
| fi | |
| if [[ "${setup_assets[0]}" != "${versioned_setup}" ]]; then | |
| mv -f "${setup_assets[0]}" "${versioned_setup}" | |
| fi | |
| release_payload="$( | |
| jq -n \ | |
| --arg tag_name "${TAG_NAME}" \ | |
| --arg target_commitish "${RELEASE_COMMIT}" \ | |
| --arg name "${release_title}" \ | |
| --rawfile body "${NOTES_FILE}" \ | |
| --argjson draft false \ | |
| --argjson prerelease "${PRERELEASE}" \ | |
| --arg make_latest "${MAKE_LATEST}" \ | |
| '{ | |
| tag_name: $tag_name, | |
| target_commitish: $target_commitish, | |
| name: $name, | |
| body: $body, | |
| draft: $draft, | |
| prerelease: $prerelease, | |
| make_latest: $make_latest | |
| }' | |
| )" | |
| release_id="$( | |
| gh api --paginate "repos/${GITHUB_REPOSITORY}/releases" \ | |
| --jq ".[] | select(.tag_name == \"${TAG_NAME}\") | .id" | | |
| head -n 1 | |
| )" | |
| if [[ -n "${release_id}" ]]; then | |
| release_json="$( | |
| gh api \ | |
| --method PATCH \ | |
| "repos/${GITHUB_REPOSITORY}/releases/${release_id}" \ | |
| --input - <<< "${release_payload}" | |
| )" | |
| else | |
| release_json="$( | |
| gh api \ | |
| --method POST \ | |
| "repos/${GITHUB_REPOSITORY}/releases" \ | |
| --input - <<< "${release_payload}" | |
| )" | |
| fi | |
| release_id="$(jq -r '.id // empty' <<< "${release_json}")" | |
| upload_url="$(jq -r '.upload_url // "" | split("{")[0]' <<< "${release_json}")" | |
| if [[ -z "${release_id}" || -z "${upload_url}" ]]; then | |
| echo "::error::Failed to resolve release id/upload URL for ${TAG_NAME}." | |
| exit 1 | |
| fi | |
| release_url="$(jq -r '.html_url // empty' <<< "${release_json}")" | |
| if [[ -z "${release_url}" ]]; then | |
| echo "::error::Failed to resolve release URL for ${TAG_NAME}." | |
| exit 1 | |
| fi | |
| { | |
| echo "release_id=${release_id}" | |
| echo "release_name=${release_title}" | |
| echo "release_url=${release_url}" | |
| } >> "${GITHUB_OUTPUT}" | |
| existing_asset_ids="$( | |
| gh api "repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets" \ | |
| --jq '.[].id' | |
| )" | |
| while IFS= read -r asset_id; do | |
| [[ -z "${asset_id}" ]] && continue | |
| gh api --method DELETE "repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" >/dev/null | |
| done <<< "${existing_asset_ids}" | |
| for asset in artifacts/*; do | |
| [[ -f "${asset}" ]] || continue | |
| asset_name="$(basename "${asset}")" | |
| encoded_asset_name="$(jq -rn --arg name "${asset_name}" '$name|@uri')" | |
| gh api \ | |
| --method POST \ | |
| "${upload_url}?name=${encoded_asset_name}" \ | |
| -H "Content-Type: application/octet-stream" \ | |
| --input "${asset}" \ | |
| >/dev/null | |
| done | |
| - name: Close fixed issues for release | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} | |
| RELEASE_ID: ${{ steps.create-release.outputs.release_id }} | |
| RELEASE_NAME: ${{ steps.create-release.outputs.release_name }} | |
| RELEASE_URL: ${{ steps.create-release.outputs.release_url }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${RELEASE_ID}" || -z "${RELEASE_NAME}" || -z "${RELEASE_URL}" ]]; then | |
| echo "::error::Release metadata outputs were not set." | |
| exit 1 | |
| fi | |
| label_name="fixed" | |
| release_marker="<!-- fixed-release-follow-up:${RELEASE_ID} -->" | |
| encoded_label="$(jq -rn --arg label "${label_name}" '$label|@uri')" | |
| mapfile -t issue_numbers < <( | |
| gh api --paginate "repos/${GITHUB_REPOSITORY}/issues?state=open&labels=${encoded_label}&per_page=100" \ | |
| --jq '.[] | select(.pull_request | not) | .number' | |
| ) | |
| if (( ${#issue_numbers[@]} == 0 )); then | |
| echo "No open issues with the '${label_name}' label to close." | |
| exit 0 | |
| fi | |
| for issue_number in "${issue_numbers[@]}"; do | |
| comments_file="$(mktemp)" | |
| gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${issue_number}/comments?per_page=100" \ | |
| --jq '.[].body' > "${comments_file}" | |
| if ! grep -Fq "${release_marker}" "${comments_file}"; then | |
| comment_body="${release_marker}"$'\n'"This issue has been fixed in the latest release, [${RELEASE_NAME}](${RELEASE_URL}). Closing this issue now that the release containing the fix is available." | |
| gh api \ | |
| --method POST \ | |
| "repos/${GITHUB_REPOSITORY}/issues/${issue_number}/comments" \ | |
| -f "body=${comment_body}" \ | |
| >/dev/null | |
| fi | |
| rm -f "${comments_file}" | |
| gh api \ | |
| --method PATCH \ | |
| "repos/${GITHUB_REPOSITORY}/issues/${issue_number}" \ | |
| -f state=closed \ | |
| -f state_reason=completed \ | |
| >/dev/null | |
| echo "Closed fixed issue #${issue_number} for ${RELEASE_NAME}." | |
| done |