I recently started to experience some very interesting issues with Groovy, Grails, and STS/GGTS while I was teaching some recent classes. I believe that most of them boiled down to idiosyncrasies on the computers in the classroom, and really most of them appeared when the students used their own computers.
So I decided to undertake a quest to find out what happens when you use Groovy, Grails, and then throw STS into the mix. Yes, I really did have to say "a quest." Anyways, it turns out that the rules are actually simple, but the folks at Apple, Microsoft, Oracle, and Pivotal (these are only ordered alphabetically) assumed that you would either not need them or learn by osmosis what they are. The good news is: you can manage multiple installed versions of Groovy, Grails, and most importantly Java, you just have to know what affects them!
By the way, even if you are not interested in the problems with Groovy and Grails, the first two sections are also very important for Java programming.
First a Word About Linux, OS X, and Windows
Well, the rules for finding an executable on Windows are the same as the rules for locating a dynamic-link library because from the OS point of view they are the same thing. The current document in Visual Studio 2013 that describes this is at http://msdn.microsoft.com/en-us/library/7d83bc18.aspx. So you do not have to go there, the rules are:
- Look in the directory containing the current executable; this will be the directory that cmd.exe is located in.
- Look in the current directory.
- Look in the %system% directory, which is the same as %windir%\system32 directory. Usually, it's c:\windows\system32.
- Look in the %windir% directory.
- Finally, look in the directories in %PATH%. This will be important when we get to Java!
On Linux and OS X, which is substantially FreeBSD, the PATH environment variable plays a more substantial role at the command line. The only directories searched are the ones on the path.
But when the GUI comes into play things are a little different. On Linux, the GUI of choice kicks in before any shell startup files are read, but the GUI usually does have a startup that can be adjusted. In OS X, there are precious few legitimate places to adjust the path before the GUI starts. The good news is that the GUI settings do not really matter for what we want to do.
Second a Word About Java
You may already know these rules hands-down, but let's review them anyways. You could be surprised:
- At a command prompt on Windows a java command will invariably execute %system%\java no matter how you try to set the path. This happens because %system% is always checked before any directory in the path.
- The source code for java.exe on Windows shows that the program ignored JAVA_HOME and looks in the registry for the version of Java to run and the location of the JRE. The registry paths are:
- HKLM/SOFTWARE/JavaSoft/Java Runtime Environment/CurrentVersion
- HKLM/SOFTWARE/JavaSoft/Java Runtime Environment/1.X/JavaHome
- On Linux /usr/bin/java links to /usr/java/default/bin/java. /usr/java/default links to the desired jdk or jre in /usr/java, usually the most recent version. You can always override the default by changing the link /usr/java/default, or you can force a particular java executable by specifying its directory earlier in the path.
- On OS X /usr/bin/java and /usr/bin/javac link to executables in the directory /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands. You can always override the default forcing a particular java executable by specifying its directory earlier in the path.
- These reasons are why scripts like startGrails always find and use the fully qualified path to the Java defined by JAVA_HOME.
- Unless otherwise configured, STS/GGTS will launch on Windows using %system%\java.exe, and on Linux and OS X it will use /usr/bin/java. Gotcha! See the last part for instructions on how to set the specific JDK to use.
And a note: Grails adds the the instrumentation library libinstrument.dylib, a library included in the JDK. The class JavaLaunchHelper is defined in both the Java command and in the library, causing a warning about the conflict to be produced. This is a problem in later versions of Java and has nothing to do with Grails. Our fix could be to remove the library, but better yet we will just ignore the warning.
Now, about Groovy on the Command Line
We typically set GROOVY_HOME and add the Groovy bin directory to the path. That's nice for running Groovy from the command line. But the only place that uses GROOVY_HOME are the scripts in the Groovy installation. When you have multiple versions and forget to set GROOVY_HOME when switching between them it will lead to cross-version classloader problems.
So my best advice is only set the GROOVY_HOME variable if you know that some other product you are using looks for it to launch the correct Groovy. Groovy on the command line is launched from a script groovy, which in turn launches startGroovy, which will figure out what GROOVY_HOME really is when it isn't set.
Grails on the Command Line
Grails 2.3 and earlier will only work with Java 6 or Java 7. Java 8 support will be included in Grails 2.4.
- In the Grails directory the grails script (in bin) launches the startGrails script twice, once to set the environment and the second time to launch grails. startGrails uses the JAVA_HOME environment variable to identify which Java to execute. When JAVA_HOME is not set, startGrails defaults to the operating system's idea of which Java to use (see the notes on Java above).
- If GRAILS_HOME is defined startGrails will use it, otherwise it will default to the Grails installation directory containing the script. startGrails hardwires jar files specific to it's Grails version into the class path to launch Grails. When defined GRAILS_HOME must point to the Grails being launched. If GRAILS_HOME points to a different installation than the one containing the script being executed then Grails will fail to launch because the required jar files will be missing. Seriously I do not have a good reason to define GRAILS_HOME; if the grails command is in the environment PATH variable it doesn't do anything, grails will identify it itself.
- GROOVY_HOME is irrelevant when to Grails. It always uses Groovy from the jar file in it's library: ${GRAILS_HOME}/lib/orgy-all/jars/groovy-all-X.X.X.jar (in Groovy 1.X the jar file is right in the lib directory, 1.X does not divide the library files).
- The Grails console has a bug in version 2.3+ and will throw an exception about a missing consoleText property when a script is loaded. After the exception the console does not function properly anymore. Update: the console bug appears to have been fixed in version 2.4.0.
Grails in STS & GGTS
I figure that you already have the Grails IDE and the Groovy compiler plugins installed. They're already there in GGTS, and in STS they can be installed from Help -> Install New Software.- The Grails IDE plugin by-passes the grails and startGrails scripts and launches Grails directly from the Grails jar file in the Grails installation. The GRAILS_HOME directory is irrelevant when commands are run from the plugin.
- When the IDE launches Grails to create a new project, Grails is launched using the Java that the IDE is launched with. When Grails is launched in a project to run a command, the IDE always launches it with the Java runtime the project is configured with. Gotcha! That means that the IDE must be configured to use Java 7, not 8! See the next part for instructions on how to fix that.
- When a Grails project is created in the IDE, sometimes the Java is configured wrong in the project: it may be too low or too high. Got to the properties for the project and open the "Java Compiler" pages, then check the box to "Enable project specific settings," and under "JDK Compliance" set the "Compiler Compliance Level" to the right version.
- Grails invoked from the IDE always uses the jar file in it's own library to run Groovy: {grails directory}/lib/org.codehaus.groovy/groovy-all/jars/groovy-all-X.X.X.jar (the jar file is in the lib directory for Grails 1.X, because 1.X does not divide the jar files into subdirectories).
- The Groovy compiler in the IDE should be set to the same major and minor version as the jar file in the Grails installation (you should be able to ignore the third "update" number). When they are not matched, the IDE will compile code with one compiler and Grails with another, and that will lead to problems. When Grails creates a project the Groovy version should be set to match the Grails library. From the IDE preferences expand Groovy on the left, select the compiler page, and set the compiler version to match the project.
- More recent versions of STS/GGTS and the Groovy plugin no longer support 1.X, so it is hard to match the previous requirement in the IDE. If you must use an older version of Grails the only choices are to set the Groovy compiler in the IDE to the lowest possible version and hope for the best, or update the Groovy jar file and the groovy-starter.conf in Grails to a later version and again hope for the best.
- Changing the Groovy compiler level in the project or checking the box to ignore compiler mismatches only suppresses the error messages, it does not fix the problem of using mismatched compilers!
- Later versions of Grails include i18n support for geographical locales. Grails will resolve the multiple messages.properties files when the application is compiled. But Eclipse/STS/GGTS does not understand that and it will issue warnings about the messages.properties files conflicting. The warnings may be safely ignored. Update: these warnings appear to be resolved in Grails 2.4.0.
- When a Grails application is launched with "Run As -> Run on Server" the default environment is "production." To change the environment to development go to the project properties, select the "Grails Run On Server" page, and then check the "dev" radio button.
Nailing Java in STS & GGTS
Big clues that Java 8 is being used with Grails 2.3 or earlier are compiler problems. Grails may terminate unexpectedly, or there may be Groovy compiler errors. A specific Groovy error that crops up identifies an ambiguous withFormat method declaration in all of the controller classes.
In Windows and Linux at the top level of the STS/GGTS folder is an STS.ini or GGTS.ini file. This is an eclipse.ini file, it is just renamed for the Pivotal versions. On OS X the actual application and the .ini file are located in the folder STS.app/Contents/MacOS/ (or GGTS.app) relative to the installation folder.
In the file there are arguments to control the Java virtual machine. By default the -vm argument is not present. Add -vm <the path to the JDK/value of JAVA_HOME> to the line in front of -vmargs. So it will look something like this partial example (the example is from OS X):
...
-vm /Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home
-vmargs
...
-vm /Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home
-vmargs
...
Now when the IDE is launched it will always run by launching Java from the designated JDK. Warning: if you delete or move the designated JDK you will have to change this file or the IDE will not launch.
Team Projects with Grails
Many projects are shared and built in a team environment using a source-code management system: Subversion, Git, etc. Project teams are an excellent way to go, but Grails presents two problems in this environment.The first problem is that the Grails plugin always writes absolute path names into the Eclipse .project file, even if the file is inside the same project. An absolute path from the root of the filesystem does not work when the project is checked out by multiple programmers because things may not be in the same place on different computers. A path that references a project in a user's folder is immediately a problem.
The second problem is that SCM software does not record anything about empty folders. So if you create an empty Grails project, commit it to an SCM repository, and then check it out on another machine you will discover that it will not build because the domains, controllers, and other folders are missing!
The best practice for the second problem is to create a dummy file in every folder before committing. In Git we often use a .gitignore file to do this because it is already an expected file in the Git environment.
The first problem may be addressed by not committing .project files. Then the best practice is to commit a copy of the initial .project file (we use .project.base) for other programmers to use when they check out the project for the first time. There is an in-depth discussion of the whole team programming problem and best practices for all Eclipse projects at X.
So that covers all of the relevant stuff that I've found. It explains all the errors that I've encountered. Many of them were cross-version problems with Java or Grails. I hope that this reduces the problems that you may be having in your environment. Good luck to you!
No comments:
Post a Comment