Skip to content

Build & Release

Build & Release #17

Workflow file for this run

name: Release PyElevate
on:
workflow_dispatch:
inputs:
release_type:
description: 'Release type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: pyelevate
asset_name: pyelevate-linux-x86_64
strip: true
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
artifact_name: pyelevate
asset_name: pyelevate-linux-musl-x86_64
strip: true
- os: macos-latest
target: x86_64-apple-darwin
artifact_name: pyelevate
asset_name: pyelevate-macos-x86_64
strip: true
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: pyelevate
asset_name: pyelevate-macos-aarch64
strip: true
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: pyelevate.exe
asset_name: pyelevate-windows-x86_64
strip: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: '.'
cache-on-failure: true
cache-all-crates: true
- name: Install musl-tools (Linux musl only)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build release binary
run: |
cargo build --release --target ${{ matrix.target }}
- name: Strip binary (Unix-like only)
if: matrix.strip
run: |
strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
- name: Prepare artifact directory
run: |
mkdir -p artifacts
cp target/${{ matrix.target }}/release/${{ matrix.artifact_name }} artifacts/
- name: Create archive (Unix)
if: runner.os != 'Windows'
run: |
cd artifacts
tar czf ../pyelevate-${{ matrix.asset_name }}.tar.gz *
cd ..
- name: Create archive (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
cd artifacts
Compress-Archive -Path * -DestinationPath ..\pyelevate-${{ matrix.asset_name }}.zip
cd ..
- name: Calculate checksums
id: checksums
run: |
if [ "${{ runner.os }}" = "Windows" ]; then
echo "archive=pyelevate-${{ matrix.asset_name }}.zip" >> $GITHUB_OUTPUT
certutil -hashfile pyelevate-${{ matrix.asset_name }}.zip SHA256 | head -1 > checksum.txt
else
echo "archive=pyelevate-${{ matrix.asset_name }}.tar.gz" >> $GITHUB_OUTPUT
sha256sum pyelevate-${{ matrix.asset_name }}.tar.gz > checksum.txt
fi
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-artifacts-${{ matrix.asset_name }}
path: |
pyelevate-${{ matrix.asset_name }}.*
checksum.txt
retention-days: 1
generate-changelog:
name: Generate Changelog
runs-on: ubuntu-latest
outputs:
changelog: ${{ steps.changelog.outputs.content }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog from commits
id: changelog
run: |
{
echo 'content<<EOF'
# Release Notes
**Release Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')**
## Changes in this release
### Features
git log --oneline --grep='feat' HEAD...HEAD~20 2>/dev/null | sed 's/^/- /' || echo "- No new features in this release"
### Bug Fixes
git log --oneline --grep='fix' HEAD...HEAD~20 2>/dev/null | sed 's/^/- /' || echo "- No bug fixes in this release"
### Documentation
git log --oneline --grep='docs' HEAD...HEAD~20 2>/dev/null | sed 's/^/- /' || echo "- No documentation updates in this release"
### Other Changes
git log --oneline HEAD~0..HEAD~19 2>/dev/null | head -10 | sed 's/^/- /' || echo "- See commit history for details"
## All Commits
EOF
git log --pretty=format:"- %h: %s (%an, %ai)" HEAD~0..HEAD~49 >> changelog.txt 2>/dev/null || echo "See commit history for details" >> changelog.txt
cat changelog.txt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
}
shell: bash
- name: Get version
id: version
run: |
VERSION=$(grep '^version' Cargo.toml | head -1 | grep -oP '(?<=")\d+\.\d+\.\d+(?=")')
echo "version=$VERSION" >> $GITHUB_OUTPUT
create-release:
name: Create Release
needs: [build, generate-changelog]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
- name: Organize artifacts
run: |
mkdir -p release-files
find release-artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "checksum.txt" \) -exec cp {} release-files/ \;
ls -lah release-files/
- name: Generate checksums manifest
run: |
{
echo "# PyElevate Release Checksums (SHA256)"
echo ""
echo "**Release Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo ""
echo "## Binary Checksums"
echo ""
for file in release-files/*.tar.gz release-files/*.zip; do
if [ -f "$file" ]; then
filename=$(basename "$file")
if [[ "$file" == *.tar.gz ]]; then
checksum=$(sha256sum "$file" | awk '{print $1}')
else
checksum=$(sha256sum "$file" | awk '{print $1}')
fi
echo "### $filename"
echo "\`\`\`"
echo "$checksum"
echo "\`\`\`"
echo ""
fi
done
} > release-files/CHECKSUMS.md
cat release-files/CHECKSUMS.md
- name: Create release body
id: release_body
run: |
{
echo 'body<<EOF'
echo "# PyElevate v${{ needs.generate-changelog.outputs.version }}"
echo ""
echo "**Released:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo ""
echo "## Release Summary"
echo ""
echo "Modern Python requirements.txt dependency upgrader with interactive TUI"
echo ""
echo "## Download"
echo ""
echo "| Platform | Binary | Checksum |"
echo "|----------|--------|----------|"
for file in release-files/*.tar.gz release-files/*.zip; do
if [ -f "$file" ]; then
filename=$(basename "$file")
checksum=$(sha256sum "$file" | awk '{print $1}')
echo "| $(echo $filename | sed 's/pyelevate-//g' | sed 's/-/ /g' | sed 's/.tar.gz//g' | sed 's/.zip//g') | \`$filename\` | \`${checksum:0:16}...\` |"
fi
done
echo ""
echo "## What's New"
echo ""
echo "See [CHANGELOG.md](CHANGELOG.md) for detailed changes."
echo ""
echo "## Installation"
echo ""
echo "### Linux"
echo "\`\`\`bash"
echo "tar xzf pyelevate-linux-x86_64.tar.gz"
echo "chmod +x pyelevate"
echo "./pyelevate"
echo "\`\`\`"
echo ""
echo "### macOS"
echo "\`\`\`bash"
echo "tar xzf pyelevate-macos-x86_64.tar.gz"
echo "chmod +x pyelevate"
echo "./pyelevate"
echo "\`\`\`"
echo ""
echo "### Windows"
echo "\`\`\`powershell"
echo "Expand-Archive pyelevate-windows-x86_64.zip"
echo ".\pyelevate\pyelevate.exe"
echo "\`\`\`"
echo ""
echo "## Features"
echo ""
echo "- 🎨 Interactive TUI with Ratatui"
echo "- πŸš€ Async PyPI fetching with Tokio"
echo "- πŸ“Š Smart version categorization"
echo "- πŸ” Fuzzy search and filtering"
echo "- β™Ώ Full keyboard navigation"
echo "- πŸ›‘οΈ Safe upgrades with automatic backups"
echo "- πŸ”’ Lock file support"
echo "- πŸ§ͺ Dry-run mode"
echo ""
echo "## Verification"
echo ""
echo "All binaries are signed with SHA256 checksums. See CHECKSUMS.md for details."
echo ""
echo "## System Requirements"
echo ""
echo "- Linux: glibc or musl"
echo "- macOS: 10.13+ (Intel or Apple Silicon)"
echo "- Windows: Windows 7+"
echo ""
echo "EOF
} >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ needs.generate-changelog.outputs.version }}
name: PyElevate v${{ needs.generate-changelog.outputs.version }}
body: ${{ steps.release_body.outputs.body }}
draft: false
prerelease: false
files: release-files/*.tar.gz
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Windows ZIP as separate release asset
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const releaseId = context.payload.release.id;
const zipFiles = fs.readdirSync('release-files').filter(f => f.endsWith('.zip'));
for (const file of zipFiles) {
const filePath = path.join('release-files', file);
const fileContent = fs.readFileSync(filePath);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
name: file,
data: fileContent,
headers: {
'content-length': fileContent.length,
'content-type': 'application/zip'
}
});
console.log(`Uploaded ${file}`);
}
- name: Upload checksums manifest
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const releaseId = context.payload.release.id;
const fileContent = fs.readFileSync('release-files/CHECKSUMS.md');
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
name: 'CHECKSUMS.md',
data: fileContent,
headers: {
'content-length': fileContent.length,
'content-type': 'text/markdown'
}
});
console.log('Uploaded CHECKSUMS.md');
finalize:
name: Finalize Release
needs: [create-release, generate-changelog]
runs-on: ubuntu-latest
if: success()
steps:
- name: Release Summary
run: |
echo "βœ… Release Complete!"
echo ""
echo "πŸ“¦ PyElevate v${{ needs.generate-changelog.outputs.version }} has been released"
echo "⏰ Release time: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo ""
echo "🎯 Available downloads:"
echo " - Linux x86_64 (glibc)"
echo " - Linux x86_64 (musl)"
echo " - macOS x86_64"
echo " - macOS aarch64 (Apple Silicon)"
echo " - Windows x86_64"
echo ""
echo "πŸ” All binaries include SHA256 checksums"
echo "πŸ“ See CHECKSUMS.md for verification"