Skip to content

fix: resolve automated release system inconsistencies and improve wor… #11

fix: resolve automated release system inconsistencies and improve wor…

fix: resolve automated release system inconsistencies and improve wor… #11

Workflow file for this run

name: Automated Release Pipeline
on:
push:
branches: [ main ]
paths-ignore:
- 'README.md'
- 'docs/**'
- '.gitignore'
- 'LICENSE'
# Grant necessary permissions for the workflow
permissions:
contents: write # Required to push commits and create tags
actions: write # Required to trigger other workflows
packages: write # Required for package publishing
pull-requests: write # Required for PR operations
issues: write # Required for issue operations
env:
PYTHON_VERSION: '3.11'
jobs:
analyze-and-version:
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.version_check.outputs.should_release }}
version_type: ${{ steps.version_check.outputs.version_type }}
new_version: ${{ steps.version_check.outputs.new_version }}
current_version: ${{ steps.version_check.outputs.current_version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
# Ensure the token can push to protected branches
persist-credentials: true
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Configure Git
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
# Configure git to use the GitHub token for authentication
git config --local url."https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Check Rust formatting
run: cargo fmt --all -- --check
- name: Run Rust linting
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Analyze commits and determine version bump
id: version_check
run: |
# Get current version
CURRENT_VERSION=$(python scripts/get_version.py)
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "Current version: $CURRENT_VERSION"
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
echo "No previous tags found, analyzing all commits"
COMMITS=$(git log --oneline)
else
echo "Last tag: $LAST_TAG"
COMMITS=$(git log ${LAST_TAG}..HEAD --oneline)
fi
echo "Analyzing commits since $LAST_TAG:"
echo "$COMMITS"
echo "Number of commits to analyze: $(echo "$COMMITS" | wc -l)"
# Determine version bump type based on commit messages
VERSION_TYPE="none"
# Check for breaking changes (major version)
if echo "$COMMITS" | grep -qiE "(BREAKING CHANGE|breaking:|major:)"; then
VERSION_TYPE="major"
echo "Found breaking change commits - will bump major version"
# Check for new features (minor version)
elif echo "$COMMITS" | grep -qiE "(feat:|feature:|minor:)"; then
VERSION_TYPE="minor"
echo "Found feature commits - will bump minor version"
# Check for bug fixes and other changes (patch version)
elif echo "$COMMITS" | grep -qiE "(fix:|patch:|chore:|docs:|style:|refactor:|perf:|test:)"; then
VERSION_TYPE="patch"
echo "Found fix/maintenance commits - will bump patch version"
fi
echo "Determined version bump type: $VERSION_TYPE"
# Skip release if no relevant changes
if [ "$VERSION_TYPE" = "none" ]; then
echo "No version-relevant changes detected. Skipping release."
echo "should_release=false" >> $GITHUB_OUTPUT
echo "version_type=none" >> $GITHUB_OUTPUT
echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
echo "Determined version bump type: $VERSION_TYPE"
echo "should_release=true" >> $GITHUB_OUTPUT
echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT
# Calculate new version
echo "Running: python scripts/bump_version.py $VERSION_TYPE"
python scripts/bump_version.py $VERSION_TYPE
NEW_VERSION=$(python scripts/get_version.py)
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "New version will be: $NEW_VERSION"
# Show what files were changed
echo "Files changed by version bump:"
git status --porcelain
- name: Commit version changes
if: steps.version_check.outputs.should_release == 'true'
run: |
# Check if there are changes to commit (check working directory, not staged)
if git diff --quiet; then
echo "No changes to commit"
else
echo "Committing version changes..."
git add -A
git commit -m "chore: bump version to ${{ steps.version_check.outputs.new_version }} [skip ci]"
# Push with retry logic
for i in {1..3}; do
if git push origin main; then
echo "[OK] Successfully pushed version bump commit"
break
else
echo "[ERROR] Push attempt $i failed, retrying in 5 seconds..."
sleep 5
fi
if [ $i -eq 3 ]; then
echo "[ERROR] Failed to push after 3 attempts"
exit 1
fi
done
fi
- name: Create and push tag
if: steps.version_check.outputs.should_release == 'true'
run: |
TAG_NAME="v${{ steps.version_check.outputs.new_version }}"
echo "Creating tag: $TAG_NAME"
# Check if tag already exists
if git tag -l | grep -q "^$TAG_NAME$"; then
echo "[WARN] Tag $TAG_NAME already exists, skipping tag creation"
else
git tag "$TAG_NAME"
echo "[OK] Created tag: $TAG_NAME"
# Push tag with retry logic
for i in {1..3}; do
if git push origin "$TAG_NAME"; then
echo "[OK] Successfully pushed tag: $TAG_NAME"
break
else
echo "[ERROR] Tag push attempt $i failed, retrying in 5 seconds..."
sleep 5
fi
if [ $i -eq 3 ]; then
echo "[ERROR] Failed to push tag after 3 attempts"
exit 1
fi
done
fi
build-and-release:
needs: analyze-and-version
if: needs.analyze-and-version.outputs.should_release == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
ref: v${{ needs.analyze-and-version.outputs.new_version }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Check Rust formatting
run: cargo fmt --all -- --check
- name: Run Rust linting
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Install maturin
run: pip install maturin
- name: Build wheels
run: maturin build --release --out dist --find-interpreter
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: dist/*.whl
build-sdist:
needs: analyze-and-version
if: needs.analyze-and-version.outputs.should_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: v${{ needs.analyze-and-version.outputs.new_version }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install maturin
run: pip install maturin
- name: Build source distribution
run: maturin sdist --out dist
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/*.tar.gz
publish:
needs: [analyze-and-version, build-and-release, build-sdist]
if: needs.analyze-and-version.outputs.should_release == 'true'
runs-on: ubuntu-latest
environment: release
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist-artifacts
- name: Flatten artifacts
run: |
mkdir -p dist
find dist-artifacts -name "*.whl" -exec cp {} dist/ \;
find dist-artifacts -name "*.tar.gz" -exec cp {} dist/ \;
ls -la dist/
- name: Check if version already exists on PyPI
id: check_version
run: |
VERSION=${{ needs.analyze-and-version.outputs.new_version }}
echo "version=$VERSION" >> $GITHUB_OUTPUT
if pip index versions demopy_gb_jj 2>/dev/null | grep -q "$VERSION"; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "[WARN] Version $VERSION already exists on PyPI"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "[OK] Version $VERSION is new, proceeding with upload"
fi
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
password: ${{ secrets.PYPI_API_TOKEN || '' }}
skip-existing: true
verbose: true
create-release:
needs: [analyze-and-version, publish]
if: needs.analyze-and-version.outputs.should_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: v${{ needs.analyze-and-version.outputs.new_version }}
- name: Generate changelog
id: changelog
run: |
# Get the previous tag
CURRENT_TAG="v${{ needs.analyze-and-version.outputs.new_version }}"
PREVIOUS_TAG=$(git describe --tags --abbrev=0 $CURRENT_TAG^ 2>/dev/null || echo "")
echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG"
# Generate changelog content
CHANGELOG="## What's Changed\n\n"
if [ -z "$PREVIOUS_TAG" ]; then
COMMITS=$(git log --oneline --pretty=format:"* %s (%h)" $CURRENT_TAG)
else
COMMITS=$(git log --oneline --pretty=format:"* %s (%h)" ${PREVIOUS_TAG}..${CURRENT_TAG})
fi
# Categorize commits
FEATURES=$(echo "$COMMITS" | grep -iE "(feat:|feature:)" || true)
FIXES=$(echo "$COMMITS" | grep -iE "(fix:|patch:)" || true)
CHORES=$(echo "$COMMITS" | grep -iE "(chore:|docs:|style:|refactor:|perf:|test:)" || true)
BREAKING=$(echo "$COMMITS" | grep -iE "(BREAKING CHANGE|breaking:|major:)" || true)
if [ ! -z "$BREAKING" ]; then
CHANGELOG="${CHANGELOG}### Breaking Changes\n${BREAKING}\n\n"
fi
if [ ! -z "$FEATURES" ]; then
CHANGELOG="${CHANGELOG}### New Features\n${FEATURES}\n\n"
fi
if [ ! -z "$FIXES" ]; then
CHANGELOG="${CHANGELOG}### Bug Fixes\n${FIXES}\n\n"
fi
if [ ! -z "$CHORES" ]; then
CHANGELOG="${CHANGELOG}### Maintenance\n${CHORES}\n\n"
fi
# Add installation instructions
CHANGELOG="${CHANGELOG}### Installation\n\n"
CHANGELOG="${CHANGELOG}\`\`\`bash\n"
CHANGELOG="${CHANGELOG}pip install demopy_gb_jj==${{ needs.analyze-and-version.outputs.new_version }}\n"
CHANGELOG="${CHANGELOG}\`\`\`\n\n"
# Add usage example
CHANGELOG="${CHANGELOG}### Usage\n\n"
CHANGELOG="${CHANGELOG}\`\`\`python\n"
CHANGELOG="${CHANGELOG}import demopy\n"
CHANGELOG="${CHANGELOG}print(demopy.hello()) # Hello from demopy_gb_jj!\n"
CHANGELOG="${CHANGELOG}print(demopy.add(5, 7)) # 12\n"
CHANGELOG="${CHANGELOG}\`\`\`\n"
# Save changelog to file and output
echo -e "$CHANGELOG" > changelog.md
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo -e "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ needs.analyze-and-version.outputs.new_version }}
name: Release v${{ needs.analyze-and-version.outputs.new_version }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
generate_release_notes: true