Support using custom CodeNarc rules defined within the Grails project.
This was discussed in:
This may require changes or enhancements to CodeNarc itself (rather than just this plugin).
From that thread:
It looks like there were some changes between these versions on how the classloader was setup when parsing the ruleset.groovy script. I was just wondering how others deal with their custom rules? Are there any integration tests that I could look at that try to load up and test with custom rules for the grails-codenarc-plugin, or am I the only crazy person who writes their own rules?
The specific change that I see that happened between the versions is in RuleSetUtil.groovy loadRuleScriptFile where the GroovyClassLoader is being setup with a parent of RuleSetUtils classloader rather than the Threads context classloader (default constructor of GroovyClassLoader). The change is commit 607 and bug #3190754 which was an attempt to solve a similar issue for a Gradle user.
I've verified thought the debugger that Thread.currentThread().getContextClassLoader().loadClass('myCustomRules') has my custom rules, but getClass().getClassLoader().loadClass('myCustomRules') results in a ClassNotFoundException.
I'm not quite sure how to get my classes onto RuleSetUtils's classloader, nor am I confident I even know what classloader that is. It makes much more sense to use the current threads context class loader since this at least is well defined given that you are not forking threads.
In the Grails context, it makes sense that the theads current context would have the classes that we need loaded, where as in the Gradle script it might not. A possible solution would be allow the ant task to accept some sort of classpath for custom rules, rather than users of it hoping that they already have their classes on the correct classloader.
Unfortunately, I think it is a known limitation in Grails that you cannot refer to static application classes from within a plugin. See:
As I understand it, the options include packaging your rules as a separate Grails plugin or jar file, and then adding that to the project's dependencies.
There is a note on the Grails CodeNarc Plugin home page:
Note that your custom ruleset cannot refer to a custom rule defined within the Grails application. This is due to a Grails classpath issue, discussed here.
but I would like to beef that up with more information and options, so please let us know if you make any progress with those options, or if you have any brilliant ideas. Thanks.
It is true that a plugin has no knowledge of classes within the Grails application at compile time. But there’s another way that other plugins handle this situation. For example, the Spring-Security plugin needs to know about the domain class which represents a credential. This domain class lives in the Grails application but is configured (through spring in this case) and is accessible on the class loader at runtime to the plugin at runtime. In general almost all of the most popular plugins manipulate and use Grails application classes at runtime. I think that Codenarc could do this too. The discussion that is linked to on the codenarc plugin's page discusses using a application class statically in a plugin whereas we are talking about using them dynamically.
This is an issue with two separate uses cases (Grails and Gradle) needing two separate class loading strategies. If I switch line 46 of RuleSetUtil.groovy within Codenarc itself from
GroovyClassLoader gcl = new GroovyClassLoader(getClass().classLoader)
which uses (I believe) ants/gants classloader back (codenarc svn commit 607 ) to
GroovyClassLoader gcl = new GroovyClassLoader()
which is the same as
GroovyClassLoader gcl = new GroovyClassLoader(Thread.currentThread().getContextClassLoader())
everything works really well with Grails. If you give me a day or so I will put together a few test cases that demonstrate this.
It would be great if we could get an authoritative opinion on the dynamic class reference issue, which is given I have a configuration file in my Grails application and that configuration file references a class elsewhere in my Grails application, should it be possible for a plugin reading that configuration file to have the referenced class available on it's classloader?
One thing that occurred to me, if there is no obvious technical solution that addresses both use cases (gradle and grails) out of the box, then perhaps another option would be to make the classloader behavior configurable, perhaps through a configuration property or a system property.