Skip to content

Publish Wheels

Publish Wheels #5

Workflow file for this run

name: Publish to PyPI
on:
push:
tags:
- "v*.*.*" # Trigger on version tags like v1.0.0
workflow_dispatch: # Allow manual trigger
# Cancel in-progress runs when a new run is triggered
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# First job: Run tests
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Set up Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies and build
run: |
uv sync --dev
uv run maturin develop
- name: Run tests
run: uv run pytest tests/ -v
# Second job: Security audit
security:
name: Security audit
runs-on: ubuntu-latest
timeout-minutes: 15
needs: test
steps:
- uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-
- name: Cache cargo-audit
uses: actions/cache@v4
with:
path: ~/.cargo/bin/cargo-audit
key: ${{ runner.os }}-cargo-audit-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-audit-
- name: Install cargo-audit
run: |
if ! command -v cargo-audit &> /dev/null; then
cargo install cargo-audit
fi
- name: Run security audit
run: |
echo "Running security audit..."
cargo audit --format json > audit-report.json || true
cargo audit
- name: Upload security audit report
if: always()
uses: actions/upload-artifact@v4
with:
name: security-audit-report
path: audit-report.json
# Third job: Build wheels for multiple platforms
build:
name: Build wheels
runs-on: ${{ matrix.os }}
timeout-minutes: 45
needs: [test, security]
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
exclude:
# Exclude some combinations to reduce build time
- os: windows-latest
python-version: "3.9"
- os: macos-latest
python-version: "3.9"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Set up Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install maturin
run: uv sync --dev
- name: Build wheels
run: |
echo "Building wheels for ${{ matrix.os }} Python ${{ matrix.python-version }}..."
uv run maturin build --release --strip
echo "Build completed successfully!"
ls -la target/wheels/
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ matrix.python-version }}
path: target/wheels/*.whl
# Fourth job: Build source distribution
build-sdist:
name: Build source distribution
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [test, security]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Set up Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Install maturin
run: uv sync --dev
- name: Build sdist
run: |
echo "Building source distribution..."
uv run maturin sdist
echo "Source distribution built successfully!"
ls -la target/wheels/
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: sdist
path: target/wheels/*.tar.gz
# Fifth job: Publish to PyPI
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [test, security, build, build-sdist]
if: startsWith(github.ref, 'refs/tags/')
environment:
name: pypi
url: https://pypi.org/project/logxide/
permissions:
id-token: write # For trusted publishing
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist/
merge-multiple: true
- name: Prepare release assets
run: |
echo "Preparing release assets..."
find dist/ -name "*.whl" -o -name "*.tar.gz" | sort
echo "Total assets: $(find dist/ -type f \( -name "*.whl" -o -name "*.tar.gz" \) | wc -l)"
- name: List artifacts
run: |
echo "Downloaded artifacts:"
find dist/ -type f -name "*.whl" -o -name "*.tar.gz" | sort
echo "Total files: $(find dist/ -type f \( -name "*.whl" -o -name "*.tar.gz" \) | wc -l)"
- name: Validate packages before publishing
run: |
echo "Validating packages before publishing..."
python -m pip install --upgrade uv
uv pip install --system twine
uv run twine check dist/*
echo "✅ All packages passed validation!"
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
verbose: true
print-hash: true
skip-existing: false
- name: Create job summary
run: |
echo "## 🚀 Publication Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Published Packages" >> $GITHUB_STEP_SUMMARY
echo "| Package | Version | Type |" >> $GITHUB_STEP_SUMMARY
echo "|---------|---------|------|" >> $GITHUB_STEP_SUMMARY
VERSION=${GITHUB_REF#refs/tags/v}
echo "| logxide | $VERSION | PyPI Package |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Links" >> $GITHUB_STEP_SUMMARY
echo "- [PyPI Package](https://pypi.org/project/logxide/$VERSION/)" >> $GITHUB_STEP_SUMMARY
echo "- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📊 Build Stats" >> $GITHUB_STEP_SUMMARY
echo "- **Wheels built**: $(find dist/ -name "*.whl" | wc -l)" >> $GITHUB_STEP_SUMMARY
echo "- **Source distribution**: $(find dist/ -name "*.tar.gz" | wc -l)" >> $GITHUB_STEP_SUMMARY
echo "- **Total files**: $(find dist/ -type f | wc -l)" >> $GITHUB_STEP_SUMMARY
- name: Verify PyPI publication
run: |
echo "Waiting for PyPI to update..."
sleep 30
VERSION=${GITHUB_REF#refs/tags/v}
echo "Checking if logxide version $VERSION is available on PyPI..."
python -c "import requests; r = requests.get('https://pypi.org/pypi/logxide/$VERSION/json'); print('✅ Published successfully!' if r.status_code == 200 else '❌ Not found on PyPI yet')"
# Sixth job: Create GitHub release
release:
name: Create GitHub Release
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [publish]
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Get version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist/
merge-multiple: true
- name: Create Release
uses: softprops/action-gh-release@v1
with:
name: LogXide v${{ steps.get_version.outputs.VERSION }}
draft: false
prerelease: false
generate_release_notes: true
files: |
dist/*.whl
body: |
## LogXide v${{ steps.get_version.outputs.VERSION }}
High-performance, Rust-powered drop-in replacement for Python's logging module.
### Installation
```bash
pip install logxide==${{ steps.get_version.outputs.VERSION }}
```
### Quick Start
```python
import logxide
logxide.install()
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("Hello from LogXide!")
```
See the [CHANGELOG](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for detailed release notes.
### Build Information
- **Platforms**: Ubuntu, Windows, macOS
- **Python versions**: 3.9, 3.10, 3.11, 3.12, 3.13
- **Security audit**: ✅ Passed
- **Tests**: ✅ All tests passed
- **Package validation**: ✅ All packages validated with twine
- name: Create release summary
run: |
echo "## 🎉 Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📋 Release Details" >> $GITHUB_STEP_SUMMARY
echo "- **Version**: ${{ steps.get_version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tag**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Quick Links" >> $GITHUB_STEP_SUMMARY
echo "- [📦 PyPI Package](https://pypi.org/project/logxide/${{ steps.get_version.outputs.VERSION }}/)" >> $GITHUB_STEP_SUMMARY
echo "- [📋 GitHub Release](https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }})" >> $GITHUB_STEP_SUMMARY
echo "- [📚 Documentation](https://Indosaram.readthedocs.io/logxide)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ✅ Quality Checks" >> $GITHUB_STEP_SUMMARY
echo "- Security audit: ✅ Passed" >> $GITHUB_STEP_SUMMARY
echo "- All tests: ✅ Passed" >> $GITHUB_STEP_SUMMARY
echo "- Package validation: ✅ Passed" >> $GITHUB_STEP_SUMMARY
echo "- Build verification: ✅ Passed" >> $GITHUB_STEP_SUMMARY
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Final job: Send notification
notify:
name: Send Notification
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [release]
if: always() && startsWith(github.ref, 'refs/tags/')
steps:
- name: Get version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Create final notification
run: |
echo "## 🚀 LogXide v${{ steps.get_version.outputs.VERSION }} Release Complete!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📊 Workflow Status" >> $GITHUB_STEP_SUMMARY
echo "- **Security**: ${{ needs.release.result == 'success' && '✅ Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tests**: ${{ needs.release.result == 'success' && '✅ Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Build**: ${{ needs.release.result == 'success' && '✅ Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Publish**: ${{ needs.release.result == 'success' && '✅ Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Release**: ${{ needs.release.result == 'success' && '✅ Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Installation" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "pip install logxide==${{ steps.get_version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📚 Next Steps" >> $GITHUB_STEP_SUMMARY
echo "- Check [PyPI](https://pypi.org/project/logxide/${{ steps.get_version.outputs.VERSION }}/) for package availability" >> $GITHUB_STEP_SUMMARY
echo "- View [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}) for detailed notes" >> $GITHUB_STEP_SUMMARY
echo "- Update documentation if needed" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.release.result }}" == "success" ]; then
echo "✅ All systems go! LogXide v${{ steps.get_version.outputs.VERSION }} is now available!"
else
echo "❌ Release encountered issues. Please check the logs above."
exit 1
fi