Remove Jansi and separate the CLI test tier from the production classpath - #16078
Remove Jansi and separate the CLI test tier from the production classpath#16078jdaugherty wants to merge 2 commits into
Conversation
|
This PR is attempting to adopt org.jline's fork, but additional issues found as part of this work:
|
762a20d to
be8a83b
Compare
|
@jamesfredley I have proposed we hold the RC1 release until we can work through the issues in this comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #16078 +/- ##
================================================
+ Coverage 0 51.9220% +51.9220%
- Complexity 0 18013 +18013
================================================
Files 0 2038 +2038
Lines 0 95680 +95680
Branches 0 16641 +16641
================================================
+ Hits 0 49679 +49679
- Misses 0 38671 +38671
- Partials 0 7330 +7330
🚀 New features to boost your workflow:
|
|
I intend to push a change that tries to adjust the issues mentioned in the previous comments. |
…s as separate from the production classpath, and remove the need for the legacy jansi library
|
The biggest reason we have to do this change is we had 2 versions of jansi on our dependency list - groovy ships with one version, and then we had the old, unmaintained one. This makes us adopt the jline version groovy uses and it ensures we don't have a CVE in our production classpath. |
🚨 TestLens detected 2 failed tests 🚨Here is what you can do:
Test SummaryCI - Groovy Joint Validation Build / build_grails > :grails-data-hibernate5-dbmigration:testCli
🏷️ Commit: a2406ab Test FailuresGroovyChangeLogSpec > outputs a warning message by calling the warn method (:grails-data-hibernate5-dbmigration:testCli in CI - Groovy Joint Validation Build / build_grails)GroovyChangeLogSpec > updates a database with Groovy Change (:grails-data-hibernate5-dbmigration:testCli in CI - Groovy Joint Validation Build / build_grails)Muted TestsSelect tests to mute in this pull request:
Reuse successful test results:
Click the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app. |
Description
Removes Jansi from Grails entirely and separates developer tooling from what an application ships.
The PR started as a coordinate swap for CVE-2026-8484 — a heap buffer overflow in Jansi's JNI
ioctl()wrapper, unfixed and unfixable: FuseSource Jansi is unmaintained, every release through2.4.3is affected, and no patched version exists.The obvious remedy was
org.jline:jansi, the maintained fork. That turns out to be a false fix. Thefork's native
ioctl__IJ_3Iis byte-for-byte identical to FuseSource's — sameGetIntArrayElementswith no length check before the syscall — differing only in the package insidethe JNI symbol name. The advisory names only the FuseSource coordinate, so migrating would have
silenced the scanner while shipping the same defect.
org.jline:jansi:4.3.1still exposespublic static native int ioctl(int, long, int[]), and JLine has no issue or PR tracking it.So Jansi is gone outright rather than replaced. The console renders its own escape sequences (a small,
fixed set of standard ones) and JLine remains only as the terminal library it already was.
Reviewing why Jansi was on application classpaths at all exposed the larger problem, and this PR fixes
that too: developer-only tooling was being shipped to every application. Before this change, an
application's production runtime classpath carried
grails-console,groovy-console,groovy-groovysh,groovy-swingand 13 JLine artifacts including Jansi — a Swing console, aGroovy shell and a terminal stack, none of which an application runs.
Why this change is possible now
8.0.xis a major release branch, where the contribution policy permits breaking API changes. Movinggrails.build.logging.GrailsConsoleand its collaborators to a companion artifact is such a change andcould not be made on a patch or minor branch. The CLI companion mechanism (
-cliartifacts,auto-provisioned onto
grailsCli) already exists in 8.0.x, so the console moves onto establishedinfrastructure rather than new machinery.
Issues addressed
This PR closes all five items raised in
this comment:
1. The CLI split needs its own test source set so tests resemble production.
clioutput and CLI-only dependencies were being added to the plugin'stestclasspath, so unit testssaw classes and libraries an application never receives — a test could pass against something that was
not there in production. Two dedicated source sets now exist, both created automatically by
org.apache.grails.gradle.grails-plugin-cliwith nothing to declare:src/testClitestCli, wired intochecksrc/integration-test-cliintegrationTestClisrc/testno longer sees the CLI tier at all. The direction is deliberately one-way: CLI tests may usetest fixtures, ordinary tests never see CLI classes. Roughly 70 existing specs moved accordingly.
2. CLI-specific classes moved to the CLI source set.
AnsiConsoleUrlMappingsRenderermoved tograils-web-url-mappings'ssrc/cli— its only consumer wasalready the URL-mappings report command. An audit of every module's main sources for
GrailsConsoleandorg.jlinereferences found one further cluster: the console itself. Nine classes moved fromgrails-bootstrapto the newgrails-bootstrap-clicompanion. The app-facinggrails-bootstrapjarnow contains 81 classes and no console classes.
ArtefactTypeAstTransformationwas the last main-source reference; it calledGrailsConsole.getInstance().error(...)on an AST-injector error path, wrapped incatch (Throwable) { ignore }solely because the console could throwNoClassDefFoundError. It runsinside the Groovy compiler and now uses a logger.
3. JLine/Jansi eliminated from the production classpath — proven and tested.
Proven by measurement and then enforced, so it cannot regress. A new
validateProductionClasspathtaskresolves
runtimeClasspathand fails on any forbidden coordinate; it is wired intocheckandconfigured on
grails-dependencies/starter-web. It was negative-tested by deliberately reinstatinggrails-consoleand confirming the failure lists every offending artifact.4. Jansi removed entirely rather than swapped.
No third-party ANSI library remains.
ConsoleAnsi(~130 lines, internal) emits the same standardsequences, buffering style changes so consecutive ones collapse into a single SGR exactly as before.
Output is byte-identical:
ESC[1;31m| LABEL ESC[22;39mMSG ESC[mforerror, andESC[1A ESC[1D ESC[0K ESC[1;33m| ESC[22;39mMSG ESC[mforoutputMessage. Emitting directly rather thanvia JLine's
AttributedStringBuilderwas deliberate — that would have regrouped the SGR runs and madecursor control depend on the terminfo database, which Jansi never did.
5.
grails-consoleis now CLI-only.It was exposed by
grails-dependencies/starter-web, which put it on every application's runtimeclasspath. It was already auto-provisioned onto
grailsCliby the Grails Gradle plugin, so the starterentry was both redundant and the cause. Removed. The
consoleandshelltasks are unaffected — theysource it from
grailsCli, which never reachesruntimeClasspath, sobootRun,bootJarandbootWarare clean.
Also found and fixed
GrailsConsoleSpechad never executed — anywhere. It was gated on@IgnoreIf({ !GrailsConsole.instance.isAnsiEnabled() }), and a Gradle test worker has no tty, so theterminal is always
dumband the gate always skipped. Its own fixture also built adumbterminal, soit would have emitted no ANSI even if it had run. It now drives an
ExternalTerminaland runs 10assertions covering colour, cursor movement, reset and the disabled path. Un-gating it immediately
surfaced two latent hazards it had been hiding.
Rather than invent a Grails equivalent,
GrailsConsolehonoursspring.output.ansi.enabled(
always/detect/never), preferring what Boot bound toAnsiOutputand falling back to the systemproperty for the CLI, which has no
Environment.alwaysis a new capability — Jansi's switch couldonly disable.
withJansiremoved from 26 Logback configurations. Logback'sConsoleAppenderloadsorg.fusesource.jansi.AnsiConsoleby name, so with Jansi gone it logged aClassNotFoundExceptionwarning on every startup and fell back to the plain stream. Nothing is lost: colour comes from Spring
Boot's
ColorConverter(%clr), which honours the same property. Generated applications already omitit (
<withJansi>true</withJansi>breaks console logging after first reload in Grails 7.1.0 #15663 — Jansi's globalSystem.outreplacement breaks console logging after a DevTools restart);only configurations carried forward were affected.
jline.versionmoved to3.30.9to match the JLine version Groovy ships,which orphaned the version-pinned licence override and failed
cyclonedxDirectBom. CycloneDX mapsJLine's
"The BSD License"to BSD-4-Clause (License Mapping for BSD-3-Clause and BSD-4-Clause CycloneDX/cyclonedx-core-java#205); the POMs declareBSD-3-Clause. Override updated.
testCli/integrationTestCliran undercheckbut were absentfrom the aggregate reports, hiding ~69 suites. Added to
grails-test-report's phases.Verification
./gradlew clean aggregateViolations :grails-test-report:check --continuetestCliand 2integrationTestClisuites present in the aggregate reportsLegacyCommandCompatibilityIntegrationSpecpassing, with the Grails 7 fixture verified as built by the Grails 7 toolchain (Grails 7.0.14,
Groovy 4.0.32, JDK 17)
runtimeClasspathconfirmed free of JLine, Jansi,grails-bootstrap-cliand
grails-console; its integration test classpath correctly resolvesgrails-bootstrap-clifrompublished metadata with no capability conflict
Contributor Checklist
Issue and Scope
7.0.x): Bug fixes only. No new features or API changes.7.1.x): New features are welcome, but breaking existing APIs must be avoided.8.0.x): Reserved for major changes. Breaking API changes are permitted.Code Quality
./gradlew build --rerun-tasks../gradlew codeStyleand resolved any violations. See Code Style for details.Licensing and Attribution
Documentation
Upgrade notes cover: removing
runtimeOnly "org.fusesource.jansi:jansi"; thespring.output.ansi.enabledproperty with a before/after mapping from Jansi's own switch; removingwithJansifrom Logback configuration; the move ofGrailsConsoleand its collaborators tograils-bootstrap-cli; and the new CLI test source sets for plugin authors.