What are the best practices for Grails module dependency management?

IntelliJ 11.02 Ultimate
Grails 2.0.1
JK 6.0.29
Mac OS 10.7

I have a project that has several modules written in Java and a Web Application that is a Grails project.

What is the best way to manage my dependencies so that the Grails application can use the classes and services defined in the Java classes?  Specifically, if I create a new class in the Java project, I want be able to use that class in a Grails controller.

What we're doing
I'll describe what we're doing, but we're very open to doing things differently.

We have maven projects for the Java modules.
We run mvn install to push to our local repositories, this is a new project, so the libraries are labeled, "1.0-SNAPSHOT".
In our Grails project we enabled mavenLocal and referenced our library.

Once we refresh dependencies, we can see the classes from the modules.  So far, so good.

What's not working well

The problems start when I add a (for example) "Distraction" class to the Java library.

I run mvn install on the Java library (If I tar tf on the local maven repository, I can see my Distraction class in the output)
I run "Synchronize Grails Settings" on the grails project.  Distraction is not available (red highlight in IntelliJ and grails compile fails)
I run grails clean & grails refresh-dependencies on the grails project.  Same result.

Remove the ~/.grails/ivy-cache/<my company> directory, re-run grails clean & grails refresh-dependencies on the grails project.  Same result.

Remove the local maven repository version
Re-run mvn install on the libs
grails refresh-dependencies, Same result (red highlight in IntelliJ and grails compile fails)

The only procedure that seems to work reliably is:
Go to the ExternalLibraries->Grails User Library, find my jars and delete them (this removes them from the maven local repository as well)

Re-run mvn install on the libs

grails refresh-dependencies and then "Synchronize Grails Settings"



What we have tried
Mainly trying to figure out what can make this process reliable.
We have attempted to export the Grails project as a Maven project.  This worked very poorly.  Application restart times climbed significantly and we lost the ability to dynamically recompile after just a controller change in the Grails code.


What I want
[Need] Sane and reliable process for getting Distraction into the Grails project
[Need] Dynamic recompile for changes on the grails project (I understand that library dependencies are going to involve a restart)
[Really Want] Ability to step into library code from Grails
[Want] Fast restart after a library change

If I can get the above working with Maven for my libraries, that would be wonderful, otherwise I am very willing to learn something else.

12 comments
Comment actions Permalink

Sorry, I did not fully understood the question. My answer was not helpul... Sorry :8}.

0
Comment actions Permalink

I appreciate the effort, thank you.

0
Comment actions Permalink

I have tried you usecase on my machine.
I created Grails module and Java Maven module. After making changes in java module it's enough for me to run "mvn install" in java module and "refresh-dependencies" in Grails module. You don't need to run "clean" or "Synchronize Grails Settings" in Grails module after making changes in java module. Command "refresh-dependencies" copies "javaModule.jar" from Maven repository to ~/.grails/ivy-cache/...

When you navigate to java-class from Grails module IDEA must open source code in java module, but it opens decompiled code from jar. It's a bug, I will fix it in 11.1.0.
Sorry for long responce.

0
Comment actions Permalink

From your description, I'm not sure you quite followed the instructions that I laid out.

What you describe works fine, once.  The issue is that if you have ongoing development, the dependency is not refreshed.

The issue seems to mainly involve compilation.  We have noticed that sometimes the grails ivy cache gets updated and that if the change was a change to the implementation, it gets picked up.  But, if the change involves a new class or a change to a method on an existing class, Grails intellisense and compiler know nothing about it, and all the crazy steps that I describe above are our attempts to get the compiler into the know.

I have attached a simple example that illustrates what we're trying to do.  I think the order of steps is important, so I am going to list them explicitly:

