This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 5ccc089d9eec chore(ci): Fix parent POM dependency change detection
(#22022)
5ccc089d9eec is described below
commit 5ccc089d9eeceb23f500a1aad3d80ca35472aca9
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Mar 16 11:38:31 2026 +0100
chore(ci): Fix parent POM dependency change detection (#22022)
* chore(ci): Fix parent POM dependency change detection
The detect-dependencies action was not finding affected modules because
it searched for ${property-name} references in module pom.xml files.
This never matches because modules inherit managed versions from the
parent without referencing the property directly.
New approach:
1. Detect changed properties in parent/pom.xml diff
2. Map each property to the artifact(s) it versions in dependencyManagement
3. Find modules that depend on those artifacts by searching for artifactId
4. Run tests for affected modules
Also adds:
- PR comment posting with test results summary
- Step summary output
- Proper skip-mvnd-install input in action.yaml
- Run only on non-experimental matrix (skip JDK 25)
- Remove overly restrictive version|dependency|artifact filter
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore(ci): Use Maveniverse Toolbox for accurate dependency detection
Instead of grepping pom.xml files for artifactId references (which
misses transitive dependencies), use toolbox:tree-find to scan the
full dependency tree of every module in the reactor.
This accurately detects modules affected by BOM version bumps (e.g.,
netty-bom -> all modules using any io.netty artifact transitively).
Key changes:
- Extract groupId:artifactId from parent pom (not just artifactId)
- Detect BOM imports and search by groupId wildcard
- Use awk to parse toolbox output (mvnd strips module prefixes
when captured to variables)
- Add coverage to exclusion list
Tested locally: jaxb-impl-version correctly finds camel-parquet-avro
(transitive via hadoop-common -> jersey-json -> jaxb-impl).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.github/actions/detect-dependencies/action.yaml | 50 ++++
.github/actions/detect-dependencies/detect-test.sh | 289 ++++++++++++++++++---
.github/workflows/pr-build-main.yml | 2 +
3 files changed, 299 insertions(+), 42 deletions(-)
diff --git a/.github/actions/detect-dependencies/action.yaml
b/.github/actions/detect-dependencies/action.yaml
index 3db5cf5bcc70..92745a7c3514 100644
--- a/.github/actions/detect-dependencies/action.yaml
+++ b/.github/actions/detect-dependencies/action.yaml
@@ -25,6 +25,10 @@ inputs:
description: 'The base branch to compare against (defaults to
github.base_ref)'
required: false
default: ''
+ skip-mvnd-install:
+ description: 'Skip mvnd installation (use if already installed)'
+ required: false
+ default: 'false'
runs:
using: "composite"
steps:
@@ -37,3 +41,49 @@ runs:
GITHUB_TOKEN: ${{ inputs.github-token }}
shell: bash
run: ${{ github.action_path }}/detect-test.sh ${{ inputs.base-ref ||
github.base_ref }} ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd
+ - name: Post dependency change comment
+ if: always()
+ uses: actions/github-script@v8
+ with:
+ github-token: ${{ inputs.github-token }}
+ script: |
+ const fs = require('fs');
+ const commentFile = 'detect-dependencies-comment.md';
+ if (!fs.existsSync(commentFile)) return;
+ const body = fs.readFileSync(commentFile, 'utf8').trim();
+ if (!body) return;
+
+ const prNumber = context.issue.number;
+ if (!prNumber) {
+ core.warning('Could not determine PR number, skipping dependency
comment');
+ return;
+ }
+
+ const marker = '<!-- ci-parent-pom-deps -->';
+
+ try {
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ });
+ const existing = comments.find(c => c.body &&
c.body.includes(marker));
+
+ if (existing) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body: body,
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ body: body,
+ });
+ }
+ } catch (error) {
+ core.warning(`Failed to post dependency change comment:
${error.message}`);
+ }
diff --git a/.github/actions/detect-dependencies/detect-test.sh
b/.github/actions/detect-dependencies/detect-test.sh
index dcdb3a9e218f..296d942b29b0 100755
--- a/.github/actions/detect-dependencies/detect-test.sh
+++ b/.github/actions/detect-dependencies/detect-test.sh
@@ -15,82 +15,287 @@
# limitations under the License.
#
+# Detects property changes in parent/pom.xml, maps them to the managed
+# artifacts that use those properties, then uses Maveniverse Toolbox to
+# find which modules depend on those artifacts (including transitive
+# dependencies) and runs their tests.
+#
+# Approach:
+# 1. Diff parent/pom.xml to find changed property names
+# 2. Parse parent/pom.xml to map property -> groupId:artifactId
+# (detecting BOM imports vs regular dependencies)
+# 3. Use toolbox:tree-find to find all modules depending on those
+# artifacts (direct + transitive)
+# 4. Run tests for affected modules
+
set -euo pipefail
+MAX_MODULES=50
+TOOLBOX_PLUGIN="eu.maveniverse.maven.plugins:toolbox"
+
+# Detect which properties changed in parent/pom.xml compared to the base
branch.
+# Returns one property name per line.
detect_changed_properties() {
local base_branch="$1"
git diff "${base_branch}" -- parent/pom.xml | \
- grep -E '^[+-]\s*<[^>]+>[^<]*</[^>]+>' | \
- grep -vE '^\+\+\+|---' | \
- grep -E 'version|dependency|artifact' | \
- sed -E 's/^[+-]\s*<([^>]+)>.*/\1/' | \
+ grep -E '^[+-][[:space:]]*<[^>]+>[^<]*</[^>]+>' | \
+ grep -vE '^\+\+\+|^---' | \
+ sed -E 's/^[+-][[:space:]]*<([^>]+)>.*/\1/' | \
sort -u || true
}
-find_affected_modules() {
- local property_name="$1"
- local mavenBinary=${2}
- local affected=()
-
- while IFS= read -r pom; do
- # skip any target that may have been built previously
- if [[ "$pom" != */target/* ]]; then
- # only consider certain modules, nothing else
- if [[ "$pom" == */catalog/* ]] || \
- [[ "$pom" == */components/* ]] || \
- [[ "$pom" == */core/* ]] || \
- ([[ "$pom" == */dsl/* ]] && [[ "$pom" != */dsl/camel-jbang* ]]); then
- if grep -q "\${${property_name}}" "$pom"; then
- affected+=("$pom")
- fi
- fi
- fi
- done < <(find . -name "pom.xml")
+# Given a property name, find which groupId:artifactId pairs in
+# parent/pom.xml use it as their <version>.
+# Also detects if the artifact is a BOM import (<type>pom</type> +
+# <scope>import</scope>), in which case it outputs "bom:groupId"
+# so the caller can search by groupId wildcard.
+# Returns one entry per line: either "groupId:artifactId" or "bom:groupId".
+find_gav_for_property() {
+ local property="$1"
+ local parent_pom="parent/pom.xml"
+
+ local matches
+ matches=$(grep -n "<version>\${${property}}</version>" "$parent_pom"
2>/dev/null || true)
- affected_transformed=""
+ if [ -z "$matches" ]; then
+ return
+ fi
+
+ echo "$matches" | while IFS=: read -r line_num _; do
+ local block
+ block=$(sed -n "1,${line_num}p" "$parent_pom")
+ local artifactId
+ artifactId=$(echo "$block" | grep '<artifactId>' | tail -1 | sed
's/.*<artifactId>\([^<]*\)<\/artifactId>.*/\1/')
+ local groupId
+ groupId=$(echo "$block" | grep '<groupId>' | tail -1 | sed
's/.*<groupId>\([^<]*\)<\/groupId>.*/\1/')
- for pom in "${affected[@]}"; do
- if [[ -f "$pom" ]]; then
- artifactId=$($mavenBinary -f "$pom" help:evaluate
-Dexpression=project.artifactId -q --raw-streams -DforceStdout)
- if [ ! -z "$artifactId" ]; then
- affected_transformed+=":$artifactId,"
- fi
+ # Check if this is a BOM import by looking at lines after the version
+ local after_version
+ after_version=$(sed -n "$((line_num+1)),$((line_num+3))p" "$parent_pom")
+ if echo "$after_version" | grep -q '<type>pom</type>' && echo
"$after_version" | grep -q '<scope>import</scope>'; then
+ echo "bom:${groupId}"
+ else
+ echo "${groupId}:${artifactId}"
fi
- done
+ done | sort -u
+}
- echo "$affected_transformed"
+# Use Maveniverse Toolbox tree-find to discover all modules that depend
+# on a given artifact (including transitive dependencies).
+# Returns one module artifactId per line.
+find_modules_with_toolbox() {
+ local mavenBinary="$1"
+ local matcher_spec="$2"
+ local search_pattern="$3"
+
+ local output
+ output=$($mavenBinary -B ${TOOLBOX_PLUGIN}:tree-find \
+ -DartifactMatcherSpec="${matcher_spec}" 2>&1 || true)
+
+ if echo "$output" | grep -q 'BUILD FAILURE'; then
+ echo " WARNING: toolbox tree-find failed, skipping" >&2
+ return
+ fi
+
+ # Parse output: track current module from "Paths found in project" lines,
+ # then when a dependency match is found, output that module's artifactId.
+ # Note: mvnd strips [module] prefixes when output is captured to a
+ # variable, so we track the current module from "Paths found" headers.
+ echo "$output" | awk -v pattern="$search_pattern" '
+ /Paths found in project/ {
+ split($0, a, "project ")
+ split(a[2], b, ":")
+ current = b[2]
+ }
+ index($0, pattern) && /->/ {
+ print current
+ }
+ ' | sort -u
}
main() {
echo "Using MVND_OPTS=$MVND_OPTS"
local base_branch=${1}
local mavenBinary=${2}
- local
exclusionList="!:camel-allcomponents,!:dummy-component,!:camel-catalog,!:camel-catalog-console,!:camel-catalog-lucene,!:camel-catalog-maven,!:camel-catalog-suggest,!:camel-route-parser,!:camel-csimple-maven-plugin,!:camel-report-maven-plugin,!:camel-endpointdsl,!:camel-componentdsl,!:camel-endpointdsl-support,!:camel-yaml-dsl,!:camel-kamelet-main,!:camel-yaml-dsl-deserializers,!:camel-yaml-dsl-maven-plugin,!:camel-jbang-core,!:camel-jbang-main,!:camel-jbang-plugin-generate,!:came
[...]
+ local log="detect-dependencies.log"
+ local
exclusionList="!:camel-allcomponents,!:dummy-component,!:camel-catalog,!:camel-catalog-console,!:camel-catalog-lucene,!:camel-catalog-maven,!:camel-catalog-suggest,!:camel-route-parser,!:camel-csimple-maven-plugin,!:camel-report-maven-plugin,!:camel-endpointdsl,!:camel-componentdsl,!:camel-endpointdsl-support,!:camel-yaml-dsl,!:camel-kamelet-main,!:camel-yaml-dsl-deserializers,!:camel-yaml-dsl-maven-plugin,!:camel-jbang-core,!:camel-jbang-main,!:camel-jbang-plugin-generate,!:came
[...]
+
+ git fetch origin "$base_branch":"$base_branch" 2>/dev/null || true
- git fetch origin $base_branch:$base_branch
+ # Check if parent/pom.xml was actually changed
+ if ! git diff --name-only "${base_branch}" -- parent/pom.xml | grep -q .;
then
+ echo "parent/pom.xml not changed, nothing to do"
+ exit 0
+ fi
+ local changed_props
changed_props=$(detect_changed_properties "$base_branch")
if [ -z "$changed_props" ]; then
- echo "โ
No property changes detected."
+ echo "No property changes detected in parent/pom.xml"
exit 0
fi
- modules_affected=""
+ echo "Changed properties in parent/pom.xml:"
+ echo "$changed_props"
+ echo ""
+ # Map properties -> GAV coordinates for toolbox lookup
+ local all_gavs=""
while read -r prop; do
- modules=$(find_affected_modules "$prop" $mavenBinary)
- modules_affected+="$modules"
+ [ -z "$prop" ] && continue
+
+ local gavs
+ gavs=$(find_gav_for_property "$prop")
+ if [ -z "$gavs" ]; then
+ echo " Property '$prop': no managed artifacts found"
+ continue
+ fi
+
+ echo " Property '$prop' manages:"
+ while read -r gav; do
+ [ -z "$gav" ] && continue
+ echo " - $gav"
+ all_gavs="${all_gavs:+${all_gavs}
+}${gav}"
+ done <<< "$gavs"
done <<< "$changed_props"
- if [ -z "$modules_affected" ]; then
- echo "โ
No components affected by property changes detected."
+ if [ -z "$all_gavs" ]; then
+ echo ""
+ echo "No managed artifacts found for changed properties"
exit 0
fi
- echo "๐งช Testing the following modules $modules_affected and its dependents"
- $mavenBinary $MVND_OPTS test -pl "$modules_affected$exclusionList" -amd
+ echo ""
+ echo "Searching for affected modules using Maveniverse Toolbox..."
+
+ # Use toolbox tree-find to find affected modules
+ local all_module_ids=""
+ local seen_modules=""
+
+ # Deduplicate GAVs
+ local unique_gavs
+ unique_gavs=$(echo "$all_gavs" | sort -u)
+
+ while read -r gav; do
+ [ -z "$gav" ] && continue
+
+ local matcher_spec search_pattern
+ if [[ "$gav" == bom:* ]]; then
+ # BOM import: search by groupId wildcard
+ local groupId="${gav#bom:}"
+ matcher_spec="artifact(${groupId}:*)"
+ search_pattern="${groupId}:"
+ echo " Searching for modules using ${groupId}:* (BOM)..."
+ else
+ matcher_spec="artifact(${gav})"
+ search_pattern="${gav}"
+ echo " Searching for modules using ${gav}..."
+ fi
+
+ local modules
+ modules=$(find_modules_with_toolbox "$mavenBinary" "$matcher_spec"
"$search_pattern")
+ if [ -n "$modules" ]; then
+ while read -r mod; do
+ [ -z "$mod" ] && continue
+ # Deduplicate
+ if ! echo "$seen_modules" | grep -qx "$mod"; then
+ seen_modules="${seen_modules:+${seen_modules}
+}${mod}"
+ all_module_ids="${all_module_ids:+${all_module_ids},}:${mod}"
+ fi
+ done <<< "$modules"
+ fi
+ done <<< "$unique_gavs"
+
+ if [ -z "$all_module_ids" ]; then
+ echo ""
+ echo "No modules depend on the changed artifacts"
+ exit 0
+ fi
+
+ # Count modules
+ local module_count
+ module_count=$(echo "$all_module_ids" | tr ',' '\n' | wc -l | tr -d ' ')
+
+ echo ""
+ echo "Found ${module_count} modules affected by parent/pom.xml property
changes:"
+ echo "$all_module_ids" | tr ',' '\n' | while read -r m; do
+ echo " - $m"
+ done
+ echo ""
+
+ if [ "${module_count}" -gt "${MAX_MODULES}" ]; then
+ echo "Too many affected modules (${module_count} > ${MAX_MODULES}),
skipping targeted tests"
+
+ write_comment "$changed_props" "$all_module_ids" "$module_count" "skip"
+ exit 0
+ fi
+
+ echo "Running targeted tests for affected modules..."
+ $mavenBinary -l $log $MVND_OPTS test -pl
"${all_module_ids},${exclusionList}" -amd
+ local ret=$?
+
+ if [ ${ret} -eq 0 ]; then
+ write_comment "$changed_props" "$all_module_ids" "$module_count" "pass"
+ else
+ write_comment "$changed_props" "$all_module_ids" "$module_count" "fail"
+ fi
+
+ # Write step summary
+ if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
+ echo "### Parent POM dependency change tests" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "Changed properties: $(echo "$changed_props" | tr '\n' ', ' | sed
's/,$//')" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "$all_module_ids" | tr ',' '\n' | while read -r m; do
+ echo "- \`$m\`" >> "$GITHUB_STEP_SUMMARY"
+ done
+
+ if [ ${ret} -ne 0 ]; then
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "Processing surefire and failsafe reports to create the summary" >>
"$GITHUB_STEP_SUMMARY"
+ echo -e "| Failed Test | Duration | Failure Type |\n| --- | --- | --- |"
>> "$GITHUB_STEP_SUMMARY"
+ find . -path '*target/*-reports*' -iname '*.txt' -exec
.github/actions/incremental-build/parse_errors.sh {} \;
+ fi
+ fi
+
+ exit $ret
+}
+
+write_comment() {
+ local changed_props="$1"
+ local modules="$2"
+ local module_count="$3"
+ local status="$4"
+ local comment_file="detect-dependencies-comment.md"
+
+ echo "<!-- ci-parent-pom-deps -->" > "$comment_file"
+
+ case "$status" in
+ pass)
+ echo ":white_check_mark: **Parent POM dependency changes: targeted tests
passed**" >> "$comment_file"
+ ;;
+ fail)
+ echo ":x: **Parent POM dependency changes: targeted tests failed**" >>
"$comment_file"
+ ;;
+ skip)
+ echo ":information_source: **Parent POM dependency changes detected**
but too many modules affected (${module_count}) to run targeted tests." >>
"$comment_file"
+ ;;
+ esac
+
+ echo "" >> "$comment_file"
+ echo "Changed properties: $(echo "$changed_props" | tr '\n' ', ' | sed
's/,$//')" >> "$comment_file"
+ echo "" >> "$comment_file"
+ echo "<details><summary>Affected modules (${module_count})</summary>" >>
"$comment_file"
+ echo "" >> "$comment_file"
+ echo "$modules" | tr ',' '\n' | while read -r m; do
+ echo "- \`$m\`" >> "$comment_file"
+ done
+ echo "" >> "$comment_file"
+ echo "</details>" >> "$comment_file"
}
main "$@"
diff --git a/.github/workflows/pr-build-main.yml
b/.github/workflows/pr-build-main.yml
index 8d46b8376673..cedc8fceb5f6 100644
--- a/.github/workflows/pr-build-main.yml
+++ b/.github/workflows/pr-build-main.yml
@@ -143,7 +143,9 @@ jobs:
core.warning(`Failed to post CI test summary comment:
${error.message}`);
}
- name: mvn test parent pom dependencies changed
+ if: always() && !matrix.experimental
uses: ./.github/actions/detect-dependencies
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
base-ref: ${{ github.base_ref || 'main' }}
+ skip-mvnd-install: 'true'