From 347d6d5e1f5051c955b385436b320a2f9fbf5d54 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 14:54:40 +0000 Subject: [PATCH 1/2] Publish a Java 11 main artifact and a jdk21 classifier Mirrors the split cfparser now publishes. One source tree, two jars: cflint-VERSION.jar class file 55 (Java 11) -- 11, 17, 21, 25 cflint-VERSION-all.jar class file 55 (Java 11) -- shaded, same cflint-VERSION-jdk21.jar class file 65 (Java 21) -- 21+ The default is the one that loads everywhere; the classifier is for consumers who specifically want a 21-targeted build. Both variants compile against the Java 11 cfparser artifact -- a pom has one dependency set, and a Java 11 CFLint linked against a Java 21 parser would fail at runtime on 11. maven.compiler.source/target become release, which checks the API surface rather than only the language level, so a Java 12+ method call fails the build instead of producing a jar that NoSuchMethodErrors on 11. The resource copy into classes-jdk21 stays at process-classes for the reason cfparser's pom documents: setting outputDirectory on a compiler execution leaks into what dependents resolve, and running the copy later breaks the test run with an error that reads like a missing resource. CI gains a java-version axis of [11, 21] across all three operating systems, with fail-fast: false. Both JDKs are installed with 21 last, so Gradle's daemon stays on 21 while the toolchain moves to the matrix JDK for compiling and running the tests -- compiling to 11 on a 21 JDK cannot see a problem that only appears on an 11 runtime. Verified: 675 Maven tests, ./gradlew build, and the packaged jars carry the expected class file versions with matching class counts between the main and jdk21 variants. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35 --- .github/workflows/gradle.yml | 15 ++++++-- build.gradle | 11 +++++- pom.xml | 67 ++++++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2a034776..d5a02c79 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,21 +9,30 @@ on: jobs: gradle: strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] + # 11 is the baseline the published main artifact targets, 21 the runtime most + # consumers are on. Compiling to 11 on a 21 JDK cannot catch a problem that only + # appears on an 11 runtime, so both are run. + java-version: [11, 21] runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up JDK 21 + # Both JDKs are installed, 21 last so it becomes JAVA_HOME and Gradle's daemon runs + # there. The matrix JDK is picked up by the toolchain to compile and run the tests. + - name: Set up JDK ${{ matrix.java-version }} and 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 21 + java-version: | + ${{ matrix.java-version }} + 21 - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Build with Gradle - run: ./gradlew build + run: ./gradlew build -PjavaTestVersion=${{ matrix.java-version }} env: GITHUB_USERNAME: ${{ secrets.GITHUB_USERNAME }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build.gradle b/build.gradle index 38d5ee39..6450fe38 100644 --- a/build.gradle +++ b/build.gradle @@ -33,8 +33,17 @@ plugins { } java { + // Bytecode targets 11 to match the Maven main artifact, whatever JDK compiles it. + // The published jdk21 classifier is produced by Maven, which is what publishes. + sourceCompatibility = 11 + targetCompatibility = 11 + + // -PjavaTestVersion selects the JDK that compiles and runs the tests, so CI can + // exercise the same Java 11 bytecode on an 11 and a 21 runtime while Gradle itself + // stays on the launcher JDK. toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of( + (project.findProperty('javaTestVersion') ?: '21') as Integer) } withJavadocJar() diff --git a/pom.xml b/pom.xml index 3d965eb5..cbfe4e01 100644 --- a/pom.xml +++ b/pom.xml @@ -77,8 +77,12 @@ UTF-8 - 21 - 21 + + 11 + 21 none 2.16.1-SNAPSHOT @@ -210,6 +214,28 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + compile-jdk21 + compile + + compile + + + ${jdk21.release} + ${project.build.directory}/classes-jdk21 + + + + org.apache.maven.plugins maven-jar-plugin @@ -229,11 +255,48 @@ + + + jar-jdk21 + package + + jar + + + jdk21 + ${project.build.directory}/classes-jdk21 + + + maven-resources-plugin 2.6 + + copy-resources-jdk21 + + process-classes + + copy-resources + + + ${project.build.directory}/classes-jdk21 + + + ${project.build.outputDirectory} + + **/*.class + + + + + copy-resources From b349d625bd1c3d73337d4b0570d63d70f0b0b8ea Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 14:58:17 +0000 Subject: [PATCH 2/2] Always re-resolve the cfparser SNAPSHOT Gradle treats a -SNAPSHOT as a changing module and caches it for 24 hours, and the CI workflows restore a Gradle cache. A green run shortly after a cfparser republish can therefore have tested the previous artifact -- which reads as a pass rather than as a stale dependency. This is not hypothetical here: the Java 11 CI jobs on this branch failed with "class file has wrong version 65.0, should be 55.0" against the cfparser SNAPSHOT published before the Java 11 split landed, and without this the re-run after republishing could have resolved the same cached jar again. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35 --- build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build.gradle b/build.gradle index 6450fe38..9dec7c54 100644 --- a/build.gradle +++ b/build.gradle @@ -120,6 +120,15 @@ repositories { } } } + +// cfparser is tracked as a SNAPSHOT. Gradle treats that as a changing module and caches +// it for 24 hours, and the CI workflows restore a Gradle cache -- so a green run shortly +// after a cfparser republish can have tested the *previous* artifact. Re-resolve every +// time; it is one small jar, and a stale one is worth far more than the download. +configurations.all { + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' +} + dependencies { implementation 'com.github.cfmleditor:cfml.parsing:2.16.1-SNAPSHOT' implementation 'commons-cli:commons-cli:1.2'