Add custom-starter module: a Boot 4 auto-configuration starter with conditions, metadata and runner tests
Two-jar starter (autoconfigure + starter), a demo app, and 17 captured transcripts covering AutoConfiguration.imports, @ConditionalOn*, ordering, optional Jackson 3 integration, configuration metadata and the Boot 4 module split. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
This commit is contained in:
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/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#<custom-starter>#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("<artifactId>spring-boot-configuration-processor</artifactId>","<artifactId>spring-boot-configuration-processor</artifactId>\n <version>${project.parent.version}</version>")
|
||||
open(p,'w').write(s)
|
||||
PY
|
||||
{
|
||||
echo "# The processor path with <version>\${project.parent.version}</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"
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file under output/.
|
||||
#
|
||||
# ./scripts/run-all.sh
|
||||
#
|
||||
# Needs a JDK 25 and Maven 3.9. Transcripts 01-06, 08-11 and 16 come out of the test suite, which is the point:
|
||||
# the figures in the article are assertions that fail the build if they stop being true.
|
||||
# Transcripts 07 and 12-15 are read from the built jars and from builds that are made to fail on purpose.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "== test suite (transcripts 01-06, 08-11, 16)"
|
||||
mvn -B test
|
||||
|
||||
echo "== jar contents, metadata, Boot 4 locations and deliberate failures (07, 12-15, 17)"
|
||||
./scripts/capture-jar-facts.sh
|
||||
|
||||
echo
|
||||
echo "output:"
|
||||
ls -1 output
|
||||
Reference in New Issue
Block a user