One of the larger features of the recently released update of Grails 2.1 is improved Maven integration support. Essentially what this means is that you can now use Maven for the complete build lifecycle. Maven was previously supported, but it only worked for traditional Maven/Java dependencies and not Grails plugins. As a result, you had to configure the Grails plugins in the
BuildConfig.groovy file and the java dependencies in the POM file. With Maven 2.1, you can now support both in the POM file. The dependency block in
BuildConfig.groovy becomes ignored at that point. This is not a huge ordeal for most developers as the default Grails dependency management through Ivy works great. However, if most of your environment is Maven or you use a custom Nexus repository, it is huge news. The problem with prior releases is that the Ivy process to locate dependencies was long and cumbersome as it had to search every repository (see my previous blog post). Using pure Maven allows Maven to directly locate artifacts and better handle dependencies, especially if not using the conventional packaging structure or version schemes. In this article I will touch on the base support. In future articles I will explain plugin projects, plugin dependencies, and multi-module projects.
Enough talking, let’s dig into it. The easiest way to get started is to simply use the
create-pom command. For example, run the following commands to create a new application and initialize it. If you already have an application, simply run the
#> grails create-app myapp
#> cd myapp
#> grails create-pom com.mycompany.groupid
The application directory should now contain a
pom.xml file. At this point, you should be able to successfully run
mvn package to generate the application WAR. That is basically all there is to it. You can now update the POM to your heart’s content and manage the project purely from Maven.
Let’s dig a little deeper though. The default
pom.xml that Grails creates is a good starting place, but it’s always good to crack it open and modify before getting too far. The first thing you will notice is that the
groupId is set to the value that was passed in when
create-pom was invoked. The second thing is that the
artifactId is set to the name of the application. You are free to modify the
groupId as needed. The
artifactId, on the other hand, must match precisely with what is defined in
application.properties. If you attempt to diverge them, the Grails validate plugin in Maven will cause the build to fail. The
version element also has a default version. You may also note that
application.properties contains a version number. The Grails maven plugin actually syncs those two versions so that
application.properties matches what is in the POM. It also does this for the Grails version and Grails dependency. In other words, you only need to change the version numbers in the POM, not in
application.properties. (Note that there is a slight bug in Grails 2.1.0 that the version syncing does not occur when “fork” mode is used…see http://jira.grails.org/browse/MAVEN-175).
The generated POM also does not contain any parent project. At this point, you can set the parent to either your company super-POM or to one of the standard super-POMs in the Maven ecosystem. You may also update the
description values to more meaningful values.
The next portion is the packaging. This is the important piece as it tells Maven how to build and manage the project. The
grails-app packaging informs Maven to essentially package the application as a Grails application which results in a WAR being deployed. You may also use
grails-plugin for plugin projects and ZIP deployments, but I will get to that in a later article.
Next comes the Maven properties. By default, Grails adds a single
grails.version property for easier referencing in dependencies. Whenever you need to change the release of Grails your application targets, simply update that property.
After the properties comes all the dependencies. When you invoke
create-pom, Grails attemps to discover all your existing dependencies, plugins, etc based on
BuildConfig.groovy. Those dependencies are then copied to the dependency section in the POM. The first entry is usually grails-dependencies which references a shared POM that contains all the built-in and core Grails dependencies for a given version. This makes it easy to reference Grails without including several other dependencies. Note that the
type must be
pom. The second entry is for testing dependencies required to test your application. The remaining entries are either standard dependencies (JAR) or Grails plugins (ZIP). You can change these and add/remove as needed. You will notice that the Grails plugins all have a
zip. When plugins are deployed in Grails, they are deployed as ZIP files that contain the project structure.
plugin section follows the dependencies. This section includes the Grails Maven plugins to perform the custom builds and delegate to the Grails command line runner. These blocks of code should NOT be removed. The best thing to do is add a pluginManagement block to a parent POM project and include these plugin sections there. You could also create a custom Maven profile that triggers when the existence of a grails-app directory is found and then automatically defines these plugins. Then, you simply add the POM as the parent and the plugin block is no longer needed. This makes managing all of your Grails projects substantially easier and centralized.
The final section of interest is the
repositories. Grails has its own Maven repository for storing plugins and its core dependencies. This is partially how the plugin support was added into Maven. Previously, the plugin repository was only supported by Ivy. This section should also NOT be removed. Otherwise, you would not be able to resolve dependencies properly. Again, the best thing to do is include these repositories in a parent or super-POM, so they are centralized and not needed in the individual Grails projects. You can also add any other repositories such as your private company repository where private plugins or dependencies may be located. If you maintain your own Nexus server, you could optionally add the Grails repositories as proxied repositories. In this way, you would only need a single reference to the single Nexus server and reference all other repositories through it.
That is the high level picture of how the Grails integration works in Maven. Now, whenever you invoke
mvn clean package for instance, Maven delegates to a custom Grails Maven plugin which, in a sense, invokes
grails clean and
grails package. The big difference is that the dependencies come from Maven rather than through Grails. There is obviously much more to how that integration directly works including how plugins are located, found, and then extracted locally to the
plugins subdirectory and installed to the local application automatically.
One last thing to note is that if you are using the release plugin in Maven, you will want to add some configuration to handle the
application.properties syncing. If you recall, the Maven integration updates the
application.properties version to the version in the POM so you don’t have to. If you are using the Maven release plugin, though, Maven updates the POM automatically to a release version, deploys the code, and then updates to the next development version and commits that file. As a result the application properties file gets out of sync with what is in source control. This is not a big deal, since running locally will re-sync the version anyways. You could commit the application properties file as part of the
completionGoals property of the Maven release plugin, but it only gets the release version, not the next development version. You could fix that by also adding the clean goal so it causes Grails to re-sync with the latest POM.
<plugin> <artifactId>maven-release-plugin</artifactId> <version>2.3.2</version> <configuration> <completionGoals>clean scm:checkin -Dmessage="sync application.properties"</completionGoals> </configuration> </plugin>
Otherwise, you could ignore the updates to the application.properties and let the versions diverge and Maven automatically re-sync them. If you do that, you would be best to add the
checkModificationExcludes section to the Maven release plugin to ignore that file. Otherwise, if it is modified locally before running the release, then Maven will complain. Note that you may also want to add this to the preparationGoals as well so that the application.properties in the tagged version will be accurate.
<preparationGoals>clean verify scm:checkin -Dmessage="sync application.properties"</preparationGoals>
The next article I write will talk about creating plugin projects in Maven and adding as a dependency to another project. Until then, enjoy the much improved Maven support in Grails!
Other Articles in this Series:
- Grails 2.1 and Maven Integration: Simple Project
- Grails 2.1 and Maven Integration: Plugins
- Grails 2.1 and Maven Integration: Multi-Module Projects