1+ # .github/workflows/release.yml
2+
13name : Release
24
35on :
46 workflow_dispatch :
7+ inputs :
8+ dry_run :
9+ description : " DRY RUN: Do not push/tag or create a GitHub Release"
10+ type : boolean
11+ default : false
12+ base_ref :
13+ description : " Branch to release from (for testing)"
14+ type : string
15+ default : " main"
16+
17+ permissions :
18+ contents : write
519
620concurrency :
7- group : release-${{ github.ref }}
21+ group : release-${{ github.event.inputs.base_ref || github. ref }}
822 cancel-in-progress : true
923
1024jobs :
11- tag :
25+ prepare-release :
1226 runs-on : ubuntu-latest
1327 outputs :
1428 tag_name : ${{ steps.set_tag.outputs.tag_name }}
1529 dsl_version : ${{ steps.get_dsl_version.outputs.dsl_version }}
1630 artifact_id : ${{ steps.get_artifact_id.outputs.artifact_id }}
1731 steps :
18- - name : Checkout code
19- uses : actions/checkout@v5
32+ - name : Checkout base branch
33+ uses : actions/checkout@v6
2034 with :
2135 fetch-depth : 0
36+ ref : ${{ github.event.inputs.base_ref || 'main' }}
37+ persist-credentials : true
2238
2339 - name : Set up Java
2440 uses : actions/setup-java@v5
@@ -29,96 +45,170 @@ jobs:
2945
3046 - name : Get DSL version from pom.xml
3147 id : get_dsl_version
48+ shell : bash
3249 run : |
33- DSL_VERSION=$(mvn help:evaluate -Dexpression=rosetta.dsl.version -q -DforceStdout)
34- echo "dsl_version=$DSL_VERSION" >> $GITHUB_OUTPUT
50+ set -euo pipefail
51+ DSL_VERSION=$(mvn -q help:evaluate -Dexpression=rosetta.dsl.version -DforceStdout)
52+ if [ -z "${DSL_VERSION:-}" ]; then
53+ echo "Failed to resolve rosetta.dsl.version from pom.xml"
54+ exit 1
55+ fi
56+ echo "dsl_version=$DSL_VERSION" >> "$GITHUB_OUTPUT"
3557
3658 - name : Get artifactId from pom.xml
3759 id : get_artifact_id
60+ shell : bash
3861 run : |
39- ARTIFACT_ID=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)
40- echo "artifact_id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
62+ set -euo pipefail
63+ ARTIFACT_ID=$(mvn -q help:evaluate -Dexpression=project.artifactId -DforceStdout)
64+ if [ -z "${ARTIFACT_ID:-}" ]; then
65+ echo "Failed to resolve project.artifactId from pom.xml"
66+ exit 1
67+ fi
68+ echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_OUTPUT"
4169
42- - name : Fetch all tags
43- run : git fetch --tags
70+ - name : Ensure up-to-date and fetch tags
71+ shell : bash
72+ run : |
73+ set -euo pipefail
74+ git config --global pull.ff only
75+ git fetch --prune --tags origin
76+ git pull --ff-only origin "${{ github.event.inputs.base_ref || 'main' }}"
4477
4578 - name : Determine next tag
4679 id : set_tag
80+ shell : bash
4781 run : |
82+ set -euo pipefail
4883 DSL_VERSION="${{ steps.get_dsl_version.outputs.dsl_version }}"
49- TAGS=$(git tag --list "${DSL_VERSION}.*" | sort -V)
50- if [ -z "$TAGS" ]; then
84+ DSL_ESCAPED=$(printf '%s\n' "$DSL_VERSION" | sed -e 's/[]\/$*.^|[]/\\&/g')
85+
86+ EXISTING=$(git tag -l "${DSL_VERSION}.*" | grep -E "^${DSL_ESCAPED}\.[0-9]+$" || true)
87+ if [ -z "$EXISTING" ]; then
5188 NEXT_TAG="${DSL_VERSION}.0"
5289 else
53- MAX_N=$(echo "$TAGS" | sed "s/^${DSL_VERSION}\.//" | sort -n | tail -1)
54- NEXT_N=$((MAX_N + 1))
55- NEXT_TAG="${DSL_VERSION}.${NEXT_N}"
90+ MAX_N=$(echo "$EXISTING" | sed -E "s/^${DSL_ESCAPED}\.//" | sort -n | tail -1)
91+ NEXT_TAG="${DSL_VERSION}.$((MAX_N + 1))"
5692 fi
57- echo "tag_name=$NEXT_TAG" >> $GITHUB_OUTPUT
93+ echo "Next tag computed: $NEXT_TAG"
94+ echo "tag_name=$NEXT_TAG" >> "$GITHUB_OUTPUT"
5895
59- - name : Create and push tag
60- env :
61- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
96+ - name : Guard only allow real release from main
97+ if : ${{ github.event.inputs.dry_run != 'true' }}
98+ shell : bash
6299 run : |
63- TAG_NAME="${{ steps.set_tag.outputs.tag_name }}"
64- if git rev-parse "$TAG_NAME" >/dev/null 2>&1 ; then
65- echo "Tag $TAG_NAME already exists. Exiting ."
100+ set -euo pipefail
101+ if [ "${{ github.event.inputs.base_ref || 'main' }}" != "main" ] ; then
102+ echo "Unable to perform a release from '${{ github.event.inputs.base_ref }}'. Use main or set dry_run=true ."
66103 exit 1
67104 fi
68- git config user.name "github-actions[bot]"
69- git config user.email "github-actions[bot]@users.noreply.github.com"
70- git tag "$TAG_NAME"
71- git push origin "$TAG_NAME"
72105
73- build-and-release :
74- needs : tag
75- runs-on : ubuntu-latest
76- steps :
77- - name : Checkout code
78- uses : actions/checkout@v5
79- with :
80- fetch-depth : 0
81-
82- - name : Set up Java
83- uses : actions/setup-java@v5
84- with :
85- distribution : temurin
86- java-version : 21
87- cache : maven
106+ - name : Bump version, tag, and push (skipped in dry run)
107+ env :
108+ TAG_NAME : ${{ steps.set_tag.outputs.tag_name }}
109+ DRY_RUN : ${{ github.event.inputs.dry_run }}
110+ BASE_REF : ${{ github.event.inputs.base_ref || 'main' }}
111+ shell : bash
112+ run : |
113+ set -euo pipefail
114+ if [ "${DRY_RUN}" = "true" ]; then
115+ echo "[DRY RUN] Would set project version to ${TAG_NAME}, commit, tag, and push atomically to ${BASE_REF}."
116+ exit 0
117+ fi
88118
89- - name : Save original pom.xml
90- run : cp pom.xml pom.xml.bak
119+ mvn -q -B versions:set -DnewVersion="${TAG_NAME}" -DgenerateBackupPoms=false
120+ if ! git diff --quiet; then
121+ git config user.name "github-actions[bot]"
122+ git config user.email "github-actions[bot]@users.noreply.github.com"
123+ git add -A
124+ git commit -m "Release ${TAG_NAME}: set project version to ${TAG_NAME}"
125+ else
126+ echo "No changes to commit (version already ${TAG_NAME})."
127+ fi
91128
92- - name : Update pom.xml version to match tag
93- run : |
94- mvn -B versions:set -DnewVersion=${{ needs.tag.outputs.tag_name }}
95- mvn -B versions:commit
129+ git fetch --prune --tags origin
130+ if git rev-parse "${TAG_NAME}" >/dev/null 2>&1; then
131+ echo "Tag ${TAG_NAME} already exists. Exiting."
132+ exit 1
133+ fi
96134
97- - name : Build JARs
98- run : mvn -B clean package
135+ git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}"
136+ git push --atomic origin HEAD:"${BASE_REF}" "${TAG_NAME}"
99137
100- - name : Revert pom.xml to original
101- run : mv pom.xml.bak pom.xml
138+ build-and-verify-release :
139+ needs : prepare-release
140+ runs-on : ubuntu-latest
141+ steps :
142+ - name : Checkout the tag (real release)
143+ if : ${{ github.event.inputs.dry_run != 'true' }}
144+ uses : actions/checkout@v6
145+ with :
146+ fetch-depth : 0
147+ ref : ${{ needs.prepare-release.outputs.tag_name }}
148+
149+ - name : Checkout the base branch (dry run)
150+ if : ${{ github.event.inputs.dry_run == 'true' }}
151+ uses : actions/checkout@v6
152+ with :
153+ fetch-depth : 0
154+ ref : ${{ github.event.inputs.base_ref || 'main' }}
155+
156+ - name : Set up Java
157+ uses : actions/setup-java@v5
158+ with :
159+ distribution : temurin
160+ java-version : 21
161+ cache : maven
162+
163+ - name : In dry run - set project version to computed tag (no commit)
164+ if : ${{ github.event.inputs.dry_run == 'true' }}
165+ env :
166+ TAG_NAME : ${{ needs.prepare-release.outputs.tag_name }}
167+ shell : bash
168+ run : |
169+ set -euo pipefail
170+ mvn -q -B versions:set -DnewVersion="${TAG_NAME}" -DgenerateBackupPoms=false
171+ # No commit; only adjust the working tree so the built artifact names match
172+
173+ - name : Build JARs
174+ shell : bash
175+ run : mvn -B clean package
176+
177+ - name : Collect artifact paths
178+ id : archive
179+ shell : bash
180+ run : |
181+ set -euo pipefail
182+ ARTIFACT_ID="${{ needs.prepare-release.outputs.artifact_id }}"
183+ TAG_NAME="${{ needs.prepare-release.outputs.tag_name }}"
184+ JAR="target/${ARTIFACT_ID}-${TAG_NAME}.jar"
185+ JDOC="target/${ARTIFACT_ID}-${TAG_NAME}-javadoc.jar"
186+ if [ ! -f "$JAR" ]; then echo "Missing $JAR"; ls -l target || true; exit 1; fi
187+ if [ ! -f "$JDOC" ]; then echo "Missing $JDOC"; ls -l target || true; exit 1; fi
188+ echo "jar_path=$JAR" >> "$GITHUB_OUTPUT"
189+ echo "javadoc_jar_path=$JDOC" >> "$GITHUB_OUTPUT"
190+
191+ - name : In dry run - upload build outputs as workflow artifacts
192+ if : ${{ github.event.inputs.dry_run == 'true' }}
193+ uses : actions/upload-artifact@v6
194+ with :
195+ name : dry-run-${{ needs.prepare-release.outputs.tag_name }}
196+ path : |
197+ ${{ steps.archive.outputs.jar_path }}
198+ ${{ steps.archive.outputs.javadoc_jar_path }}
199+ retention-days : 3
200+
201+ - name : Create GitHub Release (real release)
202+ if : ${{ github.event.inputs.dry_run != 'true' }}
203+ uses : softprops/action-gh-release@v2
204+ with :
205+ tag_name : ${{ needs.prepare-release.outputs.tag_name }}
206+ name : ${{ needs.prepare-release.outputs.tag_name }}
207+ body : |
208+ Automated release for DSL version ${{ needs.prepare-release.outputs.dsl_version }}
209+ files : |
210+ ${{ steps.archive.outputs.jar_path }}
211+ ${{ steps.archive.outputs.javadoc_jar_path }}
212+ env :
213+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
102214
103- - name : Archive JARs
104- id : archive
105- run : |
106- ARTIFACT_ID="${{ needs.tag.outputs.artifact_id }}"
107- TAG_NAME="${{ needs.tag.outputs.tag_name }}"
108- JAR="target/${ARTIFACT_ID}-${TAG_NAME}.jar"
109- JAVADOC_JAR="target/${ARTIFACT_ID}-${TAG_NAME}-javadoc.jar"
110- echo "jar_path=$JAR" >> $GITHUB_OUTPUT
111- echo "javadoc_jar_path=$JAVADOC_JAR" >> $GITHUB_OUTPUT
112-
113- - name : Create GitHub Release
114- uses : softprops/action-gh-release@v2
115- with :
116- tag_name : ${{ needs.tag.outputs.tag_name }}
117- name : ${{ needs.tag.outputs.tag_name }}
118- body : |
119- Automated release for DSL version ${{ needs.tag.outputs.dsl_version }}
120- files : |
121- ${{ steps.archive.outputs.jar_path }}
122- ${{ steps.archive.outputs.javadoc_jar_path }}
123- env :
124- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments