This is an automated email from the ASF dual-hosted git repository.

jamesfredley pushed a commit to branch 8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 301b40c20acdb8650ea36cf685fbd54a6341bccb
Merge: 66a25164f3 bdb98a6636
Author: James Fredley <[email protected]>
AuthorDate: Wed Jul 29 18:41:58 2026 -0400

    Merge pull request #16011 from apache/feat/8.0.x-legacy-command-compat
    
    Opt-in backwards compatibility for Grails 7 command plugins on Grails 8

 .github/workflows/end-to-end.yml                   | 144 ++++
 AGENTS.md                                          |   1 +
 end-to-end/README.md                               | 111 +++
 end-to-end/build.gradle                            |  41 +
 end-to-end/gradle.properties                       |  25 +
 end-to-end/gradle/wrapper/gradle-wrapper.jar       | Bin 0 -> 48462 bytes
 .../gradle/wrapper/gradle-wrapper.properties       |   9 +
 end-to-end/gradlew                                 | 248 +++++++
 end-to-end/gradlew.bat                             |  82 ++
 end-to-end/legacy-commands-plugin/build.gradle     |  44 ++
 .../legacy/commands/plugin/GreetingService.groovy  |  24 +
 .../commands/plugin/HelloLegacyAppCommand.groovy   |  51 ++
 .../plugin/HelloLegacyGrailsCommand.groovy         |  45 ++
 .../plugin/LegacyCommandsPluginGrailsPlugin.groovy |  32 +
 .../src/main/resources/META-INF/grails.factories   |  15 +
 .../src/main/scripts/hello-legacy-script.groovy    |  24 +
 end-to-end/legacy-commands/build.gradle            | 101 +++
 .../grails-app/conf/application.yml                |  41 +
 .../legacy-commands/grails-app/conf/logback.xml    |  31 +
 .../controllers/legacycommands/UrlMappings.groovy  |  35 +
 .../init/legacycommands/Application.groovy         |  30 +
 .../services/legacycommands/GreetingService.groovy |  27 +
 ...egacyCommandCompatibilityIntegrationSpec.groovy | 236 ++++++
 end-to-end/legacy-g7-command-plugin/.sdkmanrc      |   4 +
 end-to-end/legacy-g7-command-plugin/build.gradle   |  83 +++
 .../gradle/wrapper/gradle-wrapper.jar              | Bin 0 -> 48462 bytes
 .../gradle/wrapper/gradle-wrapper.properties       |   6 +
 end-to-end/legacy-g7-command-plugin/gradlew        | 248 +++++++
 end-to-end/legacy-g7-command-plugin/gradlew.bat    |  82 ++
 .../legacy-g7-command-plugin/settings.gradle       |  60 ++
 .../g7/commands/HelloDerivedNameCommand.groovy     |  39 +
 .../g7/commands/HelloG7PrecompiledCommand.groovy   |  49 ++
 .../HelloG7PrecompiledGrailsCommand.groovy         |  45 ++
 .../src/main/resources/META-INF/grails.factories   |   1 +
 end-to-end/settings.gradle                         | 112 +++
 gradle-bootstrap/build.gradle                      |  47 +-
 gradle/publish-root-config.gradle                  |   1 +
 .../GrailsApplicationContextCommandRunner.groovy   |  49 +-
 ...railsApplicationContextCommandRunnerSpec.groovy | 100 +++
 grails-core-cli-legacy/build.gradle                |  67 ++
 .../grails/dev/commands/ApplicationCommand.groovy  |  77 ++
 .../grails/dev/commands/ExecutionContext.groovy    |  55 ++
 .../dev/commands/GrailsApplicationCommand.groovy   |  50 ++
 .../dev/commands/io/FileSystemInteraction.groovy   | 146 ++++
 .../commands/io/FileSystemInteractionImpl.groovy   | 284 +++++++
 .../dev/commands/template/TemplateException.groovy |  32 +
 .../dev/commands/template/TemplateRenderer.groovy  | 174 +++++
 .../commands/template/TemplateRendererImpl.groovy  | 235 ++++++
 .../compat/LegacyApplicationCommandAdapter.groovy  |  90 +++
 .../compat/LegacyApplicationCommandProvider.groovy | 163 ++++
 .../main/resources/META-INF/grails-cli.factories   |  16 +
 .../cli/LegacyCommandRegistryLoadingSpec.groovy    | 601 +++++++++++++++
 .../LegacyApplicationCommandAdapterSpec.groovy     | 107 +++
 .../core/cli/ApplicationCommandDiagnostics.groovy  |  87 +++
 .../ApplicationCommandFactoryKeyProvider.groovy    |  29 +
 .../core/cli/ApplicationCommandProvider.groovy     |  32 +
 .../core/cli/ApplicationCommandRegistrar.groovy    |  32 +
 .../core/cli/ApplicationCommandTargetAware.groovy  |  29 +
 .../cli/ApplicationContextCommandRegistry.groovy   | 216 +++++-
 .../compiler/CommandFactoriesTransformation.groovy |  14 +-
 .../core/io/support/GrailsFactoriesLoader.groovy   |  54 ++
 .../grails/util/GrailsUtilStackFiltererSpec.groovy |  23 +-
 .../GrailsBootstrapRegistryInitializerSpec.groovy  |  19 +-
 .../core/cli/ApplicationCommandProviderSpec.groovy | 822 +++++++++++++++++++++
 .../CommandFactoriesTransformationSpec.groovy      |  16 +
 .../test/resources/META-INF/grails-cli.factories   |  16 +
 grails-doc/src/en/guide/introduction/whatsNew.adoc |  26 +-
 .../src/en/guide/upgrading/upgrading80x.adoc       | 131 +++-
 .../groovy/org/grails/forge/cli/CommandSpec.groovy |  50 +-
 .../plugin/commands/GrailsCliGradlePlugin.groovy   |  83 ++-
 .../gradle/plugin/core/GrailsExtension.groovy      |  15 +
 .../plugin/commands/CliAutoDiscoverySpec.groovy    | 129 +++-
 .../commands/LegacyCommandTaskDiscoverySpec.groovy |  44 ++
 .../cli-companion-autodiscovery/app/build.gradle   |  37 +-
 .../included-digit-bearing-plugin/build.gradle     |  21 +
 .../my-plugin-2fa-one/build.gradle                 |   1 +
 .../my-plugin-2fa-snapshot/build.gradle            |   1 +
 .../my-plugin-2fa-timestamp/build.gradle           |   1 +
 .../my-plugin-2fa-unspecified/build.gradle         |   1 +
 .../included-digit-bearing-plugin/settings.gradle  |   3 +
 .../cli-companion-autodiscovery/settings.gradle    |   9 +
 .../legacy-command-discovery/app/build.gradle      |  15 +
 .../legacy-command-discovery/gradle.properties     |   2 +
 .../legacy-command-api/build.gradle                |   3 +
 .../grails/dev/commands/ApplicationCommand.java    |  28 +
 .../java/grails/dev/commands/ExecutionContext.java |  22 +
 .../legacy-command-plugin/build.gradle             |  14 +
 .../example/legacy/HelloLegacyAppCommand.groovy    |  40 +
 .../src/main/resources/META-INF/grails.factories   |   1 +
 .../legacy-command-discovery/settings.gradle       |   3 +
 .../org/grails/cli/profile/AbstractProfile.groovy  |   4 +-
 .../ApplicationContextCommandFactory.groovy        |  62 +-
 .../factory/GroovyScriptCommandFactory.groovy      |   2 +
 .../grails/cli/profile/AbstractProfileSpec.groovy  | 119 +++
 .../ApplicationContextCommandFactorySpec.groovy    | 113 +++
 .../factory/LegacyPluginScriptCompatSpec.groovy    | 107 +++
 settings.gradle                                    |   1 +
 97 files changed, 6790 insertions(+), 107 deletions(-)

