Copilot commented on code in PR #16011:
URL: https://github.com/apache/grails-core/pull/16011#discussion_r3610389201


##########
grails-core/src/cli/groovy/org/apache/grails/core/cli/ApplicationContextCommandRegistry.groovy:
##########
@@ -46,6 +53,37 @@ class ApplicationContextCommandRegistry {
                 commands[cmd.name] = cmd
             }
         }
+
+        loadLegacyCommands(ApplicationContextCommandRegistry.classLoader)
+        loadLegacyCommands(Thread.currentThread().contextClassLoader)

Review Comment:
   `loadLegacyCommands` is invoked for both 
`ApplicationContextCommandRegistry.classLoader` and the thread context 
classloader unconditionally. If those are the same (or both can see the same 
legacy command), this can instantiate the same legacy command twice 
(constructors may have side effects) even though the second instance will be 
discarded due to the name collision check. Consider only calling the second 
load when the classloader differs to avoid duplicate instantiation.



##########
grails-core/src/cli/groovy/org/apache/grails/core/cli/ApplicationContextCommandRegistry.groovy:
##########
@@ -46,6 +53,37 @@ class ApplicationContextCommandRegistry {
                 commands[cmd.name] = cmd
             }
         }
+
+        loadLegacyCommands(ApplicationContextCommandRegistry.classLoader)
+        loadLegacyCommands(Thread.currentThread().contextClassLoader)
+    }
+
+    @SuppressWarnings('deprecation')
+    private void loadLegacyCommands(ClassLoader classLoader) {
+        // Instantiate each legacy command in isolation: a single stale Grails 
7 command whose
+        // no-arg constructor (or getName()) throws under Grails 8 must be 
skipped with a warning,
+        // never abort the whole registry and take valid legacy and 
new-contract commands down with it.
+        List<Class<grails.dev.commands.ApplicationCommand>> legacyClasses = 
GrailsFactoriesLoader.loadFactoryClasses(
+                grails.dev.commands.ApplicationCommand, classLoader, 
FactoriesLoaderSupport.FACTORIES_RESOURCE_LOCATION)
+        for (Class<grails.dev.commands.ApplicationCommand> legacyClass : 
legacyClasses) {
+            try {
+                grails.dev.commands.ApplicationCommand legacyCommand = 
legacyClass.getDeclaredConstructor().newInstance()
+                ApplicationCommand command = new 
LegacyApplicationCommandAdapter(legacyCommand)
+                String name = command.name
+                if (commands.containsKey(name)) {
+                    continue
+                }
+                commands[name] = command
+                if (!legacyCommandWarningLogged) {
+                    LOG.warn("Command '{}' from a Grails 7 plugin was loaded 
through the deprecated grails.dev.commands compatibility layer. Ask the plugin 
author to migrate to the org.apache.grails.core.cli command API and publish a 
-cli companion artifact; this compatibility path will be removed in a future 
major release.", name)
+                    legacyCommandWarningLogged = true
+                }
+            }
+            catch (Throwable e) {
+                LOG.warn("Failed to load a Grails 7 legacy command from class 
'{}' through the deprecated grails.dev.commands compatibility layer; skipping 
it. Cause: {}",
+                        legacyClass?.name, e.message)
+            }

Review Comment:
   When a legacy command fails to load, the warning log omits the exception 
itself, so there’s no stack trace to diagnose the root cause (only 
`e.message`). Passing the throwable as the final argument lets SLF4J include 
the stack trace while keeping the warning message the same.



-- 
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