From ee5c43aa9b749bfd0f2ee2e77c3d10cc5dad6c8d Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 6 Nov 2025 08:30:04 -0600 Subject: [PATCH 1/3] WIP: Support releasing within a multi-module repo THIS IS NOT YET FUNCTIONAL. It is a first-cut AI-supported modification to release-version.sh that implements an approach we think is ultimately going to work, but still needs refinement and testing. Bugs: - the version prompt logic happens before the multi-module detection - reverting the snapshot-to-release prep commit is insufficient: we also need to bump the released component version snapshot to next dev version everywhere it is a dependency in other modules The workflow is to release only one module per invocation. The steps are: 1. comment out all modules other than the one being released 2. roll back the target module's SNAPSHOT deps to newest release versions 3. run mvn release:prepare with the -pl flag pointing at target module - this excludes the aggregator itself from the reactor, to avoid future useless scijava-aggregator POMs on Central 4. squash the release + prep + bump commits into one on the main branch 5. continue business as usual: push main, rewrite release tag and push The ci-build.sh probably still needs modification to use -pl as well. Open questions: - is -pl with release:prepare really needed? or only at perform step? - do we squash the release prep commits into a single release commit? - or do we leave them as a sequence on the orphaned tag? --- release-version.sh | 154 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 8 deletions(-) diff --git a/release-version.sh b/release-version.sh index 8f95226..43a0fce 100755 --- a/release-version.sh +++ b/release-version.sh @@ -30,6 +30,26 @@ no_changes_pending() { git diff-index --cached --quiet --ignore-submodules HEAD -- } +# Get list of modules from aggregator POM +get_modules() { + grep -oP '\K[^<]+' pom.xml 2>/dev/null || true +} + +# Check if current directory is a multi-module aggregator +is_aggregator() { + test -f pom.xml && grep -q '' pom.xml +} + +# Resolve snapshot dependencies to latest releases (TODO for later) +resolve_snapshot_deps() { + module_pom=$1 + debug "Resolving snapshot dependencies in $module_pom" + echo "TODO: Implement automatic snapshot dependency resolution" + echo "For now, please manually update any *.version properties to releases" + echo "Press enter when ready to continue..." + read +} + # -- Constants and settings -- SCIJAVA_BASE_REPOSITORY=-DaltDeploymentRepository=scijava.releases::default::dav:https://maven.scijava.org/content/repositories @@ -119,8 +139,16 @@ Options include: debug "Extracting project details" echoArg='${project.version}:${license.licenseName}:${project.parent.groupId}:${project.parent.artifactId}:${project.parent.version}' -projectDetails=$(mvn -B -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) -test $? -eq 0 || projectDetails=$(mvn -B -U -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) + +if test "$IS_AGGREGATOR" +then + projectDetails=$(mvn -B -N -f "$MODULE_NAME/pom.xml" -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) + test $? -eq 0 || projectDetails=$(mvn -B -U -N -f "$MODULE_NAME/pom.xml" -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) +else + projectDetails=$(mvn -B -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) + test $? -eq 0 || projectDetails=$(mvn -B -U -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) +fi + test $? -eq 0 || die "Could not extract version from pom.xml. Error follows:\n$projectDetails" printf '%s' "$projectDetails\n" | grep -Fqv '[ERROR]' || die "Error extracting version from pom.xml. Error follows:\n$projectDetails" @@ -213,6 +241,30 @@ If you are certain you want to release from this branch, try again with --skip-branch-check flag." } +# -- Detect multi-module structure -- +debug "Detecting project structure" + +IS_AGGREGATOR= +MODULE_NAME= + +if is_aggregator +then + IS_AGGREGATOR=t + debug "Multi-module aggregator detected" + + # Prompt for which module to release + modules=$(get_modules) + echo "Available modules:" + echo "$modules" | sed 's/^/ /' + printf 'Which module to release?: ' + read MODULE_NAME + test "$MODULE_NAME" || die 'Module name is required for aggregator releases' + + # Validate module exists + test -d "$MODULE_NAME" || die "Module directory '$MODULE_NAME' not found" + echo "$modules" | grep -qx "$MODULE_NAME" || die "Module '$MODULE_NAME' not found in aggregator POM" +fi + # If REMOTE is unset, use branch's upstream remote by default. REMOTE="${REMOTE:-$remote}" @@ -314,6 +366,37 @@ then -m 'The Discourse software updated the tags path from /tags/ to /tag/.' fi +# For multi-module aggregators, prepare the release environment. +if test "$IS_AGGREGATOR" +then + debug "Preparing multi-module release for $MODULE_NAME" + + # Filter aggregator POM to only include this module + $DRY_RUN awk -v module="$MODULE_NAME" ' + //,/<\/modules>/ { + if ($0 ~ //) { + if ($0 ~ module) { + print + } else { + gsub(//, "") + print + } + next + } + } + { print } + ' pom.xml > pom.xml.filtered + $DRY_RUN mv pom.xml.filtered pom.xml + + # Resolve snapshot dependencies + resolve_snapshot_deps "$MODULE_NAME/pom.xml" + + # Commit these preparation changes (Commit O) + $DRY_RUN git add pom.xml "$MODULE_NAME/pom.xml" + $DRY_RUN git commit -m "Prepare $MODULE_NAME for release: filter aggregator and resolve snapshot dependencies" +fi + # Ensure license headers are up-to-date. test "$SKIP_LICENSE_UPDATE" -o -z "$licenseName" -o "$licenseName" = "N/A" || { debug "Ensuring that license headers are up-to-date" @@ -330,10 +413,24 @@ exclude them by setting license.excludes in your POM; e.g.: Alternately, try again with the --skip-license-update flag.' } +# -- Set up multi-module arguments if needed -- +MAVEN_PL_ARGS= +if test "$IS_AGGREGATOR" +then + debug "Configuring release:prepare for multi-module" + MAVEN_PL_ARGS="-pl $MODULE_NAME -DautoVersionSubmodules=false" + + # Override tag name to include module name + if test -z "$TAG" + then + TAG="-Dtag=${MODULE_NAME}-${VERSION}" + fi +fi + # Prepare new release without pushing (requires the release plugin >= 2.1). debug "Preparing new release" $DRY_RUN mvn $BATCH_MODE release:prepare -DpushChanges=false -Dresume=false $TAG \ - $PROFILE $DEV_VERSION -DreleaseVersion="$VERSION" \ + $PROFILE $DEV_VERSION -DreleaseVersion="$VERSION" $MAVEN_PL_ARGS \ "-Darguments=-Dgpg.skip=true ${EXTRA_ARGS# }" || die 'The release preparation step failed -- look above for errors and fix them. Use "mvn javadoc:javadoc | grep error" to check for javadoc syntax errors.' @@ -346,10 +443,40 @@ then "$(git show -s --format=%s HEAD)" || die "maven-release-plugin's commits are unexpectedly missing!" fi -$DRY_RUN git reset --soft HEAD^^ && -if ! git diff-index --cached --quiet --ignore-submodules HEAD -- + +if test "$IS_AGGREGATOR" then - $DRY_RUN git commit -s -m "Bump to next development cycle" + # For multi-module: revert the prep commit (O), then squash O+A+B+R + debug "Reverting preparation commit and squashing" + + # Current state: ...prev → O → A → B + # Revert O to produce R + $DRY_RUN git revert --no-edit HEAD~2 && + + # Now: ...prev → O → A → B → R + # Squash the last 4 commits + $DRY_RUN git reset --soft HEAD~4 && + + # Net changes staged: only module version bump (O+A+B+R = just the version change) + if ! git diff-index --cached --quiet --ignore-submodules HEAD -- + then + if test -z "$DRY_RUN" + then + next_version=$(grep '' "$MODULE_NAME/pom.xml" | head -1 | sed 's/.*\(.*\)<\/version>.*/\1/') + else + next_version="" + fi + $DRY_RUN git commit -s -m "Bump to next development cycle + +$MODULE_NAME: $VERSION → $next_version" + fi +else + # Original single-module logic: squash A+B + $DRY_RUN git reset --soft HEAD^^ && + if ! git diff-index --cached --quiet --ignore-submodules HEAD -- + then + $DRY_RUN git commit -s -m "Bump to next development cycle" + fi fi && # Extract the name of the new tag. @@ -361,7 +488,7 @@ else tag="" fi && -# Rewrite the tag to include release.properties. +# Rewrite the tag to include release.properties (and filtered aggregator for multi-module). debug "Rewriting tag to include release.properties" test -n "$tag" && # HACK: SciJava projects use SSH (git@github.com:...) for developerConnection. @@ -373,7 +500,18 @@ test -n "$tag" && $DRY_RUN sed -i.bak -e 's|^scm.url=scm\\:git\\:git@github.com\\:|scm.url=scm\\:git\\:https\\://github.com/|' release.properties && $DRY_RUN rm release.properties.bak && $DRY_RUN git checkout "$tag" && -$DRY_RUN git add -f release.properties && + +if test "$IS_AGGREGATOR" +then + # For multi-module: get the filtered aggregator from the parent commit (O) + debug "Incorporating filtered aggregator into tag" + $DRY_RUN git checkout HEAD~1 -- pom.xml && + $DRY_RUN git add -f release.properties pom.xml +else + # Original: just add release.properties + $DRY_RUN git add -f release.properties +fi && + $DRY_RUN git commit --amend --no-edit && $DRY_RUN git tag -d "$tag" && $DRY_RUN git tag "$tag" HEAD && From 933ada86d833a30b72dd65a1b1623deb88c0a24d Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 6 Nov 2025 08:49:30 -0600 Subject: [PATCH 2/3] TEMP: First attempt at fixing the two big bugs - detect version of to-be-released component - bump deps of that component to next snapshot --- release-version.sh | 62 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/release-version.sh b/release-version.sh index 43a0fce..c534432 100755 --- a/release-version.sh +++ b/release-version.sh @@ -50,6 +50,29 @@ resolve_snapshot_deps() { read } +# Update all modules that depend on the released module to use the next snapshot version +update_inter_module_deps() { + released_module=$1 + next_snapshot_version=$2 + debug "Updating inter-module dependencies on $released_module to $next_snapshot_version" + + # Property name pattern: scijava-meta -> scijava-meta.version + property_name="${released_module}.version" + + # Find all module POMs that have this property + for module_dir in $(get_modules) + do + module_pom="$module_dir/pom.xml" + if test -f "$module_pom" && grep -q "<${property_name}>" "$module_pom" + then + debug "Updating $property_name in $module_pom to $next_snapshot_version" + sed -i.bak "s|<${property_name}>.*|<${property_name}>${next_snapshot_version}|" "$module_pom" + rm -f "$module_pom.bak" + git add "$module_pom" + fi + done +} + # -- Constants and settings -- SCIJAVA_BASE_REPOSITORY=-DaltDeploymentRepository=scijava.releases::default::dav:https://maven.scijava.org/content/repositories @@ -136,19 +159,12 @@ Options include: " # -- Extract project details -- +# Note: For multi-module projects, this will be re-extracted after module selection debug "Extracting project details" echoArg='${project.version}:${license.licenseName}:${project.parent.groupId}:${project.parent.artifactId}:${project.parent.version}' - -if test "$IS_AGGREGATOR" -then - projectDetails=$(mvn -B -N -f "$MODULE_NAME/pom.xml" -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) - test $? -eq 0 || projectDetails=$(mvn -B -U -N -f "$MODULE_NAME/pom.xml" -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) -else - projectDetails=$(mvn -B -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) - test $? -eq 0 || projectDetails=$(mvn -B -U -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) -fi - +projectDetails=$(mvn -B -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) +test $? -eq 0 || projectDetails=$(mvn -B -U -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) test $? -eq 0 || die "Could not extract version from pom.xml. Error follows:\n$projectDetails" printf '%s' "$projectDetails\n" | grep -Fqv '[ERROR]' || die "Error extracting version from pom.xml. Error follows:\n$projectDetails" @@ -263,6 +279,18 @@ then # Validate module exists test -d "$MODULE_NAME" || die "Module directory '$MODULE_NAME' not found" echo "$modules" | grep -qx "$MODULE_NAME" || die "Module '$MODULE_NAME' not found in aggregator POM" + + # Re-extract project details from the selected module + debug "Extracting module project details" + projectDetails=$(mvn -B -N -f "$MODULE_NAME/pom.xml" -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) + test $? -eq 0 || projectDetails=$(mvn -B -U -N -f "$MODULE_NAME/pom.xml" -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) + test $? -eq 0 || die "Could not extract version from $MODULE_NAME/pom.xml. Error follows:\n$projectDetails" + projectDetails=$(printf '%s' "$projectDetails" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g") + projectDetails=$(printf '%s' "$projectDetails" | tr -d '\n') + currentVersion=${projectDetails%%:*} + projectDetails=${projectDetails#*:} + licenseName=${projectDetails%%:*} + parentGAV=${projectDetails#*:} fi # If REMOTE is unset, use branch's upstream remote by default. @@ -453,11 +481,19 @@ then # Revert O to produce R $DRY_RUN git revert --no-edit HEAD~2 && - # Now: ...prev → O → A → B → R - # Squash the last 4 commits + # Update inter-module dependencies to use the next snapshot version + # (The revert brought back old snapshot deps, but other modules should use the next snapshot) + if test -z "$DRY_RUN" + then + next_version=$(grep '' "$MODULE_NAME/pom.xml" | head -1 | sed 's/.*\(.*\)<\/version>.*/\1/') + update_inter_module_deps "$MODULE_NAME" "$next_version" + fi && + + # Now: ...prev → O → A → B → R (+ inter-module dep updates) + # Squash the last 4 commits (plus any inter-module dep updates) $DRY_RUN git reset --soft HEAD~4 && - # Net changes staged: only module version bump (O+A+B+R = just the version change) + # Net changes staged: module version bump + inter-module deps updated to next snapshot if ! git diff-index --cached --quiet --ignore-submodules HEAD -- then if test -z "$DRY_RUN" From 63ed3e43760302dd5efd1806167297a864caba17 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 6 Nov 2025 08:53:17 -0600 Subject: [PATCH 3/3] TEMP: include needed -pl flag in release.properties --- release-version.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/release-version.sh b/release-version.sh index c534432..2e15dcf 100755 --- a/release-version.sh +++ b/release-version.sh @@ -534,6 +534,24 @@ test -n "$tag" && # the release.properties file to use the public (https://github.com/...) URL. # This is OK, since release:perform does not need write access to the repo. $DRY_RUN sed -i.bak -e 's|^scm.url=scm\\:git\\:git@github.com\\:|scm.url=scm\\:git\\:https\\://github.com/|' release.properties && + +# For multi-module: add -pl to arguments so release:perform only builds the released module +if test "$IS_AGGREGATOR" +then + debug "Adding -pl $MODULE_NAME to release.properties arguments" + # Extract existing arguments, append -pl, write back + existing_args=$(sed -n 's/^exec.additionalArguments=//p' release.properties) + if test -n "$existing_args" + then + # Append to existing args + $DRY_RUN sed -i.bak2 "s|^exec.additionalArguments=.*|exec.additionalArguments=$existing_args -pl $MODULE_NAME|" release.properties + else + # Add new line + echo "exec.additionalArguments=-pl $MODULE_NAME" >> release.properties + fi + test -f release.properties.bak2 && $DRY_RUN rm release.properties.bak2 +fi && + $DRY_RUN rm release.properties.bak && $DRY_RUN git checkout "$tag" &&