Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bump Version and Create Release #46

Bump Version and Create Release

Bump Version and Create Release #46

name: Bump Version and Create Release
# =============================================================================
# RELEASE WORKFLOW
# =============================================================================
#
# This workflow creates releases using CHANGELOG.md as the single source of truth.
#
# BEFORE RUNNING THIS WORKFLOW:
# 1. Update CHANGELOG.md with your changes under the [Unreleased] section
# 2. Follow Keep a Changelog format (https://keepachangelog.com/)
# 3. Use categories: Added, Changed, Deprecated, Removed, Fixed, Security
#
# WHAT THIS WORKFLOW DOES:
# 1. Reads release notes from CHANGELOG.md [Unreleased] section
# 2. Creates a git tag and GitHub release with those notes
# 3. Updates CHANGELOG.md: renames [Unreleased] to [version] - date
# 4. Adds a new empty [Unreleased] section
# 5. Commits the CHANGELOG.md changes
# 6. Triggers downstream workflows (assets, docs, etc.)
#
# See CONTRIBUTING.md for detailed documentation.
# =============================================================================
on:
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- major
- minor
- patch
default: patch
pre_release:
description: 'Pre-release type (leave empty for stable release)'
required: false
type: choice
options:
- none
- alpha
- beta
- rc
default: none
pre_number:
description: 'Pre-release number (if pre_release is set)'
required: false
type: string
default: '1'
create_release:
description: 'Create GitHub release'
required: false
type: boolean
default: true
jobs:
bump-version:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
outputs:
tag: ${{ steps.new_version.outputs.tag }}
new_version: ${{ steps.new_version.outputs.new_version }}
current_version: ${{ steps.current_version.outputs.current }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Get current version
id: current_version
run: |
# Get latest stable version tag (exclude pre-releases)
CURRENT=$(git tag --sort=-version:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 | sed 's/^v//' | sed 's/^xbbg==//')
if [ -z "$CURRENT" ]; then
CURRENT="0.0.0"
fi
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
echo "Current version: $CURRENT"
- name: Calculate new version
id: new_version
run: |
CURRENT="${{ steps.current_version.outputs.current }}"
BUMP_TYPE="${{ inputs.bump_type }}"
PRE_RELEASE="${{ inputs.pre_release }}"
PRE_NUM="${{ inputs.pre_number }}"
# Parse current version
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
# Bump version
case "$BUMP_TYPE" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
# Add pre-release suffix if specified
if [ -n "$PRE_RELEASE" ] && [ "$PRE_RELEASE" != "none" ]; then
# Map alpha/beta/rc to a/b/rc format
case "$PRE_RELEASE" in
alpha) PRE_SUFFIX="a" ;;
beta) PRE_SUFFIX="b" ;;
rc) PRE_SUFFIX="rc" ;;
*) PRE_SUFFIX="$PRE_RELEASE" ;;
esac
NEW_VERSION="${NEW_VERSION}${PRE_SUFFIX}${PRE_NUM}"
fi
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v${NEW_VERSION}" >> "$GITHUB_OUTPUT"
echo "New version: $NEW_VERSION"
echo "Tag: v${NEW_VERSION}"
- name: Validate and extract release notes from CHANGELOG.md
id: changelog
run: |
TAG="${{ steps.new_version.outputs.tag }}"
PREV_TAG="v${{ steps.current_version.outputs.current }}"
# Extract content between [Unreleased] and the next ## heading
# This captures all content under [Unreleased] section
RELEASE_NOTES=$(awk '
/^## \[Unreleased\]/ { capture=1; next }
/^## \[/ { if(capture) exit }
capture { print }
' CHANGELOG.md | head -100)
# Remove leading/trailing whitespace and empty lines for validation
RELEASE_NOTES_TRIMMED=$(echo "$RELEASE_NOTES" | sed '/^[[:space:]]*$/d' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Validation: Check if [Unreleased] section has meaningful content
if [ -z "$RELEASE_NOTES_TRIMMED" ]; then
echo "::error::CHANGELOG.md [Unreleased] section is empty!"
echo "::error::Please update CHANGELOG.md before releasing."
echo ""
echo "============================================================"
echo "RELEASE BLOCKED: Empty changelog"
echo "============================================================"
echo ""
echo "Before running this workflow, you must update CHANGELOG.md:"
echo ""
echo "1. Add your changes under the [Unreleased] section"
echo "2. Use categories: Added, Changed, Deprecated, Removed, Fixed, Security"
echo "3. Follow Keep a Changelog format (https://keepachangelog.com/)"
echo ""
echo "Example:"
echo " ## [Unreleased]"
echo ""
echo " ### Added"
echo " - New feature X for doing Y (#123)"
echo ""
echo " ### Fixed"
echo " - Bug in Z that caused W (#456)"
echo ""
echo "See CONTRIBUTING.md and .github/RELEASE_INSTRUCTIONS.md for details."
echo "============================================================"
exit 1
fi
# Validation: Check for placeholder text that shouldn't be released
if echo "$RELEASE_NOTES_TRIMMED" | grep -qiE '(TODO|FIXME|WIP|PLACEHOLDER|TBD|coming soon)'; then
echo "::warning::CHANGELOG.md may contain placeholder text (TODO/FIXME/WIP/TBD)"
echo "::warning::Please review the [Unreleased] section before releasing."
fi
# Validation: Check for minimum content (at least one category header)
if ! echo "$RELEASE_NOTES_TRIMMED" | grep -qE '^### (Added|Changed|Deprecated|Removed|Fixed|Security)'; then
echo "::warning::CHANGELOG.md [Unreleased] section doesn't follow Keep a Changelog format"
echo "::warning::Consider using categories: Added, Changed, Deprecated, Removed, Fixed, Security"
fi
# Write to file for multi-line handling (preserve original formatting)
echo "$RELEASE_NOTES" > /tmp/release_notes.md
# Append full changelog link
echo "" >> /tmp/release_notes.md
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${TAG}" >> /tmp/release_notes.md
echo "Release notes extracted and validated:"
echo "============================================================"
cat /tmp/release_notes.md
echo "============================================================"
- name: Update CHANGELOG.md and README.md
if: inputs.create_release == true
run: |
VERSION="${{ steps.new_version.outputs.new_version }}"
TAG="${{ steps.new_version.outputs.tag }}"
DATE=$(date -u +"%Y-%m-%d")
# Create the new version header
NEW_HEADER="## [${VERSION}] - ${DATE}"
# Replace [Unreleased] section header with new version, and add new [Unreleased]
# Uses awk to:
# 1. When we see "## [Unreleased]", print new [Unreleased] section + new version header
# 2. Skip the old [Unreleased] line
# 3. Print everything else as-is
awk -v new_header="$NEW_HEADER" '
/^## \[Unreleased\]/ {
print "## [Unreleased]"
print ""
print new_header
next
}
{ print }
' CHANGELOG.md > /tmp/CHANGELOG.md.new
mv /tmp/CHANGELOG.md.new CHANGELOG.md
# Extract previous tag from the current [Unreleased] compare link
# e.g., [Unreleased]: .../compare/v0.12.0b3...HEAD → v0.12.0b3
PREV_TAG_LINK=$(grep '^\[Unreleased\]:' CHANGELOG.md | sed 's|.*compare/||;s|\.\.\.HEAD||')
# Add version link using compare format (Keep a Changelog 1.1.0)
if ! grep -q "^\[${VERSION}\]:" CHANGELOG.md; then
sed -i "/^\[Unreleased\]:/a [${VERSION}]: https://github.com/${{ github.repository }}/compare/${PREV_TAG_LINK}...${TAG}" CHANGELOG.md
fi
# Update the [Unreleased] comparison link to compare from new version
sed -i "s|\[Unreleased\]: .*/compare/v.*\.\.\.HEAD|[Unreleased]: https://github.com/${{ github.repository }}/compare/${TAG}...HEAD|" CHANGELOG.md
echo "CHANGELOG.md updated:"
head -50 CHANGELOG.md
python - <<'PY'
from pathlib import Path
version = "${{ steps.new_version.outputs.new_version }}"
tag = "${{ steps.new_version.outputs.tag }}"
readme = Path("README.md")
text = readme.read_text(encoding="utf-8")
start = "<!-- xbbg:latest-release-start -->"
end = "<!-- xbbg:latest-release-end -->"
replacement = (
f"{start}\n"
f"Latest release: xbbg=={version} (release: [notes](https://github.com/${{ github.repository }}/releases/tag/{tag}))\n"
f"{end}"
)
if start not in text or end not in text:
raise SystemExit("README.md latest release markers not found")
before, rest = text.split(start, 1)
_, after = rest.split(end, 1)
readme.write_text(before + replacement + after, encoding="utf-8")
PY
echo "README.md release block updated:"
sed -n '20,30p' README.md
- name: Detect latest downloadable Bloomberg C++ SDK version
id: sdk
run: |
INDEX_URL="https://blpapi.bloomberg.com/repository/releases/python/simple/blpapi/"
versions=$(curl -fsSL "$INDEX_URL" \
| grep -oP 'blpapi-\K[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?' \
| sort -Vr \
| uniq)
for VERSION in $versions; do
linux_url="https://blpapi.bloomberg.com/download/releases/raw/files/blpapi_cpp_${VERSION}-linux.tar.gz"
macos_url="https://blpapi.bloomberg.com/download/releases/raw/files/blpapi_cpp_${VERSION}-macos-arm64.tar.gz"
windows_url="https://blpapi.bloomberg.com/download/releases/raw/files/blpapi_cpp_${VERSION}-windows.zip"
if curl -fsIL "$linux_url" >/dev/null \
&& curl -fsIL "$macos_url" >/dev/null \
&& curl -fsIL "$windows_url" >/dev/null; then
echo "Detected Bloomberg C++ SDK version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "No downloadable Bloomberg C++ SDK archives found" >&2
exit 1
- name: Setup Bloomberg SDK
id: sdk-setup
uses: ./.github/actions/setup-blpapi-sdk
with:
version: ${{ steps.sdk.outputs.version }}
os: Linux
- name: Export Bloomberg SDK env
run: |
SDK_ROOT="$GITHUB_WORKSPACE/${{ steps.sdk-setup.outputs.sdk-rel-root }}"
LIB_DIR="$GITHUB_WORKSPACE/${{ steps.sdk-setup.outputs.lib-rel-dir }}"
{
echo "BLPAPI_ROOT=$SDK_ROOT"
echo "LIBRARY_PATH=$LIB_DIR:$LIBRARY_PATH"
echo "LD_LIBRARY_PATH=$LIB_DIR:$LD_LIBRARY_PATH"
} >> "$GITHUB_ENV"
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
- name: Regenerate type stubs
run: |
cargo build -p pyo3-xbbg --bin stub_gen --no-default-features --features live,stub-gen
CARGO_MANIFEST_DIR=${{ github.workspace }}/bindings/pyo3-xbbg ./target/debug/stub_gen
- name: Commit release doc changes
if: inputs.create_release == true
run: |
VERSION="${{ steps.new_version.outputs.new_version }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md README.md py-xbbg/src/xbbg/_core/__init__.pyi py-xbbg/src/xbbg/__init__.pyi py-xbbg/src/xbbg/py.typed
if git diff --cached --quiet; then
echo "No release doc changes"
else
git commit -m "docs(release): update docs and type stubs for ${VERSION} release"
BRANCH="${GITHUB_REF#refs/heads/}"
git push origin HEAD:"${BRANCH}"
fi
- name: Create git tag
run: |
TAG="${{ steps.new_version.outputs.tag }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"
- name: Create GitHub Release
if: inputs.create_release == true
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.new_version.outputs.tag }}
name: xbbg ${{ steps.new_version.outputs.new_version }}
body_path: /tmp/release_notes.md
prerelease: ${{ inputs.pre_release != 'none' && inputs.pre_release != '' }}
generate_release_notes: false
make_latest: ${{ inputs.pre_release == 'none' || inputs.pre_release == '' }}
draft: false
# Summary job
summary:
needs: [bump-version]
if: always() && inputs.create_release == true
runs-on: ubuntu-latest
steps:
- name: Output summary
run: |
{
echo "## Release Summary"
echo ""
echo "- **Previous version**: ${{ needs.bump-version.outputs.current_version }}"
echo "- **New version**: ${{ needs.bump-version.outputs.new_version }}"
echo "- **Tag**: ${{ needs.bump-version.outputs.tag }}"
echo "- **Type**: ${{ inputs.bump_type }}${{ inputs.pre_release != 'none' && inputs.pre_release != '' && format(' ({0})', inputs.pre_release) || '' }}"
echo ""
echo "### Next Steps"
echo "The tag push will automatically trigger **pypi_upload.yml** for Python artifacts and **npm-publish.yml** for @xbbg/core native npm packages."
} >> "$GITHUB_STEP_SUMMARY"