Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions .github/actions/compute-version/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: Compute Version
description: >-
Derive the next semantic version from the latest git tag and the Conventional
Commits made since it. Produces the RELEASE_VERSION / RELEASE_SUFFIX values the
docker-bake files consume, plus flags telling the caller whether to build and
whether to publish a release.

inputs:
fallback-version:
description: "Version to start from when no git tag exists yet."
required: false
default: "1.0.7"
release-branch:
description: "Branch whose pushes produce a real release."
required: false
default: "main"

outputs:
version:
description: "Semantic version without a leading v (e.g. 1.1.0)."
value: ${{ steps.compute.outputs.version }}
suffix:
description: "Tag suffix: empty for releases, -rc.<pr> for PRs, -dev otherwise."
value: ${{ steps.compute.outputs.suffix }}
base-version:
description: >-
The last RELEASED version this build sits on (no bump applied), or the
fallback when no tag exists yet — e.g. 1.0.7. Used to build FROM an
already-published image when the current version's image isn't published
yet (e.g. cluster on a PR that doesn't rebuild pytorch).
value: ${{ steps.compute.outputs.base_version }}
tag:
description: "Git tag for the release (e.g. v1.1.0)."
value: ${{ steps.compute.outputs.tag }}
bump:
description: "Detected bump level: major | minor | patch | none."
value: ${{ steps.compute.outputs.bump }}
should-build:
description: "'true' when the caller should build images for this event."
value: ${{ steps.compute.outputs.should_build }}
should-release:
description: "'true' when the caller should publish a git tag + release."
value: ${{ steps.compute.outputs.should_release }}

runs:
using: composite
steps:
- id: compute
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
# We squash-merge, so the PR title becomes the single commit subject on
# main. Deriving the RC bump from the title (not the branch commits)
# makes the release-candidate version match what the merge will produce.
PR_TITLE: ${{ github.event.pull_request.title }}
FALLBACK: ${{ inputs.fallback-version }}
RELEASE_BRANCH: ${{ inputs.release-branch }}
run: |
set -euo pipefail

# Make sure tags are present even on shallow-ish checkouts.
git fetch --tags --force --quiet || true

HEAD_SHA="$(git rev-parse HEAD)"

# Highest release tag that is a proper ancestor of HEAD. Two filters:
# * skip tags ON HEAD — keeps the computation stable even if
# release.yml already tagged this same push (avoids an empty commit
# range -> a false "none" bump).
# * skip tags NOT reachable from HEAD — so re-running an OLDER commit
# after a newer release exists doesn't pick that newer, unrelated
# tag as its base (it would compute a wrong/backwards version).
# Tags are visited highest-version-first, so the first match is the last
# release this commit actually sits on.
LATEST=""
while IFS= read -r tag; do
[ -z "$tag" ] && continue
[ "$(git rev-list -n1 "$tag")" = "$HEAD_SHA" ] && continue
git merge-base --is-ancestor "$tag" HEAD 2>/dev/null || continue
LATEST="$tag"
break
done < <(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname)

if [ -n "$LATEST" ]; then
BASE="${LATEST#v}"
RANGE="${LATEST}..HEAD"
else
BASE="$FALLBACK"
RANGE=""
fi

# Detect the Conventional Commits bump level for a block of text.
detect_bump() {
local text="$1"
if grep -qE '(^|[[:space:]])BREAKING[ -]CHANGE' <<<"$text" \
|| grep -qiE '^[a-z]+(\([^)]*\))?!:' <<<"$text"; then
echo "major"
elif grep -qiE '^feat(\([^)]*\))?:' <<<"$text"; then
echo "minor"
elif grep -qiE '^(fix|perf)(\([^)]*\))?:' <<<"$text"; then
echo "patch"
else
echo "none"
fi
}

if [ "$EVENT_NAME" = "pull_request" ]; then
# Squash merge: the PR title is the future main commit subject.
BUMP="$(detect_bump "$PR_TITLE")"
else
# Push/release: read the actual commits landed since the base.
if [ -n "$RANGE" ]; then
LOG="$(git log "$RANGE" --pretty=format:'%s%n%b%n--END--')"
else
LOG="$(git log --pretty=format:'%s%n%b%n--END--')"
fi
BUMP="$(detect_bump "$LOG")"
fi

IFS='.' read -r MAJOR MINOR PATCH <<<"$BASE"
MAJOR="${MAJOR:-0}"; MINOR="${MINOR:-0}"; PATCH="${PATCH:-0}"

apply_bump() {
case "$1" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac
}

SHOULD_BUILD="false"
SHOULD_RELEASE="false"
SUFFIX=""

case "$EVENT_NAME" in
pull_request)
# Preview the version the merge would produce. When there is no
# releasable change (ci/chore/docs) we keep the base version — the
# RC just carries an -rc suffix, no phantom version bump.
apply_bump "$BUMP"
SUFFIX="-rc.${PR_NUMBER:-0}"
SHOULD_BUILD="true"
;;
workflow_dispatch)
apply_bump "$BUMP"
SUFFIX="-dev"
SHOULD_BUILD="true"
;;
*)
# push (or release) on the release branch.
apply_bump "$BUMP"
if [ "$BUMP" != "none" ]; then
SHOULD_BUILD="true"
SHOULD_RELEASE="true"
fi
;;
esac

VERSION="${MAJOR}.${MINOR}.${PATCH}"
TAG="v${VERSION}"

{
echo "version=${VERSION}"
echo "suffix=${SUFFIX}"
echo "base_version=${BASE}"
echo "tag=${TAG}"
echo "bump=${BUMP}"
echo "should_build=${SHOULD_BUILD}"
echo "should_release=${SHOULD_RELEASE}"
} >> "$GITHUB_OUTPUT"

echo "Base version: ${BASE}${LATEST:+ (from ${LATEST})}"
echo "Detected bump: ${BUMP}"
echo "Computed version: ${VERSION}${SUFFIX}"
echo "should_build=${SHOULD_BUILD} should_release=${SHOULD_RELEASE}"
28 changes: 0 additions & 28 deletions .github/actions/docker-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ inputs:
description: 'Maximum parallelism for Docker Build'
required: false
default: 8
outputs:
is-production:
description: 'Whether this is a production build'
value: ${{ steps.build-type.outputs.is_production }}
release-suffix:
description: 'Release suffix for non-production builds'
value: ${{ steps.env-vars.outputs.release_suffix }}

runs:
using: 'composite'
Expand Down Expand Up @@ -84,24 +77,3 @@ runs:
echo "::group::docker buildx inspect --bootstrap"
docker buildx inspect --bootstrap
echo "::endgroup::"

- name: Determine if this is a production build
id: build-type
shell: bash
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "is_production=true" >> $GITHUB_OUTPUT
else
echo "is_production=false" >> $GITHUB_OUTPUT
fi

- name: Set Environment Variables
id: env-vars
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dj/containers-v1'
shell: bash
run: |
RAW_BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF}}"
BRANCH_NAME=$(echo "${RAW_BRANCH}" | sed 's/\//-/g')
SUFFIX="-dev-${BRANCH_NAME}"
echo "RELEASE=${SUFFIX}" >> $GITHUB_ENV
echo "release_suffix=${SUFFIX}" >> $GITHUB_OUTPUT
Loading