diff --cc grails-doc/src/en/guide/upgrading/upgrading80x.adoc
index 2389f9f34d,86ea9f63cc..cfb81c4111
--- a/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
+++ b/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
@@@ -1778,36 -1743,84 +1803,84 @@@ The renamed framework command packages 
  |`org.apache.grails.security.cli.*` (grails-spring-security-cli)
  |===
  
- **Third-party plugins must be rebuilt against Grails 8 for their commands to 
be available.**
- Apply the new `org.apache.grails.gradle.grails-plugin-cli` Gradle plugin, 
move command sources from
- `grails-app/commands` to `src/cli/groovy`, and update the contract imports — 
recompiling then
- regenerates the `grails-cli.factories` registration automatically and the 
companion `-cli`
- artifact is published alongside the plugin (see
- xref:commandLine#creatingCustomCommands[Creating Custom Commands] for the 
complete workflow).
- Hand-authored command registrations must move from
- `src/main/resources/META-INF/grails.factories` to `.../grails-cli.factories`. 
A Grails 7 command
- plugin dropped onto a Grails 8 application unchanged will not have its 
commands discovered.
- 
- **What this change means for upgrading an application.** Plugin re-releases 
are *not* a
- prerequisite for upgrading the application itself — the impact of the CLI 
split is bounded as
- follows:
- 
- * *Plugins that ship no commands* are entirely unaffected by this change.
- * *Command-bearing plugins that have not yet been rebuilt for Grails 8* keep 
their runtime
-   functionality — controllers, services, taglibs, and other artefacts 
continue to work exactly as
-   before, because the runtime plugin jar never depended on the command 
contract. Only the plugin's
-   *commands* are unavailable: their registrations in the legacy 
`grails.factories` location are
-   ignored (the clean break above), so they disappear from the command list 
rather than fail. The
-   commands return once the plugin publishes a Grails 8 release with a 
companion `-cli` artifact.
- * *The application's own commands* in `grails-app/commands` need only the 
import rename shown
-   above; the build wiring is automatic.
- 
- NOTE: The statements above are about the CLI split specifically. 
Independently of it, Grails 8
- removes APIs that were deprecated in Grails 7 — a Grails 7 plugin that 
avoided those deprecations
- generally continues to work on Grails 8 unchanged, while one that relied on 
them (or on other
- Grails 8 changes such as Spring Boot 4) needs an update for that reason.
+ ===== 35.1 Backwards Compatibility for Existing Command Plugins
+ 
+ Grails 8 provides a deprecated, execution-only compatibility layer for 
already-published Grails 7 command
+ plugins. Application commands created with `create-command` that implement
+ `grails.dev.commands.ApplicationCommand` or 
`grails.dev.commands.GrailsApplicationCommand` remain
+ available without a plugin re-release when the bridge is enabled. The 
deprecated
+ `grails.dev.commands.*` types are retained in 
`org.apache.grails:grails-core-cli-legacy`, which is
+ provisioned only on the command runner classpath when both `cliAutoProvision` 
and
+ `legacyCommandSupport` are enabled (`legacyCommandSupport` defaults to 
`false`), and registrations
+ in `META-INF/grails.factories` under `grails.dev.commands.ApplicationCommand` 
continue to be
+ discovered at runtime.
+ 
+ Enable the bridge in the consuming application:
+ 
+ [source,groovy]
+ .build.gradle
+ ----
+ grails {
+     legacyCommandSupport = true
+ }
+ ----
+ 
+ Or set the project property `grailsLegacyCommandSupport=true`.
+ 
+ When the bridge is disabled, Grails still inspects legacy 
`META-INF/grails.factories` resources at
+ command runtime without loading their command classes. If a plugin still 
publishes
+ `grails.dev.commands.ApplicationCommand` entries, Grails logs an error naming 
the plugin artifact.
+ An unknown command also includes the remediation to enable 
`legacyCommandSupport` or upgrade the
+ plugin. This detection does not resolve application dependencies during 
Gradle configuration.
+ 
+ Grails logs a one-time deprecation warning when it uses this compatibility 
layer. It is intended
+ to keep existing published plugins working while plugin authors migrate; it 
is deprecated and may
+ be removed in a future major release. No removal major has been announced.
+ 
+ Legacy application commands do not receive a dedicated Gradle task. Run them 
through the generic
+ `runCommand` task (or the shell, which routes legacy adapters there), for 
example:
+ 
+ [source,bash]
+ ----
+ ./gradlew runCommand "-Pargs=generate-controller example.Book"
+ ----
+ 
+ Named per-command Gradle tasks remain available only for migrated commands 
published in a
+ companion `-cli` artifact.
+ 
+ New and migrated commands should always compile against 
`org.apache.grails.core.cli.*` from
+ `grails-core-cli`. A plugin that intentionally continues to author commands 
against the deprecated
+ contract must declare `compileOnly 
'org.apache.grails:grails-core-cli-legacy'` explicitly. Consumers
+ of already-published Grails 7 command plugins must opt in with
+ `grails { legacyCommandSupport = true }` (or declare `grailsCliLegacy` 
themselves).
+ 
+ **What just works:**
+ 
+ * Code generation scripts created with `create-script`, including scripts 
packaged as
+   `META-INF/commands/*.groovy` such as `audit-quickstart` and 
`s2-quickstart`, are unaffected.
+ * YAML commands packaged as `META-INF/commands/*.yml` are unaffected.
+ * Existing published Grails 7 application-command plugins continue to work on 
Grails 8 without a
+   re-release once `legacyCommandSupport` is enabled.
+ 
+ **What consumers of Grails 7 command plugins must do:**
+ 
+ * Enable `grails { legacyCommandSupport = true }` (or 
`-PgrailsLegacyCommandSupport=true`), or
+   declare `grailsCliLegacy 'org.apache.grails:grails-core-cli-legacy'` 
explicitly when
+   `cliAutoProvision` is disabled.
+ 
+ **What plugin authors must do:**
+ 
+ * Migrate application commands to the `org.apache.grails.core.cli.*` API and 
register them in
+   `META-INF/grails-cli.factories`.
+ * Publish commands in a companion `-cli` artifact for the CLI classpath split 
and per-command
+   Gradle task support. See xref:commandLine#creatingCustomCommands[Creating 
Custom Commands] for
+   the complete workflow.
+ 
+ NOTE: The compatibility layer applies only to the legacy application-command 
contract. Independently
+ of it, a Grails 7 plugin that relies on APIs removed in Grails 8, Spring Boot 
4, or other Grails 8
+ changes still requires an update.
  
 -==== 36. jQuery Webjar Upgraded to 4.0.0
 +==== 37. jQuery Webjar Upgraded to 4.0.0
  
  The jQuery webjar managed by the `grails-bom` (`org.webjars.npm:jquery`) 
moves from 3.7.1 to 4.0.0.
  jQuery 4 is a major release: it drops Internet Explorer and other legacy 
browsers and removes long-deprecated utilities such as `jQuery.trim`, 
`jQuery.type`, `jQuery.isArray`, `jQuery.isFunction`, `jQuery.proxy`, and 
`jQuery.isWindow`.

Reply via email to