Maven provides an easy mechanism for packaging applications in the standard formats and when a custom packaging is required, the Assembly plugin is the most common option. Though it is a good solution, it tends to overload the build specification with its configuration and the descriptor file. Also, it is not easy to share or reuse among similar projects.
A custom packaging is an appropriate alternative for this cases, as all the magic can be encapsulated inside a Maven plugin, and the pom would look like:
<packaging> my-custom-packaging </packaging>
Lets create a custom packaging, called jzip for packaging the application in a zip file with a start script.
A packaging for zip format
A custom packaging is distributed in a Maven plugin, so the first thing to do is to create a project for this type of artifact using the following archetype:
mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-plugin -DgroupId=org.softwaredistilled -DartifactId=mypackaging-maven-plugin
Each Maven packaging requires a build life cycle to be defined. It is possible to include more or less phases, but for this example, we will define a build cycle equals to the standard. A file named components.xml under META-INF/plexus folder must be created with the following definition:
<?xml version="1.0"?>
<component-set>
<components>
<component>
<role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
<role-hint>jzip</role-hint>
<implementation>
org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
</implementation>
<configuration>
<lifecycles>
<lifecycle>
<id>default</id>
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:resources
</process-resources>
<compile>
org.apache.maven.plugins:maven-compiler-plugin:compile
</compile>
<process-test-resources>
org.apache.maven.plugins:maven-resources-plugin:testResources
</process-test-resources>
<test-compile>
org.apache.maven.plugins:maven-compiler-plugin:testCompile
</test-compile>
<test>
org.apache.maven.plugins:maven-surefire-plugin:test
</test>
<package>
org.softwaredistilled:mypackaging-maven-plugin:zip
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:deploy
</deploy>
</phases>
</lifecycle>
</lifecycles>
</configuration>
</component>
<component>
<role>org.apache.maven.artifact.handler.ArtifactHandler</role>
<role-hint>jzip</role-hint>
<implementation>
org.apache.maven.artifact.handler.DefaultArtifactHandler
</implementation>
<configuration>
<type>jzip</type>
<extension>zip</extension>
<language>java</language>
<addedToClasspath>false</addedToClasspath>
</configuration>
</component>
</components>
</component-set>
In each phase, one goal (or eventually more) is specified to be executed during the build process. For this example, only the package phase has been modified in order to execute a goal that creates the zip file.
The mojo that handles the zip goal, will be distributed within the same plugin as the packaging definition, and would look like any other Mojo:
@Mojo(name = "zip")
public final class ZipMojo extends AbstractMojo {
@Component
private MavenProject project;
@Parameter(property = "project.build.directory", readonly = true)
private String outputDirectory;
@Parameter(property = "project.build.finalName", readonly = true)
private String finalName;
@Override
public void execute() throws MojoExecutionException {
File artifact = new File(this.outputDirectory +"/"+ this.finalName + ".zip"));
...
Here a zip file is created using a library like commons compress.
Then the project sources and a start script are added to the zip.
...
this.project.getArtifact().setFile(artifact);
}
}
The important part of the code above, is setting the file to the project's artifact. This will tell Maven which file is the final artifact in order to be installed or deployed in a repository.
Once the plugin is finished and deployed in a repository, any project can use the packaging in the following way:
But, as the packaging is not distributed with Maven, a reference to the plugin must be specified in the build section of the project pom:
The example above can be explored deeper in EasyPack Maven Plugin. This plugin facilitates the distribution of Java applications in tar, zip and tar.gz formats, by defining three new packaging types: jzip, jtar and jtargz.
In this post, a way for creating a custom Maven packaging has been explained. The use of a custom packaging can make a build specification cleaner and less verbose. It also leverages reuse and evolution as any change on the packaging process is done in the plugin, therefore, any project that uses it only has to wait for the new version. And one more advantage, the packaging process can be unit tested as it is Java code.
mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-plugin -DgroupId=org.softwaredistilled -DartifactId=mypackaging-maven-plugin
Each Maven packaging requires a build life cycle to be defined. It is possible to include more or less phases, but for this example, we will define a build cycle equals to the standard. A file named components.xml under META-INF/plexus folder must be created with the following definition:
<?xml version="1.0"?>
<component-set>
<components>
<component>
<role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
<role-hint>jzip</role-hint>
<implementation>
org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
</implementation>
<configuration>
<lifecycles>
<lifecycle>
<id>default</id>
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:resources
</process-resources>
<compile>
org.apache.maven.plugins:maven-compiler-plugin:compile
</compile>
<process-test-resources>
org.apache.maven.plugins:maven-resources-plugin:testResources
</process-test-resources>
<test-compile>
org.apache.maven.plugins:maven-compiler-plugin:testCompile
</test-compile>
<test>
org.apache.maven.plugins:maven-surefire-plugin:test
</test>
<package>
org.softwaredistilled:mypackaging-maven-plugin:zip
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:deploy
</deploy>
</phases>
</lifecycle>
</lifecycles>
</configuration>
</component>
<component>
<role>org.apache.maven.artifact.handler.ArtifactHandler</role>
<role-hint>jzip</role-hint>
<implementation>
org.apache.maven.artifact.handler.DefaultArtifactHandler
</implementation>
<configuration>
<type>jzip</type>
<extension>zip</extension>
<language>java</language>
<addedToClasspath>false</addedToClasspath>
</configuration>
</component>
</components>
</component-set>
In each phase, one goal (or eventually more) is specified to be executed during the build process. For this example, only the package phase has been modified in order to execute a goal that creates the zip file.
The mojo that handles the zip goal, will be distributed within the same plugin as the packaging definition, and would look like any other Mojo:
@Mojo(name = "zip")
public final class ZipMojo extends AbstractMojo {
@Component
private MavenProject project;
@Parameter(property = "project.build.directory", readonly = true)
private String outputDirectory;
@Parameter(property = "project.build.finalName", readonly = true)
private String finalName;
@Override
public void execute() throws MojoExecutionException {
File artifact = new File(this.outputDirectory +"/"+ this.finalName + ".zip"));
...
Here a zip file is created using a library like commons compress.
Then the project sources and a start script are added to the zip.
...
this.project.getArtifact().setFile(artifact);
}
}
The important part of the code above, is setting the file to the project's artifact. This will tell Maven which file is the final artifact in order to be installed or deployed in a repository.
Using the packaging
Once the plugin is finished and deployed in a repository, any project can use the packaging in the following way:
<packaging> jzip </packaging>
But, as the packaging is not distributed with Maven, a reference to the plugin must be specified in the build section of the project pom:
<plugin>
<groupId>org.softwaredistilled</groupId>
<artifactId>mypackagin-maven-plugin</artifactId>
<version>1.0</version>
<extensions>true</extensions>
<groupId>org.softwaredistilled</groupId>
<artifactId>mypackagin-maven-plugin</artifactId>
<version>1.0</version>
<extensions>true</extensions>
</plugin>
Hi, I've been attempting (and failing) to create a plugin that would simply specify "zip" as a valid packaging type. In my case, I don't have any need for java compilation, just run some npm commands, copy the products, and zip it up. My understanding from reading not only your post, but others as well, is that I only need the META-INF/plexus/component.xml file specifying the new packaging type, but that never works. What am I missing?
ReplyDeleteHello Dave,
ReplyDeleteIt could be several things. You not only specify the new packaging type in META-INF/plexus/component.xml but also the goals (commands) that will be executed during build time (also known as build life cycle).
The npm commands must be executed by a maven goal at certain moment of the build cycle, so it is likely that you have to implement a Mojo (Java class) that runs npm, if there is not an implementation already out there.
Check whether the plugin artifact, with the packaging definition, has been installed in your .m2 repository.
And check if the project your attempting to build specifies the use of your new plugin correctly
your.plugin.groupId
your-plugin-name
1.0
true
Check this plugin I developed that defines 3 new packaging types for java apps in tar and zip https://github.com/easypack/easypack-maven-plugin/blob/master/src/main/resources/META-INF/plexus/components.xml
Hope it helps
I followed all the steps but it keeps saying "Unknown packaging: vdb @ line 9, column 13" when use the "vdb" as packing type in my example. What could be wrong?
ReplyDelete