From e17c9ad7f1106ea0a92040849824896331388d44 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Wed, 24 Jun 2026 08:12:54 +0800 Subject: [PATCH 1/9] xnat-8763: upgrade to Java 21 + monorepo 1.10.1-SNAPSHOT - vXnat 1.8.5-SNAPSHOT -> 1.10.1-SNAPSHOT - Gradle 7.4.2 -> 9.4.1 - git-version 0.12.1 -> 3.0.0, dep-mgmt 1.0.11 -> 1.1.7 - Java 1.8 -> 21 in java {} block - Hardcode lombok 1.18.34, jacoco 0.8.12, log4j 1.2.17 (BOM 1.10.1 dropped importedProperties) - Remove implementation org.nrg.xnat:xnat-data-models (merged into xnat-web) - Replace top-level manifest {} with gitManifestAttrs Map (Gradle 9 removed) - classifier "X" -> archiveClassifier.set("X") (Gradle 9 stricter) - Add buildscript classpath xnat-data-models for xnatDataBuilder to resolve xs:import xnat.xsd in nihss.xsd (xdat-core no longer ships them) - Add turbine + velocity + servlet-api compileOnly (xnat-web no longer surfaces these on consumer compileClasspath) --- build.gradle | 99 +++++++++++++++++------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/build.gradle b/build.gradle index 1884109..421b9c5 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,18 @@ buildscript { ext { - vXnat = "1.8.5-SNAPSHOT" + vXnat = "1.10.1-SNAPSHOT" + } + repositories { + mavenLocal() + maven { url "https://nrgxnat.jfrog.io/nrgxnat/libs-release" } + maven { url "https://nrgxnat.jfrog.io/nrgxnat/libs-snapshot" } + mavenCentral() + } + dependencies { + // xnat-data-builder needs core XNAT schemas (xnat.xsd) on its classpath to resolve + // xs:import in plugin-local schemas (nihss/nihss.xsd) + classpath "org.nrg.xnat:xnat-data-models:${vXnat}" } } @@ -20,9 +31,9 @@ plugins { id "java" id "maven-publish" id "com.dorongold.task-tree" version "1.5" - id "com.palantir.git-version" version "0.12.1" + 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.0.11.RELEASE" + id "io.spring.dependency-management" version "1.1.7" id "net.linguica.maven-settings" version "0.5" id "org.nrg.xnat.build.xnat-data-builder" version "${vXnat}" } @@ -38,25 +49,55 @@ repositories { mavenCentral() } -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} dependencyManagement.imports { mavenBom "org.nrg:parent:${vXnat}" } +lombok { + version = "1.18.34" + sha256 = "1ea5ad6c6afcff902d75072b2aaa6695585aebee9f12127e15c0036ba95d2918" +} + dependencies { implementation("org.nrg.xnat:web") { transitive = false } - implementation("org.nrg.xnat:xnat-data-models") { - transitive = false - } + // xnat-data-models merged into xnat-web in monorepo 1.10 implementation "org.nrg.xdat:core" implementation "org.apache.commons:commons-lang3" implementation "org.slf4j:slf4j-api" - implementation "log4j:log4j" + implementation "log4j:log4j:1.2.17" + + // Compile-only deps that xnat-web no longer surfaces on consumer compileClasspath in monorepo 1.10.1+ + implementation("turbine:turbine") { + exclude group: "commons-dbcp" + exclude group: "commons-email" + exclude group: "commons-pool" + exclude group: "fulcrum" + exclude group: "javax.activation", module: "activation" + exclude group: "javax.mail" + exclude group: "javax.servlet" + exclude group: "jython" + exclude group: "xalan" + exclude group: "xml-apis" + exclude group: "log4j", module: "log4j" + exclude group: "berkeleydb" + exclude group: "javax.sql" + exclude group: "jndi" + exclude group: "mysql" + exclude group: "tomcat" + exclude group: "xerces" + exclude group: "velocity", module: "texen" + exclude group: "jamon", module: "jamon" + } + compileOnly("org.apache.velocity:velocity") { transitive = false } + compileOnly "javax.servlet:javax.servlet-api" testImplementation "junit:junit" testImplementation "org.springframework:spring-test" @@ -83,7 +124,7 @@ configurations { } jacoco { - toolVersion = dependencyManagement.importedProperties["jacoco.version"] + toolVersion = "0.8.12" } jacocoTestReport { @@ -130,19 +171,19 @@ try { isCleanTag = false } -ext.gitManifest = manifest { - attributes "Application-Name": project.description, - "Build-Date": buildDate, - "Build-Number": buildNumber, - "Implementation-Version": project.version, - "Implementation-Sha": gitHash, - "Implementation-Sha-Full": gitHashFull, - "Implementation-Commit": commitDistance, - "Implementation-LastTag": lastTag, - "Implementation-Branch": branchName, - "Implementation-CleanTag": isCleanTag, - "Implementation-Dirty": isDirty -} +ext.gitManifestAttrs = [ + "Application-Name" : project.description, + "Build-Date" : buildDate, + "Build-Number" : buildNumber, + "Implementation-Version" : project.version, + "Implementation-Sha" : gitHash, + "Implementation-Sha-Full" : gitHashFull, + "Implementation-Commit" : commitDistance, + "Implementation-LastTag" : lastTag, + "Implementation-Branch" : branchName, + "Implementation-CleanTag" : isCleanTag, + "Implementation-Dirty" : isDirty +] logger.info """ Building artifacts with manifest attributes: @@ -160,17 +201,17 @@ Building artifacts with manifest attributes: """ task sourceJar(type: Jar, dependsOn: classes) { - classifier "sources" + archiveClassifier.set("sources") manifest { - from gitManifest + attributes gitManifestAttrs } from sourceSets.main.allSource } task javadocJar(type: Jar) { - classifier "javadoc" + archiveClassifier.set("javadoc") manifest { - from gitManifest + attributes gitManifestAttrs } from javadoc.destinationDir } @@ -179,7 +220,7 @@ jar { dependsOn test, sourceJar, javadocJar enabled = true manifest { - from gitManifest + attributes gitManifestAttrs } } @@ -188,7 +229,7 @@ task xnatPluginJar(type: Jar) { zip64 true archiveClassifier.set "xpl" manifest { - from gitManifest + attributes gitManifestAttrs } // files and folders with "-dev" or "--xx" in their name // will not be in the compiled jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 92f06b5..daee2e0 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.4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 5a79443d3f7ff7604b37851403e216f1d5246605 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Wed, 24 Jun 2026 08:12:54 +0800 Subject: [PATCH 2/9] 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..7900fae --- /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.6.0-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: 'nihss-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..e4b702a --- /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.7.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.7.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.6.0-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..0f54e05 --- /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.6.0-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.6.0-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 246548415498396f25a4c285fbaa97fa1b01153a Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Wed, 24 Jun 2026 08:41:25 +0800 Subject: [PATCH 3/9] xnat-8763: switch buildscript classpath from xnat-data-models to xnat-web xnat-data-models was merged into xnat-web in monorepo 1.10 and no longer has a backing module in monorepo source. Switch to xnat-web. --- build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 421b9c5..5ba60e2 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,9 @@ buildscript { } dependencies { // xnat-data-builder needs core XNAT schemas (xnat.xsd) on its classpath to resolve - // xs:import in plugin-local schemas (nihss/nihss.xsd) - classpath "org.nrg.xnat:xnat-data-models:${vXnat}" + // xs:import in plugin-local schemas (nihss/nihss.xsd). xnat-web bundles the core schemas + // in monorepo 1.10+ (xnat-data-models artifact is unmaintained on JFrog). + classpath "org.nrg.xnat:web:${vXnat}" } } From ac5053988273af6dff0cd159bd1aa6d285888e6e Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Wed, 24 Jun 2026 08:44:52 +0800 Subject: [PATCH 4/9] xnat-8763: mark xnat-web buildscript classpath as transitive=false --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5ba60e2..4ea6dd5 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { // xnat-data-builder needs core XNAT schemas (xnat.xsd) on its classpath to resolve // xs:import in plugin-local schemas (nihss/nihss.xsd). xnat-web bundles the core schemas // in monorepo 1.10+ (xnat-data-models artifact is unmaintained on JFrog). - classpath "org.nrg.xnat:web:${vXnat}" + classpath("org.nrg.xnat:web:${vXnat}") { transitive = false } } } From f0b50bb4655fd0daec554732e163bcac3c9616d7 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Wed, 24 Jun 2026 23:10:56 +0800 Subject: [PATCH 5/9] xnat-8763: remove buildscript classpath xnat-web workaround Monorepo xnat-data-builder (PR #11 commit 9aa48e633) now provides xnat-web at runtime. --- build.gradle | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/build.gradle b/build.gradle index 4ea6dd5..4581b35 100644 --- a/build.gradle +++ b/build.gradle @@ -11,18 +11,6 @@ buildscript { ext { vXnat = "1.10.1-SNAPSHOT" } - repositories { - mavenLocal() - maven { url "https://nrgxnat.jfrog.io/nrgxnat/libs-release" } - maven { url "https://nrgxnat.jfrog.io/nrgxnat/libs-snapshot" } - mavenCentral() - } - dependencies { - // xnat-data-builder needs core XNAT schemas (xnat.xsd) on its classpath to resolve - // xs:import in plugin-local schemas (nihss/nihss.xsd). xnat-web bundles the core schemas - // in monorepo 1.10+ (xnat-data-models artifact is unmaintained on JFrog). - classpath("org.nrg.xnat:web:${vXnat}") { transitive = false } - } } plugins { From 722405b9e14aaa3ba99b5df9bf442b15c9fa0c36 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Thu, 25 Jun 2026 00:03:33 +0800 Subject: [PATCH 6/9] 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 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4581b35..d22e0ed 100644 --- a/build.gradle +++ b/build.gradle @@ -271,9 +271,17 @@ publishing { repositories { maven { - url project.version.endsWith("-SNAPSHOT") ? "https://nrgxnat.jfrog.io/nrgxnat/libs-snapshot-local" : "https://nrgxnat.jfrog.io/nrgxnat/libs-release-local" - // The value for name must match in ~/.m2/settings.xml 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 { + username = (project.findProperty("artifactoryUser") ?: System.getenv("ARTIFACTORY_USER")) ?: "" + password = (project.findProperty("artifactoryToken") ?: System.getenv("ARTIFACTORY_TOKEN")) ?: "" + } } } } From 4c011833ec7be6710cca2febff9f4ecd1717b60c Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Thu, 25 Jun 2026 22:02:07 +0800 Subject: [PATCH 7/9] 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 | 148 ++-------------------- 2 files changed, 21 insertions(+), 299 deletions(-) diff --git a/.github/workflows/release-cut-rc.yml b/.github/workflows/release-cut-rc.yml index e4b702a..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.7.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.7.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.6.0-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 0f54e05..09416fb 100644 --- a/.github/workflows/release-promote.yml +++ b/.github/workflows/release-promote.yml @@ -1,17 +1,9 @@ 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.6.0-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: @@ -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 322396f4758caed96653864000e6556c2c33de98 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Fri, 26 Jun 2026 23:23:05 +0800 Subject: [PATCH 8/9] 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 7900fae..7c27d0e 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: From 4427d2f7395e0783529dcb49efcd1961e8178a78 Mon Sep 17 00:00:00 2001 From: Bin Zhang Date: Mon, 29 Jun 2026 00:01:30 +0800 Subject: [PATCH 9/9] xnat-8763: remove vestigial net.linguica.maven-settings plugin --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index d22e0ed..1f0cf8d 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,6 @@ plugins { 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 "net.linguica.maven-settings" version "0.5" id "org.nrg.xnat.build.xnat-data-builder" version "${vXnat}" }