Build & Release Bangen #41
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: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.extract.outputs.version }} | |
| tag: ${{ steps.extract.outputs.tag }} | |
| exists: ${{ steps.check.outputs.exists }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - 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 | |
| - id: check | |
| run: | | |
| if git rev-parse "refs/tags/${{ steps.extract.outputs.tag }}" >/dev/null 2>&1; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - if: steps.check.outputs.exists == 'false' | |
| 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: | |
| needs: tag | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| platform: linux | |
| bin: bangen | |
| - os: windows-latest | |
| platform: windows | |
| bin: bangen.exe | |
| - os: macos-latest | |
| platform: macos | |
| bin: bangen | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Windows & macOS native Python setup (Pinned to 3.11 for parity) | |
| - if: matrix.platform != 'linux' | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| # Core Build step handling logic per platform | |
| - name: Build Executable | |
| shell: bash | |
| env: | |
| VERSION: ${{ needs.tag.outputs.version }} | |
| PLATFORM: ${{ matrix.platform }} | |
| run: | | |
| printf 'from bangen.app import main\nif __name__=="__main__": main()\n' > _entry.py | |
| # 1. Bootstrap Python packages needed for the build environment | |
| if [[ "$PLATFORM" == "linux" ]]; then | |
| # Inside the container we use py3.11 to guarantee wheel availability | |
| DOCKER_CMD="py3.11 -m pip install -U pip wheel pyinstaller packaging --no-cache-dir && py3.11 -m pip install . --no-cache-dir" | |
| else | |
| python -m pip install -U pip wheel pyinstaller packaging | |
| pip install . | |
| fi | |
| # 2. Extract dependencies dynamically using standardized PEP 508 parsing | |
| GENERATE_FLAGS_SCRIPT=$(cat <<'PYTHON_SCRIPT' | |
| import tomllib | |
| from packaging.requirements import Requirement | |
| pkg_to_module = {"Pillow": "PIL"} | |
| modules = ["bangen"] | |
| with open("pyproject.toml", "rb") as f: | |
| deps = tomllib.load(f).get("project", {}).get("dependencies", []) | |
| for dep in deps: | |
| name = Requirement(dep).name | |
| modules.append(pkg_to_module.get(name, name)) | |
| print(" ".join(f"--collect-all {m}" for m in sorted(set(modules)))) | |
| PYTHON_SCRIPT | |
| ) | |
| # 3. Compile executable | |
| if [[ "$PLATFORM" == "linux" ]]; then | |
| # Run inside modern manylinux container with Python 3.11 to avoid native compilation errors | |
| docker run --rm \ | |
| -u $(id -u):$(id -g) \ | |
| -e HOME=/tmp \ | |
| -v "${{ github.workspace }}:/workspace" \ | |
| -w /workspace \ | |
| ghcr.io/yt-dlp/manylinux2014_x86_64-shared \ | |
| bash -c " | |
| set -euo pipefail | |
| $DOCKER_CMD | |
| COLLECT_FLAGS=\$(py3.11 -c '$GENERATE_FLAGS_SCRIPT') | |
| py3.11 -m PyInstaller --onefile --noconfirm --strip --noupx --name bangen --distpath dist --workpath build \$COLLECT_FLAGS --optimize 2 _entry.py | |
| strings dist/bangen | grep GLIBC_ | sort -V | uniq | |
| " | |
| else | |
| COLLECT_FLAGS=$(python -c "$GENERATE_FLAGS_SCRIPT") | |
| python -m PyInstaller --onefile --noconfirm --noupx --name bangen --distpath dist --workpath build $COLLECT_FLAGS --optimize 2 _entry.py | |
| fi | |
| # 4. Standardized artifact compression and packaging | |
| - name: Package Output | |
| shell: bash | |
| run: | | |
| mkdir -p release | |
| NAME="bangen-${{ matrix.platform }}-${{ needs.tag.outputs.version }}" | |
| if [[ "${{ matrix.platform }}" == "windows" ]]; then | |
| mv dist/${{ matrix.bin }} release/$NAME.exe | |
| powershell -Command "Compress-Archive -Path release\\$NAME.exe -DestinationPath release\\$NAME.zip" | |
| rm release/$NAME.exe | |
| else | |
| mv dist/${{ matrix.bin }} release/$NAME | |
| tar -czf "release/$NAME.tar.gz" -C release "$NAME" | |
| rm release/$NAME | |
| fi | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.platform }}-build | |
| path: release/* | |
| retention-days: 1 | |
| release: | |
| needs: [tag, build] | |
| if: needs.tag.outputs.exists == 'false' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Checksums | |
| run: | | |
| cd artifacts | |
| sha256sum * > SHA256SUMS.txt | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.tag.outputs.tag }} | |
| name: "Bangen ${{ needs.tag.outputs.version }}" | |
| files: artifacts/* | |