name: Release on: workflow_dispatch: inputs: version: description: Version from moli/Cargo.toml (for example, 0.1.1) required: true type: string prerelease: description: Mark this release as a prerelease required: true default: false type: boolean draft: description: Create a draft instead of publishing immediately required: true default: false type: boolean concurrency: group: release-${{ inputs.version }} cancel-in-progress: false permissions: contents: read env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: safe.directory GIT_CONFIG_VALUE_0: ${{ github.workspace }} jobs: validate: name: Validate release request runs-on: ubuntu-latest timeout-minutes: 10 outputs: tag: ${{ steps.release.outputs.tag }} version: ${{ steps.release.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - name: Validate version and tag id: release shell: bash env: INPUT_VERSION: ${{ inputs.version }} run: | set -euo pipefail version="${INPUT_VERSION#v}" semver_pattern='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' if [[ ! "$version" =~ $semver_pattern ]]; then echo "Invalid semantic version: $INPUT_VERSION" >&2 exit 1 fi manifest_version=$(awk '$1 == "version" && $2 == "=" { gsub(/"/, "", $3); print $3; exit }' moli/Cargo.toml) if [[ "$version" != "$manifest_version" ]]; then echo "Requested version $version does not match moli/Cargo.toml ($manifest_version)." >&2 exit 1 fi tag="v$version" if git show-ref --verify --quiet "refs/tags/$tag"; then echo "Tag $tag already exists." >&2 exit 1 fi echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" build-linux: name: Build Linux ${{ matrix.arch }} needs: validate runs-on: ${{ matrix.runner }} timeout-minutes: 90 strategy: fail-fast: false matrix: include: - arch: x86_64 runner: ubuntu-latest target: x86_64-unknown-linux-gnu artifact: release-linux-x86_64 - arch: aarch64 runner: ubuntu-24.04-arm target: aarch64-unknown-linux-gnu artifact: release-linux-aarch64 container: rust:1.96.1-bookworm steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install packaging dependencies shell: bash run: | apt-get update apt-get install --yes --no-install-recommends binutils cmake libclang-dev python3 - name: Validate shell installer shell: bash run: sh -n scripts/install.sh - name: Build and package shell: bash run: >- python3 scripts/release.py --version "${{ needs.validate.outputs.version }}" --expected-target "${{ matrix.target }}" - name: Upload Linux artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.artifact }} path: dist/ if-no-files-found: error compression-level: 0 retention-days: 7 build-macos: name: Build macOS ${{ matrix.arch }} needs: validate runs-on: ${{ matrix.runner }} timeout-minutes: 120 strategy: fail-fast: false matrix: include: - arch: x86_64 runner: macos-15-intel target: x86_64-apple-darwin artifact: release-macos-x86_64 - arch: aarch64 runner: macos-15 target: aarch64-apple-darwin artifact: release-macos-aarch64 env: MACOSX_DEPLOYMENT_TARGET: "13.0" steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install pinned Rust toolchain shell: bash run: | set -euo pipefail toolchain=$(tr -d '[:space:]' < rust-toolchain) rustup toolchain install "$toolchain" --profile minimal --no-self-update rustc --version - name: Build and package shell: bash run: >- python3 scripts/release.py --version "${{ needs.validate.outputs.version }}" --expected-target "${{ matrix.target }}" - name: Upload macOS artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.artifact }} path: dist/ if-no-files-found: error compression-level: 0 retention-days: 7 build-windows: name: Build Windows ${{ matrix.arch }} needs: validate runs-on: ${{ matrix.runner }} timeout-minutes: 120 strategy: fail-fast: false matrix: include: - arch: x86_64 runner: windows-2025 target: x86_64-pc-windows-msvc artifact: release-windows-x86_64 - arch: aarch64 runner: windows-11-arm target: aarch64-pc-windows-msvc artifact: release-windows-aarch64 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install pinned Rust toolchain shell: pwsh run: | $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true $toolchain = (Get-Content rust-toolchain -Raw).Trim() rustup toolchain install $toolchain --profile minimal --no-self-update rustc --version - name: Validate PowerShell installer shell: pwsh run: | $tokens = $null $parseErrors = $null [System.Management.Automation.Language.Parser]::ParseFile( (Resolve-Path scripts/install.ps1), [ref] $tokens, [ref] $parseErrors ) | Out-Null if ($parseErrors.Count -ne 0) { $parseErrors | ForEach-Object { Write-Error $_ } exit 1 } - name: Build and package shell: pwsh run: >- python scripts/release.py --version "${{ needs.validate.outputs.version }}" --expected-target "${{ matrix.target }}" - name: Upload Windows artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.artifact }} path: dist/ if-no-files-found: error compression-level: 0 retention-days: 7 publish: name: Create GitHub Release needs: - validate - build-linux - build-macos - build-windows runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Download packaged artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: release-* path: dist merge-multiple: true - name: Add stable installers shell: bash run: | install -m 0755 scripts/install.sh dist/moli-installer.sh install -m 0644 scripts/install.ps1 dist/moli-installer.ps1 - name: Verify release assets shell: bash run: | set -euo pipefail expected_archives=( "moli-x86_64-unknown-linux-gnu.tar.gz" "moli-aarch64-unknown-linux-gnu.tar.gz" "moli-x86_64-apple-darwin.tar.gz" "moli-aarch64-apple-darwin.tar.gz" "moli-x86_64-pc-windows-msvc.zip" "moli-aarch64-pc-windows-msvc.zip" ) for archive in "${expected_archives[@]}"; do test -f "dist/$archive" done test -f dist/moli-installer.sh test -f dist/moli-installer.ps1 sh -n dist/moli-installer.sh artifact_count=$(find dist -maxdepth 1 -type f | wc -l) if [[ "$artifact_count" -ne 8 ]]; then echo "Expected 8 release assets, found $artifact_count." >&2 find dist -maxdepth 1 -type f -print >&2 exit 1 fi - name: Create GitHub Release shell: bash env: GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ needs.validate.outputs.tag }} RELEASE_DRAFT: ${{ inputs.draft }} RELEASE_PRERELEASE: ${{ inputs.prerelease }} run: | set -euo pipefail assets=(dist/*) args=( release create "$RELEASE_TAG" "${assets[@]}" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "Moli $RELEASE_TAG" --generate-notes ) if [[ "$RELEASE_PRERELEASE" == true ]]; then args+=(--prerelease) elif [[ "$RELEASE_DRAFT" != true ]]; then args+=(--latest) fi if [[ "$RELEASE_DRAFT" == true ]]; then args+=(--draft) fi release_url=$(gh "${args[@]}") printf 'Created %s\n' "$release_url" printf '### Release created\n\n[%s](%s)\n' \ "$RELEASE_TAG" "$release_url" >> "$GITHUB_STEP_SUMMARY"