From 7c33ebe2617b82f1b3aad92372651b8aca6c2448 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Tue, 23 Jun 2026 22:05:29 +0800 Subject: [PATCH 1/5] xnat-8763: upgrade to Java 21 + monorepo 1.10.1-SNAPSHOT - sourceCompatibility/targetCompatibility -> Java 21 (in java { } block) - Gradle wrapper 7.6 -> 9.4.1 - vXnat 1.9.0 -> 1.10.1-SNAPSHOT - gradle plugins: com.palantir.git-version 0.12.3 -> 3.0.0, io.franzbecker.gradle-lombok 4.0.0 -> 5.0.0, io.spring.dependency-management 1.0.11.RELEASE -> 1.1.7 - add lombok { version + sha256 } block (1.18.34) - jacocoTestReport: xml.enabled/csv.enabled/html.enabled -> .required (Gradle 9 API) - idea outputDir: compileJava.destinationDir -> .destinationDirectory.asFile.get() (Gradle 9 API) - drop org.nrg.xnat:xnat-data-models dep (merged into xnat-web in monorepo 1.10+) Local ./gradlew build passes against monorepo 1.10.1-SNAPSHOT in local maven. --- build.gradle | 33 ++++++++++++++---------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 22bbe20..d752847 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { ext { - vXnat = "1.9.0" + vXnat = "1.10.1-SNAPSHOT" } } @@ -19,9 +19,9 @@ plugins { id "jacoco" id "java" id "maven-publish" - id "com.palantir.git-version" version "0.12.3" - id "io.franzbecker.gradle-lombok" version "4.0.0" - id "io.spring.dependency-management" version "1.0.11.RELEASE" + id "com.palantir.git-version" version "3.0.0" + id "io.franzbecker.gradle-lombok" version "5.0.0" + id "io.spring.dependency-management" version "1.1.7" id "org.nrg.xnat.build.xnat-data-builder" version "${vXnat}" } @@ -36,8 +36,15 @@ repositories { mavenCentral() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +lombok { + version = "1.18.34" + sha256 = "1ea5ad6c6afcff902d75072b2aaa6695585aebee9f12127e15c0036ba95d2918" +} dependencyManagement.imports { mavenBom "org.nrg:parent:${vXnat}" @@ -47,9 +54,7 @@ dependencies { implementation("org.nrg.xnat:web") { transitive = false } - implementation("org.nrg.xnat:xnat-data-models") { - transitive = false - } + // xnat-data-models is merged into xnat-web in monorepo 1.10+ — no separate dep implementation("org.nrg.xdat:core") { transitive = false } @@ -78,8 +83,8 @@ dependencies { idea { module { inheritOutputDirs = false - outputDir = compileJava.destinationDir - testOutputDir = compileTestJava.destinationDir + outputDir = compileJava.destinationDirectory.asFile.get() + testOutputDir = compileTestJava.destinationDirectory.asFile.get() } } @@ -123,9 +128,9 @@ jacoco { jacocoTestReport { dependsOn test reports { - xml.enabled = false - csv.enabled = false - html.enabled = true + xml.required = false + csv.required = false + html.required = true } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb70..221c4f9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From f3d43dcd3579233e719d078ddb5a8a35b34455de Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Tue, 23 Jun 2026 22:05:29 +0800 Subject: [PATCH 2/5] xnat-8763: add CI workflows (build-publish + cut-rc + promote) --- .github/workflows/build-publish.yml | 39 ++++++ .github/workflows/release-cut-rc.yml | 187 ++++++++++++++++++++++++++ .github/workflows/release-promote.yml | 165 +++++++++++++++++++++++ 3 files changed, 391 insertions(+) create mode 100644 .github/workflows/build-publish.yml create mode 100644 .github/workflows/release-cut-rc.yml create mode 100644 .github/workflows/release-promote.yml diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml new file mode 100644 index 0000000..a5ba02a --- /dev/null +++ b/.github/workflows/build-publish.yml @@ -0,0 +1,39 @@ +name: Build and Publish + +# Thin caller around the shared reusable workflow in +# NrgXnat/xnat-ci-workflows. Plugin variant — no GHCR Docker image +# (plugins ship a jar to JFrog only). + +on: + push: + branches: + - main + - develop + - 'releases/*' # RC / release branches, e.g. releases/1.3.1-rc + workflow_dispatch: + inputs: + publish_jfrog: + description: 'Publish jar to JFrog Artifactory (nrgxnat)' + type: boolean + default: false + +permissions: + contents: read + +jobs: + build-publish: + uses: NrgXnat/xnat-ci-workflows/.github/workflows/gradle-build-publish.yml@v1 + with: + # ──── Artifact ──── + artifact-glob: 'build/libs/*.jar' + artifact-name: 'ldap-auth-plugin-jar' + + # ──── JFrog ──── + # On any push to a triggered branch: publish. On dispatch: only when publish_jfrog ticked. + publish-jfrog: ${{ github.event_name == 'push' || inputs.publish_jfrog }} + + # publish-ghcr deliberately omitted — defaults to false in the reusable workflow. + + # secrets: inherit passes XNAT_ARTIFACTORY_USER/TOKEN (org-level) and + # the auto-injected GITHUB_TOKEN through to the reusable workflow. + secrets: inherit diff --git a/.github/workflows/release-cut-rc.yml b/.github/workflows/release-cut-rc.yml new file mode 100644 index 0000000..5ba2edf --- /dev/null +++ b/.github/workflows/release-cut-rc.yml @@ -0,0 +1,187 @@ +name: Release - Cut RC Branch + +# Cuts an RC branch from `source_branch` (default: develop) and bumps +# source_branch to the next dev version. Plugin variant of the monorepo +# release-cut-rc workflow — no dicom-edit6 bump; version lives in +# build.gradle (groovy DSL: `version "..."`). +# +# Inputs: +# next_dev_version - version source_branch should become after cutting +# (e.g. 1.4.0-SNAPSHOT, MUST end with -SNAPSHOT) +# source_branch - branch to cut from (default: develop) +# +# Produces: +# - new branch releases/-rc with version "-RC-SNAPSHOT" +# - source_branch with version "" + +on: + workflow_dispatch: + inputs: + next_dev_version: + description: 'Next develop SNAPSHOT version (e.g. 1.4.0-SNAPSHOT)' + type: string + required: true + source_branch: + description: 'Branch to cut from (default: develop)' + type: string + required: false + default: develop + trigger_build: + description: 'After cut, dispatch build/publish on RC and source_branch' + type: boolean + required: false + default: true + +permissions: + contents: write + actions: write # dispatch build-publish workflow + +jobs: + cut-rc: + runs-on: ubuntu-latest + + steps: + - name: Checkout ${{ inputs.source_branch }} + uses: actions/checkout@v4 + with: + ref: ${{ inputs.source_branch }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Read current state and derive versions + id: vars + run: | + set -euo pipefail + + # Groovy DSL: `version "1.3.1-SNAPSHOT"` + CURRENT_VERSION="$(grep -E '^version ' build.gradle | head -n1 | sed -E 's/^version "(.+)"$/\1/')" + if [[ -z "${CURRENT_VERSION}" ]]; then + echo "::error::Could not read version from build.gradle" + exit 1 + fi + + RELEASE_VERSION="${CURRENT_VERSION%-SNAPSHOT}" + RC_VERSION="${RELEASE_VERSION}-RC-SNAPSHOT" + RC_BRANCH="releases/${RELEASE_VERSION}-rc" + + { + echo "current_version=${CURRENT_VERSION}" + echo "release_version=${RELEASE_VERSION}" + echo "rc_version=${RC_VERSION}" + echo "rc_branch=${RC_BRANCH}" + } >> "$GITHUB_OUTPUT" + + echo "::notice::source=${{ inputs.source_branch }} current=${CURRENT_VERSION} → rc=${RC_VERSION} branch=${RC_BRANCH}" + + - name: Validate + env: + NEXT_DEV_VERSION: ${{ inputs.next_dev_version }} + CURRENT_VERSION: ${{ steps.vars.outputs.current_version }} + RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} + run: | + set -euo pipefail + + if [[ "${CURRENT_VERSION}" != *-SNAPSHOT ]]; then + echo "::error::source_branch is on ${CURRENT_VERSION}; expected -SNAPSHOT. Refusing to cut RC from a release/RC branch." + exit 1 + fi + if [[ "${NEXT_DEV_VERSION}" != *-SNAPSHOT ]]; then + echo "::error::next_dev_version=${NEXT_DEV_VERSION} must end with -SNAPSHOT" + exit 1 + fi + if [[ "${NEXT_DEV_VERSION}" == "${CURRENT_VERSION}" ]]; then + echo "::error::next_dev_version equals current version (${CURRENT_VERSION}); nothing to bump" + exit 1 + fi + if git ls-remote --exit-code origin "refs/heads/${RC_BRANCH}" >/dev/null 2>&1; then + echo "::error::RC branch ${RC_BRANCH} already exists on origin" + exit 1 + fi + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Cut RC branch + env: + CURRENT_VERSION: ${{ steps.vars.outputs.current_version }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + RC_VERSION: ${{ steps.vars.outputs.rc_version }} + RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} + run: | + set -euo pipefail + + git switch -c "${RC_BRANCH}" + + sed -i "s|^version \"${CURRENT_VERSION}\"\$|version \"${RC_VERSION}\"|" build.gradle + grep -E '^version ' build.gradle + git add build.gradle + + git commit -m "chore(release): cut RC for ${RELEASE_VERSION}" + git push -u origin "${RC_BRANCH}" + + - name: Bump source_branch to next dev version + env: + SOURCE_BRANCH: ${{ inputs.source_branch }} + CURRENT_VERSION: ${{ steps.vars.outputs.current_version }} + NEXT_DEV_VERSION: ${{ inputs.next_dev_version }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + run: | + set -euo pipefail + + git switch "${SOURCE_BRANCH}" + + sed -i "s|^version \"${CURRENT_VERSION}\"\$|version \"${NEXT_DEV_VERSION}\"|" build.gradle + grep -E '^version ' build.gradle + + git add build.gradle + git commit -m "chore(release): bump ${SOURCE_BRANCH} to ${NEXT_DEV_VERSION} after cutting ${RELEASE_VERSION}-rc" + git push origin "${SOURCE_BRANCH}" + + - name: Trigger build/publish on RC branch and source_branch + if: ${{ inputs.trigger_build }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} + SOURCE_BRANCH: ${{ inputs.source_branch }} + run: | + set -euo pipefail + + echo "::group::Dispatch build-publish on RC branch (${RC_BRANCH})" + gh workflow run build-publish.yml --repo "${REPO}" \ + --ref "${RC_BRANCH}" \ + -f publish_jfrog=true + echo "::endgroup::" + + echo "::group::Dispatch build-publish on source_branch (${SOURCE_BRANCH})" + gh workflow run build-publish.yml --repo "${REPO}" \ + --ref "${SOURCE_BRANCH}" \ + -f publish_jfrog=true + echo "::endgroup::" + + - name: Job summary + env: + SOURCE_BRANCH: ${{ inputs.source_branch }} + NEXT_DEV_VERSION: ${{ inputs.next_dev_version }} + RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} + RC_VERSION: ${{ steps.vars.outputs.rc_version }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + SERVER_URL: ${{ github.server_url }} + REPOSITORY: ${{ github.repository }} + run: | + { + echo "## Release RC cut: ${RELEASE_VERSION}" + echo "" + echo "**RC branch**: [\`${RC_BRANCH}\`](${SERVER_URL}/${REPOSITORY}/tree/${RC_BRANCH})" + echo "" + echo "| file | value |" + echo "|------|-------|" + echo "| \`build.gradle\` version | \`${RC_VERSION}\` |" + echo "" + echo "**${SOURCE_BRANCH}** bumped to: \`${NEXT_DEV_VERSION}\`" + echo "" + echo "### Next step" + echo "Stabilize on \`${RC_BRANCH}\`; when ready, dispatch the release-promote workflow." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-promote.yml b/.github/workflows/release-promote.yml new file mode 100644 index 0000000..0293a28 --- /dev/null +++ b/.github/workflows/release-promote.yml @@ -0,0 +1,165 @@ +name: Release - Promote RC to Main + +# Promotes a stabilized RC branch to a tagged release on main. +# Plugin variant of the monorepo release-promote workflow — no +# dicom-edit6 sync; version lives in build.gradle (groovy DSL: `version "..."`). +# +# Inputs: +# rc_branch - RC branch to promote (e.g. releases/1.3.1-rc); REQUIRED +# target_main - branch to merge into (default main) +# trigger_build - dispatch build/publish on target_main +# +# Produces: +# - RC branch with -RC-SNAPSHOT suffix stripped (release version string) +# - target_main fast-merged (--no-ff) with RC + tag (no v prefix) + +on: + workflow_dispatch: + inputs: + rc_branch: + description: 'RC branch to promote (e.g. releases/1.3.1-rc)' + type: string + required: true + target_main: + description: 'Branch to merge into (default main)' + type: string + required: false + default: main + trigger_build: + description: 'After promote, dispatch build/publish on target_main' + type: boolean + required: false + default: true + +permissions: + contents: write # push RC/main + create tag + actions: write # dispatch build-publish workflow + +jobs: + promote: + runs-on: ubuntu-latest + + steps: + - name: Checkout ${{ inputs.rc_branch }} + uses: actions/checkout@v4 + with: + ref: ${{ inputs.rc_branch }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Read RC state and derive release version + id: vars + env: + RC_BRANCH: ${{ inputs.rc_branch }} + run: | + set -euo pipefail + + RC_VERSION="$(grep -E '^version ' build.gradle | head -n1 | sed -E 's/^version "(.+)"$/\1/')" + if [[ -z "${RC_VERSION}" ]]; then + echo "::error::Could not read version from build.gradle" + exit 1 + fi + RELEASE_VERSION="${RC_VERSION%-RC-SNAPSHOT}" + + { + echo "rc_version=${RC_VERSION}" + echo "release_version=${RELEASE_VERSION}" + } >> "$GITHUB_OUTPUT" + + echo "::notice::rc=${RC_BRANCH} rc_version=${RC_VERSION} → release=${RELEASE_VERSION}" + + - name: Validate + env: + RC_BRANCH: ${{ inputs.rc_branch }} + RC_VERSION: ${{ steps.vars.outputs.rc_version }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + run: | + set -euo pipefail + + if [[ ! "${RC_BRANCH}" =~ ^releases/.+-rc$ ]]; then + echo "::error::rc_branch=${RC_BRANCH} must match 'releases/*-rc'" + exit 1 + fi + if [[ "${RC_VERSION}" != *-RC-SNAPSHOT ]]; then + echo "::error::RC branch version=${RC_VERSION} must end with -RC-SNAPSHOT" + exit 1 + fi + if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then + echo "::error::Tag ${RELEASE_VERSION} already exists on origin" + exit 1 + fi + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Finalize RC branch (strip -RC-SNAPSHOT) + env: + RC_BRANCH: ${{ inputs.rc_branch }} + RC_VERSION: ${{ steps.vars.outputs.rc_version }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + run: | + set -euo pipefail + + sed -i "s|^version \"${RC_VERSION}\"\$|version \"${RELEASE_VERSION}\"|" build.gradle + grep -E '^version ' build.gradle + git add build.gradle + + git commit -m "chore(release): finalize ${RELEASE_VERSION}" + git push origin "HEAD:${RC_BRANCH}" + + - name: Merge RC into target_main and tag + env: + RC_BRANCH: ${{ inputs.rc_branch }} + TARGET_MAIN: ${{ inputs.target_main }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + run: | + set -euo pipefail + + git fetch origin "${TARGET_MAIN}" + git switch "${TARGET_MAIN}" + git pull --ff-only origin "${TARGET_MAIN}" + + # --no-ff to create an explicit merge commit on release boundary; + # set -e + no -X strategy → fail fast on conflict (release manager intervenes) + git merge --no-ff "origin/${RC_BRANCH}" -m "release: ${RELEASE_VERSION}" + + git tag "${RELEASE_VERSION}" # no 'v' prefix (matches monorepo tag convention) + git push origin "${TARGET_MAIN}" + git push origin "refs/tags/${RELEASE_VERSION}" + + - name: Trigger build/publish on target_main + if: ${{ inputs.trigger_build }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + TARGET_MAIN: ${{ inputs.target_main }} + run: | + set -euo pipefail + + echo "::group::Dispatch build-publish on target_main (${TARGET_MAIN})" + gh workflow run build-publish.yml --repo "${REPO}" \ + --ref "${TARGET_MAIN}" \ + -f publish_jfrog=true + echo "::endgroup::" + + - name: Job summary + env: + RC_BRANCH: ${{ inputs.rc_branch }} + TARGET_MAIN: ${{ inputs.target_main }} + RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} + SERVER_URL: ${{ github.server_url }} + REPOSITORY: ${{ github.repository }} + run: | + { + echo "## Release promoted: ${RELEASE_VERSION}" + echo "" + echo "**Tag**: [\`${RELEASE_VERSION}\`](${SERVER_URL}/${REPOSITORY}/releases/tag/${RELEASE_VERSION})" + echo "**${TARGET_MAIN}**: [HEAD](${SERVER_URL}/${REPOSITORY}/tree/${TARGET_MAIN})" + echo "**${RC_BRANCH}**: kept (finalized to release strings; available for future patches)" + echo "" + echo "| file | value |" + echo "|------|-------|" + echo "| \`build.gradle\` version | \`${RELEASE_VERSION}\` |" + } >> "$GITHUB_STEP_SUMMARY" From 46b34516bb3a3326228386dd7a2a8d29c844f460 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Thu, 25 Jun 2026 00:03:37 +0800 Subject: [PATCH 3/5] xnat-8763: align publishing.repositories credentials with monorepo convention Read artifactoryUser/Token via project.findProperty (matches CI workflow's ORG_GRADLE_PROJECT_artifactoryUser/Token env-var mapping), with ARTIFACTORY_USER/TOKEN env-var fallback. Standardize URL to -local repos. Mirrors monorepo's buildSrc/buildlogic.java-common-conventions.gradle pattern. --- build.gradle | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index d752847..a915b54 100644 --- a/build.gradle +++ b/build.gradle @@ -213,17 +213,17 @@ publishing { repositories { maven { + name = "XNAT_Artifactory" + url = project.version.toString().endsWith("-SNAPSHOT") + ? "https://nrgxnat.jfrog.io/nrgxnat/libs-snapshot-local" + : "https://nrgxnat.jfrog.io/nrgxnat/libs-release-local" + // CI injects ORG_GRADLE_PROJECT_artifactoryUser/Token env vars (Gradle maps + // these to project properties). Local devs can set artifactoryUser/Token in + // ~/.gradle/gradle.properties. Mirrors monorepo's buildSrc convention. credentials { - // These properties must be set in the ~/.gradle/gradle.properties file or passed on the Gradle command - // line in the form -PrepoUsername=foo -PrepoPassword=bar. - username propertyWithDefault("repoUsername", "username") - password propertyWithDefault("repoPassword", "password") + username = (project.findProperty("artifactoryUser") ?: System.getenv("ARTIFACTORY_USER")) ?: "" + password = (project.findProperty("artifactoryToken") ?: System.getenv("ARTIFACTORY_TOKEN")) ?: "" } - url "https://nrgxnat.jfrog.io/nrgxnat/libs-${project.version.endsWith("-SNAPSHOT") ? "snapshot" : "release"}-local" } } } - -String propertyWithDefault(String name, Object value) { - hasProperty(name) ? property(name) : value -} From b717a99ada9ae4771a0cde140012bcec163c2ec6 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Thu, 25 Jun 2026 22:01:51 +0800 Subject: [PATCH 4/5] xnat-8763: switch release-cut-rc + release-promote to thin caller pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 187-line + 165-line inline workflows with ~37-line callers that delegate to NrgXnat/xnat-ci-workflows reusable workflows: - gradle-release-cut-rc.yml@v1 - gradle-release-promote.yml@v1 Total YAML saved per plugin: ~278 lines. Behavior unchanged — the reusable workflows were extracted verbatim from the previous inline scripts. --- .github/workflows/release-cut-rc.yml | 172 ++------------------------ .github/workflows/release-promote.yml | 150 ++-------------------- 2 files changed, 22 insertions(+), 300 deletions(-) diff --git a/.github/workflows/release-cut-rc.yml b/.github/workflows/release-cut-rc.yml index 5ba2edf..b54e824 100644 --- a/.github/workflows/release-cut-rc.yml +++ b/.github/workflows/release-cut-rc.yml @@ -1,24 +1,15 @@ name: Release - Cut RC Branch -# Cuts an RC branch from `source_branch` (default: develop) and bumps -# source_branch to the next dev version. Plugin variant of the monorepo -# release-cut-rc workflow — no dicom-edit6 bump; version lives in -# build.gradle (groovy DSL: `version "..."`). -# -# Inputs: -# next_dev_version - version source_branch should become after cutting -# (e.g. 1.4.0-SNAPSHOT, MUST end with -SNAPSHOT) -# source_branch - branch to cut from (default: develop) -# -# Produces: -# - new branch releases/-rc with version "-RC-SNAPSHOT" -# - source_branch with version "" +# Thin caller around the shared reusable workflow in NrgXnat/xnat-ci-workflows. +# Cuts a `releases/-rc` branch from source_branch and bumps +# source_branch to the next dev version. All release-cut logic lives in the +# reusable workflow; this file only defines our triggers + inputs. on: workflow_dispatch: inputs: next_dev_version: - description: 'Next develop SNAPSHOT version (e.g. 1.4.0-SNAPSHOT)' + description: 'Next dev SNAPSHOT version (e.g. 1.7.0-SNAPSHOT)' type: string required: true source_branch: @@ -38,150 +29,9 @@ permissions: jobs: cut-rc: - runs-on: ubuntu-latest - - steps: - - name: Checkout ${{ inputs.source_branch }} - uses: actions/checkout@v4 - with: - ref: ${{ inputs.source_branch }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Read current state and derive versions - id: vars - run: | - set -euo pipefail - - # Groovy DSL: `version "1.3.1-SNAPSHOT"` - CURRENT_VERSION="$(grep -E '^version ' build.gradle | head -n1 | sed -E 's/^version "(.+)"$/\1/')" - if [[ -z "${CURRENT_VERSION}" ]]; then - echo "::error::Could not read version from build.gradle" - exit 1 - fi - - RELEASE_VERSION="${CURRENT_VERSION%-SNAPSHOT}" - RC_VERSION="${RELEASE_VERSION}-RC-SNAPSHOT" - RC_BRANCH="releases/${RELEASE_VERSION}-rc" - - { - echo "current_version=${CURRENT_VERSION}" - echo "release_version=${RELEASE_VERSION}" - echo "rc_version=${RC_VERSION}" - echo "rc_branch=${RC_BRANCH}" - } >> "$GITHUB_OUTPUT" - - echo "::notice::source=${{ inputs.source_branch }} current=${CURRENT_VERSION} → rc=${RC_VERSION} branch=${RC_BRANCH}" - - - name: Validate - env: - NEXT_DEV_VERSION: ${{ inputs.next_dev_version }} - CURRENT_VERSION: ${{ steps.vars.outputs.current_version }} - RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} - run: | - set -euo pipefail - - if [[ "${CURRENT_VERSION}" != *-SNAPSHOT ]]; then - echo "::error::source_branch is on ${CURRENT_VERSION}; expected -SNAPSHOT. Refusing to cut RC from a release/RC branch." - exit 1 - fi - if [[ "${NEXT_DEV_VERSION}" != *-SNAPSHOT ]]; then - echo "::error::next_dev_version=${NEXT_DEV_VERSION} must end with -SNAPSHOT" - exit 1 - fi - if [[ "${NEXT_DEV_VERSION}" == "${CURRENT_VERSION}" ]]; then - echo "::error::next_dev_version equals current version (${CURRENT_VERSION}); nothing to bump" - exit 1 - fi - if git ls-remote --exit-code origin "refs/heads/${RC_BRANCH}" >/dev/null 2>&1; then - echo "::error::RC branch ${RC_BRANCH} already exists on origin" - exit 1 - fi - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Cut RC branch - env: - CURRENT_VERSION: ${{ steps.vars.outputs.current_version }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - RC_VERSION: ${{ steps.vars.outputs.rc_version }} - RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} - run: | - set -euo pipefail - - git switch -c "${RC_BRANCH}" - - sed -i "s|^version \"${CURRENT_VERSION}\"\$|version \"${RC_VERSION}\"|" build.gradle - grep -E '^version ' build.gradle - git add build.gradle - - git commit -m "chore(release): cut RC for ${RELEASE_VERSION}" - git push -u origin "${RC_BRANCH}" - - - name: Bump source_branch to next dev version - env: - SOURCE_BRANCH: ${{ inputs.source_branch }} - CURRENT_VERSION: ${{ steps.vars.outputs.current_version }} - NEXT_DEV_VERSION: ${{ inputs.next_dev_version }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - run: | - set -euo pipefail - - git switch "${SOURCE_BRANCH}" - - sed -i "s|^version \"${CURRENT_VERSION}\"\$|version \"${NEXT_DEV_VERSION}\"|" build.gradle - grep -E '^version ' build.gradle - - git add build.gradle - git commit -m "chore(release): bump ${SOURCE_BRANCH} to ${NEXT_DEV_VERSION} after cutting ${RELEASE_VERSION}-rc" - git push origin "${SOURCE_BRANCH}" - - - name: Trigger build/publish on RC branch and source_branch - if: ${{ inputs.trigger_build }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} - SOURCE_BRANCH: ${{ inputs.source_branch }} - run: | - set -euo pipefail - - echo "::group::Dispatch build-publish on RC branch (${RC_BRANCH})" - gh workflow run build-publish.yml --repo "${REPO}" \ - --ref "${RC_BRANCH}" \ - -f publish_jfrog=true - echo "::endgroup::" - - echo "::group::Dispatch build-publish on source_branch (${SOURCE_BRANCH})" - gh workflow run build-publish.yml --repo "${REPO}" \ - --ref "${SOURCE_BRANCH}" \ - -f publish_jfrog=true - echo "::endgroup::" - - - name: Job summary - env: - SOURCE_BRANCH: ${{ inputs.source_branch }} - NEXT_DEV_VERSION: ${{ inputs.next_dev_version }} - RC_BRANCH: ${{ steps.vars.outputs.rc_branch }} - RC_VERSION: ${{ steps.vars.outputs.rc_version }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - SERVER_URL: ${{ github.server_url }} - REPOSITORY: ${{ github.repository }} - run: | - { - echo "## Release RC cut: ${RELEASE_VERSION}" - echo "" - echo "**RC branch**: [\`${RC_BRANCH}\`](${SERVER_URL}/${REPOSITORY}/tree/${RC_BRANCH})" - echo "" - echo "| file | value |" - echo "|------|-------|" - echo "| \`build.gradle\` version | \`${RC_VERSION}\` |" - echo "" - echo "**${SOURCE_BRANCH}** bumped to: \`${NEXT_DEV_VERSION}\`" - echo "" - echo "### Next step" - echo "Stabilize on \`${RC_BRANCH}\`; when ready, dispatch the release-promote workflow." - } >> "$GITHUB_STEP_SUMMARY" + uses: NrgXnat/xnat-ci-workflows/.github/workflows/gradle-release-cut-rc.yml@v1 + with: + next_dev_version: ${{ inputs.next_dev_version }} + source_branch: ${{ inputs.source_branch }} + trigger_build: ${{ inputs.trigger_build }} + secrets: inherit diff --git a/.github/workflows/release-promote.yml b/.github/workflows/release-promote.yml index 0293a28..09416fb 100644 --- a/.github/workflows/release-promote.yml +++ b/.github/workflows/release-promote.yml @@ -1,23 +1,15 @@ name: Release - Promote RC to Main -# Promotes a stabilized RC branch to a tagged release on main. -# Plugin variant of the monorepo release-promote workflow — no -# dicom-edit6 sync; version lives in build.gradle (groovy DSL: `version "..."`). -# -# Inputs: -# rc_branch - RC branch to promote (e.g. releases/1.3.1-rc); REQUIRED -# target_main - branch to merge into (default main) -# trigger_build - dispatch build/publish on target_main -# -# Produces: -# - RC branch with -RC-SNAPSHOT suffix stripped (release version string) -# - target_main fast-merged (--no-ff) with RC + tag (no v prefix) +# Thin caller around the shared reusable workflow in NrgXnat/xnat-ci-workflows. +# Promotes a stabilized RC branch to a tagged release on target_main. All +# release-promote logic lives in the reusable workflow; this file only defines +# our triggers + inputs. on: workflow_dispatch: inputs: rc_branch: - description: 'RC branch to promote (e.g. releases/1.3.1-rc)' + description: 'RC branch to promote (e.g. releases/1.6.0-rc)' type: string required: true target_main: @@ -37,129 +29,9 @@ permissions: jobs: promote: - runs-on: ubuntu-latest - - steps: - - name: Checkout ${{ inputs.rc_branch }} - uses: actions/checkout@v4 - with: - ref: ${{ inputs.rc_branch }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Read RC state and derive release version - id: vars - env: - RC_BRANCH: ${{ inputs.rc_branch }} - run: | - set -euo pipefail - - RC_VERSION="$(grep -E '^version ' build.gradle | head -n1 | sed -E 's/^version "(.+)"$/\1/')" - if [[ -z "${RC_VERSION}" ]]; then - echo "::error::Could not read version from build.gradle" - exit 1 - fi - RELEASE_VERSION="${RC_VERSION%-RC-SNAPSHOT}" - - { - echo "rc_version=${RC_VERSION}" - echo "release_version=${RELEASE_VERSION}" - } >> "$GITHUB_OUTPUT" - - echo "::notice::rc=${RC_BRANCH} rc_version=${RC_VERSION} → release=${RELEASE_VERSION}" - - - name: Validate - env: - RC_BRANCH: ${{ inputs.rc_branch }} - RC_VERSION: ${{ steps.vars.outputs.rc_version }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - run: | - set -euo pipefail - - if [[ ! "${RC_BRANCH}" =~ ^releases/.+-rc$ ]]; then - echo "::error::rc_branch=${RC_BRANCH} must match 'releases/*-rc'" - exit 1 - fi - if [[ "${RC_VERSION}" != *-RC-SNAPSHOT ]]; then - echo "::error::RC branch version=${RC_VERSION} must end with -RC-SNAPSHOT" - exit 1 - fi - if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then - echo "::error::Tag ${RELEASE_VERSION} already exists on origin" - exit 1 - fi - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Finalize RC branch (strip -RC-SNAPSHOT) - env: - RC_BRANCH: ${{ inputs.rc_branch }} - RC_VERSION: ${{ steps.vars.outputs.rc_version }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - run: | - set -euo pipefail - - sed -i "s|^version \"${RC_VERSION}\"\$|version \"${RELEASE_VERSION}\"|" build.gradle - grep -E '^version ' build.gradle - git add build.gradle - - git commit -m "chore(release): finalize ${RELEASE_VERSION}" - git push origin "HEAD:${RC_BRANCH}" - - - name: Merge RC into target_main and tag - env: - RC_BRANCH: ${{ inputs.rc_branch }} - TARGET_MAIN: ${{ inputs.target_main }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - run: | - set -euo pipefail - - git fetch origin "${TARGET_MAIN}" - git switch "${TARGET_MAIN}" - git pull --ff-only origin "${TARGET_MAIN}" - - # --no-ff to create an explicit merge commit on release boundary; - # set -e + no -X strategy → fail fast on conflict (release manager intervenes) - git merge --no-ff "origin/${RC_BRANCH}" -m "release: ${RELEASE_VERSION}" - - git tag "${RELEASE_VERSION}" # no 'v' prefix (matches monorepo tag convention) - git push origin "${TARGET_MAIN}" - git push origin "refs/tags/${RELEASE_VERSION}" - - - name: Trigger build/publish on target_main - if: ${{ inputs.trigger_build }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - TARGET_MAIN: ${{ inputs.target_main }} - run: | - set -euo pipefail - - echo "::group::Dispatch build-publish on target_main (${TARGET_MAIN})" - gh workflow run build-publish.yml --repo "${REPO}" \ - --ref "${TARGET_MAIN}" \ - -f publish_jfrog=true - echo "::endgroup::" - - - name: Job summary - env: - RC_BRANCH: ${{ inputs.rc_branch }} - TARGET_MAIN: ${{ inputs.target_main }} - RELEASE_VERSION: ${{ steps.vars.outputs.release_version }} - SERVER_URL: ${{ github.server_url }} - REPOSITORY: ${{ github.repository }} - run: | - { - echo "## Release promoted: ${RELEASE_VERSION}" - echo "" - echo "**Tag**: [\`${RELEASE_VERSION}\`](${SERVER_URL}/${REPOSITORY}/releases/tag/${RELEASE_VERSION})" - echo "**${TARGET_MAIN}**: [HEAD](${SERVER_URL}/${REPOSITORY}/tree/${TARGET_MAIN})" - echo "**${RC_BRANCH}**: kept (finalized to release strings; available for future patches)" - echo "" - echo "| file | value |" - echo "|------|-------|" - echo "| \`build.gradle\` version | \`${RELEASE_VERSION}\` |" - } >> "$GITHUB_STEP_SUMMARY" + uses: NrgXnat/xnat-ci-workflows/.github/workflows/gradle-release-promote.yml@v1 + with: + rc_branch: ${{ inputs.rc_branch }} + target_main: ${{ inputs.target_main }} + trigger_build: ${{ inputs.trigger_build }} + secrets: inherit From 3e5ae698aacdb4bc2343dcd51b83b36932327db5 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Fri, 26 Jun 2026 23:22:48 +0800 Subject: [PATCH 5/5] xnat-8763: add 'packages: write' to build-publish.yml caller permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared reusable workflow gradle-build-publish.yml@v1 has a publish-ghcr job declaring 'permissions: contents: read, packages: write'. Even though that job is gated by 'if: inputs.publish-ghcr' (default false), GitHub Actions parses the permissions at workflow startup. If the caller doesn't grant 'packages: write', the run fails with startup_failure before any job runs. Validated by dispatching build-publish on mfa-plugin/fake-develop: - Without packages: write → startup_failure in 2s - With packages: write → reaches Stage 1 Gradle build Mirrors xnat monorepo's build-publish.yml caller pattern. --- .github/workflows/build-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index a5ba02a..fd0aa22 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -19,6 +19,7 @@ on: permissions: contents: read + packages: write # required by reusable workflow's publish-ghcr job (gated by if: inputs.publish-ghcr but parsed at startup) jobs: build-publish: