#!/usr/bin/env bash # Facts that a test cannot show, read from the built jars and from deliberately failing builds: # 07 @ConditionalOnClass on a @Bean method versus on a nested class, on classpaths with and without Jackson # 12 what is in the starter jar and the auto-configuration jar, and what the starter pulls in # 13 the generated configuration metadata and auto-configuration metadata, as they sit in the jar # 14 Boot 4's split of spring-boot-autoconfigure, and the Boot 3 import that no longer compiles # 15 the annotation-processor version trap when the module has its own parent pom # 17 which Boot 4.1.1 jar holds each class the starter imports set -euo pipefail cd "$(dirname "$0")/.." mkdir -p output M2=~/.m2/repository/org/springframework/boot AC=masker-spring-boot-autoconfigure JP() { "$@" 2>&1 | grep -v -E '^(Picked up|WARNING)'; } ROOT=$(pwd) clean() { sed "s#$ROOT##g; s#$HOME#~#g"; } echo "== build the jars" mvn -B -q package -DskipTests dependency:tree -Dscope=compile -DoutputFile=target/tree.txt >/dev/null 2>&1 || mvn -B package -DskipTests dependency:tree -Dscope=compile -DoutputFile=target/tree.txt echo "== 07" mvn -B -q -pl "$AC" test-compile dependency:build-classpath -Dmdep.outputFile=/tmp/masker-cp.txt -Dmdep.includeScope=test >/dev/null 2>&1 FULL=$(cat /tmp/masker-cp.txt) NOJACKSON=$(tr ':' '\n' < /tmp/masker-cp.txt | grep -v jackson | paste -sd:) { echo "# @ConditionalOnClass(JsonMapper.class) on a @Bean method, versus on a nested class, with and without Jackson" echo echo "--- condition on the @Bean method, classpath without any Jackson jar ---" JP java -cp "$AC/target/test-classes:$AC/target/classes:$NOJACKSON" com.ankurm.traps.MethodLevelMain method | grep -v -E '^[0-9:.]+ \[main\]' echo echo "--- condition on a nested class, classpath without any Jackson jar ---" JP java -cp "$AC/target/test-classes:$AC/target/classes:$NOJACKSON" com.ankurm.traps.MethodLevelMain nested | grep -v -E '^[0-9:.]+ \[main\]' echo echo "--- condition on the @Bean method, classpath with Jackson ---" JP java -cp "$AC/target/test-classes:$AC/target/classes:$FULL" com.ankurm.traps.MethodLevelMain method | grep -v -E '^[0-9:.]+ \[main\]' } | clean > output/07-conditional-on-class-on-a-bean-method.txt echo "== 12" { echo "# What is inside the two jars, and what the starter drags in" echo echo "--- masker-spring-boot-starter-1.0.0.jar ---" unzip -l masker-spring-boot-starter/target/masker-spring-boot-starter-1.0.0.jar | awk 'NR>3 && $4!="" {print $4}' | grep -v '/$' echo echo "--- masker-spring-boot-autoconfigure-1.0.0.jar (META-INF and the classes) ---" unzip -l $AC/target/$AC-1.0.0.jar | awk 'NR>3 && $4!="" {print $4}' | grep -v '/$' | grep -v -E 'pom\.(xml|properties)|MANIFEST' echo echo "--- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ---" unzip -p $AC/target/$AC-1.0.0.jar META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports echo echo "--- dependency tree of masker-spring-boot-starter (Jackson is not in it) ---" cat masker-spring-boot-starter/target/tree.txt echo echo "--- dependency tree of masker-spring-boot-autoconfigure (Jackson is there, marked optional in the pom) ---" cat $AC/target/tree.txt } | clean > output/12-jar-contents-and-dependency-trees.txt echo "== 13" { echo "# Metadata the two annotation processors write into the auto-configuration jar" echo echo "--- META-INF/spring-configuration-metadata.json, written by spring-boot-configuration-processor ---" echo unzip -p $AC/target/$AC-1.0.0.jar META-INF/spring-configuration-metadata.json echo echo echo "--- META-INF/additional-spring-configuration-metadata.json, hand-written and kept as is ---" unzip -p $AC/target/$AC-1.0.0.jar META-INF/additional-spring-configuration-metadata.json echo echo echo "--- META-INF/spring-autoconfigure-metadata.properties, written by spring-boot-autoconfigure-processor ---" unzip -p $AC/target/$AC-1.0.0.jar META-INF/spring-autoconfigure-metadata.properties } > output/13-configuration-metadata-in-the-jar.txt echo "== 14" BOOT_AC=$M2/spring-boot-autoconfigure/4.1.1/spring-boot-autoconfigure-4.1.1.jar BOOT_JACKSON=$M2/spring-boot-jackson/4.1.1/spring-boot-jackson-4.1.1.jar { echo "# Boot 4.1.1: where the auto-configuration classes a starter refers to now live" echo echo "--- entries in spring-boot-autoconfigure's own AutoConfiguration.imports ---" unzip -p "$BOOT_AC" META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | grep -c . echo echo "--- JacksonAutoConfiguration: which jar, which package ---" echo "spring-boot-autoconfigure-4.1.1.jar, classes named JacksonAutoConfiguration: $(unzip -l "$BOOT_AC" | grep -c 'JacksonAutoConfiguration' || true)" unzip -l "$BOOT_JACKSON" | awk '/JacksonAutoConfiguration.class/ {print "spring-boot-jackson-4.1.1.jar: " $4}' echo X=$(mktemp -d) unzip -q -o "$BOOT_AC" 'org/springframework/boot/autoconfigure/AutoConfiguration.class' \ 'org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.class' -d "$X" echo "--- what @AutoConfiguration is made of (javap -v, class-level annotations) ---" JP javap -v -cp "$X" org.springframework.boot.autoconfigure.AutoConfiguration \ | sed -n '/^RuntimeVisibleAnnotations/,$p' | grep -E '^ (org\.springframework)|proxyBeanMethods' | sed 's/^ *//' echo echo "--- AutoConfigurationExcludeFilter, the filter @SpringBootApplication's scan applies: the classes it tests for (javap -c) ---" JP javap -p -c -cp "$X" org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter \ | grep -E 'ldc|ImportCandidates.load' | sed -E 's/^ *[0-9]+: //; s/ +/ /g' rm -rf "$X" echo echo "--- the Boot 3 import, compiled against the 4.1.1 jars ---" mkdir -p /tmp/masker-broken JP javac -d /tmp/masker-broken -cp "$BOOT_AC:$BOOT_JACKSON" $AC/src/broken/OldJacksonImport.java | clean || true } > output/14-boot4-autoconfiguration-locations.txt echo "== 17" { echo "# Which 4.1.1 jar holds each Boot class this starter and its tests import (searched in every Boot jar the build resolved)" echo for c in AutoConfiguration AutoConfigureAfter AutoConfigurations ConditionalOnClass ConditionalOnBean ConditionalOnMissingBean ConditionalOnProperty \ ConditionEvaluationReportLoggingListener ImportCandidates Configurations JacksonAutoConfiguration ApplicationContextRunner FilteredClassLoader; do for j in "$M2"/spring-boot*/4.1.1/spring-boot*-4.1.1.jar; do unzip -l "$j" | awk -v c="/$c.class" -v j="$(basename "$j")" 'index($4, c) && $4 !~ /\$/ {printf "%-46s %s\n", j, $4}' done done } > output/17-where-the-classes-live.txt echo "== 15" TMP=$(mktemp -d) cp -r pom.xml "$AC" "$TMP/" mkdir -p "$TMP/masker-spring-boot-starter" "$TMP/masker-demo" cp masker-spring-boot-starter/pom.xml "$TMP/masker-spring-boot-starter/"; cp masker-demo/pom.xml "$TMP/masker-demo/" rm -rf "$TMP/$AC/target" python3 - "$TMP/$AC/pom.xml" <<'PY' import sys p=sys.argv[1] s=open(p).read() s=s.replace("spring-boot-configuration-processor","spring-boot-configuration-processor\n ${project.parent.version}") open(p,'w').write(s) PY { echo "# The processor path with \${project.parent.version}, when the module's parent is not Spring Boot's" echo (cd "$TMP" && mvn -B -U -pl $AC compile 2>&1 | grep -E 'ERROR.*(annotationProcessorPath|Could not find artifact)' | head -1 | sed -E 's/^\[ERROR\] //; s/ -> \[Help 1\]//') } > output/15-annotation-processor-version-trap.txt rm -rf "$TMP" echo "done"