Skip to content

ci-release-build

ci-release-build #2

# Release build: produces signed installers and winget manifests for a tagged
# xonsh release.
#
# Triggers:
# * schedule — polls xonsh/xonsh upstream for a new release tag; if it finds
# one that does not yet exist here, creates a matching release in this repo
# and builds installers for it. This is the main automation path.
# * release (created) — someone created a release in this repo by hand; reuse
# its tag as the version and attach installers to it.
# * workflow_dispatch — manual rebuild. Uses whatever xonsh/xonsh's latest
# release is; if a release with that tag already exists here, re-uploads
# assets (--clobber) instead of skipping.
name: ci-release-build
on:
release:
types: [created]
workflow_dispatch:
schedule:
# Once a day at 04:00 UTC (just after the nightly build at 03:00).
- cron: "0 4 * * *"
permissions:
contents: write
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
skip: ${{ steps.resolve.outputs.skip }}
steps:
- name: Resolve version & ensure release exists
id: resolve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "release" ]; then
# Release was created in this repo by hand — reuse its tag.
VERSION="${{ github.event.release.tag_name }}"
echo "Using release tag: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# schedule / workflow_dispatch: mirror xonsh/xonsh's latest release.
VERSION=$(gh api repos/xonsh/xonsh/releases/latest --jq '.tag_name')
echo "xonsh/xonsh latest release: $VERSION"
if gh release view "$VERSION" >/dev/null 2>&1; then
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "Release $VERSION already built — skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# workflow_dispatch: rebuild existing release, overwriting assets.
echo "Release $VERSION exists — will rebuild and overwrite assets."
else
echo "Creating release $VERSION."
UPSTREAM_URL="https://github.com/xonsh/xonsh/releases/tag/$VERSION"
gh release create "$VERSION" \
--title "$VERSION" \
--notes "Installer build for [xonsh/xonsh $VERSION]($UPSTREAM_URL)." \
--target "${{ github.sha }}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
build:
needs: prepare
if: needs.prepare.outputs.skip != 'true'
strategy:
fail-fast: false
matrix:
include:
# Inno Setup 6 + latest Python — main build for Windows 10/11.
# This one gets signed and used for the winget manifest.
- inno: "6"
python: "3.14.3"
variant: "inno6"
extra-args: ""
# Inno Setup 5 + Python 3.13 — legacy build for Windows 8.1.
# Python 3.13 is the last version supporting Windows 8.1.
# https://docs.python.org/3.13/using/windows.html
- inno: "5"
python: "3.13.1"
variant: "inno5"
extra-args: "--include-python-tag --include-inno-tag"
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install .
- name: Install Inno Setup 6
if: matrix.inno == '6'
run: choco install innosetup -y
- name: Install Inno Setup 5
if: matrix.inno == '5'
shell: pwsh
run: |
$url = "https://files.jrsoftware.org/is/5/innosetup-5.6.1.exe"
$exe = "$env:RUNNER_TEMP\innosetup5.exe"
Invoke-WebRequest -Uri $url -OutFile $exe
Start-Process -FilePath $exe -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/SP-' -Wait
- name: Build installer
shell: bash
run: |
VERSION="${{ needs.prepare.outputs.version }}"
xonsh build.xsh build --version "$VERSION" --python-version "${{ matrix.python }}"
xonsh build.xsh installer --version "$VERSION" --inno-version "${{ matrix.inno }}" ${{ matrix.extra-args }}
- name: Upload unsigned installer
uses: actions/upload-artifact@v4
with:
name: xonsh-${{ matrix.variant }}-unsigned
path: dist/xonsh-*-setup.exe
sign:
needs: [prepare, build]
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- name: Look up inno6 artifact ID
id: artifact
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ID=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \
--jq '.artifacts[] | select(.name == "xonsh-inno6-unsigned") | .id')
echo "id=$ID" >> "$GITHUB_OUTPUT"
- name: Sign installer (inno6)
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
project-slug: xonsh-winget
signing-policy-slug: release-signing
github-artifact-id: ${{ steps.artifact.outputs.id }}
wait-for-completion: true
output-artifact-directory: signed/
- name: Upload signed installer artifact
uses: actions/upload-artifact@v4
with:
name: xonsh-inno6-signed
path: signed/
- name: Upload signed installer to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: gh release upload "${{ needs.prepare.outputs.version }}" signed/xonsh-*-setup.exe --clobber
upload-legacy:
# The inno5 build is unsigned — upload it directly to the release.
needs: [prepare, build]
runs-on: ubuntu-latest
steps:
- name: Download legacy installer
uses: actions/download-artifact@v4
with:
name: xonsh-inno5-unsigned
path: dist/
- name: Upload to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: gh release upload "${{ needs.prepare.outputs.version }}" dist/xonsh-*-setup.exe --clobber
manifests:
# winget manifest points at the main (inno6, signed) installer.
needs: [prepare, build, sign]
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install .
- name: Download signed installer
uses: actions/download-artifact@v4
with:
name: xonsh-inno6-signed
path: dist/
- name: Generate winget manifests
shell: bash
run: xonsh build.xsh manifest --version "${{ needs.prepare.outputs.version }}"
- name: Upload manifest artifact
uses: actions/upload-artifact@v4
with:
name: winget-manifests
path: manifests/