| #!/usr/bin/env bash |
| # |
| # Release a new version of cpp-httplib. |
| # |
| # Usage: ./release.sh [--run] [--minor] |
| # |
| # By default, runs in dry-run mode (no changes made). |
| # Pass --run to actually update files, commit, tag, and push. |
| # Pass --minor to force a minor bump even when ABI is unchanged |
| # (use this for behavioral breaking changes that don't break ABI). |
| # |
| # This script: |
| # 1. Reads the current version from httplib.h |
| # 2. Checks that the working directory is clean |
| # 3. Verifies CI status of the latest commit (all must pass except abidiff) |
| # 4. Determines the next version automatically: |
| # - abidiff passed → patch bump (e.g., 0.38.0 → 0.38.1) |
| # - abidiff failed → minor bump (e.g., 0.38.1 → 0.39.0) |
| # - --minor passed → forces minor bump regardless of abidiff |
| # 5. Updates httplib.h and docs-src/config.toml |
| # 6. Commits, tags (vX.Y.Z), and pushes |
| |
| set -euo pipefail |
| |
| DRY_RUN=1 |
| FORCE_MINOR=0 |
| while [ $# -gt 0 ]; do |
| case "$1" in |
| --run) |
| DRY_RUN=0 |
| shift |
| ;; |
| --minor) |
| FORCE_MINOR=1 |
| shift |
| ;; |
| *) |
| echo "Usage: $0 [--run] [--minor]" |
| exit 1 |
| ;; |
| esac |
| done |
| |
| # --- Step 1: Read current version from httplib.h --- |
| CURRENT_VERSION=$(sed -n 's/^#define CPPHTTPLIB_VERSION "\([^"]*\)"/\1/p' httplib.h) |
| IFS='.' read -r V_MAJOR V_MINOR V_PATCH <<< "$CURRENT_VERSION" |
| |
| echo "==> Current version: $CURRENT_VERSION" |
| |
| # --- Step 2: Check working directory is clean --- |
| if [ -n "$(git status --porcelain)" ]; then |
| echo "Error: working directory is not clean" |
| exit 1 |
| fi |
| |
| # --- Step 3: Check CI status of the latest commit --- |
| echo "" |
| echo "==> Checking CI status of the latest commit..." |
| |
| HEAD_SHA=$(git rev-parse HEAD) |
| HEAD_SHORT=$(git rev-parse --short HEAD) |
| echo " Latest commit: $HEAD_SHORT" |
| |
| # Fetch all workflow runs for the HEAD commit |
| RUNS=$(gh run list --commit "$HEAD_SHA" --json name,status,conclusion,headSha) |
| |
| NUM_RUNS=$(echo "$RUNS" | jq 'length') |
| |
| if [ "$NUM_RUNS" -eq 0 ]; then |
| echo "Error: No CI runs found for commit $HEAD_SHORT." |
| echo " Wait for CI to complete before releasing." |
| exit 1 |
| fi |
| |
| echo " Found $NUM_RUNS workflow run(s):" |
| |
| FAILED=0 |
| RUNNING=0 |
| ABIDIFF_PASSED=0 |
| while IFS=$'\t' read -r name status conclusion; do |
| # A run that hasn't completed yet has an empty conclusion; don't treat it |
| # as a failure — the release should wait until CI finishes. |
| if [ "$status" != "completed" ]; then |
| echo " [ .. ] $name (still running)" |
| RUNNING=1 |
| continue |
| fi |
| |
| if [[ "$name" == *abidiff* ]] || [[ "$name" == *abi* && "$name" != *stability* ]]; then |
| if [ "$conclusion" = "success" ]; then |
| echo " [ OK ] $name" |
| ABIDIFF_PASSED=1 |
| else |
| echo " [FAIL] $name ($conclusion) → ABI break detected, minor bump" |
| ABIDIFF_PASSED=0 |
| fi |
| continue |
| fi |
| |
| if [ "$conclusion" = "success" ]; then |
| echo " [ OK ] $name" |
| else |
| echo " [FAIL] $name ($conclusion)" |
| FAILED=1 |
| fi |
| done < <(echo "$RUNS" | jq -r '.[] | [.name, .status, .conclusion] | @tsv') |
| |
| if [ "$RUNNING" -eq 1 ]; then |
| echo "" |
| echo "Error: Some CI checks are still running. Wait for them to complete before releasing." |
| exit 1 |
| fi |
| |
| if [ "$FAILED" -eq 1 ]; then |
| echo "" |
| echo "Error: Some CI checks failed. Fix them before releasing." |
| exit 1 |
| fi |
| |
| echo " All non-abidiff CI checks passed." |
| |
| # --- Step 4: Determine new version --- |
| if [ "$FORCE_MINOR" -eq 1 ] && [ "$ABIDIFF_PASSED" -eq 1 ]; then |
| NEW_MINOR=$((V_MINOR + 1)) |
| NEW_VERSION="$V_MAJOR.$NEW_MINOR.0" |
| echo "" |
| echo "==> abidiff passed but --minor specified → forced minor bump" |
| elif [ "$ABIDIFF_PASSED" -eq 1 ]; then |
| NEW_PATCH=$((V_PATCH + 1)) |
| NEW_VERSION="$V_MAJOR.$V_MINOR.$NEW_PATCH" |
| echo "" |
| echo "==> abidiff passed → patch bump" |
| else |
| NEW_MINOR=$((V_MINOR + 1)) |
| NEW_VERSION="$V_MAJOR.$NEW_MINOR.0" |
| echo "" |
| if [ "$FORCE_MINOR" -eq 1 ]; then |
| echo "==> abidiff failed → minor bump (--minor also specified)" |
| else |
| echo "==> abidiff failed → minor bump" |
| fi |
| fi |
| |
| VERSION_HEX=$(printf "0x%02x%02x%02x" "${NEW_VERSION%%.*}" "$(echo "$NEW_VERSION" | cut -d. -f2)" "${NEW_VERSION##*.}") |
| |
| if [ "$DRY_RUN" -eq 1 ]; then |
| echo "==> [DRY RUN] New version: $NEW_VERSION ($VERSION_HEX)" |
| else |
| echo "==> New version: $NEW_VERSION ($VERSION_HEX)" |
| fi |
| |
| # --- Step 5: Update files --- |
| echo "" |
| if [ "$DRY_RUN" -eq 1 ]; then |
| echo "==> [DRY RUN] Would update httplib.h:" |
| echo " CPPHTTPLIB_VERSION = \"$NEW_VERSION\"" |
| echo " CPPHTTPLIB_VERSION_NUM = \"$VERSION_HEX\"" |
| echo "" |
| echo "==> [DRY RUN] Would update docs-src/config.toml:" |
| echo " version = \"$NEW_VERSION\"" |
| echo "" |
| echo "==> [DRY RUN] Would commit, tag v$NEW_VERSION and latest, and push." |
| echo "" |
| echo "==> Dry run complete. No changes were made." |
| else |
| echo "==> Updating httplib.h..." |
| sed -i '' "s/#define CPPHTTPLIB_VERSION \"[^\"]*\"/#define CPPHTTPLIB_VERSION \"$NEW_VERSION\"/" httplib.h |
| sed -i '' "s/#define CPPHTTPLIB_VERSION_NUM \"0x[0-9a-fA-F]*\"/#define CPPHTTPLIB_VERSION_NUM \"$VERSION_HEX\"/" httplib.h |
| echo " CPPHTTPLIB_VERSION = \"$NEW_VERSION\"" |
| echo " CPPHTTPLIB_VERSION_NUM = \"$VERSION_HEX\"" |
| |
| echo "" |
| echo "==> Updating docs-src/config.toml..." |
| sed -i '' "s/^version = \"[^\"]*\"/version = \"$NEW_VERSION\"/" docs-src/config.toml |
| echo " version = \"$NEW_VERSION\"" |
| |
| # --- Step 6: Commit, tag, and push --- |
| echo "" |
| echo "==> Committing and tagging..." |
| git add httplib.h docs-src/config.toml |
| git commit -m "Release v$NEW_VERSION" |
| git tag "v$NEW_VERSION" |
| git tag -f "latest" |
| |
| echo "" |
| echo "==> Pushing..." |
| git push && git push --tags --force |
| |
| echo "" |
| echo "==> Released v$NEW_VERSION" |
| fi |