Coming Up for Air

Custom Maven Packaging Type

As I’ve noted in a previous post, I recently moved my blog from Awestruct to JBake. This also allowed me to migrate the building and publishing of the blog contents to the toolchain that I know pretty well (Maven). What bothered me, though, was that my POM defined the project as a jar packaging type: the build produces no jar file and, in fact, doesn’t process any Java at all. What I wanted, then, was to be able to define the lifecycle in such a way the the compile phase didn’t try to compile anything, and the install phase didn’t try to put anything in my local repo. Unfortunately, either I’m a bit dense, or the documentation wasn’t very clear (it’s likely a combination of both :). At any rate, I finally had a eureka moment late last night and figured it out. Here is a distillation of my findings.

To define a custom packaging type, you define a custom lifecycle and put the packaging type in the role-hint element. All of this is done in components.xml, which is found in src/main/resources/META-INF/plexus. This file can be in a project whose packaging is either pom or maven-plugin (others may be possible. I haven’t tried).

At any rate, here is the components.xml which accomplishes my goal (no pun intended ;) for the JBake Maven plugin:

<component-set>
    <components>
        <component>
            <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
            <role-hint>jbake</role-hint> (1)
            <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
            <configuration>
                <lifecycles>
                    <lifecycle>
                        <id>default</id> (2)
                        <phases>
                            <generate-resources></generate-resources>
                            <process-resources></process-resources>
                            <compile>
                                ${project.groupId}:${project.artifactId}:${project.version}:generate (3)
                            </compile>
                            <process-test-resources></process-test-resources>
                            <test-compile></test-compile>
                            <test></test>
                            <package></package>
                            <install></install>
                            <deploy></deploy>
                        </phases>
                    </lifecycle>
                    <lifecycle>
                        <id>site</id> (2)
                        <phases>
                            <pre-site></pre-site>
                            <site>
                                ${project.groupId}:${project.artifactId}:${project.version}:generate
                            </site>
                            <post-site></post-site>
                            <site-deploy></site-deploy>
                        </phases>
                    </lifecycle>
                </lifecycles>
            </configuration>
        </component>
    </components>
</component-set>
  1. role-hint defines the new packaging type

  2. We’re overriding the default and site lifecycles, providing goals only for the relevant phases.

  3. Note how the desired goal is specified.

On the plugin side, that’s literally all there is to it. When the target project is configured correctly, all the user must do to generate the site is issue a simple mvn, mvn compile, or mvn site, and the site is generated in target/${project.artifactId} (by default).

The plugin build does, though, need one tweak:

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

This allows us to update the project metadata without having to edit components.xml. Bump the version? Change the artifactId? No problem, thanks to resource filtering.

In the target project(s), you obviously need to configure the plugin. For my blog build, that looks like this:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.steeplesoft</groupId>
    <artifactId>steeplesoft-blog</artifactId>
    <name>Steeplesoft Blog</name>
    <version>1.0-SNAPSHOT</version>
    <packaging>jbake</packaging> (1)
<!-- ... -->
            <plugin>
                <groupId>br.com.ingenieux</groupId>
                <artifactId>jbake-maven-plugin</artifactId>
                <version>0.0.10-SNAPSHOT</version>
                <extensions>true</extensions> (2)
<!-- ... -->

Note that we’ve changed the packaging type now to jbake (1), and we’ve add the plugin to the build with extensions set to true (2). This is very important, as I understand it, as it instructs Maven to load the plugin early enough in the process so that our new lifecycles are applied to the project.

And that’s all there is to it. For the curious, you can see this change in action in my fork of jbake-maven-plugin on GitHub. I’ve submitted a PR, but the main project doesn’t seem to be too active, so we’ll what happens and go from there.

At any rate, I hope this adds some clarity to the topic of custom Maven packaging types. If you have questions, comments, criticisms, etc., hit the form below.

tags: Maven

Quotes

Sample quote

Quote source