diff --git a/scripts/next_release_version.sh b/scripts/next_release_version.sh new file mode 100755 index 0000000..5dff582 --- /dev/null +++ b/scripts/next_release_version.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Compute the next release version for this repo. +# +# With existing vX.Y.Z tags — highest tag + 1 patch (e.g. v0.0.1 -> 0.0.2). +# Non-semver / pre-release tags (e.g. v0.1.0-alpha) are ignored. +# With no vX.Y.Z tags — base version from the first match of setup.py +# RELEASE_VERSION, pyproject.toml [project] version, a VERSION file, else 0.0.1. +# +# Usage: +# ./scripts/next_release_version.sh +# ./scripts/next_release_version.sh --verbose + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOURCE_REPO="$(cd "$SCRIPT_DIR/.." && pwd)" + +VERBOSE=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --verbose | -v) + VERBOSE=1 + shift + ;; + --help | -h) + sed -n '2,12p' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +read_base_version() { + local v="" + if [[ -f "$SOURCE_REPO/setup.py" ]]; then + v="$(grep -E '^\s*RELEASE_VERSION\s*=' "$SOURCE_REPO/setup.py" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" + [[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && { + echo "$v" + return 0 + } + fi + if [[ -f "$SOURCE_REPO/pyproject.toml" ]]; then + v="$(grep -E '^\s*version\s*=\s*"[0-9]' "$SOURCE_REPO/pyproject.toml" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" + [[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && { + echo "$v" + return 0 + } + fi + if [[ -f "$SOURCE_REPO/VERSION" ]]; then + v="$(tr -d '[:space:]' < "$SOURCE_REPO/VERSION")" + [[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && { + echo "$v" + return 0 + } + fi + echo "0.0.1" +} + +increment_patch() { + local version="${1#v}" + local major minor patch + IFS=. read -r major minor patch <<< "$version" + echo "${major}.${minor}.$((patch + 1))" +} + +highest_tag_version() { + local tag="" + cd "$SOURCE_REPO" + tag="$(git tag -l 'v[0-9]*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)" + if [[ -z "$tag" ]]; then + return 1 + fi + echo "${tag#v}" +} + +cd "$SOURCE_REPO" + +if LAST_VERSION="$(highest_tag_version)"; then + VERSION="$(increment_patch "$LAST_VERSION")" + VERSION_SOURCE="git tag v${LAST_VERSION} + 1 patch" +else + VERSION="$(read_base_version)" + VERSION_SOURCE="base version (no release tags yet)" +fi + +if [[ "$VERBOSE" -eq 1 ]]; then + echo "Version source: ${VERSION_SOURCE}" + if [[ -n "${LAST_VERSION:-}" ]]; then + echo "Last release tag: v${LAST_VERSION}" + else + echo "Last release tag: (none)" + fi + echo "Next version: ${VERSION}" +else + echo "$VERSION" +fi diff --git a/scripts/publish_release_tag.sh b/scripts/publish_release_tag.sh index 2d55a61..1a979e0 100755 --- a/scripts/publish_release_tag.sh +++ b/scripts/publish_release_tag.sh @@ -1,38 +1,51 @@ #!/usr/bin/env bash -# Create and push a semver release tag. Pushing vX.Y.Z triggers publish-micropython-lib -# (sync micropython-lib, MIP index, and TestPyPI upload). +# Create and push a release tag for this repo. Same interface in every PyDevices repo. +# +# Version is the optional VERSION argument, else auto-computed by +# next_release_version.sh (highest vX.Y.Z tag + 1 patch). Pushing the tag +# triggers this repo's publish workflow. # # Usage: -# ./scripts/publish_release_tag.sh 0.0.5 -# ./scripts/publish_release_tag.sh 0.0.5 --push +# ./scripts/publish_release_tag.sh # auto version; create tag +# ./scripts/publish_release_tag.sh --push # auto version; create + push +# ./scripts/publish_release_tag.sh 0.0.5 --push # explicit version; create + push +# ./scripts/publish_release_tag.sh --dry-run # preview only +# +# Preview the next version: ./scripts/next_release_version.sh --verbose set -euo pipefail DO_PUSH=0 +DRY_RUN=0 +VERSION="" usage() { cat <<'EOF' -Usage: ./scripts/publish_release_tag.sh VERSION [--push] +Usage: ./scripts/publish_release_tag.sh [VERSION] [--push] [--dry-run] Create an annotated git tag vVERSION on the current commit. - --push Push the tag to origin (triggers the publish workflow) - -Without --push, prints the git push command to run manually. + VERSION Optional semver X.Y.Z. When omitted, computed by + scripts/next_release_version.sh (highest tag + 1 patch). + --push Push the tag to origin (triggers this repo's publish workflow) + --dry-run Print the version that would be tagged; do not create a tag Examples: + ./scripts/publish_release_tag.sh --push ./scripts/publish_release_tag.sh 0.0.5 --push - git push origin v0.0.5 EOF } -VERSION="" while [[ $# -gt 0 ]]; do case "$1" in --push) DO_PUSH=1 shift ;; + --dry-run) + DRY_RUN=1 + shift + ;; --help | -h) usage exit 0 @@ -54,9 +67,13 @@ while [[ $# -gt 0 ]]; do esac done +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOURCE_REPO="$(cd "$SCRIPT_DIR/.." && pwd)" + +AUTO=0 if [[ -z "$VERSION" ]]; then - usage >&2 - exit 1 + AUTO=1 + VERSION="$("$SCRIPT_DIR/next_release_version.sh")" fi VERSION="${VERSION#v}" @@ -65,8 +82,6 @@ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then exit 1 fi -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SOURCE_REPO="$(cd "$SCRIPT_DIR/.." && pwd)" TAG="v$VERSION" cd "$SOURCE_REPO" @@ -81,12 +96,23 @@ if git rev-parse "$TAG" >/dev/null 2>&1; then exit 1 fi +if [[ "$AUTO" -eq 1 ]]; then + "$SCRIPT_DIR/next_release_version.sh" --verbose +else + echo "Version: ${VERSION} (explicit)" +fi + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "Dry run — would create tag $TAG on $(git rev-parse --short HEAD)" + exit 0 +fi + git tag -a "$TAG" -m "Release $VERSION" echo "Created annotated tag $TAG on $(git rev-parse --short HEAD)" if [[ "$DO_PUSH" -eq 1 ]]; then git push origin "$TAG" - echo "Pushed $TAG — publish-micropython-lib workflow should start shortly." + echo "Pushed $TAG — this repo's publish workflow should start shortly." else echo "Push to publish: git push origin $TAG" fi