Build & Release Bangen #25
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: Build & Release Bangen | |
| on: | |
| workflow_dispatch: | |
| push: | |
| paths: | |
| - "pyproject.toml" | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| tag: | |
| name: Extract version & create tag | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.extract.outputs.version }} | |
| tag: ${{ steps.extract.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract version from pyproject.toml | |
| id: extract | |
| run: | | |
| VERSION=$(python3 - <<'EOF' | |
| import tomllib | |
| with open("pyproject.toml", "rb") as f: | |
| print(tomllib.load(f)["project"]["version"]) | |
| EOF | |
| ) | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Abort if tag already exists | |
| run: | | |
| if git rev-parse "refs/tags/${{ steps.extract.outputs.tag }}" &>/dev/null; then | |
| echo "Tag ${{ steps.extract.outputs.tag }} already exists β skipping release." | |
| exit 1 | |
| fi | |
| - name: Create and push tag | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag "${{ steps.extract.outputs.tag }}" | |
| git push origin "${{ steps.extract.outputs.tag }}" | |
| build-linux: | |
| name: Build (linux) | |
| needs: tag | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build inside manylinux2014 (glibc 2.17) | |
| env: | |
| VERSION: ${{ needs.tag.outputs.version }} | |
| run: | | |
| printf 'from bangen.app import main\nif __name__ == "__main__":\n main()\n' > _entry.py | |
| docker run --rm \ | |
| -v "${{ github.workspace }}:/workspace" \ | |
| -w /workspace \ | |
| -e VERSION="$VERSION" \ | |
| quay.io/pypa/manylinux2014_x86_64 \ | |
| bash -c ' | |
| set -euo pipefail | |
| PY=/opt/python/cp311-cp311/bin/python | |
| $PY -m pip install -q -U pip wheel pyinstaller | |
| $PY -m pip install -q ".[all]" | |
| $PY -m PyInstaller \ | |
| --onefile \ | |
| --noconfirm \ | |
| --strip \ | |
| --noupx \ | |
| --name bangen \ | |
| --distpath dist \ | |
| --workpath build \ | |
| --collect-all bangen \ | |
| --collect-all pyfiglet \ | |
| --collect-all rich \ | |
| --collect-all PIL \ | |
| --collect-all typer \ | |
| --exclude-module tkinter \ | |
| --exclude-module unittest \ | |
| --exclude-module test \ | |
| --exclude-module distutils \ | |
| --exclude-module setuptools \ | |
| --exclude-module pkg_resources \ | |
| --exclude-module email \ | |
| --exclude-module http \ | |
| --exclude-module html \ | |
| --exclude-module xml \ | |
| --exclude-module xmlrpc \ | |
| --optimize 2 \ | |
| _entry.py | |
| echo "=== GLIBC symbols ===" | |
| strings dist/bangen | grep "GLIBC_" | sort -V | uniq | |
| if strings dist/bangen | grep -E "GLIBC_2\.(1[89]|[2-9][0-9])"; then | |
| echo "ERROR: Binary requires glibc newer than 2.17!" | |
| exit 1 | |
| fi | |
| echo "β glibc 2.17 compatible" | |
| ' | |
| - name: Package artifact | |
| env: | |
| VERSION: ${{ needs.tag.outputs.version }} | |
| run: | | |
| mkdir -p release | |
| NAME="bangen-linux-$VERSION" | |
| mv dist/bangen release/"$NAME" | |
| (cd release && tar -czf "$NAME.tar.gz" "$NAME" && rm "$NAME") | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-build | |
| path: release/* | |
| retention-days: 1 | |
| build-windows-macos: | |
| name: Build (${{ matrix.name }}) | |
| needs: tag | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| name: windows | |
| binary: bangen.exe | |
| - os: macos-latest | |
| name: macos | |
| binary: bangen | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Cache pip | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cache/pip | |
| ~/AppData/Local/pip/Cache | |
| key: pip-${{ runner.os }}-${{ hashFiles('pyproject.toml') }} | |
| restore-keys: pip-${{ runner.os }}- | |
| - name: Install Python deps | |
| shell: bash | |
| run: | | |
| python -m pip install -U pip wheel pyinstaller | |
| pip install ".[all]" | |
| - name: Build with PyInstaller | |
| shell: bash | |
| env: | |
| VERSION: ${{ needs.tag.outputs.version }} | |
| run: | | |
| printf 'from bangen.app import main\nif __name__ == "__main__":\n main()\n' > _entry.py | |
| python -m PyInstaller \ | |
| --onefile \ | |
| --noconfirm \ | |
| --noupx \ | |
| --name bangen \ | |
| --distpath dist \ | |
| --workpath build \ | |
| --collect-all bangen \ | |
| --collect-all pyfiglet \ | |
| --collect-all rich \ | |
| --collect-all PIL \ | |
| --collect-all typer \ | |
| --exclude-module tkinter \ | |
| --exclude-module unittest \ | |
| --exclude-module test \ | |
| --exclude-module distutils \ | |
| --exclude-module setuptools \ | |
| --exclude-module pkg_resources \ | |
| --exclude-module email \ | |
| --exclude-module http \ | |
| --exclude-module html \ | |
| --exclude-module xml \ | |
| --exclude-module xmlrpc \ | |
| --optimize 2 \ | |
| _entry.py | |
| - name: Package artifact | |
| shell: bash | |
| env: | |
| VERSION: ${{ needs.tag.outputs.version }} | |
| BINARY: ${{ matrix.binary }} | |
| run: | | |
| mkdir -p release | |
| NAME="bangen-${{ matrix.name }}-$VERSION" | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| mv "dist/$BINARY" "release/$NAME.exe" | |
| powershell -Command "Compress-Archive -Path release\\$NAME.exe -DestinationPath release\\$NAME.zip" | |
| rm "release/$NAME.exe" | |
| else | |
| mv "dist/$BINARY" "release/$NAME" | |
| (cd release && tar -czf "$NAME.tar.gz" "$NAME" && rm "$NAME") | |
| fi | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.name }}-build | |
| path: release/* | |
| retention-days: 1 | |
| release: | |
| name: Publish Release | |
| needs: [tag, build-linux, build-windows-macos] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Generate checksums | |
| run: | | |
| cd artifacts | |
| sha256sum * > SHA256SUMS.txt | |
| - name: Build release notes | |
| id: notes | |
| env: | |
| VERSION: ${{ needs.tag.outputs.version }} | |
| run: | | |
| PREV_TAG=$(git tag --sort=-version:refname | grep -v "^$VERSION$" | head -1) | |
| if [[ -n "$PREV_TAG" ]]; then | |
| COMMITS=$(git log "$PREV_TAG"..HEAD --pretty=format:"- %s (%h)" --no-merges) | |
| COMPARE_URL="https://github.com/${{ github.repository }}/compare/$PREV_TAG...$VERSION" | |
| SINCE_LINE="> Changes since \`$PREV_TAG\` Β· [Full diff]($COMPARE_URL)" | |
| else | |
| COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges) | |
| SINCE_LINE="> Initial release" | |
| fi | |
| FEATS=$(echo "$COMMITS" | grep -E "^- feat" | sed 's/^- feat[^:]*: /- /' || true) | |
| FIXES=$(echo "$COMMITS" | grep -E "^- fix" | sed 's/^- fix[^:]*: /- /' || true) | |
| CHORES=$(echo "$COMMITS" | grep -E "^- (chore|refactor|perf|build|ci)" | sed 's/^- [^:]*: /- /' || true) | |
| OTHER=$(echo "$COMMITS" | grep -vE "^- (feat|fix|chore|refactor|perf|build|ci)" || true) | |
| LINUX_SHA=$(grep "bangen-linux-$VERSION.tar.gz" artifacts/SHA256SUMS.txt | awk '{print $1}' || true) | |
| WINDOWS_SHA=$(grep "bangen-windows-$VERSION.zip" artifacts/SHA256SUMS.txt | awk '{print $1}' || true) | |
| MACOS_SHA=$(grep "bangen-macos-$VERSION.tar.gz" artifacts/SHA256SUMS.txt | awk '{print $1}' || true) | |
| { | |
| echo "body<<NOTES_EOF" | |
| echo "# β¨ Bangen $VERSION" | |
| echo "" | |
| echo "$SINCE_LINE" | |
| echo "" | |
| echo "---" | |
| echo "" | |
| if [[ -n "$FEATS" ]]; then | |
| echo "## π What's New" | |
| echo "$FEATS" | |
| echo "" | |
| fi | |
| if [[ -n "$FIXES" ]]; then | |
| echo "## π Bug Fixes" | |
| echo "$FIXES" | |
| echo "" | |
| fi | |
| if [[ -n "$CHORES" ]]; then | |
| echo "## π§ Maintenance" | |
| echo "$CHORES" | |
| echo "" | |
| fi | |
| if [[ -n "$OTHER" ]]; then | |
| echo "## π Other Changes" | |
| echo "$OTHER" | |
| echo "" | |
| fi | |
| echo "---" | |
| echo "" | |
| echo "## π¦ Downloads" | |
| echo "" | |
| echo "Pre-built native binaries β **no Python installation required.**" | |
| echo "" | |
| echo "| Platform | Architecture | File | SHA-256 |" | |
| echo "|----------|-------------|------|---------|" | |
| echo "| π§ Linux | x86_64 | \`bangen-linux-$VERSION.tar.gz\` | \`$LINUX_SHA\` |" | |
| echo "| πͺ Windows | x86_64 | \`bangen-windows-$VERSION.zip\` | \`$WINDOWS_SHA\` |" | |
| echo "| π macOS | arm64 | \`bangen-macos-$VERSION.tar.gz\` | \`$MACOS_SHA\` |" | |
| echo "" | |
| echo "### π§ Linux compatibility" | |
| echo "" | |
| echo "The Linux binary is built inside a \`manylinux2014\` (CentOS 7) container against **glibc 2.17**," | |
| echo "making it compatible with virtually any Linux system from 2014 onwards β including:" | |
| echo "" | |
| echo "| Environment | Compatible |" | |
| echo "|-------------|-----------|" | |
| echo "| Google Colab | β |" | |
| echo "| Ubuntu 16.04+ | β |" | |
| echo "| Debian 8+ | β |" | |
| echo "| RHEL / CentOS 7+ | β |" | |
| echo "| Fedora 20+ | β |" | |
| echo "| Old VPS / servers | β |" | |
| echo "| GitHub Actions runners | β |" | |
| echo "" | |
| echo "### π Quick Install" | |
| echo "" | |
| echo "\`\`\`bash" | |
| echo "# Linux" | |
| echo "tar -xzf bangen-linux-$VERSION.tar.gz" | |
| echo "chmod +x bangen-linux-$VERSION" | |
| echo "sudo mv bangen-linux-$VERSION /usr/local/bin/bangen" | |
| echo "" | |
| echo "# macOS" | |
| echo "tar -xzf bangen-macos-$VERSION.tar.gz" | |
| echo "chmod +x bangen-macos-$VERSION" | |
| echo "sudo mv bangen-macos-$VERSION /usr/local/bin/bangen" | |
| echo "" | |
| echo "# Windows (PowerShell)" | |
| echo "Expand-Archive bangen-windows-$VERSION.zip ." | |
| echo "# Move bangen.exe somewhere on your PATH" | |
| echo "" | |
| echo "# Google Colab" | |
| echo "!wget -q https://github.com/${{ github.repository }}/releases/download/$VERSION/bangen-linux-$VERSION.tar.gz" | |
| echo "!tar -xzf bangen-linux-$VERSION.tar.gz" | |
| echo "!chmod +x bangen-linux-$VERSION && mv bangen-linux-$VERSION /usr/local/bin/bangen" | |
| echo "!bangen --help" | |
| echo "\`\`\`" | |
| echo "" | |
| echo "### π Verify Your Download" | |
| echo "" | |
| echo "\`\`\`bash" | |
| echo "sha256sum -c SHA256SUMS.txt" | |
| echo "\`\`\`" | |
| echo "" | |
| echo "> Full checksums for all artifacts are in \`SHA256SUMS.txt\`, included in the release assets below." | |
| echo "NOTES_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.tag.outputs.tag }} | |
| name: "β¨ Bangen ${{ needs.tag.outputs.version }}" | |
| body: ${{ steps.notes.outputs.body }} | |
| files: artifacts/* |