+1 opt-in
Walter > On Jul 21, 2026, at 4:11 PM, James Fredley <[email protected]> wrote: > > Plugin support for newer version is a real sore spot for Grails. What is the > argument for non-default, opt-in support for Grails 7 plugins in Grails 8 > apps? > > https://github.com/apache/grails-core/pull/15947 ended up as on opt-in for > the breaking changes > > James > > On 2026/07/21 20:44:12 Søren Berg Glasius wrote: >> Hi all, >> >> +1 with James D. >> >> Making legacy support an explicit opt-in (legacyCommandSupport = true) with >> loud detection makes total sense. It avoids dragging legacy debt into every >> Grails 8 build by default while providing an easy, well-documented path for >> those who need it. >> >> Let's move forward with this approach and the proposed testing gates. >> >> Den tirs. 21. jul. 2026 kl. 22.22 skrev James Daugherty < >> [email protected]>: >> >>> Do we plan to revert every breaking API change then? Deprecations have >>> already broken multiple plugins (I recently fixed the grails web console in >>> one example). Why is changing a single flag to enable compatibility not >>> acceptable while removing an entire API is? This inconsistently does not >>> make sense to me. >>> >>> On 2026/07/21 19:52:25 James Fredley wrote: >>>> I am taking another spin through >>> https://github.com/apache/grails-core/pull/16011 in an attempt to get >>> this closer to a compromise. >>>> >>>> But I am strongly of the opinion that existing, already published Grails >>> 7 plugins should work in Grails 8, by default with warnings. And I think >>> we should keep the compatibility shims for an extended period of time. >>>> >>>> If we do not have to break end apps, I would like to keep them working, >>> while still doing the split and letting teams move to the modern plugin >>> approach, at their own pace. >>>> >>>> On the 1 of 4, it seems like the other 3 could also have similar >>> classpath issues, based on what they import. >>>> >>>> James >>>> >>>> On 2026/07/21 16:52:21 James Daugherty wrote: >>>>> Hi all, >>>>> >>>>> Following James Fredley's -1 on the CLI split, I want to lay out where >>>>> we actually agree, correct a couple of points in the record, and >>>>> propose a concrete path so we can settle this on the list rather than >>>>> in PR or voting threads. >>>>> >>>>> Where we agree >>>>> >>>>> Nobody is proposing that users wait on plugin re-releases. We all >>>>> agree on the goal: an application upgrading to Grails 8 must be able >>>>> to keep using commands from already-published, unchanged Grails 7 >>>>> plugins. #16011 provides a mechanism for that, and after the review >>>>> iterations its core design is right: the deprecated >>>>> grails.dev.commands.* contract lives in a separate, execution-only >>>>> grails-core-cli-legacy artifact, off every compile classpath, >>>>> discovered through a neutral SPI, so grails-core-cli carries zero >>>>> legacy references and retiring the window later means dropping one >>>>> artifact. I support that design and want it to land. >>>>> >>>>> Correcting the record >>>>> >>>>> 1. "We only addressed 1 of 4 paths that provide commands." #16011's >>>>> own audit table reaches the opposite conclusion: of the four >>>>> mechanisms (application commands, codegen scripts, YAML commands, >>>>> ServiceLoader/profile commands), only application commands were broken >>>>> by the split. Codegen scripts, YAML, and ServiceLoader commands were >>>>> verified unaffected. The hyphenated-script issue is a separate Groovy >>>>> 5 regression that exists independently of #15948 and is fixed in >>>>> #16011. So the compatibility gap is one mechanism, not four — and it's >>>>> the one both PRs are focused on. >>>>> >>>>> 2. "#15948 was merged before a path was identified." It was merged >>>>> into a milestone, explicitly with the note that the design would >>>>> iterate before an RC. Milestones exist precisely so breaking changes >>>>> can land and the compatibility story can be worked out before anything >>>>> users depend on ships. Nothing has GA'd; no user is currently >>>>> stranded. Framing a milestone merge as the final word — and a -1 as >>>>> the response — escalates a design discussion we were already having. >>>>> >>>>> The actual open disagreement: default-on vs. explicit opt-in >>>>> >>>>> I am firmly for shipping the compatibility layer, and firmly against >>>>> provisioning it by default. My proposal keeps the upgrade >>>>> zero-confusion without committing the framework to zero-touch support >>>>> of a deprecated ABI: >>>>> >>>>> - Detection always-on, provisioning opt-in. When the build or registry >>>>> sees a grails.dev.commands.ApplicationCommand entry on the classpath >>>>> and grails { legacyCommandSupport = true } is not set, it fails loudly >>>>> with exact remediation: "Plugin X ships Grails 7 commands; set grails >>>>> { legacyCommandSupport = true } or upgrade the plugin." Nothing is >>>>> silent. The discoverability concern behind default-on is fully >>>>> answered. The user flips one documented switch during an upgrade they >>>>> are already performing. >>>>> >>>>> - Precedent. This is how we've handled every comparable transition: >>>>> indy, and the BOM-handling change in this very release, shipped behind >>>>> explicit settings. The wider ecosystem does the same: Spring Boot did >>>>> not keep Jackson 2 on the classpath by default next to Jackson 3; you >>>>> add it explicitly if you need it. I know of no precedent for >>>>> default-provisioning a deprecated contract onto every build, test >>>>> runtime included. >>>>> >>>>> - Costs of default-on fall on people who don't need it. Every Grails 8 >>>>> application would carry grails-core-cli-legacy and the >>>>> dual-classloader factories scan on its command and test-runtime >>>>> classpaths for the lifetime of the window. >>>>> >>>>> - Convergence. Default-on removes the only pressure that makes the >>>>> deprecation window converge. A one-time runtime warning doesn't reach >>>>> plugin authors — they never see their users' logs. And when the >>>>> artifact is eventually dropped, plugins that never migrated break at >>>>> that point with no action ever having been asked of anyone — a silent >>>>> break outside the visible opt-in contract. A visible switch is the >>>>> signal, to users and authors both, that migration is actually >>>>> expected. >>>>> >>>>> What must be true before we advertise the compat contract (regardless >>>>> of the default) >>>>> >>>>> These are in my review of #16011 and I consider them merge/RC gates, >>>>> not follow-ups: >>>>> >>>>> 1. Prove the headline claim. The current fixture recompiles legacy >>>>> sources with Groovy 5, which answers a different question than whether >>>>> a genuine Grails 7/Groovy 4 trait-consumer binary links against the >>>>> restored traits. The repo's existing composite-build pattern >>>>> (includeBuild) gives us a pinned grails-core:7.1.1 fixture without any >>>>> toolchain fight. Until that two-app test is green, "unchanged plugins >>>>> keep working without a re-release" is a hope, not a contract — and >>>>> it's the exact claim the -1 rests on. >>>>> >>>>> 2. No nondeterministic task graphs. The legacy per-command task >>>>> discovery resolves runtimeClasspath eagerly in afterEvaluate under >>>>> catch (Throwable), making the task set scheduling-dependent. Either >>>>> drop the legacy per-command tasks (the generic runCommand -Pargs=... >>>>> already works) or source names from the lenient withDependencies >>>>> mechanism the companion discovery uses. We spent Grails 7 removing >>>>> exactly this pattern. >>>>> >>>>> 3. Loud linkage failures. A LinkageError loading a legacy command is >>>>> the compat layer failing its contract, not a faulty plugin; it must >>>>> log at error level with clear framework-bug wording, distinct from >>>>> ordinary per-command isolation. >>>>> >>>>> Proposal >>>>> >>>>> Land #16011 with: the separate-artifact design as-is; >>>>> detection-always-on + legacyCommandSupport opt-in; the precompiled >>>>> 7.1.1 fixture via composite build; legacy per-command tasks dropped in >>>>> favor of runCommand; the LinkageError split. Document the switch in >>>>> the upgrade notes as a one-line step. >>>>> >>>>> To be clear, I'm not asking James to carry these items alone — I'm >>>>> offering to do the heavy lifting on them. Specifically, I'll take the >>>>> precompiled 7.1.1 fixture (the composite-build wiring is the pattern I >>>>> set up for grails-gradle and build-logic, so it's fastest for me to >>>>> stand up), and I'm happy to do the opt-in gating and detection wiring >>>>> in the Gradle plugin as well, either as commits into #16011 or as a >>>>> stacked PR on top of it, whichever James prefers. The LinkageError >>>>> split is a few lines either of us can do. None of this needs to slow >>>>> the PR down. >>>>> >>>>> If the community consensus after discussion is genuinely default-on, >>>>> I'll accept that outcome — but let's decide it here, deliberately, >>>>> with the trade-offs on the table. With the gates above shared between >>>>> us, I think we can have this landed well before the RC either way. >>>>> >>>> >>> >>
