jamesfredley opened a new pull request, #16011:
URL: https://github.com/apache/grails-core/pull/16011

   ## Summary
   
   [#15948](https://github.com/apache/grails-core/pull/15948) split the CLI 
command tier off the application runtime classpath. As part of that split the 
command contract moved from `grails.dev.commands.*` to 
`org.apache.grails.core.cli.*` (with no forwarding alias) and command 
registration moved from `META-INF/grails.factories` (key 
`grails.dev.commands.ApplicationCommand`) to `META-INF/grails-cli.factories`.
   
   The classpath split is good and is preserved here. The side effect is that 
an **unchanged, already‑published Grails 7 command plugin silently loses all of 
its application commands on Grails 8**: the command classes no longer link 
(their `grails.dev.commands.*` supertypes are gone) and their factory entries 
are never read. Every plugin that ships an application command would otherwise 
have to be re‑released just to work on Grails 8, and Grails 8 is arriving late 
enough that the plugin ecosystem will not have caught up when users start 
upgrading.
   
   This PR adds a **deprecated backwards‑compatibility layer**, entirely inside 
the `grails-core-cli` tier (so it stays off the application runtime classpath 
and the split is preserved), so unchanged Grails 7 command plugins keep working 
**without a re‑release**, while nudging authors to migrate.
   
   ## The full picture: every way a plugin provides a command, and what happens 
on Grails 8
   
   There are several distinct mechanisms for authoring/shipping a command in a 
Grails plugin. This PR audited all of them so nothing is silently left broken:
   
   | # | Mechanism (how authored) | Packaged as | Discovered by | Broken by 
#15948? | This PR |
   |---|---|---|---|---|---|
   | 1 | `create-command` → `grails.dev.commands.ApplicationCommand` / 
`GrailsApplicationCommand` (runs with an application context) | class + 
`META-INF/grails.factories` (key `grails.dev.commands.ApplicationCommand`) | 
`ApplicationContextCommandRegistry` (now reads `grails-cli.factories` / new 
key) | **Yes — dead** (types removed, key + file changed, no alias) | **Fixed** 
— restored deprecated contract, adapter, registry dual‑load, runner unwrap, 
legacy per‑command Gradle tasks |
   | 2 | `create-script` → codegen script in `src/main/scripts/*.groovy` (runs 
without an app context) | `META-INF/commands/*.groovy` | 
`GroovyScriptCommandFactory` (compiled from source at runtime vs 
`GroovyScriptCommand`) | No (packaging + resolver unchanged) — **but** a 
separate Groovy 5 name regression broke hyphenated scripts | **Fixed** the 
hyphenated‑name regression (see below) |
   | 3 | YAML / multi‑step commands | `META-INF/commands/*.yml` | 
`YamlCommandFactory` | No | Unaffected — verified, documented |
   | 4 | `ServiceLoader` `CommandFactory` / profile commands | 
`META-INF/services` / profile jar | `ServiceCommandFactory` / profile mechanism 
| No | Unaffected — verified, documented |
   | 5 | Legacy Gant `scripts/*.groovy` (pre‑Grails 3) | — | removed in Grails 
3 | Already gone pre‑8 | Out of scope — noted |
   
   So the only mechanism actually broken by the CLI split is **#1 (application 
commands)**; that is the bulk of this PR. **#2 (codegen scripts)** are not 
broken by the split, but testing them surfaced a separate, real Grails 8 
regression (below), which is also fixed here because it otherwise breaks every 
hyphenated `*-quickstart`‑style script command without a re‑release.
   
   ## What this PR does
   
   **Application‑command compatibility (mechanism #1):**
   
   - **Restore** the deprecated `grails.dev.commands.*` command contract 
(`ApplicationCommand`, `GrailsApplicationCommand`, `ExecutionContext`, and the 
`io` / `template` helpers) into the `grails-core-cli` tier so pre‑compiled 
Grails 7 command classes link again. The restored public signatures were 
confirmed against Grails 7.1.1 bytecode.
   - **Adapt** legacy commands to the new contract via 
`LegacyApplicationCommandAdapter` / `LegacyApplicationCommandAware`, and 
discover them from the legacy `META-INF/grails.factories` key in 
`ApplicationContextCommandRegistry`. Each legacy command is instantiated **in 
isolation**, so one faulty command cannot suppress the rest; a real 
new‑contract command always wins a name clash. A one‑time deprecation warning 
nudges migration.
   - **Unwrap** the adapter in `GrailsApplicationContextCommandRunner` so 
Spring autowiring and the `skipBootstrap` lookup target the real legacy command 
that is subsequently executed.
   - **Register per‑command Gradle tasks** for legacy commands found on the 
runtime classpath (so `grails <command>`, which routes through a Gradle task, 
keeps working), degrading gracefully when the classpath cannot be resolved at 
configuration time (the generic `runCommand` task always works).
   
   **Codegen‑script fix (mechanism #2):** a hyphenated script filename (e.g. 
`audit-quickstart`) produced a class name without the hyphen under Groovy 5, so 
the command resolved under the wrong name (`auditquickstart`). 
`GroovyScriptCommandFactory` now uses the filename‑derived command name (a 
no‑op for non‑hyphenated commands). This restores `audit-quickstart`, 
`s2-quickstart`, `dbm-*`, `create-job`, etc.
   
   ## Testing
   
   - **Unit:** `LegacyApplicationCommandAdapterSpec`, 
`LegacyCommandRegistryLoadingSpec` (incl. an exception‑isolation regression 
where one throwing legacy command does not suppress valid legacy/new commands), 
`GrailsApplicationContextCommandRunnerSpec` (adapter unwrap), 
`LegacyPluginScriptCompatSpec` (hyphenated‑script name resolution).
   - **End‑to‑end functional example** under 
`grails-test-examples/legacy-commands` (+ `-plugin`): a plugin that ships a 
legacy `ApplicationCommand`, a legacy `GrailsApplicationCommand`, and a 
hyphenated codegen script; an `@Integration` spec boots a real Grails 8 
application and executes them through the registry. Verified running (`Grails: 
8.0.0-SNAPSHOT | Groovy: 5.0.7 | Spring Boot: 4.1.0`).
   - `codeStyle` passes on the changed modules.
   
   ## Migration path
   
   The compatibility layer is `@Deprecated` and logs a one‑time warning. Plugin 
authors should migrate to the `org.apache.grails.core.cli.*` command API and 
publish a `-cli` companion artifact for full support (classpath split, 
first‑class per‑command task discovery). The layer is intended to be removed in 
a future major release.
   
   ## Follow‑up (minor, non‑blocking)
   
   Reviewers flagged one minor gap: there is no dedicated TestKit test 
asserting the Gradle legacy per‑command task registration in isolation (its 
behavior is covered by the runtime functional test and confirmed via a task 
dry‑run). A focused TestKit fixture could be added later.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to