Building a Maven Complex Release Artifact: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
No edit summary
 
(130 intermediate revisions by the same user not shown)
Line 2: Line 2:


* [[Maven_assembly_Plugin#Other_Assembly_Use_Cases|Maven assembly Plugin]]
* [[Maven_assembly_Plugin#Other_Assembly_Use_Cases|Maven assembly Plugin]]
* [[Multi-Module Maven Projects]]
* [[nort Concepts#Binary_Distributions|nort binary distributions]]


=Overview=
=Overview=


This article describes the procedure of configuring Maven to build one complex release artifact, containing multiple individual artifacts, possibly produced by multiple modules, as well as arbitrary files from the project, dependencies, and so on, or otherwise what is known to Maven as an [[Maven_assembly_Plugin#Assembly|assembly]].
This article describes the procedure of configuring Maven to build one complex release artifact, which includes multiple individual artifacts, possibly produced by multiple modules, as well as dependencies and arbitrary files from the project tree. This is achieved by using the Maven assembly plugin, whose final product is an [[Maven_assembly_Plugin#Assembly|assembly file]].


=Don't Follow your Intuition=


!!!Multi-Module Assembly
An intuitive approach would be to assemble the release artifact of a multi-module project by declaring the assembly plug-in in the [[Multi-Module_Maven_Projects#The_Parent_POM|project root pom.xml]]. However, this approach does not work well in practice. A few of the reasons are mentioned below:


!!!Overview
==Root Module Builds First==


The most intuitive approach when dealing with multi-module projects is to build the "final" assembly in the root module. However, this has several drawbacks, as indicated by [http://www.sonatype.com/books/mvnref-book/reference/assemblies-sect-best-practices.html].
... so if the final assembly depends on the artifacts of the children modules, we'll get into a deadlock.


==Repeated Execution==


!!Drawback One: Repeated Execution
Because the assembly plug-in is specified in the parent POM, the plug-in will be executed for each module (including root) at the specified phase, usually "package". When executing, the module will try to resolve the specified descriptor relative to ''the module root'', so if we have the following declaration and layout:


If the assembly plug-in is specified in the top pom.xml file of a multi-module project, the plug-in will be executed for each module (including root) at the specified phase.
<pre>
 
When executing, the module will try to resolve the specified descriptor ''relative to the module root'', so if we have the following declaration and layout:
 
{{{
<project>
<project>
   </build>
   </build>
Line 37: Line 37:
     </build>
     </build>
</project>
</project>
}}}
</pre>


{{{
<pre>
   .
   .
   +-- pom.xml
   +-- pom.xml
Line 45: Line 45:
   +-- assembly.xml  (1)
   +-- assembly.xml  (1)
   |
   |
   +-- mod1
   +-- module-1
       |
       |
       +-- pom.xml
       +-- pom.xml
       |
       |
       +-- assembly.xml (2)
       +-- assembly.xml (2)
}}}
</pre>
 
then the root module will create its assembly based on assembly.xml (1) and module-1 will create its assembly based on assembly.xml (2).
 
=Recommended Approach=
 
==Dedicated Release Module==
 
Create a separated module called "release". The only purpose of this module is to build the final release assembly. Thus, it can be declared as dependent of all other modules that contribute artifacts to the final release bundle. The modules will be built in order, the final release module will be last, when all internal dependencies are available, and there won't be any complications related to dependencies.
 
===The Version of the Release Module Artifact===
 
There are two main options:
 
# The project's top level pom.xml maintains the public release information, as usually, as a value of its <version> element. This implies that all modules increment their versions at the same time, in what is describe here as "[[Multi-Module_Maven_Projects#Lockstep_Versions|lockstep versions]]". This model is appropriate for simple cases, where modules do not need to evolve their versions independently, which is usually the case.
# The release module pom.xml maintains the version of the public release. This model allows the component modules' versions to evolve independently. This model is described hare as "[[Multi-Module_Maven_Projects#Independent_Versions|independent versions]]".
 
===Project Top-Level pom.xml===
 
Assuming that the parent project artifact ID is "my-project" (for a discussion on how to name the parent project artifact ID, see the "[[Multi-Module_Maven_Projects#The_Parent_POM|Multi-Module Maven Projects]]" section), the other modules are "module-1" and "module-2", and the release module is "release", the relevant sections of the main pom.xml file are:
 
<pre>
<project ...>
    ...
    <groupId>...</groupId>
    <artifactId>my-project</artifactId>
    <!--
            Initialize <version> with the project version for the lockstep version
            model or with 0 for independent version model.
    -->
    <version>0|project-version</version>
    <packaging>pom</packaging>
    ...
    <modules>
        <module>module-1</module>
        <module>module-2</module>
        <module>release</module>   
    </modules>   
    ...
</project>
</pre>
 
If there is just one other module, it is conventionally named "main".


then the root module will create its assembly based on assembly.xml (1) and mod1 will create its assembly based on assembly.xml (2).
===Business Module pom.xml===


!!Drawback Two: Root Module Builds First
The relevant sections of a business module pom.xml file are:


... so if the "final" assembly depends on the artifacts of the children modules, we'll get into a deadlock.
<pre>
<project ...>
    ...
    <parent>
        <groupId>...</groupId>
        <artifactId>my-project</artifactId>
        <!--
                Initialize <version> with the project version for the lockstep version
                model or with 0 for independent version model.
        -->
        <version>0|project-version</version>
    </parent>


!!!Recommended Approach
    <name>User Friendly Name that will Show Up in Reactor Report</name>
    <artifactId>module-1</artifactId>
    <packaging>jar</packaging>


Create a separated module called "distribution", dedicated to building the final assembly, and make it a dependent of all other modules. Assuming that the root module is 'maven-root', "other" modules are 'mod1', then the "distribution" pom.xml should look similar to (with its private assembly.xml file in the module root):
    <!--
        The version should be specified here only in the case of independent version model.
    -->
    <!--
    <version>module-version</version>
    -->
    ...
</project>
</pre>


{{{
===Dedicated Release Module POM===
<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/maven-v4_0_0.xsd">
The "release" pom.xml should look similar to:
 
<pre>
<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/maven-v4_0_0.xsd">


     <modelVersion>4.0.0</modelVersion>
     <modelVersion>4.0.0</modelVersion>
    <name>assembly</name>


     <parent>
     <parent>
         <groupId>org.novaordis.playground</groupId>
         <groupId>my.example.group</groupId>
         <artifactId>maven-root</artifactId>
         <artifactId>my-project</artifactId>
         <version>1.0.0</version>
         <!--
                Initialize <version> with the project version for the lockstep version
                model or with 0 for independent version model.
        -->
        <version>0|project-version</version>
     </parent>
     </parent>


     <groupId>org.novaordis.playground</groupId>
     <name>... Release Module</name>
     <artifactId>distribution</artifactId>
     <artifactId>release</artifactId>
     <packaging>pom</packaging>
     <packaging>pom</packaging>
    <!--
        The version should be specified here only in the case of independent version model,
        otherwise it coincides with the parent version.
    -->
    <!--
    <version>release-version</version>
    -->


     <build>
     <build>
Line 84: Line 162:
             <plugin>
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
                 <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                 <configuration>
                 <configuration>
                    <!--
                          finalName impacts only the locally built artifact,
                          not the name of the artifact that gets deployed in the repository.
                    -->
                    <finalName>my-public-application-name-${project.version}</finalName>
                    <appendAssemblyId>false</appendAssemblyId>
                     <descriptors>
                     <descriptors>
                         <descriptor>assembly.xml</descriptor>
                         <descriptor>src/assembly/release.xml</descriptor>
                     </descriptors>
                     </descriptors>
                 </configuration>
                 </configuration>
Line 104: Line 189:
     <dependencies>
     <dependencies>
         <dependency>
         <dependency>
             <groupId>org.novaordis.playground</groupId>
             <groupId>my.example.group</groupId>
             <artifactId>mod1</artifactId>
             <artifactId>module-1</artifactId>
             <version>1.0.0</version>
            <!--
                    Initialize <version> with ${project.version} for the lockstep version
                    model or with the module version for independent version model.
            -->
             <version>module-1-version|${project.version}</version>
        </dependency>
        <dependency>
            <groupId>my.example.group</groupId>
            <!--
                    Initialize <version> with ${project.version} for the lockstep version
                    model or with the module version for independent version model.
            -->
            <version>module-2-version|${project.version}</version>
            <version>...</version>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>
</project>
</pre>
The "release" module must be declared in the project parent pom.xml, amongst the project's modules, on the last position, as shown in the [[Building_a_Maven_Complex_Release_Artifact#Project_Top-Level_pom.xml|project top-level pom.xml]] example.
Conventionally, we name the artifactId of a release module "release",  we use a <finalName> as shown above, to specify the name of the release artifact, for reasons described in the "[[Maven_assembly_Plugin#.3CfinalName.3E|Name of a Release Artifact - <finalName>]]" section.  Note that <finalName> impacts only the locally built artifact, not the name of the artifact that gets deployed in the repository.
====release Module Structure====
<pre>
src
|
+-assembly
|  |
|  +-- release.xml
|
+-main
    |
    +-- bash
    |    |
    |    +-- .install
    |    +-- <application-shell-wrapper>
    |    +-- <application-shell-wrapper>.shlib
    |
    +-- resources
          |
          +-- log4j.xml
          +-- VERSION
</pre>
====Custom Assembly File====
Template to use when starting a new project: {{Internal|Project release.xml|release.xml}}
Working examples of a custom assembly file used to build the release assembly of multi-module project are available below:
<blockquote style="background-color: AliceBlue; border: solid thin LightSteelBlue;">
:https://github.com/NovaOrdis/gld/blob/master/core/release/src/assembly/release.xml
</blockquote>
Aways use:
<pre>
<assembly ...>
    <id>public-binary-release</id>
    ...
</assembly>
</pre>
====.install File Example====
Template to use when starting a new project: {{Internal|Project .install|.install}}
Make it executable
chmod a+x src/main/bash/.install
Also see:
{{External|Embedded Installation Logic for Binary Distributions}}
====Application Shell Wrapper====
The application shell wrappers can find the majority of the functionality they need already packaged in [[bash-wrapper-functions]]. To use  [[bash-wrapper-functions]] with your project:
* copy the last version of https://github.com/NovaOrdis/bash-wrapper-functions/blob/master/bash-wrapper-functions in <tt>release/src/main/bash</tt>.
* copy https://github.com/NovaOrdis/bash-wrapper-functions/blob/master/application-shell-wrapper-template into <tt>release/src/main/bash</tt>, renaming as appropriate, and update project-specific placeholders declared at the top of the file, as:
MAIN_CLASS=io.novaordis.tda.Main
<font color=red>
An example of application shell wrapper for an application that does use [[clad]] is available here (it needs a clad.shlib library):
https://github.com/NovaOrdis/nort/blob/master/release/src/main/bash/nort
https://github.com/NovaOrdis/nort/blob/master/release/src/main/bash/clad.shlib
It is good practice to unit test the shell wrapper. This can be done from Java. For that, separate the wrapper in a function library file (.shlib) and the actual wrapper that only sources the library and calls <tt>main()</tt>. For an example of how this is actually done, see:


</project>
{{External2|https://github.com/NovaOrdis/gld/blob/master/core/release/pom.xml|https://github.com/NovaOrdis/gld/blob/master/core/release/src/test/java/io/novaordis/gld/wrapper/ShellWrapperTest.java}}
}}}
 
</font>
 
====log4j.xml File Example====
 
An example of log4j.xml suitable for command line use is available here:


Don't forget to declare the "distribution" module in root.  
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;">
:[[Command Line log4j.xml]]
</blockquote>


!!!Example of a multi-module assembly file
====VERSION File Example====


{{{
An example of VERSION file is available here:
}}}


__Referenced by:__\\
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;">
[{INSERT com.ecyrd.jspwiki.plugin.ReferringPagesPlugin WHERE max=20, maxwidth=50}]
:[[Nova_Ordis_Utilities_Version_Metadata_Handling#VERSION_File|VERSION File Example]]
</blockquote>

Latest revision as of 13:49, 1 August 2017

Internal

Overview

This article describes the procedure of configuring Maven to build one complex release artifact, which includes multiple individual artifacts, possibly produced by multiple modules, as well as dependencies and arbitrary files from the project tree. This is achieved by using the Maven assembly plugin, whose final product is an assembly file.

Don't Follow your Intuition

An intuitive approach would be to assemble the release artifact of a multi-module project by declaring the assembly plug-in in the project root pom.xml. However, this approach does not work well in practice. A few of the reasons are mentioned below:

Root Module Builds First

... so if the final assembly depends on the artifacts of the children modules, we'll get into a deadlock.

Repeated Execution

Because the assembly plug-in is specified in the parent POM, the plug-in will be executed for each module (including root) at the specified phase, usually "package". When executing, the module will try to resolve the specified descriptor relative to the module root, so if we have the following declaration and layout:

<project>
   </build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptors>
                        <descriptor>assembly.xml</descriptor>
                    </descriptors>
                </configuration>
                ...
            </plugin>
        </plugins>
    </build>
</project>
  .
  +-- pom.xml
  |
  +-- assembly.xml   (1)
  |
  +-- module-1
       |
       +-- pom.xml
       |
       +-- assembly.xml (2)

then the root module will create its assembly based on assembly.xml (1) and module-1 will create its assembly based on assembly.xml (2).

Recommended Approach

Dedicated Release Module

Create a separated module called "release". The only purpose of this module is to build the final release assembly. Thus, it can be declared as dependent of all other modules that contribute artifacts to the final release bundle. The modules will be built in order, the final release module will be last, when all internal dependencies are available, and there won't be any complications related to dependencies.

The Version of the Release Module Artifact

There are two main options:

  1. The project's top level pom.xml maintains the public release information, as usually, as a value of its <version> element. This implies that all modules increment their versions at the same time, in what is describe here as "lockstep versions". This model is appropriate for simple cases, where modules do not need to evolve their versions independently, which is usually the case.
  2. The release module pom.xml maintains the version of the public release. This model allows the component modules' versions to evolve independently. This model is described hare as "independent versions".

Project Top-Level pom.xml

Assuming that the parent project artifact ID is "my-project" (for a discussion on how to name the parent project artifact ID, see the "Multi-Module Maven Projects" section), the other modules are "module-1" and "module-2", and the release module is "release", the relevant sections of the main pom.xml file are:

<project ...>
    ...
    <groupId>...</groupId>
    <artifactId>my-project</artifactId>
    <!--
             Initialize <version> with the project version for the lockstep version 
             model or with 0 for independent version model.
    -->
    <version>0|project-version</version> 
    <packaging>pom</packaging>
    ...
    <modules>
        <module>module-1</module>
        <module>module-2</module>
        <module>release</module>    
    </modules>    
    ...
</project>

If there is just one other module, it is conventionally named "main".

Business Module pom.xml

The relevant sections of a business module pom.xml file are:

<project ...>
    ...
    <parent>
        <groupId>...</groupId>
        <artifactId>my-project</artifactId>
        <!--
                 Initialize <version> with the project version for the lockstep version 
                 model or with 0 for independent version model.
        -->
        <version>0|project-version</version>
    </parent>

    <name>User Friendly Name that will Show Up in Reactor Report</name>
    <artifactId>module-1</artifactId>
    <packaging>jar</packaging>

    <!--
        The version should be specified here only in the case of independent version model.
    -->
    <!--
    <version>module-version</version>
    -->
    ...
</project>

Dedicated Release Module POM

The "release" pom.xml should look similar to:

<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/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>my.example.group</groupId>
        <artifactId>my-project</artifactId>
        <!--
                 Initialize <version> with the project version for the lockstep version 
                 model or with 0 for independent version model.
        -->
        <version>0|project-version</version>
    </parent>

    <name>... Release Module</name>
    <artifactId>release</artifactId>
    <packaging>pom</packaging>

    <!--
        The version should be specified here only in the case of independent version model,
        otherwise it coincides with the parent version.
    -->
    <!--
    <version>release-version</version>
    -->

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!--
                          finalName impacts only the locally built artifact, 
                          not the name of the artifact that gets deployed in the repository.
                    -->
                    <finalName>my-public-application-name-${project.version}</finalName>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptors>
                        <descriptor>src/assembly/release.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>my.example.group</groupId>
            <artifactId>module-1</artifactId>
            <!--
                     Initialize <version> with ${project.version} for the lockstep version 
                     model or with the module version for independent version model.
            -->
            <version>module-1-version|${project.version}</version>
        </dependency>
        <dependency>
            <groupId>my.example.group</groupId>
            <!--
                     Initialize <version> with ${project.version} for the lockstep version 
                     model or with the module version for independent version model.
            -->
            <version>module-2-version|${project.version}</version>
            <version>...</version>
        </dependency>
    </dependencies>
</project>

The "release" module must be declared in the project parent pom.xml, amongst the project's modules, on the last position, as shown in the project top-level pom.xml example.

Conventionally, we name the artifactId of a release module "release", we use a <finalName> as shown above, to specify the name of the release artifact, for reasons described in the "Name of a Release Artifact - <finalName>" section. Note that <finalName> impacts only the locally built artifact, not the name of the artifact that gets deployed in the repository.

release Module Structure

src
 |
 +-assembly
 |   |
 |   +-- release.xml
 |
 +-main 
     |
     +-- bash
     |    |
     |    +-- .install
     |    +-- <application-shell-wrapper>
     |    +-- <application-shell-wrapper>.shlib
     |
     +-- resources
          |
          +-- log4j.xml
          +-- VERSION

Custom Assembly File

Template to use when starting a new project:

release.xml

Working examples of a custom assembly file used to build the release assembly of multi-module project are available below:

https://github.com/NovaOrdis/gld/blob/master/core/release/src/assembly/release.xml

Aways use:

<assembly ...>

    <id>public-binary-release</id>
    ...
</assembly>

.install File Example

Template to use when starting a new project:

.install

Make it executable

chmod a+x src/main/bash/.install


Also see:

Embedded Installation Logic for Binary Distributions

Application Shell Wrapper

The application shell wrappers can find the majority of the functionality they need already packaged in bash-wrapper-functions. To use bash-wrapper-functions with your project:

MAIN_CLASS=io.novaordis.tda.Main

An example of application shell wrapper for an application that does use clad is available here (it needs a clad.shlib library):

https://github.com/NovaOrdis/nort/blob/master/release/src/main/bash/nort
https://github.com/NovaOrdis/nort/blob/master/release/src/main/bash/clad.shlib

It is good practice to unit test the shell wrapper. This can be done from Java. For that, separate the wrapper in a function library file (.shlib) and the actual wrapper that only sources the library and calls main(). For an example of how this is actually done, see:

https://github.com/NovaOrdis/gld/blob/master/core/release/pom.xml
https://github.com/NovaOrdis/gld/blob/master/core/release/src/test/java/io/novaordis/gld/wrapper/ShellWrapperTest.java

log4j.xml File Example

An example of log4j.xml suitable for command line use is available here:

Command Line log4j.xml

VERSION File Example

An example of VERSION file is available here:

VERSION File Example