From de8bfc57df34c5ba97efb1f5c11c02564fe040fe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 14:42:31 +0000 Subject: [PATCH 1/2] Publish a Java 11 main artifact and a jdk21 classifier Both modules now emit two jars from one source tree: the main artifact at class file 55 and a jdk21 classifier at 65. A Java 11 jar loads on 11, 17, 21 and 25, so the default is the one that works everywhere and the classifier is there for consumers who want a 21-targeted build. Nothing in the source required 21. Both modules compile clean at --release 11, which checks the API surface and not just the language level, and no runtime dependency is above class file 55 -- in CFLint's 36-jar classpath the only Java 21 artifacts were cfparser's own two modules. The baseline was raised in 5d21f6c, whose message notes it broke Java 11 and 17 consumers; this restores them without giving up a 21 build. ## The resource copy has to run at process-classes maven-jar-plugin takes a single classesDirectory, and classes-jdk21 holds only .class files, so the classified jar needs the resources copied in or it ships without cfml.dictionary's XML. That copy must not move later than process-classes. Setting outputDirectory on a compiler execution leaks into what the reactor hands dependent modules, so cfml.parsing's tests resolve cfml.dictionary to classes-jdk21. Copy the resources at prepare-package instead and all 326 tests fail with "Problem loading dictionaryconfig.xml", which reads like a missing resource rather than a phase ordering problem. The pom says so at the point where it matters. skipIfEmpty keeps the pom-packaged parent from emitting and deploying a spurious cfparser-VERSION-jdk21.jar, which it did before that was added. ## CI now runs on both gradle.yml gains a java-version axis of 11 and 21 across all three operating systems. Compiling to 11 on a 21 JDK cannot catch a problem that only appears on an 11 runtime, and this environment has no Java 11 available to test with, so the matrix is the only thing that actually exercises it. Gradle builds the Java 11 line only. Maven is what publishes, so the classifier is produced there. Verified: 326 tests, ./gradlew build, CFLint's 675 against the Java 11 parser, and a deploy to a local file repository producing main, jdk21, sources and javadoc jars for both modules with identical class and resource counts, differing only in bytecode level. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35 --- .github/workflows/gradle.yml | 9 +++- build.gradle | 7 ++- pom.xml | 85 +++++++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 547116b..af93e34 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -10,18 +10,23 @@ 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. Running the suite on both is the only way to catch a Java 11 + # problem that compiling to 11 on a 21 JDK cannot see. + java-version: [11, 21] runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up JDK 21 + - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v4 with: distribution: temurin - java-version: 21 + java-version: ${{ matrix.java-version }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 diff --git a/build.gradle b/build.gradle index 5ff466b..2233427 100644 --- a/build.gradle +++ b/build.gradle @@ -133,8 +133,11 @@ subprojects { } } - sourceCompatibility = 21 - targetCompatibility = 21 + // Matches the Maven main artifact. The published jdk21 classifier is produced by + // Maven, which is what publishes; Gradle builds the Java 11 line that CI exercises + // on both an 11 and a 21 runner. + sourceCompatibility = 11 + targetCompatibility = 11 tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } task sourcesJar(type: Jar) { diff --git a/pom.xml b/pom.xml index 91dba92..a139d56 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,11 @@ UTF-8 - 21 + + 11 + 21 0.8.12 + + 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-resources-plugin + 3.3.1 + + + copy-resources-jdk21 + process-classes + + copy-resources + + + ${project.build.directory}/classes-jdk21 + + + ${project.build.outputDirectory} + + **/*.class + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.1 + + + jar-jdk21 + package + + jar + + + jdk21 + ${project.build.directory}/classes-jdk21 + + true + + + + org.jacoco jacoco-maven-plugin From 6068ce0fb1154fe82296a7b0c9522a845e95e7c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 14:46:55 +0000 Subject: [PATCH 2/2] Run the Java 11 CI leg through a toolchain, not the daemon JDK The first attempt put Java 11 straight on the runner and all three 11 jobs failed before compiling anything: UnsupportedClassVersionError: aQute/bnd/gradle/BndBuilderPlugin has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0 The bnd OSGi plugin needs Java 17 or newer to load, so Gradle itself cannot run on an 11 JVM. That is a build-tooling limit and says nothing about the artifacts, which are Java 11 and were fine. Both JDKs are now installed, with 21 last so it becomes JAVA_HOME and the daemon keeps running there. -PjavaTestVersion selects the toolchain that compiles and runs the tests, so the 11 leg exercises a real Java 11 runtime while bnd still loads. sourceCompatibility and targetCompatibility stay at 11 regardless of the toolchain, so both legs test the same class file 55 output on two different runtimes rather than testing two different builds. Verified locally at -PjavaTestVersion=21 and with no property set, both emitting major version 55. The 11 leg cannot be run here: no Java 11 is installed and the proxy blocks Adoptium and foojay, which is the reason this matrix exists. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35 --- .github/workflows/gradle.yml | 12 +++++++++--- build.gradle | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index af93e34..3de34e9 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,11 +22,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up JDK ${{ matrix.java-version }} + # Both JDKs are installed. The last entry becomes JAVA_HOME, so Gradle's daemon + # always runs on 21 -- the bnd OSGi plugin is class file 61 and cannot load on an + # 11 JVM. The matrix JDK is picked up by the toolchain for compiling and running + # the tests, which is the part that needs to happen on Java 11. + - name: Set up JDK ${{ matrix.java-version }} and 21 uses: actions/setup-java@v4 with: distribution: temurin - java-version: ${{ matrix.java-version }} + java-version: | + ${{ matrix.java-version }} + 21 - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 @@ -34,4 +40,4 @@ jobs: gradle-version: wrapper - name: Build with Gradle - run: ./gradlew build \ No newline at end of file + run: ./gradlew build -PjavaTestVersion=${{ matrix.java-version }} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2233427..2ae7298 100644 --- a/build.gradle +++ b/build.gradle @@ -133,11 +133,22 @@ subprojects { } } - // Matches the Maven main artifact. The published jdk21 classifier is produced by - // Maven, which is what publishes; Gradle builds the Java 11 line that CI exercises - // on both an 11 and a 21 runner. + // Bytecode stays at 11 to match the Maven main artifact, whatever JDK is running. + // 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. Gradle itself keeps + // running on the launcher JDK: the bnd OSGi plugin is class file 61 and throws + // UnsupportedClassVersionError if the daemon is on Java 11, so the toolchain has to + // move rather than the whole build. + java { + toolchain { + languageVersion = JavaLanguageVersion.of( + (project.findProperty('javaTestVersion') ?: '21') as Integer) + } + } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } task sourcesJar(type: Jar) {