Skip to content

Release

Release #104

Workflow file for this run

name: Release
on:
schedule:
- cron: "40 23 * * *"
workflow_dispatch:
inputs:
version:
description: "Release version (e.g. 0.1.0). Leave empty for nightly."
required: false
type: string
permissions:
contents: write
concurrency:
group: release-${{ github.event.inputs.version || 'nightly' }}
cancel-in-progress: true
env:
DOTNET_VERSION: "10.0.x"
NODE_VERSION: "24"
NIGHTLY_BADGE_BRANCH: "badges"
jobs:
prepare:
name: Prepare metadata
runs-on: ubuntu-latest
outputs:
is_nightly: ${{ steps.meta.outputs.is_nightly }}
version: ${{ steps.meta.outputs.version }}
tag: ${{ steps.meta.outputs.tag }}
ref: ${{ steps.meta.outputs.ref }}
steps:
- name: Resolve release mode
id: meta
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version || '' }}
run: |
set -euo pipefail
VERSION="${INPUT_VERSION}"
if [ -z "${VERSION}" ]; then
echo "is_nightly=true" >> "$GITHUB_OUTPUT"
echo "version=nightly" >> "$GITHUB_OUTPUT"
echo "tag=nightly" >> "$GITHUB_OUTPUT"
echo "ref=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid version: ${VERSION}" >&2
exit 1
fi
echo "is_nightly=false" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "ref=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
prepare-nightly-metadata:
name: Prepare nightly metadata
needs: prepare
if: needs.prepare.outputs.is_nightly == 'true'
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.detect.outputs.should_run }}
steps:
- name: Compare current SHA with published nightly SHA
id: detect
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
BRANCH: ${{ env.NIGHTLY_BADGE_BRANCH }}
SHA: ${{ needs.prepare.outputs.ref }}
run: |
set -euo pipefail
if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
echo "Manual nightly trigger detected; forcing rebuild."
echo "should_run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
mkdir -p prior-meta
PUBLISHED_SHA=""
if gh api "repos/${REPO}/contents/nightly-meta.json?ref=${BRANCH}" --jq '.content' > prior-meta/nightly-meta.base64 2>/dev/null; then
tr -d '\n' < prior-meta/nightly-meta.base64 | base64 -d > prior-meta/nightly-meta.json
PUBLISHED_SHA="$(jq -r '.current_artifact.published_sha // ""' prior-meta/nightly-meta.json)"
fi
SHOULD_RUN="true"
if [ -n "${PUBLISHED_SHA}" ] && [ "${PUBLISHED_SHA}" = "${SHA}" ]; then
SHOULD_RUN="false"
echo "Nightly release already published for ${SHA}; skipping rebuild."
fi
echo "should_run=${SHOULD_RUN}" >> "$GITHUB_OUTPUT"
- name: Checkout repository
if: steps.detect.outputs.should_run == 'true'
uses: actions/checkout@v6
with:
fetch-depth: 1
ref: ${{ needs.prepare.outputs.ref }}
- name: Render nightly status files
if: steps.detect.outputs.should_run == 'true'
uses: ./.github/actions/render-nightly-status
with:
github_token: ${{ github.token }}
repo: ${{ github.repository }}
branch: ${{ env.NIGHTLY_BADGE_BRANCH }}
run_id: ${{ github.run_id }}
event_name: ${{ github.event_name }}
sha: ${{ needs.prepare.outputs.ref }}
phase: start
- name: Update nightly release
if: steps.detect.outputs.should_run == 'true'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
SHA: ${{ needs.prepare.outputs.ref }}
run: |
set -euo pipefail
if gh release view nightly -R "${REPO}" >/dev/null 2>&1; then
gh release edit nightly -R "${REPO}" \
--title "nightly" \
--prerelease \
--notes-file out/release-notes.md
else
gh release create nightly -R "${REPO}" \
--target "${SHA}" \
--title "nightly" \
--prerelease \
--notes-file out/release-notes.md
fi
- name: Publish nightly metadata branch
if: steps.detect.outputs.should_run == 'true'
uses: ./.github/actions/publish-nightly-metadata
with:
github_token: ${{ github.token }}
repo: ${{ github.repository }}
branch: ${{ env.NIGHTLY_BADGE_BRANCH }}
bump-version:
name: Bump version, tag, and push
needs: prepare
if: needs.prepare.outputs.is_nightly == 'false'
runs-on: ubuntu-latest
outputs:
ref: ${{ steps.push.outputs.ref }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node and Yarn
uses: ./.github/actions/setup-node-yarn
with:
node_version: ${{ env.NODE_VERSION }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Bump Cargo version
run: yarn build:bump --version "${{ needs.prepare.outputs.version }}"
- name: Update Cargo.lock
run: cargo update
- name: Commit, tag, and push
id: push
shell: bash
env:
VERSION: ${{ needs.prepare.outputs.version }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Cargo.toml Cargo.lock
if git diff --cached --quiet; then
echo "No version changes to commit." >&2
exit 1
fi
git commit -m "build: bump version to v${VERSION}"
git tag "${TAG}"
git push origin HEAD
git push origin "${TAG}"
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.name }}
needs:
- prepare
- prepare-nightly-metadata
- bump-version
if: always() && needs.prepare.result == 'success' && ((needs.prepare.outputs.is_nightly == 'true' && needs.prepare-nightly-metadata.result == 'success' && needs.prepare-nightly-metadata.outputs.should_run == 'true') || (needs.prepare.outputs.is_nightly == 'false' && needs.bump-version.result == 'success'))
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: linux-x64
os: ubuntu-latest
rust-target: x86_64-unknown-linux-gnu
dotnet-rid: linux-x64
rust-binary: udon-decompiler
dumper-binary: UdonProgramDumper
- name: windows-x64
os: windows-latest
rust-target: x86_64-pc-windows-msvc
dotnet-rid: win-x64
rust-binary: udon-decompiler.exe
dumper-binary: UdonProgramDumper.exe
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.ref || needs.prepare.outputs.ref }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust-target }}
- name: Setup Node and Yarn
uses: ./.github/actions/setup-node-yarn
with:
node_version: ${{ env.NODE_VERSION }}
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Create output directory
shell: bash
run: mkdir -p dist/${{ matrix.name }}
- name: Copy clang-format
shell: bash
run: |
set -euo pipefail
CLANG_FORMAT_PATH="$(node -e "process.stdout.write(require('clang-format-node').clangFormatPath)")"
LLVM_LICENSE_URL="https://raw.githubusercontent.com/llvm/llvm-project/main/LICENSE.TXT"
TARGET_DIR="dist/${{ matrix.name }}"
cp "${CLANG_FORMAT_PATH}" "${TARGET_DIR}/$(basename "${CLANG_FORMAT_PATH}")"
curl -fL \
--retry 6 \
--retry-delay 2 \
--retry-max-time 120 \
--retry-all-errors \
"${LLVM_LICENSE_URL}" \
-o "${TARGET_DIR}/LLVM_LICENSE"
- name: Build udon-decompiler
shell: bash
run: |
set -euo pipefail
TARGET_DIR="dist/${{ matrix.name }}"
cargo build --release --locked --target ${{ matrix.rust-target }} --bin udon-decompiler
cp "target/${{ matrix.rust-target }}/release/${{ matrix.rust-binary }}" "${TARGET_DIR}/${{ matrix.rust-binary }}"
cp LICENSE "${TARGET_DIR}/LICENSE"
- name: Build UdonProgramDumper
shell: bash
run: |
set -euo pipefail
TARGET_DIR="dist/${{ matrix.name }}"
dotnet publish tools/UdonProgramDumper/UdonProgramDumper.csproj \
-c Release \
-r ${{ matrix.dotnet-rid }} \
-p:PublishAot=true
cp "tools/UdonProgramDumper/bin/Release/net10.0/${{ matrix.dotnet-rid }}/publish/${{ matrix.dumper-binary }}" "${TARGET_DIR}/${{ matrix.dumper-binary }}"
- name: Upload artifact directory
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.name }}
path: dist/${{ matrix.name }}
if-no-files-found: error
retention-days: 14
package-editor-scripts:
name: Package editor scripts
needs:
- prepare
- prepare-nightly-metadata
- bump-version
if: always() && needs.prepare.result == 'success' && ((needs.prepare.outputs.is_nightly == 'true' && needs.prepare-nightly-metadata.result == 'success' && needs.prepare-nightly-metadata.outputs.should_run == 'true') || (needs.prepare.outputs.is_nightly == 'false' && needs.bump-version.result == 'success'))
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.ref || needs.prepare.outputs.ref }}
- name: Create output directory
shell: bash
run: mkdir -p dist/editor-scripts
- name: Copy editor scripts
shell: bash
run: |
set -euo pipefail
cp -r tools/Editor/. dist/editor-scripts/
cp LICENSE dist/editor-scripts/LICENSE
- name: Upload artifact directory
uses: actions/upload-artifact@v6
with:
name: editor-scripts
path: dist/editor-scripts
if-no-files-found: error
retention-days: 14
publish-nightly:
name: Publish nightly GitHub release
needs:
- prepare
- prepare-nightly-metadata
- build
- package-editor-scripts
if: always() && needs.prepare.outputs.is_nightly == 'true' && needs.prepare-nightly-metadata.result == 'success' && needs.prepare-nightly-metadata.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
ref: ${{ needs.prepare.outputs.ref }}
- name: Render nightly status files
id: nightly-status
uses: ./.github/actions/render-nightly-status
with:
github_token: ${{ github.token }}
repo: ${{ github.repository }}
branch: ${{ env.NIGHTLY_BADGE_BRANCH }}
run_id: ${{ github.run_id }}
event_name: ${{ github.event_name }}
sha: ${{ needs.prepare.outputs.ref }}
phase: finalize
build_result: ${{ needs.build.result }}
package_result: ${{ needs.package-editor-scripts.result }}
- name: Download build artifacts
if: steps.nightly-status.outputs.result == 'success'
uses: actions/download-artifact@v8
with:
path: release-assets
- name: Package nightly assets
if: steps.nightly-status.outputs.result == 'success'
shell: bash
run: |
set -euo pipefail
cd release-assets
for dir in */; do
[ -d "$dir" ] || continue
name="${dir%/}"
zip -r "${name}.zip" "$name"
done
- name: Checkout repository
if: steps.nightly-status.outputs.result == 'success'
uses: actions/checkout@v6
with:
fetch-depth: 0
path: repo
ref: ${{ needs.prepare.outputs.ref }}
- name: Move nightly tag to current commit
if: steps.nightly-status.outputs.result == 'success'
shell: bash
env:
SHA: ${{ needs.prepare.outputs.ref }}
run: |
set -euo pipefail
git -C repo config user.name "github-actions[bot]"
git -C repo config user.email "github-actions[bot]@users.noreply.github.com"
git -C repo tag -f nightly "${SHA}"
git -C repo push origin refs/tags/nightly --force
- name: Update nightly release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
SHA: ${{ needs.prepare.outputs.ref }}
RESULT: ${{ steps.nightly-status.outputs.result }}
run: |
set -euo pipefail
if gh release view nightly -R "${REPO}" >/dev/null 2>&1; then
gh release edit nightly -R "${REPO}" \
--title "nightly" \
--prerelease \
--notes-file out/release-notes.md
else
gh release create nightly -R "${REPO}" \
--target "${SHA}" \
--title "nightly" \
--prerelease \
--notes-file out/release-notes.md
fi
if [ "${RESULT}" = "success" ]; then
gh release upload nightly -R "${REPO}" release-assets/*.zip --clobber
fi
- name: Publish nightly metadata branch
uses: ./.github/actions/publish-nightly-metadata
with:
github_token: ${{ github.token }}
repo: ${{ github.repository }}
branch: ${{ env.NIGHTLY_BADGE_BRANCH }}
draft-release:
name: Create draft GitHub release
needs:
- prepare
- build
- package-editor-scripts
if: needs.prepare.outputs.is_nightly == 'false' && needs.build.result == 'success' && needs.package-editor-scripts.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
path: release-assets
- name: Package release assets
shell: bash
run: |
set -euo pipefail
cd release-assets
for dir in */; do
[ -d "$dir" ] || continue
name="${dir%/}"
zip -r "${name}.zip" "$name"
done
- name: Create draft release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare.outputs.tag }}
name: ${{ needs.prepare.outputs.tag }}
draft: true
generate_release_notes: true
fail_on_unmatched_files: true
files: release-assets/*.zip