Skip to main content

Maven POM "composition" by means of profiles

Posted by fabriziogiudici on July 19, 2011 at 9:17 AM PDT

Maven fundamental principle is to translate your esperience and best practices into machine-readable configuration so to enforce your best practices in your build process. As Wikipedia says:

A maven (also mavin) is a trusted expert in a particular field, who seeks to pass knowledge on to others. The word maven comes from the Hebrew, via Yiddish, and means one who understands, based on an accumulation of knowledge.

That's one of the reasons superPOMs are for: to accumulate your plugin configurations and practices in a reusable way by means of inheritance. In fact, my personal superPOM is getting quite large (1400+ lines) and it's valuable to refer to it from all my projects without cut & paste.

But one limitation is given by the fact that Maven only supports single inheritance, such as Java. Let's suppose I have some specific configuration for very different scenarios/tools such as:

  • working with webapps and Jetty or Tomcat
  • using AspectJ
  • using Android
  • having a specific, customized release process

While all my projects always share the customized release process, some aren't using Jetty, or Android, or AspectJ. This on one side means that Jetty, Android or AspectJ stuff shouldn't be in the superpom; but since I can't do multiple inheritance, how do I reuse my knowledge?

The answer is: with profiles. A profile is a subsection of a POM that usually is ignored, but can be made part of the effective POM by means of, say, a command-line switch. For instance, this is my Jetty configuration:

<profile>
    <id>jetty</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <configuration>
                    <stopPort>${tft.jetty.stopPort}</stopPort>
                    <stopKey>${tft.jetty.stopKey}</stopKey>
                    <scanIntervalSeconds>${tft.jetty.scanIntervalSeconds}</scanIntervalSeconds>
                    <webAppConfig>
                        <contextPath>${tft.webapp.contextPath}</contextPath>
                        <baseResource implementation="org.mortbay.resource.ResourceCollection">
      <!-- Workaround for Maven/Jetty issue http://jira.codehaus.org/browse/JETTY-680 -->
      <!-- <resources>src/main/webapp,${project.build.directory}/${project.build.finalName}</resources> -->
                            <resourcesAsCSV>src/main/webapp,${project.build.directory}/${project.build.finalName}</resourcesAsCSV>
                        </baseResource>
                    </webAppConfig>
                </configuration>
            </plugin>
        </plugins>
    </build>
</profile>

I've left the comments about a bug workaround (copied from the web) because they are part of the problem. As part of your knowledge of Maven and plugins there are also bug workarounds (I'll illustrate a moderately complex one in the next blog post). As those workaround usually evolve and - hopefully - sooner or later disappear, you don't want to copy & paste them multiple times around. 

Back to the profile shown above, it can be enabled by running Maven with the -Pjetty command-line switch. Now, the problem is that I want it to be always enabled in some modules and not in others (while Jetty support could be only needed in some tasks, such as testing, making the use of a command line switch acceptable, think of AspectJ: it's required during the compilation phase of modules using it, or you'll produce invalid artifacts). Now, the command line activates a profile for the whole build, so if you're launching a build from the root of a complex, multi-module project, the profile will be enabled even when you don't need it. It's a all-or-nothing-at-all kind of thing.

Profiles can be enabled in other ways too. For instance, by checking the existence of a system property. Unfortunately, that's a system property, not a property in the POM itself (that's why the profile can control the values of POM properties, thus it can't be controlled by them at the same time). And we're back again at the beginning, as system properties can't be singularily enabled or disabled for each module.

Fortunately, profiles can be also activated when Maven finds a certain file. That's the solution. My Jetty profile actually contains this section:

    <activation>
        <file>
            <exists>src/config/activate-jetty-profile</exists>
        </file>
    </activation>
Now, I just need to create an empty file with the name src/config/activate-jetty-profile for modules when I want Jetty support. Of course, the Tomcat, AspectJ etc... profiles work in the same way, just with different activating file names. By creating the right files, I can activate profiles in any configuration I like.
Even though I'm still working with inheritance as it's the only tool that Maven offers me to reuse stuff in POMs (*), this approach offers me a sort of composition feature, as I can compose as many pieces of POMs as I want. Composition is better than inheritance, even in POMs.
This makes me live happy until Maven 3.1 is released, bringing POM "mixins" out-of-the-box.

 

(*) Actually there's a form of composition that can be done between POMs (by means of the "import" scope), but it only relates to the dependencyManagement section

 

Related Topics >>