Run Maven Install on the dependency module
Build the Grails Appp
You should be able to run and navigate to the FishController (it doesn't actually show anything)

Look at the code for the Fish Controller, it references the RedFish class from the dependency module; so far, so good.

Now, go to the dependencies module, move the BlueFish.java file next to the the RedFish file

Maven Install the dependency module, a quick check of the maven repo shows that the jar has indeed been updated.

$tar tf ~/.m2/repository/com/koanhealth/dependency/1.0-SNAPSHOT/dependency-1.0-SNAPSHOT.jar

META-INF/

META-INF/MANIFEST.MF

com/

com/koanhealth/

com/koanhealth/dependency/

com/koanhealth/dependency/BlueFish.class

com/koanhealth/dependency/RedFish.class

META-INF/maven/

META-INF/maven/com.koanhealth/

META-INF/maven/com.koanhealth/dependency/

META-INF/maven/com.koanhealth/dependency/pom.xml

META-INF/maven/com.koanhealth/dependency/pom.properties



Now, back at the grails project, I want to uncomment the BlueFish reference in the Fish controller.  That's where all my previously described gyrations begin.  I did try a simple refresh-dependencies, but it did not work, IDEA and Grails refuse to see my update.  The really aggrivating thing is that the updates are reflected in maven, just not in the grails ivy cache.

What do I do to get the Grails compiler to see BlueFish?

Notes:

  • I'll not hide the fact that I am new to JVM development, so it is entirely possible that I am doing something very wrong.  I don't think that's the case, but if you see something really idiotic in my setup, I will not mind if you point it out.
  • We have been suffixing our dependencies with the -SNAPSHOT.  If we don't use SNAPSHOT, it seems that the dependency is never refreshed.  This is consistent with the documentation that we have seen.  However, you did not use snapshot in your explanation, so I am curious if I have missed something.
  • I am working on Mac OS 10.7


Attachment(s):
grails_demo.zip
0
Comment actions Permalink

I've found the problem!!!
There is a bug in 'refresh-dependencies'. To find outdated dependencies Grails gets modification time of .pom files in maven repository. But 'mvn install' doesn't change modification time during coping of pom file to repository so 'refresh-dependencies' does not know what jar in ivy cache must be refreshed. This issue occurs with maven3 only, maven2 updates modification time of pom file after each 'install' goal calling. When I wrote previous message I tested with maven2 so I did not understand that is a problem.
As workaround you can use maven2 or make small changes in pom file before each 'mvn install'.
Note: I could not following steps provided by you on attached project. Grails App can't resolve dependency to com.koanhealth:dependency:1.0.0 . You have to write "com.koanhealth:dependency:1.0-SNAPSHOT" instead of "com.koanhealth:dependency:1.0.0" in BuildConfig.groovy.

0
Comment actions Permalink

BTW if you want to have the ability to step into library code from GrailsApp you have to make GrailsApp module depend on 'dependency' module and remove 'dependency-1.0-SNAPSHOT.jar' from dependencies of GrailsApp. Unfortunately current version of IDEA will add 'dependency-1.0-SNAPSHOT.jar' to dependencies of GrailsApp automatically, so you can not remove it. In 11.1.0 it will work fine.

0
Comment actions Permalink

Thank you!  Tried it with a simple touch and that cused the dependency to refresh correctly.  We'll see what we can do to work around the issue more permanently.  

The update dependencies issue sounds like a Grails issue, do you know if it has been reported to them?

You are, of course, correct on the issue with my project.  I had everything as 1.0-snapshot, then converted it to 1.0.0 to see if that was the issue.  Obviously, I didn't get it all changed back.

Thank you again,
Andy

0
Comment actions Permalink

Weird.  I had tried doing a module dependency and it blew up spectacularly in our application.  It does, however, work really well for my simple application.  For what we're doing, I think that is the best practice, I will try it again with our application.

Because the dependency is not specified in the BuildConfig, I believe that will mean that we have to build any app we deploy from within IDEA.  That's OK for the moment, but we would certainly want to move beyond this as we get into automated deployments.  Do you have any suggestions on how we might manage this?

I am thinking that we could have a step that manipulated the file and slid those module dependencies in, but I'm sure there's a better way.  What might be nice is if I could mark dependencies in the BuildConfig as IDEA module dependencies.  Then when IDEA invokes grails, it gives it a file with those items removed.  Then I can use command line and IDEA and the switching will be at least somewhat transparent to me.

I didn't quite understand what you were saying about removing the 1.0-SNAPSHOT.jar, perhaps you are describing this same thing.

Again, thank you for the help.

0
Comment actions Permalink

You understood me incorrectly. I meant dependency in IDE configuration, this dependencies are visible in IDE only, you should not change BuildConfig.groovy.
See attached screenshot.



Attachment(s):
a.png
0
Comment actions Permalink

If I understand you correctly, I should have the dependency in both places?

Honestly, that doesn't seem to work nearly as well as the module dependency by itself.  But if you only use a module dependency, you cannot execute grails from the command line.  That was the issue I was trying to work around.

==ALSO==
I don't know if you saw my other reply, but is the pom update issue a Grails or a Maven issue?  It doesn't seem like it would be an IntelliJ issue, Grails seems most likely.  I didn't see anything in the Grails JIRA that looked like the issue, do you know if it has been reported?

0
Comment actions Permalink

Two more things:

How should we handle transitive dependencies?  If I extend my example so that there is a library called "transitive_dependency" which is a dependency of "dependency".  This works fine, unless I attempt to use a class from transitive dependency in my Grails app.  IntelliJ is unable to find the transitive dependency class in the Grails project.  If I declare that transitive_dependency is also a module dependency of the Grails app, everything works correctly.  Is that how I should handle things?

When we try to run our real app with the module dependencies enabled, we get the following error:

Base Directory: /Users/andy/koan/Dev/Emr/src/koanhealth-emr-web

| Loading Grails 2.0.1

| Error Error executing script RunApp: Provider for javax.xml.parsers.SAXParserFactory cannot be found

javax.xml.parsers.FactoryConfigurationError: Provider for javax.xml.parsers.SAXParserFactory cannot be found

 at javax.xml.parsers.SAXParserFactory.newInstance(Unknown Source)

 at org.apache.ivy.core.settings.XmlSettingsParser.doParse(XmlSettingsParser.java:160)

 at org.apache.ivy.core.settings.XmlSettingsParser.parse(XmlSettingsParser.java:150)

 at org.apache.ivy.core.settings.IvySettings.load(IvySettings.java:412)

 at org.apache.ivy.core.settings.IvySettings.loadDefault(IvySettings.java:440)

 at org.apache.ivy.Ivy.configureDefault(Ivy.java:433)

 at org.apache.ivy.core.IvyContext.getDefaultIvy(IvyContext.java:201)

 at org.apache.ivy.core.IvyContext.getIvy(IvyContext.java:179)

 at org.apache.ivy.core.IvyContext.getSettings(IvyContext.java:215)

 at org.apache.ivy.core.module.status.StatusManager.getCurrent(StatusManager.java:42)

 at org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor.<init>(DefaultModuleDescriptor.java:198)

 at org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor.newDefaultInstance(DefaultModuleDescriptor.java:103)

 at org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor.newDefaultInstance(DefaultModuleDescriptor.java:63)

 at org.codehaus.groovy.grails.resolve.AbstractIvyDependencyManager.createModuleDescriptor(AbstractIvyDependencyManager.java:648)

 at org.codehaus.groovy.grails.resolve.AbstractIvyDependencyManager.parseDependencies(AbstractIvyDependencyManager.java:492)

 at org.codehaus.groovy.grails.resolve.DependencyDefinitionParser$parseDependencies.call(Unknown Source)

 at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)

 at grails.util.BuildSettings.configureDependencyManager(BuildSettings.groovy:1210)

 at grails.util.BuildSettings$configureDependencyManager.callCurrent(Unknown Source)

 at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)

 at grails.util.BuildSettings.postLoadConfig(BuildSettings.groovy:1102)

 at grails.util.BuildSettings.loadConfig(BuildSettings.groovy:999)

 at grails.util.BuildSettings$loadConfig$0.callCurrent(Unknown Source)

 at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)

 at grails.util.BuildSettings.loadConfig(BuildSettings.groovy:982)

 at grails.util.BuildSettings$loadConfig.callCurrent(Unknown Source)

 at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)

 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)

 at grails.util.BuildSettings.loadConfig(BuildSettings.groovy:959)

 at org.codehaus.groovy.grails.cli.GrailsScriptRunner.executeCommand(GrailsScriptRunner.java:339)

 at org.codehaus.groovy.grails.cli.GrailsScriptRunner.main(GrailsScriptRunner.java:225)

 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

 at java.lang.reflect.Method.invoke(Method.java:597)

 at com.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1231)

 at org.codehaus.groovy.grails.cli.support.GrailsStarter.rootLoader(GrailsStarter.java:234)

 at org.codehaus.groovy.grails.cli.support.GrailsStarter.main(GrailsStarter.java:262)

 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

 at java.lang.reflect.Method.invoke(Method.java:597)

 at com.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1231)

 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

| Error Error executing script RunApp: Provider for javax.xml.parsers.SAXParserFactory cannot be found

Any thoughts on that one?
0
Comment actions Permalink

The last one looked to be a version conflict between our version of Hibernate Annotations and Spring Framework and the ones that Grails references.

I can't seem to resolve the issue with hibernate, I have posted a question specific to that here: http://devnet.jetbrains.net/thread/435001.

0

Please sign in to leave a comment.