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.
I have tried a few things, still without any luck.
I was using a Grails 1.3.6 project. I tried to load a ruleset that referenced a rule script file that imported another class. I also tried directly referencing a rule class (defined in sr/groovy) – which did not work, as expected.
I tried using an older version of CodeNarc: 0.12, which was before the CodeNarc code change to RuleSetUtil mentioned above. That did not seem to make any dfference in referencing/loading a class from a separate file. Perhaps I was not trying the same thing that Ben mentioned in his original posts.
Note that the code change that Ben mentioned was to the loadRuleScriptFile() method of RuleSetUtil, which is only used to load Groovy rule scripts, not rule (precompiled) classes.
I also tried making a custom version of CodeNarc with that same code change Ben suggested. Same results.
Any updates on this issue?
Not yet. I'll take another look at this from the CodeNarc point of view (rather than from the plugin), but it will be at least a month before I get to that.
I have made a change in CodeNarc to enable optionally using Thread Context classpath. See https://sourceforge.net/p/codenarc/bugs/157/
That will be available in CodeNarc 0.22 and then in the 0.22 version of the Grails CodeNarc plugin.
That should restore the ability to define rule scripts locally within the Grails projects.
Available in Grails CodeNarc 0.22.
Note this allows CodeNarc rule scripts (not rule classes), though the scripts can reference other classes within the Grails application.