Automate Challenges jar compilation for Plugwright E2E tests and enforce Java 21 - #430
Conversation
Previously, developers had to manually run 'mvnw package' before running e2e tests. This commit adds a 'buildChallenges' Gradle task that runs the Maven wrapper automatically, resolving the Java toolchain and finding the compiled jar without manual intervention.
|
build.gradle.kts executes ./mvnw, but the PR adds mvnw with Git mode 100644, not 100755. E2E testing will fail with Permission denied on Unix systems and GitHub’s Ubuntu runner. buildChallenges declares build/Challenges.jar as an output but declares no source, resource, or pom.xml inputs. Once that JAR exists, Gradle may mark the task UP-TO-DATE even after plugin code changes. This undermines the entire purpose of the feature. Declare the Maven project files as inputs or deliberately make the task always run. The workflow still explicitly runs Maven, then plugwrightTest invokes buildChallenges, which runs Maven again: e2e.yml. Existing target JARs can break local testing. The Maven enforcer accepts Java 21 or newer, but the Gradle toolchain requests exactly Java 21. There is no toolchain download resolver configured, so developers who only have Java 25/26 can receive “no matching Java installation” despite satisfying the stated requirement. Verdict though; sensible feature, and i dont see anything dodgy with it. So once a human dev has made some logical changes i think this could be considered by tasty. |
- Fix mvnw execution permissions for CI/Unix - Declare task inputs for buildChallenges to fix caching - Use clean package in Maven to prevent singleFile crashing on stale artifacts - Resolve JavaToolchain dynamically only if Gradle runs on < Java 21, allowing devs on Java 22+ to build out of the box - Remove redundant Maven run from e2e GitHub Actions workflow
1563ec3 to
c9415f6
Compare
|
Thanks for the review, good catches. Pushed a fix: mvnw is executable again, the Gradle task has its inputs declared, and clean is back in the Maven command. On the Java version conflict: I changed the Gradle toolchain logic so it only requests Java 21 if the daemon's on <21. So devs on 17 still get it working out of the box, and reviewers on 22+ don't hit a strict lock. Also cut the redundant Maven step in e2e.yml. Tested on 22 and 17, both fine. |
tastybento
left a comment
There was a problem hiding this comment.
Thanks for this, and thanks @mrfloris for the first pass — those were good catches.
Context for anyone reading: I'm using Challenges as a vehicle to test plugwright. That's still a new approach and I'm waiting to see how it gets maintained, so I'm deliberately conservative about how much of the repo gets restructured around it. But the direction here is sensible and I'd like to take it.
I checked the branch out and actually ran it on macOS (Apple Silicon, Homebrew JDKs) rather than just reading the diff. Confirming the fixes hold up:
| Check | Result |
|---|---|
mvnw file mode |
100755 — fixed |
mvnw content |
byte-identical to upstream maven-wrapper-distribution-3.3.2 |
mvnw.cmd content |
identical to upstream modulo line endings (LF — but e2e/gradlew.bat is already LF in this repo, so pre-existing, not yours) |
.mvn/wrapper/maven-wrapper.jar |
byte-identical to the official maven-wrapper-3.3.2.jar on Maven Central (sha256 3d8f20ce…39c7a8) |
./gradlew buildChallenges on JDK 21 |
builds, copies the right jar to e2e/build/Challenges.jar |
| same from a JDK 17 daemon | resolved a local Java 21 toolchain and built |
| re-run with no change | UP-TO-DATE |
edit a .java, re-run |
rebuilds |
edit locales/en-US.yml, re-run |
rebuilds |
.singleFile vs original-Challenges-*.jar and -sources.jar in target/ |
correctly picks the shaded jar |
So the exec bit and the bogus up-to-date checking are genuinely fixed, not just claimed fixed. The redundant Maven step is gone from e2e.yml. The enforcer is harmless — every workflow already runs JDK 21 and <release>21</release> already fails on older JDKs, so it's purely a nicer error message, which is a fine small win.
Two things I'd like before I merge, both inline below:
- Regenerate the wrapper scripts-only, to drop the committed binary.
- Lose the
clean— it's doing a lot of work just to keep.singleFilehappy.
The rest of the inline comments are optional polish, take them or leave them.
Unrelated pre-existing issue I found while testing, flagging so nobody chases it as a regression here: ./gradlew in e2e/ won't start at all on a machine whose default JDK is 25 — Gradle 8.10 doesn't understand that version and you get a bare * What went wrong: 25.0.1 with no further explanation. This reproduces on unmodified develop, so it is not caused by this PR. @mrfloris I suspect that's the macOS problem you hit. Bumping e2e/gradle/wrapper/gradle-wrapper.properties to Gradle 9.x fixes it — happy to take that as a separate follow-up.
I wasn't able to complete a full plugwrightTest run locally: port 25565 was occupied by another server on my machine, so Paper couldn't bind. That's my environment, not the PR.
| @@ -0,0 +1,2 @@ | |||
| distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip | |||
| wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar | |||
There was a problem hiding this comment.
Could you regenerate this scripts-only?
mvn wrapper:wrapper -Dtype=only-script -Dmaven=3.9.12
That drops .mvn/wrapper/maven-wrapper.jar entirely — mvnw becomes a self-contained script that fetches the distribution itself. Same behaviour, but I'd rather not carry a 63KB binary in the repo that has to be re-vetted by hand every time it's bumped. (I checked this one and it's clean — byte-identical to Maven Central — but that's a check someone has to remember to repeat.)
It also gets us off 3.9.6, which is from January 2024. 3.9.12 is what I have locally.
While you're in here: distributionSha256Sum is worth setting too, so the downloaded Maven distribution is verified rather than trusted.
| val isWindows = System.getProperty("os.name").lowercase().contains("win") | ||
| val executable = if (isWindows) listOf("cmd", "/c", "mvnw.cmd") else listOf("./mvnw") | ||
| // Use 'clean' to avoid multiple jars causing singleFile to fail | ||
| commandLine(executable + listOf("-q", "clean", "package", "-DskipTests")) |
There was a problem hiding this comment.
The clean is here only to stop .singleFile below choking on stale jars, and it's an expensive way to buy that. Every source change now forces a full recompile of the whole addon, and it wipes target/ — including surefire-reports and any jar someone has built to drop on a live server for manual testing.
Making the resolution robust instead means you don't need clean at all — see my comment on the doLast block.
| val builtJar = fileTree("../target") { | ||
| include("Challenges-*.jar") | ||
| exclude("*sources*", "*javadoc*") | ||
| }.singleFile |
There was a problem hiding this comment.
.singleFile is the thing forcing clean up on line 56. Picking the newest match is just as correct after a package and removes that constraint:
val builtJar = fileTree("../target") {
include("Challenges-*.jar")
exclude("*sources*", "*javadoc*")
}.files.maxByOrNull { it.lastModified() }
?: throw GradleException("No Challenges jar produced in ../target")With that, line 56 can go back to -q package -DskipTests and the dev loop stays incremental.
(For what it's worth, I verified the current filter does correctly skip original-Challenges-*.jar and -sources.jar — original-… doesn't match the Challenges-* include. So this is about the clean, not about the filter being wrong.)
| workingDir = file("..") | ||
|
|
||
| inputs.dir(file("../src")) | ||
| inputs.file(file("../pom.xml")) |
There was a problem hiding this comment.
Minor completeness point: .mvn/, mvnw and mvnw.cmd aren't inputs, so bumping the Maven version wouldn't retrigger the build. Cheap to add:
inputs.dir(file("../.mvn"))
inputs.file(file("../mvnw"))Not a blocker — the src + pom.xml inputs are the ones that matter day to day, and I confirmed those work.
|
|
||
| // Pass the correct JAVA_HOME to Maven if we needed a custom toolchain | ||
| if (javaLauncherProvider != null) { | ||
| environment["JAVA_HOME"] = javaLauncherProvider.get().metadata.installationPath.asFile.absolutePath |
There was a problem hiding this comment.
Optional: javaLauncherProvider.get() here resolves the toolchain at configuration time, which forces the lookup (and potentially a provision) whenever the task is realised, even on a no-op run. Moving it into doFirst { environment("JAVA_HOME", …) } keeps it lazy and is friendlier to the configuration cache if this build ever turns that on.
Separately, the else branch overriding JAVA_HOME with java.home is a no-op in the normal case (Exec inherits the environment) but does override a JAVA_HOME the developer set deliberately when the daemon JVM was chosen via org.gradle.java.home. Narrow edge case, just noting it.
| // If Gradle is running on Java 17, try to find a Java 21+ toolchain to satisfy Maven/Paper. | ||
| // If Gradle is already running on Java 21+ (e.g., 22, 23), don't force a strict toolchain lock. | ||
| val currentJava = JavaVersion.current() | ||
| val javaLauncherProvider = if (currentJava < JavaVersion.VERSION_21) { |
There was a problem hiding this comment.
This narrows @mrfloris's toolchain point nicely, but doesn't fully close it: a developer on Java 17 with no JDK 21 installed still gets "no matching toolchains found" and no way to recover automatically. Gradle can provision one if you add the foojay resolver to e2e/settings.gradle.kts:
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}Optional, but it's one line and it makes the "just run ./gradlew plugwrightTest" promise in the PR description actually true on a clean machine.
(On my Mac Gradle did find the Homebrew JDK 21 from a 17 daemon, so this only bites people who genuinely don't have a 21 anywhere.)
| <configuration> | ||
| <rules> | ||
| <requireJavaVersion> | ||
| <version>[21,)</version> |
There was a problem hiding this comment.
Tiny thing: [21,) duplicates <java.version>21</java.version> up at line 42, so this silently goes stale when the project bumps. [${java.version},) keeps them in sync.
…JDKs
Gradle 8.10 cannot start on a JDK it does not recognise. On a machine
whose default JDK is 25, every ./gradlew invocation in e2e/ died before
evaluating the build script with nothing but:
* What went wrong:
25.0.1
This pre-dates the rest of this PR (it reproduces on develop unmodified),
but it defeats the "just run ./gradlew plugwrightTest" workflow this PR is
adding, so fix it here rather than leave the new entry point broken for
anyone not pinned to an older JDK.
- Wrapper regenerated with the documented two-pass `wrapper` task run, so
gradle-wrapper.jar is the real 9.7.0 one. Its SHA-256 is
7a9ce74cff467ca1bf60a4fcd9f05185acceda4d0f382434d393e17864262c5d,
matching the checksum Gradle publishes for gradle-9.7.0-wrapper.jar.
- distributionSha256Sum pinned so the downloaded distribution is verified
rather than trusted.
- buildChallenges switched from `by tasks.registering(Exec::class)` to
`tasks.register<Exec>(...)`. Gradle 9.6 deprecated the delegate form;
without this the bump emits three deprecation warnings and flags the
build as incompatible with Gradle 10.
Verified on macOS/arm64 with JDK 25 as the default: ./gradlew
buildChallenges succeeds with no deprecation warnings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AyrUvHMduHzV4hFj84PExn
|
Pushed a commit to this branch directly (be00869) rather than leaving the Gradle thing as a vague follow-up — it was blocking the very workflow this PR adds, so it belongs here. What it does: bumps the To be clear @Drownek, this was not your bug — it reproduces on unmodified Three parts:
Verified on macOS/arm64, JDK 25 as the default, full suite: That's the whole chain — Sorry for pushing to your branch without asking first; shout if you'd rather I'd left it separate and I'll happily pull it back out. That leaves just the two asks from my review: the scripts-only Maven wrapper, and dropping |
- Switch Maven wrapper to script-only type, dropping the committed binary jar - Remove clean from buildChallenges Maven args as requested - Pick newest built jar dynamically to avoid singleFile conflicts
|
Thanks @tastybento for testing this out on macOS I've pushed a new commit addressing both points:
Should be good to merge |
Finishes the scripts-only wrapper migration:
- Bump the wrapper distribution 3.9.6 (Jan 2024) -> 3.9.12 and set
distributionSha256Sum. With the wrapper jar gone, mvnw fetches a 9 MB
Maven distribution at build time, so without a checksum there was no
committed artifact *and* no verification. 3.9.12 also ships newer
jansi/guava, which silences the sun.misc.Unsafe restricted-method
warnings 3.9.6 emits on every build under JDK 25.
- Add ../.mvn and ../mvnw as buildChallenges inputs. The wrapper pins the
Maven version, so bumping it has to retrigger the build - without this
the commit above would not have rebuilt anything.
- Use [${java.version},) in the enforcer rule so it tracks the property at
the top of the pom instead of going stale. Verified that POM
interpolation resolves this to the pom property, not the JVM's
java.version system property.
- Drop a stray blank line left by removing the Maven step from e2e.yml.
Verified on macOS/arm64: mvn test 522/522 on both JDK 21 and 25, and the
full plugwrightTest suite 4/4 green from a clean target/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBMBsT7vUtgBXUk3gtajgt
|
Merged — thanks @Drownek, this is a genuinely nice quality-of-life win. I verified the last round on macOS/arm64 with JDK 25 as the default before merging:
Nice detail worth recording: the sources jar gets an mtime identical to the second to the shaded jar, so I pushed one more commit (64a98f4) rather than sending you round again for small stuff:
Left as follow-ups, no action needed from you: the foojay resolver and the lazy Two things for anyone reading the red check rather than the logs:
|
What does this PR do?
mvnw packagebefore runningplugwrightTest. The newly introducedbuildChallengesGradle task automatically runs the Maven wrapper to build theChallenges.jardynamically as a test dependency.java { toolchain { ... } }setup. Plugwright gracefully inherits this out-of-the-box, removing boilerplate code.build.gradle.kts: Leverages idiomatic GradlefileTreeAPIs for resolving the output.jarinstead of verbose manual file traversal, making the build script significantly more readable.How to test
Simply run
./gradlew plugwrightTest(orgradlew.bat plugwrightTeston Windows) inside thee2edirectory. The project will seamlessly compile the Maven artifact and boot the Paper server for E2E testing without any manual prerequisites.