Copy-maven-plugin

From Evgeny Goldin

(Redirected from Maven-copy-plugin)
Jump to: navigation, search

Contents

Introduction

Most build scripts or applications operate with lots and lots of archives: big, small, huge, "*.zip", and "*.tar.gz". Their content comes from various resources: files, directories, Maven dependencies and other archives, sometimes downloaded from HTTP or FTP. The resulting archives may need to become Maven artifacts and occasionally uploaded back to FTP or SCP.


Traditionally, Maven's way to deal with this kind of tasks was lengthy and unclear:


"maven-resources-plugin" allows to copy resources
"maven-dependency-plugin" allows to copy and unpack dependencies
"truezip-maven-plugin" allows to unpack and pack archives
"maven-assembly-plugin" also allows to pack archives
"build-helper-maven-plugin" allows to attach created archives as Maven artifacts
"maven-antrun-plugin" provides required networking support for FTP and SCP, also used for archiving needs


Eventually, when one needs to perform above operations, the result is usually a messy POM with quite a few plugin configurations and Ant snippets. "copy-maven-plugin" solves this issue and provides an elegant solution for all above tasks. It allows you to easily perform and configure the following operations:

  1. Copy files, directories and Maven dependencies.
  2. Filter and replace text files as they're copied.
  3. Pack, update, and unpack archives, zip entries and Maven <dependencies>.
  4. Attach archives created as Maven artifacts or deploy them directly to Maven repository manager.
  5. Download and upload archives from and to HTTP, SCP, and FTP.
  6. Use Groovy "extension points" for text replaces, files filtering and post-processing.


Details

Provided By
Mailing List Nabble
Source Code GitHub
License
Tests GitHub
GroovyDoc <groovydoc>
Issue Tracker YouTrack
Build Server Maven
TeamCity
Maven Coordinates
  • com.github.goldin:copy-maven-plugin:0.2.5
Goal
  • copy
Default Phase
  • package
Maven Repository Artifactory Online


Example

Full documentation is provided below this quick example, demonstrating some common operations:

  • Resources, directories and <dependencies> are copied
  • Two data archives, provided as <dependency>, are unpacked
  • Result is packed as "zipName.zip", attached as Maven artifact


<plugin>
    <groupId>com.github.goldin</groupId>
    <artifactId>copy-maven-plugin</artifactId>
    <version>0.2.5</version>
    <executions>
        <execution>
            <id>create-archive</id>
            <phase>package</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <resources>
                    <!-- ~~~~~~~~~~~~~~ -->
                    <!-- Copy resources -->
                    <!-- ~~~~~~~~~~~~~~ -->
                    <resource>
                        <targetPath>${project.build.outputDirectory}/scripts</targetPath>
                        <directory>${project.basedir}/src/main/resources</directory>
                        <includes>
                            <include>bat/setenv*.bat, bat/wrapper*.bat, bat/install*.bat, bat/uninstall*.bat</include>
                            <include>lib/core</include>
                            <include>*.jar</include>
                        </includes>
                    </resource>
                    <resource>
                        <targetPath>${project.build.outputDirectory}/scripts</targetPath>
                        <directory>${project.basedir}/conf</directory>
                        <include>resources, scripts</include>
                    </resource>
                    <!-- ~~~~~~~~~~~~~~ -->
                    <!-- Copy directory -->
                    <!-- ~~~~~~~~~~~~~~ -->
                    <resource>
                        <targetPath>${project.build.outputDirectory}/spring</targetPath>
                        <directory>${project.basedir}/src/main/resources/spring</directory>
                    </resource>
                    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
                    <!-- Copy "compile" dependencies -->
                    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
                    <resource>
                        <targetPath>${project.build.outputDirectory}/lib</targetPath>
                        <dependency>
                            <includeScope>compile</includeScope>
                        </dependency>
                    </resource>
                    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~ -->
                    <!-- Unpack two data archives -->
                    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~ -->
                    <resource>
                        <targetPath>${project.build.outputDirectory}/data</targetPath>
                        <dependencies>
                            <dependency>
                                <groupId>someData</groupId>
                                <artifactId>dependencyArchive</artifactId>
                            </dependency>
                            <dependency>
                                <groupId>anotherData</groupId>
                                <artifactId>dependencyArchive</artifactId>
                            </dependency>
                        </dependencies>
                        <unpack>true</unpack>
                    </resource>
                    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
                    <!-- Create final archive and attach it as Maven artifact -->
                    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
                    <resource>
                        <targetPath>${project.build.directory}/zipName.zip</targetPath>
                        <directory>${project.build.outputDirectory}</directory>
                        <pack>true</pack>
                        <attachArtifact>true</attachArtifact>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>


<configuration>

You configure the plugin by specifying a number of <resources> or a single <resource>:

<!-- Specifying multiple <resources> -->
<configuration>
    <resources>
        <resource>
            <targetPath> .. </targetPath>
            ...
        </resource>
        <resource>
            <targetPath> .. </targetPath>
            ...
        </resource>
    </resources>
</configuration>
 
<!-- Specifying single <resource> -->
<configuration>
    <resource>
        <targetPath> .. </targetPath>
        ...
    </resource>
</configuration>


  • Each <resource> specifies an action to be performed and result to be stored in the <targetPath>: something to copy, pack or unpack. All <resources> are executed in the order they're specified, there's no parallel execution.
  • It is possible to specify a single <resource> instead of multiply <resources>. You will see this pattern in many other places: whenever there is a single entity - there's no need to wrap it with "plurals" tag. Following tags can be shortened this way:


Single Plural
<resource> <resources>
<targetPath> <targetPaths>
<include> <includes>
<exclude> <excludes>
<dependency> <dependencies>
<replace> <replaces>
<zipEntry> <zipEntries>
<classpath> <classpaths>


<configuration>


Name Default value Description
<runIf> Controls whether or not plugin should be executed
<skipIdentical> false Specifies whether identical files should not be copied
<defaultExcludes> See below Comma-separated patterns of files to be excluded from copy, pack, unpack or clean operations
<verbose> true Controls logging verbosity
<filterWithDollarOnly> false Controls whether only ${ .. } expressions should be filtered
<nonFilteredExtensions> Comma-separated list of file extensions that shouldn't be filtered
<failIfNotFound> true Controls whether execution should fail if no files were found to copy, pack, unpack or clean
<useTrueZipForPack> false Controls whether TrueZip library (or Ant) should be used when archives are packed
<useTrueZipForUnpack> true Controls whether TrueZip library (or Ant) should be used when archives are unpacked
<resource> / <resources> List of resources to perform operations on
<groovyConfig> Allows to control Groovy execution environment and logging verbosity


<resource>


Name Default value Description
General Configurations
<runIf> Controls whether or not this <resource> should be executed
<description> Allows to provide a textual description to <resource> and time its execution in milliseconds
<targetRoots> Comma-separated list of directories to prepend to <targetPath> / <targetPaths>
<targetPath> / <targetPaths> Target directory or network location to copy, unpack or upload files to
<directory> Source directory or network location to copy, pack or download files from
<include> / <includes> Comma-separated patterns of files to be included in copy, pack, unpack or clean operation
<exclude> / <excludes> Comma-separated patterns of files to be excluded from copy, pack, unpack or clean operation
<file> Specifies a single file to copy, pack or unpack as alternative to <directory> and <include> combination
<dependency> / <dependencies> Maven dependencies to copy, pack or unpack
<dependenciesAtM2> true Specifies whether <dependencies> should be copied or unpacked directly from local Maven repository
<defaultExcludes> See below Comma-separated patterns of files to always be excluded from copy, pack, unpack or clean operations
<verbose> true Controls logging verbosity of this <resource>
<failIfNotFound> true Controls whether execution should fail if no files were found to copy, pack, unpack or clean in this <resource>
Copying files and <dependencies>
<move> false Specifies whether files should be moved instead of being copied
<skipIdentical> false Specifies whether identical files should not be copied
<destFileName> Destination file name
<preservePath> false Specifies whether file relative path should be preserved when copying it to new location
<stripVersion> false Specifies whether <dependency> version should be removed from archive name when it is copied
Filtering files
<filtering> false Whether Maven filtering should be applied when files are copied
<filterWithDollarOnly> Controls whether only ${ .. } expressions should be filtered
<nonFilteredExtensions> Comma-separated list of file extensions that shouldn't be filtered
<encoding> UTF-8 Specifies encoding to use
Replacing content
<replace> / <replaces> Allows to replace content of files copied with regex matches and Groovy substitutions
<encoding> UTF-8 Specifies encoding to use
Packing archives
<pack> false Specifies whether <targetPath> archive should be packed
<useTrueZipForPack> Controls whether TrueZip library (or Ant) should be used
<update> false Specifies if archive should be updated as opposed to creating it from scratch
<prefix> Prefix to be used in the archive
<destFileName> Destination file name in the archive
<attachArtifact> false Specifies whether to attach archive created as Maven artifact, similarly to "build-helper-maven-plugin:attach-artifact"
<artifactClassifier> Specifies artifact's classifier when archive created is attached as Maven artifact with <attachArtifact>
Unpacking archives and <dependencies>
<unpack> false Specifies whether archives or dependencies should be unpacked
<useTrueZipForUnpack> Controls whether TrueZip library (or Ant) should be used
<zipEntry> / <zipEntries> Allows to specify ZIP entries to unpack instead of unpacking complete archive. Patterns are supported
Groovy extension points
<filter> Groovy expression to filter files, specified as "List<File> files" Groovy variable
<process> Groovy expression to post-process files, specified as "List<File> files" Groovy variable
FTP Download
<retries> 5 Specifies FTP download attempts
<timeout> 3600 Specifies FTP download timeout, in seconds
<wget> Specifies FTP download to list files first and download them with "wget"
<listFilter> When <wget> is used - Groovy expression to filter files downloaded from FTP server, specified as "Map<String, Long> files" Groovy variable
Other operations
<clean> false Specifies whether to delete files matched by <directory> + <includes> - <excludes> patterns
<cleanEmptyDirectories> false Specifies whether to delete empty directories left after <clean> operation
<mkdir> false Specifies whether to create <targetPath> / <targetPaths> directory or directories
<deploy> Allows to deploy archive created to Maven repository and coordinates specified


<replace>


Name Default value Description
<from> Regular expression of content to be replaced, all content is replaced if omitted
<to> Replacement content
<endOfLine> "End of line" to use in replacement content
<addDollar> Allows to convert {expression} to ${expression} in replacement content to prevent Maven interpolation
<quoteReplacement> false Allows to quote replacement content
<replaceAll> true Specifies whether all regex matches should be replaced or only the first one
<failIfNotFound> true Specifies whether execution should fail if regex didn't match any content to replace
<groovy> false Specifies whether {{ .. }} sections should be Groovy-evaluated in <to>


<dependency>


Name Default value Description
<groupId> Dependency <groupId>
<artifactId> Dependency <artifactId>
<version> Dependency <version>
<type> Dependency <type>
<classifier> Dependency <classifier>
<optional> false Marks dependency as "optional": build doesn't fail if it fails to be resolved
<excludeTransitive> false When dependencies are filtered: specifies whether or not they should be resolved transitively
<includeScope> When dependencies are filtered: scope of dependencies to include
<excludeScope> When dependencies are filtered: scope of dependencies to exclude
<includeGroupIds> When dependencies are filtered: comma-separated <groupId>s of dependencies to include
<excludeGroupIds> When dependencies are filtered: comma-separated <groupId>s of dependencies to exclude
<includeArtifactIds> When dependencies are filtered: comma-separated <artifactId>s of dependencies to include
<excludeArtifactIds> When dependencies are filtered: comma-separated <artifactId>s of dependencies to exclude
<includeClassifiers> When dependencies are filtered: comma-separated <classifier>s of dependencies to include
<excludeClassifiers> When dependencies are filtered: comma-separated <classifier>s of dependencies to exclude
<includeTypes> When dependencies are filtered: comma-separated <type>s of dependencies to include
<excludeTypes> When dependencies are filtered: comma-separated <type>s of dependencies to exclude


<groovyConfig>


Name Default value Description
<verbose> true Whether Groovy evaluations are verbose and return results are logged
<verboseBinding> false Whether Groovy evaluations are verbose and all variables bound to the scope are logged
<classpath> / <classpaths> Additional classpath locations to use when running Groovy code


<description>

Every <resource> may have a textual description specified. It is helpful to make it standing out in logs as they may be quite verbose at times.


  • When specified, its value is logged before and after <resource> is processed.
  • In addition, <resource> execution time is also logged in milliseconds.
  • If <description> starts with "{{" and ends with "}}" then it is evaluated as Groovy expression for each log message.


<resource>
    <description>Testing "latest" filter</description>
    ...
</resource>
 
<resource>
    <description>{{ '"3 == 3" test: [' + new Date() +']' }}</description>
    ...
</resource>


Later:


[INFO] ==> Processing <resource> [Testing "latest" filter]
...
[INFO] ==> <resource> [Testing "latest" filter] processed, [448] ms
 
[INFO] ==> Processing <resource> ["3 == 3" test: [Sat Feb 26 04:08:56 PST 2011]]
...
[INFO] ==> <resource> ["3 == 3" test: [Sat Feb 26 04:08:57 PST 2011]] processed, [48] ms


Copying

Copying directories and files: <directory> + <includes> - <excludes> = <targetPath>

Copying is the most common operation:

<!-- Specifying single <targetPath>, <include> and <exclude> -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <directory>${project.basedir}</directory>
    <include>src/main/scripts/**, src/test/scripts/**</include>
    <exclude>**/*.pl</exclude>
</resource>
 
<!-- Specifying multiple <targetPaths>, <includes> and <excludes> -->
<resource>
    <targetPaths>
        <targetPath>${project.build.outputDirectory}/scripts</targetPath>
        <targetPath>${project.build.directory}/scripts</targetPath>
    </targetPaths>
    <directory>${project.basedir}</directory>
    <includes>
        <include>src/main/resources/**</include>
        <include>src/main/scripts/**</include>
    </includes>
    <excludes>
        <exclude>**/*.sh</exclude>
        <exclude>**/*.pl</exclude>
    </excludes>
</resource>


  • This is the most common form of copying files: files matched by <directory> + <includes> - <excludes> patterns are copied to a single <targetPath> or multiply <targetPaths>.
  • Comma-separated <includes> and <excludes> patterns are the same as for "maven-resources-plugin". Pattern matching is case-sensitive!
  • See <defaultExcludes> below for the list of files that are always excluded and an option to control or disable it.
  • Empty directories are not copied!
  • Unlike "maven-resources-plugin", "copy-maven-plugin" fails if <directory> + <includes> - <excludes> combination matches no files. This is a deliberate design decision to make sure that everything is going the way it is supposed to. If there were no failures you can be 100% sure that no files were missing and each and every step has executed successfully. This behavior can be overridden with <failIfNotFound> boolean flag.


Copying files to locations specified externally - <targetRoots>

It is easy to work with the plugin if all target locations are known to you in advance. But what happens if archive created should be copied to various locations specified by external parameter? <targetRoots> takes a comma-separated list of locations and prepends each of them to every <targetPath>:

<resource>
    <targetRoots>${project.build.outputDirectory}/1, ${project.build.outputDirectory}/2, ${project.build.outputDirectory}/3</targetRoots>
    <targetPath>myDir</targetPath>
    <file>${project.basedir}/pom.xml</file>
</resource>


This example copies project's "pom.xml" to 3 locations:

  • ${project.build.outputDirectory}/1/myDir/pom.xml
  • ${project.build.outputDirectory}/2/myDir/pom.xml
  • ${project.build.outputDirectory}/3/myDir/pom.xml


<targetRoots> doesn't make much sense if it's hardcoded in a POM file as one can use <targetPath> or <targetPaths> with identical result. It starts making sense when its value is specified externally to POM, for example, by Jenkins job parameter:


<resource>
    <targetRoots>${targetRootsParameter}</targetRoots>
    <targetPaths>
        <targetPath>myDir1</targetPath>
        <targetPath>myDir2</targetPath>
    </targetPaths>
    <file>${project.basedir}/pom.xml</file>
</resource>


If Jenkins user specifies ${targetRootsParameter} to be "directory1, directory2, directory3", whatever they are, each directory will have "myDir1/pom.xml" and "myDir2/pom.xml" created in it. Number of copy operations is therefore equal to "Number of locations in <targetRoots>" * "Number of <targetPaths>".


Moving files with <move>

Normally, files are copied from <directory> to <targetPath>. If you need to move them, specify <move>true</move>:


<resource>
    <targetPath> .. </targetPath>
    <directory> .. </directory>
    <move>true</move>
</resource>
<resource>
    <targetPath>file.zip</targetPath>
    <directory> .. </directory>
    <pack>true</pack>
    <move>true</move>
</resource>


<move> can be combined with packing archives, filtering files and replacing content. With <move> source files are deleted after operation required is executed so you don't need to explicitly use <clean>.


Keeping relative paths when copying: <preservePath>

In order to preserve directory's structure when its files are copied, you need to use <preservePath> tag:

<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <directory>${project.basedir}</directory>
    <includes>
        <include>src/main/resources/**</include>
        <include>src/main/scripts/**</include>
    </includes>
    <preservePath>true</preservePath> <!-- "false" by default -->
</resource>

Otherwise, all files matched will be copied to the same <targetPath>, regardless of their original location. It is useful when lots of different files from various folders need to be stored in the same folder.


Copying "shortcuts"

Shorter forms are available for common cases:

<!-- Copies all "directory" files to the same "targetPath" -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <directory>${project.basedir}</directory>
</resource>
 
<!-- Copies all "directory" files to "targetPath" preserving their original paths -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <directory>${project.basedir}</directory>
    <preservePath>true</preservePath>
</resource>
 
<!-- Copies a single file -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <file>${project.basedir}/some/file.txt</file>
</resource>
 
<!-- Copies a single files, modifies its target name -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <file>${project.basedir}/some/oldName.txt</file>
    <destFileName>newName.txt</destFileName>
</resource>
 
<!-- Uses a single "include" -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <directory>${project.basedir}</directory>
    <include>**/*.xml</include>
</resource>
 
<!-- Uses a single "exclude" -->
<resource>
    <targetPath>${project.build.outputDirectory}/scripts</targetPath>
    <directory>${project.basedir}</directory>
    <exclude>**/*.sh</exclude>
</resource>
  • Single file can be copied by using <file> instead of <directory> and <include>.
  • File's destination name can be changed with <destFileName>.


Filtering and replacing text files

<filtering>

When text or XML files are copied sometimes there's a need to modify their content. This operation is known as "filtering" ("How do I filter resource files?") in Maven where all ${property} appearances are replaced with their corresponding values, built-in like ${project.groupId} or those defined in POM <properties>. Filtering is enabled identically to "maven-resources-plugin" and optional <encoding> allows to specify the encoding, 'UTF-8' by default:

<resource>
    <targetPath>${project.build.outputDirectory}/config</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <include>*.xml</include>
    <filtering>true</filtering>
    <encoding>ASCII</encoding>
</resource>


If you copy various files filtering some of them and would like to stop certain file extensions from being filtering use <nonFilteredExtensions>:


<configuration>
    <nonFilteredExtensions>pdf, exe, class</nonFilteredExtensions>
    <resources>
        ...
    </resources>
</configuration>


Or


<resource>
    <targetPath> .. </targetPath>
    <directory> .. </directory>
    <filtering>true</filtering>
    <nonFilteredExtensions>pdf, exe, class</nonFilteredExtensions>
</resource>


Bat files are not filtered - use <filterWithDollarOnly>

Many people notice "*.bat" files are not filtered even though all other files are filtered as expected. This usually happens when "*.bat" files contain the "@echo off" statement and "@" is a delimiter.
In order to use ${ .. }-only expressions for filtering use <filterWithDollarOnly>:


<configuration>
    <filterWithDollarOnly>true</filterWithDollarOnly>
    <resources>
        ...
    </resources>
</configuration>


Or


<resource>
    <targetPath> .. </targetPath>
    <file>file.bat</file>
    <filtering>true</filtering>
    <filterWithDollarOnly>true</filterWithDollarOnly>
</resource>


<replaces>

In addition to standard filtering, Regex-based replacement can also be used:

<!-- Replacing all instances of "set JAVA_HOME=..." to "call $setenv.bat$" in all "*.bat" files -->
<resource>
    <targetPath>${project.build.outputDirectory}/bin</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <include>*.bat</include>
    <encoding>ASCII</encoding>
    <replaces>
        <replace>
            <from>set\s+(JAVA_HOME|DOM4J_HOME|JAXB_HOME)\s*=.*</from> <!-- Regex              -->
            <to>call $setenv.bat$</to>                                <!-- Replacement String -->
            <quoteReplacement>true</quoteReplacement>                 <!-- "false" by default -->
            <replaceAll>false</replaceAll>                            <!-- "true" by default -->
            <failIfNotFound>false</failIfNotFound>                    <!-- "true"  by default -->
            <groovy>false</groovy>                                    <!-- "false" by default -->
        </replace>
    </replaces>
</resource>
 
<!-- Replacing entire content of all *.bat files -->
<resource>
    <targetPath>${project.build.outputDirectory}/bin</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <include>*.bat</include>
    <replace>
        <to>echo Batch files are not supported any more!</to>
    </replace>
</resource>
 
<!-- Replacing with <endOfLine> and <addDollar> -->
<resource>
    <targetPath>${project.build.outputDirectory}/bin</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <include>*.bat</include>
    <replace>
        <from> .. </from>
        <to> .. {something} .. {anything}</to>
        <endOfLine>windows</endOfLine>
        <addDollar>something, anything</addDollar>
    </replace>
</resource>


  • Optional <encoding> allows to specify filtering encoding, "UTF-8" by default.
  • <quoteReplacement> is used to quote "\" and "$" characters in <to>. Otherwise, they're treated like literal characters escaping ("\[") or captured subsequences references ("$1", "$2").
  • <replaceAll> specifies whether all regex matches should be replaced or only the first one.
  • <failIfNotFound> allows to ignore cases when Regex pattern wasn't matched in the text. The default behavior is to fail but sometimes this is not required and can be turned off.
  • <groovy> specifies whether {{ .. }} expressions in <to> are evaluated as Groovy expressions.
  • <endOfLine> specifies end of line to use in result text:
    • "\r\n" if <endOfLine>windows</endOfLine> is specified
    • "\n" if <endOfLine>anyOtherValue</endOfLine> is specified
    • Original end of line if <endOfLine> is not specified at all
  • <addDollar> allows to convert {..} expressions to ${..}:
    • <addDollar>something, anything</addDollar> - {something} will be converted to ${something} in result, same with {anything}
    • <addDollar>true</addDollar> - all {..} expressions will be converted to ${..} in result
    • <addDollar>false</addDollar> - no {..} expressions will be converted to ${..} in result
    • This capability is necessary if you need to generate an ${expression} in result without Maven interpolating it
  • When <from> tag is missing, the entire file content is replaced with <to>.
  • When <to> tag is missing and <addDollar> is specified, only {..} expressions have "$" added in result.
  • <to> can use Maven properties, they're interpolated by Maven before plugin is executed:


<resource>
    <targetPath>${project.build.outputDirectory}/config</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <include>*.xml</include>
    <replaces>
        <from>#timestamp#</from>
        <to>${fileVersion}\nBuilt on [${timestamp-date}] at [${timestamp-time}]\n{{ 'Java version is: ' + System.getProperty( 'java.version' ) }}</to>
        <groovy>true</groovy>
        <endOfLine>linux</endOfLine>
    </replaces>
</resource>

... assuming we have <properties> such as ${fileVersion}, ${timestamp-date} and ${timestamp-time} in our POM (the last two were probably set by "timestamp-maven-plugin"). Note the use of {{ .. }} Groovy expression together with <groovy>true</groovy>.


Copying dependencies

Files and folders don't provide everything we need. When we think Maven, we usually think <dependencies>.


Explicit <dependencies>

You can specify explicit <dependencies> or a single <dependency> to copy:

<!-- Specifying multiple <dependencies> with <groupId>:<artifactId>:<version> -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.gmaven</groupId>
            <artifactId>gmaven-plugin</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.gmaven.runtime</groupId>
            <artifactId>gmaven-runtime-1.7</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>
</resource>
 
<!-- Specifying single <dependency> with <groupId>:<artifactId>:<version>:<type>:<classifier> -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <groupId> .. </groupId>
        <artifactId> .. </artifactId>
        <version> .. </version>
        <type> .. </type>
        <classifier> .. </classifier>
    </dependency>
</resource>
 
<!-- Using <optional> and <stripVersion> -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <groupId>commons-httpclient</groupId>
        <artifactId>commons-httpclient</artifactId>
        <version>3.1</version>
        <optional>true</optional>     <!-- "false" by default -->
    </dependency>
    <stripVersion>true</stripVersion> <!-- "false" by default -->
</resource>


  • <optional> allows to mark a <dependency> as optional so the build won't fail if it fails to be resolved.
  • <stripVersion> allows to remove the versioning part from file names when they're copied.


"Filtering" <dependencies>

Any <dependency> lacking <groupId> or <artifactId> is considered to be a "filtering <dependency>", that is it is a <dependency> referencing other dependencies, like "All compile dependencies". Combinations of following elements may be used in a "filtering <dependency>", all together they will be "AND"-ed to create a filtering condition:


Name Description Example
<excludeTransitive> Whether dependencies shouldn't be included transitively, false by default. <excludeTransitive>true</excludeTransitive>
<includeScope> Scope of dependencies to include <includeScope>compile</includeScope>
<excludeScope> Scope of dependencies to exclude <excludeScope>compile</excludeScope>
<includeGroupIds> Set of groupIds to include, comma-separated. <includeGroupIds>org.jvnet.hudson.main,org.jvnet.hudson.plugins</includeGroupIds>
<excludeGroupIds> Set of groupIds to exclude, comma-separated. <excludeGroupIds>org.jvnet.hudson.plugins</excludeGroupIds>
<includeArtifactIds> Set of artifactIds to include, comma-separated. <includeArtifactIds>greenballs</includeArtifactIds>
<excludeArtifactIds> Set of artifactIds to exclude, comma-separated. <excludeArtifactIds>greenballs</excludeArtifactIds>
<includeClassifiers> Set of classifiers to include, comma-separated. <includeClassifiers>windows</includeClassifiers>
<excludeClassifiers> Set of classifiers to exclude, comma-separated. <excludeClassifiers>jdk14</excludeClassifiers>
<includeTypes> Set of types to include, comma-separated. <includeTypes>zip,tar.gz</includeTypes>
<excludeTypes> Set of types to exclude, comma-separated. <excludeTypes>jar</excludeTypes>


<!-- Copying all "compile" <dependencies> transitively -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <includeScope>compile</includeScope>
    </dependency>
</resource>
 
<!-- Copying all "compile" <dependencies> non-transitively -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <includeScope>compile</includeScope>
        <excludeTransitive>true</excludeTransitive>
    </dependency>
</resource>
 
<!-- Copying all "test" <dependencies> but excluding "runtime" scope -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <includeScope>test</includeScope>
        <excludeScope>runtime</excludeScope>
    </dependency>
</resource>
 
<!-- Copying "compile" <dependencies> but only "com.company" groupIds -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <includeScope>compile</includeScope>
        <includeGroupIds>com.company</includeGroupIds>
    </dependency>
</resource>
 
<!-- Copying all "compile" <dependencies> but excluding "com.company" groupIds -->
<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependency>
        <includeScope>compile</includeScope>
        <excludeGroupIds>com.company</excludeGroupIds>
    </dependency>
</resource>


Mixing both kinds

Both kinds of <dependencies> can be mixed together in the same <resource>:

<resource>
    <targetPath>${project.build.outputDirectory}/lib</targetPath>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.gmaven</groupId>
            <artifactId>gmaven-plugin</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.gmaven.runtime</groupId>
            <artifactId>gmaven-runtime-1.7</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <includeScope>compile</includeScope>
            <excludeTransitive>true</excludeTransitive>
        </dependency>
    </dependencies>
</resource>


Archiving

Packing archives: <pack>

Most of the time we use build tools such as Maven to create an archive. It is done using <pack>:

<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    ...
</resource>
<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    ...
</resource>
<resource>
    <targetPath>${project.build.directory}/finalName.zip</targetPath>
    <directory>${project.build.outputDirectory}</directory>
    <pack>true</pack>
</resource>

This is the most repeating pattern in most builds: copy N resources and pack the resulting archive. Note that you need to specify archive name in <targetPath> in addition to <pack>true</pack>, this will specify archive's format.

Currently supported formats are listed in this file and include jar, war, ear, zip, hpi, tar, tgz, tar.gz.


Controlling file location with <destFileName>

When packing a single file into an archive you can specify its location there:


<resource>
    <targetPath>pom.zip</targetPath>
    <file>${project.basedir}/pom.xml</file>
    <pack>true</pack>
    <destFileName>a/b/c/pom.txt</destFileName>
</resource>


This example copies project's "pom.xml" to "pom.zip" but not as "pom.zip/pom.xml" as it would normally happen. The file will be located at "pom.zip/a/b/c/pom.txt". Note that using <destFileName> together with <pack> works for single files only, the execution will fail if more than one file is packed.


Controlling file prefix with <prefix>

It is possible to specify files prefix when they are packed into an archive:


<resource>
    <targetPath>archive.zip</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <pack>true</pack>
    <prefix>resources</prefix>
</resource>


This example packs all files under "${project.basedir}/src/main/resources" to "archive.zip/resources" and not to archive's root as it would normally happen. You would achieve the same result with:


<resource>
    <targetPath>archive.zip</targetPath>
    <directory>${project.basedir}/src/main</directory>
    <include>resources</include>
    <pack>true</pack>
</resource>


But <prefix> allows you to disregard original folder source files were located at:


<resource>
    <targetPath>archive.zip</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <pack>true</pack>
    <prefix>springResources</prefix>
</resource>


Updating existing archives

It is possible to update an existing ZIP-based archive, such as "*.jar", "*.war" or "*.hpi":

<resource>
    <targetPath>${project.build.directory}/name.jar</targetPath>
    <directory>${project.build.outputDirectory}</directory>
    <include>**/*.xml, **/*.txt</include>
    <pack>true</pack>
    <update>true</update>
</resource>
<resource>
    <targetPath>${project.build.directory}/name.zip</targetPath>
    <file>${project.build.outputDirectory}/someFile.xml</file>
    <pack>true</pack>
    <update>true</update>
</resource>


  • Updating doesn't work for "*.tar", "*.tgz", and "*.tar.gz" archives.
  • You have to use Ant to update a ZIP-based archive which is the default option that you shouldn't modify.


Attaching archives as Maven artifact: <attachArtifact>

<attachArtifact> allows to mark an archive created as "Maven's artifact". This will deploy it to remote repository when "mvn deploy" is run. It works the same if you use Artifactory integration with CI servers to deploy artifacts.

<resource>
    <targetPath>${project.build.directory}/finalName.zip</targetPath>
    <directory>${project.build.outputDirectory}</directory>
    <pack>true</pack>
    <attachArtifact>true</attachArtifact> <!-- "false" by default -->
</resource>


  • Using <attachArtifact> doesn't make the plugin to deploy anything, it merely "marks" archives created as "Maven artifacts". The deployment is later done by Maven if "mvn deploy" is run.
  • See <deploy> for explicit invocation of deploy operation.


Maven coordinates: <artifactClassifier>

  • Full coordinates of each Maven artifacts are expressed as <groupId>:<artifactId>:<packaging>:<classifier>:<version>.
  • <groupId>:<artifactId>:<version> coordinates of the artifact attached are those of the POM where plugin is running.
  • Its <packaging> (<type>) is set by the packaging type: "zip", "gz" or "tar", taken from the archive file extension
  • When more than one archive created in the same POM there's a need to differentiate between them with a <classifier>.

Latter can be done by an optional <artifactClassifier> tag:

<resource>
    <targetPath>${project.build.directory}/firstArchive.zip</targetPath>
    <directory>${project.build.outputDirectory}</directory>
    <include>**/*.xml</include>
    <pack>true</pack>
    <attachArtifact>true</attachArtifact>
    <artifactClassifier>firstArchive</artifactClassifier>
</resource>
<resource>
    <targetPath>${project.build.directory}/secondArchive.zip</targetPath>
    <directory>${project.build.outputDirectory}</directory>
    <exclude>**/*.dll</exclude>
    <pack>true</pack>
    <attachArtifact>true</attachArtifact>
    <artifactClassifier>secondArchive</artifactClassifier>
</resource>

Later, both archives can be pulled and used as follows:

<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    <dependencies>
        <dependency>
            <groupId> ... </groupId>
            <artifactId> ... </artifactId>
            <version> ... </version>
            <type>zip</type>
            <classifier>firstArchive</classifier>
        </dependency>
        <dependency>
            <groupId> ... </groupId>
            <artifactId> ... </artifactId>
            <version> ... </version>
            <type>zip</type>
            <classifier>secondArchive</classifier>
        </dependency>
    </dependencies>
    <unpack>true</unpack>
</resource>


Deploying artifacts: <deploy>

<attachArtifact> merely marks an archive created as Maven artifact, it will be deployed according to POM coordinates only when "mvn deploy" is run. It is possible to deploy archives created directly to repository and coordinates specified with <deploy>:


<resource>
    <targetPath>${project.build.directory}/file.zip</targetPath>
    <directory> ... </directory>
    <pack>true</pack>
    <deploy>deployUrl|groupId|artifactId|version[|classifier]</deploy>
</resource>
<resource>
    <targetPath>${project.build.directory}/file.zip</targetPath>
    <directory> ... </directory>
    <pack>true</pack>
    <deploy>http://evgenyg.artifactoryonline.com/evgenyg/libs-releases-local|com.github.goldin|test|3</deploy>
</resource>
<resource>
    <targetPath>${project.build.directory}/file.zip</targetPath>
    <directory> ... </directory>
    <pack>true</pack>
    <deploy>http://evgenyg.artifactoryonline.com/evgenyg/libs-releases-local|com.github.goldin|test|3|win</deploy>
</resource>


  • Similarly to <attachArtifact>, artifact's <packaging> (<type>) is determined automatically according to file extension.
    It is "tar.gz" and "tar.bz2" for corresponding archives.
  • Currently, <deploy> only works for Maven 2 due to internal changes in Maven 3.


Unpacking archives: <unpack>

Unpacking can be used very similarly using <unpack> tag:

<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    <file>/some/file.zip</file>
    <unpack>true</unpack>
</resource>
<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    <directory>${project.basedir}/some/dir</directory>
    <include>**/*.zip</include>
    <unpack>true</unpack>
</resource>
<resource>
    <targetPath>${project.build.directory}/product.zip</targetPath>
    <directory>${project.build.outputDirectory}</directory>
    <pack>true</pack>
</resource>

Currently supported formats are the same as for <pack> operation.


Packing and unpacking <dependencies>

As with copying, you can <pack> and <unpack> <dependencies> as well:

<resource>
    <targetPath>${project.build.outputDirectory}/allDeps.tar.gz</targetPath>
    <dependency>
        <includeScope>compile</includeScope>
    </dependency>
    <pack>true</pack>
</resource>
<resource>
    <targetPath>${project.build.outputDirectory}/allDepsUnpacked</targetPath>
    <dependency>
        <includeScope>compile</includeScope>
    </dependency>
    <unpack>true</unpack>
</resource>
<resource>
    <targetPath>${project.build.outputDirectory}/allDepsUnpacked</targetPath>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.gmaven</groupId>
            <artifactId>gmaven-plugin</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.gmaven.runtime</groupId>
            <artifactId>gmaven-runtime-1.7</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <includeScope>compile</includeScope>
            <excludeTransitive>true</excludeTransitive>
        </dependency>
    </dependencies>
    <unpack>true</unpack>
</resource>


Unpacking <zipEntries>

Occasionally, all you need to unpack is a number of ZIP entries and not the whole archive. This can speed up the process tremendously and is achieved with <zipEntries> or a single <zipEntry>:


<!-- Multiple Zip entries -->
<resource>
    <targetPath>${outputDir}</targetPath>
    <file>${resourcesDir}/apache-maven-3.0.1.zip</file>
    <zipEntries>
        <zipEntry>apache-maven-3.0.1/lib/maven-core-3.0.1.jar</zipEntry>
        <zipEntry>apache-maven-3.0.1/LICENSE.txt</zipEntry>
        <zipEntry>apache-maven-3.0.1/bin/mvnDebug.bat</zipEntry>
        <zipEntry>apache-maven-3.0.1/lib/maven-compat-3.0.1.jar</zipEntry>
    </zipEntries>
    <unpack>true</unpack>
</resource>
<!-- Single Zip entry -->
<resource>
    <targetPath>${outputDir}</targetPath>
    <file>${resourcesDir}/apache-maven-3.0.1.zip</file>
    <zipEntry>apache-maven-3.0.1/bin/mvnDebug.bat</zipEntry>
    <unpack>true</unpack>
</resource>
<!-- Single Zip entry with a pattern -->
<resource>
    <targetPath>${outputDir}</targetPath>
    <file>${resourcesDir}/apache-maven-3.0.1.zip</file>
    <zipEntry>**/*.jar</zipEntry>
    <preservePath>true</preservePath>
    <unpack>true</unpack>
</resource>


  • When <preservePath> is false (default value) then all archive content matched by <zipEntry> is copied to "<targetPath>" regardless of original Zip path.
  • When <preservePath> is true then all archive content matched by <zipEntry> is copied to "<targetPath>/original/zip/path".


Choosing between TrueZip and Ant

It is possible to choose either TrueZip or Ant libraries for pack and unpack operations:


<configuration>
    <!-- Default value -->
    <useTrueZipForPack>false</useTrueZipForPack>
    <!-- Default value -->
    <useTrueZipForUnpack>true</useTrueZipForUnpack>
    <resources>
        ...
    </resources>
</configuration>


Or


<resource>
    <!-- Default value -->
    <useTrueZipForPack>false</useTrueZipForPack>
    ...
    <pack>true</pack>
</resource>
<resource>
    <!-- Default value -->
    <useTrueZipForUnpack>true</useTrueZipForUnpack>
    ...
    <unpack>true</unpack>
</resource>


By default, Ant is used when archives are packed and TrueZip is used when archives are unpacked as it demonstrated a better performance with large archives.


Networking Support

Sometime, files are not available locally so networking support is provided. Those two tests provide examples of HTTP, SCP, FTP download and SCP, FTP upload. HTTP upload capability is planned to be implemented later.


Downloading files: HTTP, SCP, FTP

<resource>
    <targetPath>...</targetPath>
    <directory>\\server\directory</directory>
</resource>
<resource>
    <targetPath>...</targetPath>
    <file>http://server/file.zip</file>
    <unpack>true</unpack>
</resource>
<resource>
    <targetPath>...</targetPath>
    <file>scp://user:password@server:/path/to/file.zip</file>
    <unpack>true</unpack>
</resource>
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@server:/path</directory>
    <include>file.zip</include>
    <unpack>true</unpack>
</resource>


Downloading files - advanced FTP

This plugin provides a special support for downloading FTP files:

<!-- Downloading files with <include> / <exclude> patterns -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>*.zip, *.jar, *.xml</include>
    <exclude>*-2010-*.zip</exclude>
</resource>
 
<!-- Downloading files with <include> / <exclude> patterns specified in external file -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>file:path/to/includePatterns.txt</include>
</resource>
 
<!-- Downloading files with <include> / <exclude> patterns specified in a file available in a classpath -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
</resource>
 
<!-- Downloading files specifying download attempts and timeout (in seconds) -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <retries>10</retries>   <!-- Default is "5" -->
    <timeout>1800</timeout> <!-- Default is "3600" -->
</resource>


Downloading files - FTP with "wget" and <listFilter>

Some FTP servers are too slow and trying to download files can timeout. In this case it is possible to get a list of files to download and retrieve them with "wget":

<!-- Downloading files specifying "wget" for listing files -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <!-- Command to run "wget" -->
    <wget>wget</wget>
</resource>
 
<!-- Downloading files specifying listing file name -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <wget>wget.exe|ftp-list.txt</wget> <!-- Default listing file name is "ftp-list.txt" in user's current directory -->
</resource>
 
<!-- Downloading files specifying whether or not listing file should be deleted upon completion -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <wget>wget.exe|ftp-list.txt|false</wget> <!-- By default, listing file is deleted -->
</resource>
 
<!-- Downloading files specifying whether or not native FTP listing should be applied -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <wget>wget.exe|ftp-list.txt|false|true</wget> <!-- By default, Ant task is used to list the files -->
</resource>
 
<!-- Downloading files specifying "list filter" to filter files that end with "2011.zip" -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <wget>wget.exe</wget>
    <listFilter>
    {{
        files.keySet().findAll{ it.endsWith( '2011.zip' ) }
    }}
    </listFilter>
</resource>
 
<!-- Downloading files specifying "list filter" to filter files that are larger than 500 bytes -->
<resource>
    <targetPath>...</targetPath>
    <directory>ftp://user:password@host:/path</directory>
    <include>classpath:includePatterns.txt</include>
    <wget>wget.exe</wget>
    <listFilter>
    {{
        files.keySet().findAll{ files[ it ] > 500 }
    }}
    </listFilter>
</resource>


  • wget for Windows platform can be downloaded from http://gnuwin32.sourceforge.net/packages/wget.htm.
  • "Native FTP listing" means include patterns will be passed as glob patterns to FTP server, i.e., the listing process will happen on the server side (by default, Ant task is used to list the files which can be significantly slower).
    When "Native FTP listing" is applied, exclude patterns can still be used, they will be applied on files listed by FTP server.
  • Similarly to <filter>, <listFilter> Groovy snippet has "files" variables passed to it which is a Map<String, Long> of file FTP URLs to their size. Sample key is "ftp://host.com/path/file.xml.zip", sample value is 23505456. Return value from evaluating Groovy expression is expected to be either Collection<String> or String - URL(s) of files to download.
  • <listFilter> is only active when <wget>..</wget> is specified. Otherwise, no listing file is created and there's nothing to filter.


Uploading files: SCP, FTP

<resource>
    <targetPath>\\server\directory</targetPath>
    <directory>...</directory>
</resource>
<resource>
    <targetPath>scp://user:password@server:/path/to/dir</targetPath>
    <file>...</file>
</resource>
<resource>
    <targetPath>scp://user:password@server:/path/to/dir</targetPath>
    <directory>...</directory>
    <include>...</include>
    <exclude>...</exclude>
</resource>
<resource>
    <targetPath>ftp://user:password@server:/path/to/dir</targetPath>
    <directory>...</directory>
    <include>...</include>
    <exclude>...</exclude>
</resource>


Additional Operations

Most of the time being able to copy, pack, unpack, download and upload files is enough. Sometimes, additional operations are required.


<clean>

<!-- Clean up matching files only -->
<resource>
    <directory>...</directory>
    <includes>...</includes>
    <excludes>...</excludes>
    <clean>true</clean>
</resource>
 
<!-- Clean up matching files and any empty directories that are left -->
<resource>
    <directory>...</directory>
    <includes>...</includes>
    <excludes>...</excludes>
    <clean>true</clean>
    <cleanEmptyDirectories>true</cleanEmptyDirectories>
</resource>
  • Instead of copying from, <clean> deletes all files matched by a usual <directory> + <includes> - <excludes> combination.
  • Optional <cleanEmptyDirectories> allows to clean any empty directories that resulted from cleaning files, false by default.
  • If you need to move files, consider using <move> operation instead.


<mkdir>

<resource>
    <targetPath>...</targetPath>
    <mkdir>true</mkdir>
</resource>

Instead of copying to, <mkdir> creates directory specified by the <targetPath>. Remember that empty directories are not copied so feel it with data before attempting to copy it somewhere.


Groovy "extension points"

I believe in extendable software. Software that can be used beyond its original capabilities. Within time I've witnessed how Perl modules, Firefox extensions and IDEA plugins provided ways for people to implement their own custom needs. Those that couldn't be met originally since it is simply not realistically to meet everybody's needs in one single piece of software, be it a product or a language library.

So extension points or plugins is the real "must" for any platform, technology or a product and "copy-maven-plugin" is not exception to this rule, of course.

Some tags support {{ ... }} sections for Groovy code snippets which are executed and results returned are used according to the situation in place.


Maven properties as Groovy variables

All Maven <properties> that are explicitly set in POM are available as Groovy variables in the scope of {{ ... }} snippet.


<properties>
    <dogName>Rony</dogName>
</properties>


allows to use it like: {{ println "My dog's name is $dogName" }}

Note the following: if you use it as {{ println "My dog's name is ${dogName}" }} Maven 2 will interpolate ${dogName} as Maven property long before plugin has started and will replace it with a value known to it or "null". If this is not what you want, use either of the following to stop Maven's interpolation from happening:

  • {{ println "My dog's name is $dogName" }}
  • {{ println "My dog's name is " + dogName }}

Maven 3 will also try to interpolate every POM's ${expression} but it will leave unknown expressions untouched so they can still be used in Groovy snippets.


Maven properties with illegal Groovy characters in their names have illegal characters removed and following letter capitalized, similarly to how CSS properties are converted to JavaScript variables. This way, ${build-url} Maven property becomes a "buildUrl" Groovy variable.

"Built-in" properties like ${project.groupId} and ${project.build.directory} are not available in Groovy snippet as "projectGroupId" or "projectBuildDirectory" since they're not specified explicitly as Maven <properties>. They are POM elements that can be access as if they were properties but this is not the same. But you can redefine them again:

<properties>
    <groupId>${project.groupId}</groupId>
    <dist-dir>${project.build.directory}</dist-dir>
</properties>

and then "groupId" and "distDir" Groovy variables will be available in the scope of snippet. Or you can use "project" and "session" provided variables, as described below.


System properties and environment variables as Groovy variables

Both system properties and environment variables are also accessible, as demonstrated in this test:


/**
 * System Properties
 */
"java.version" : javaVersion
"os.name"      : osName
 
/**
 * Environment Variables
 */
M2_HOME        : envM2_HOME
JAVA_HOME      : envJAVA_HOME


"project", "session" and "mavenVersion" in Groovy snippet

Three additional Groovy variables are available in the scope of snippet:


Name Description
"project" Plugin's org.apache.maven.project.MavenProject
"session" Plugin's org.apache.maven.execution.MavenSession
"mavenVersion" Maven version as appears in "META-INF/maven/org.apache.maven/maven-core/pom.properties"


One can write:

  • {{ println project.groupId }}
  • {{ println project.basedir }}
  • {{ println project.properties.propertyName }} - if "propertyName" is set dynamically by "properties-maven-plugin", otherwise you can just use {{ println propertyName }}
  • {{ println session.userProperties.propertyName }}
  • {{ println session.startTime }}


For other cases, "properties-maven-plugin" provides another way to introduce new Maven properties and, therefore, Groovy variables by evaluating a Groovy snippet.


Replacing text content

Some <to> replacements can't be expressed statically and need to be calculated dynamically. This can be done by adding a <groovy> tag:

<resource>
    <targetPath>${project.build.outputDirectory}/config</targetPath>
    <directory>${project.basedir}/src/main/resources</directory>
    <include>*.xml</include>
    <replace>
        <from>#timestamp#</from>
        <to>Built at {{ new Date() }} by {{ buildUrl.contains( '0000' ) ? 'Local build' : buildUrl }}</to>
        <groovy>true</groovy>
    </replace>
</resource>

In above scenario there's no need to specify <groovy>true</groovy> but it is required here since {{ ... }} section can be part of a usual replacement string, not related to Groovy in any way.


Filtering files: <filter>

Files or dependencies to copy, pack or unpack can be filtered with <include/exclude>, <include/excludeScope>, and <include/excludeGroupId>. But sometimes that is not enough. What if you need to pick up the biggest file? Or the latest one? Or the one having some special token? Since there are endless options for how a set of files can be filtered out before copying, it didn't make sense to add a special tag for all of them.

Instead, <filter> extension point handles those cases:

<resource>
    <!-- Only copy files larger than 50Mb -->
    <targetPath>...</targetPath>
    <directory>...</directory>
    <includes>...</includes>
    <filter>{{ files.findAll{ it.size > ( 50 * 1024 * 1024 ) } }}</filter>
</resource>

Before files are copied they are passed as "files" Groovy variable of type List<File> to the snippet. It is expected to return a File or Collection<File> and only those files returned will be copied.

A shortcut is available: <filter>{{latest}}</filter> will only copy the latest file and other Ant selectors are planned to be supported as well.

When files are downloaded their temp location, where they're downloaded to, is specified in "files".

<filter> works similarly for <pack>, <unpack> and <clean> operations where it filters out files to pack, unpack or delete. It is ignored for <mkdir> operation.

Do not confuse <filter> with <filtering>!
<filter> is used to filter out files before copying them while <filtering> is used to enable Maven properties filtering when they're copied. While it sounds similar it serves two absolutely different purposes.


Post-processing files: <process>

After files are copied, pack or unpacked you may want to <process> them for reporting or "chmod"-ing:

<resource>
    <targetPath>...</targetPath>
    <directory>...</directory>
    <includes>
        ...
    </includes>
    <filter>{{  println "Files copied from: $files"; files }}</filter> <!-- We need to return a value from filter! -->
    <process>{{ println "Files copied to:   $files" }}</process>
</resource>
<resource>
    <targetPath>${project.build.directory}/${zipName}.zip</targetPath>
    <directory>${build-dir}</directory>
    <pack>true</pack>
    <process>{{ println "Archive is created at [${ files.first().canonicalPath }] of size [${ files.first().size / ( 1024 * 1024 ) }] Mb" }}</process>
</resource>
<resource>
    <targetPath>${project.build.directory}</targetPath>
    <directory>${build-dir}</directory>
    <include>**/*.sh</include>
    <process>{{ files.each{ "chmod +x '$it'".execute() } }}</process>
</resource>
<resource>
    <targetPath>${project.build.directory}</targetPath>
    <directory>${build-dir}</directory>
    <include>**/*.sh</include>
    <process>{{ ( "chmod +x '" + files.join( ", " ) + "'" ).execute() }}</process>
</resource>

After files are copied they are passed as "files" Groovy variable of type List<File> to the snippet. Its return type is ignored since there's nothing left to do with "files" after this step.

When files are uploaded their final location, where they're uploaded to, is specified in "files".

<process> works similarly for <pack>, <unpack> and <mkdir> operations where it processes files packed, unpacked or directory created. It is ignored for <clean> operation.


Supporting Groovy classes with <classpath>

Sometimes Groovy logic can't be expressed by one-liner or you don't like the idea of putting too may Groovy code in POMs. In those cases you may want to call a regular Groovy class from the snippet:

<configuration>
    <groovyConfig>
        <classpath>${project.basedir}/src/main/scripts</classpath>
    </groovyConfig>
    <resources>
        ...
        <resource>
            <targetPath>${project.build.directory}/${zipName}.zip</targetPath>
            <directory>${build-dir}</directory>
            <pack>true</pack>
            <process>{{ Utils.report( files.first()) }}</process>
        </resource>
        ...
    </resources>
</configuration>

<groovyConfig> allows to add classpath entries for Groovy snippet evaluation using either a single <classpath> entry or multiply <classpaths>:

<configuration>
    <groovyConfig>
        <classpaths>
            <classpath>${project.basedir}/src/main/scripts</classpath>
            <classpath>${project.basedir}/src/main/groovy</classpath>
            <classpath>${project.basedir}/src/main/utils</classpath>
        </classpaths>
        ...
    </groovyConfig>
</configuration>

Now, "src/main/scripts/Utils.groovy":

import java.text.SimpleDateFormat
 
final class Utils
{
    private static final long Kb = (      1024L );
    private static final long Mb = ( Kb * 1024L );
    private static final long Gb = ( Mb * 1024L );
    private static final long Tb = ( Gb * 1024L );
    private static final long Pb = ( Tb * 1024L );
 
    def Utils ()
    {
    }
 
    static void report ( File file )
    {
        def sdf  = simpleDateFormat( 'dd/MM/yyyy HH:mm (\'GMT\'Z)', 'Israel' );
        println ( "[" + file + "]: " + size( file.size()) + " [" + sdf.format( new Date()) + "]" );
    }
 
    static SimpleDateFormat simpleDateFormat( String format, String timeZone )
    {
        SimpleDateFormat sdf = new SimpleDateFormat( format );
        sdf.setTimeZone( TimeZone.getTimeZone( timeZone ));
        return sdf;
    }
 
    static String size( Number bytes )
    {
        (( bytes < Kb ) ? "${ bytes } bytes" :
         ( bytes < Mb ) ? "${ bytes.intdiv( Kb )} Kb" :
         ( bytes < Gb ) ? "${ bytes.intdiv( Mb )} Mb" :
         ( bytes < Tb ) ? "${ bytes.intdiv( Gb )} Gb" :
         ( bytes < Pb ) ? "${ bytes.intdiv( Tb )} Tb" :
                          "${ bytes.intdiv( Pb )} Pb" );
    }
}

Utils.report() produces a nice summary of archive size and timestamp creation:

...
[C:\.hudson\jobs\...\Something.zip]: 136 Mb [17/05/2010 18:09 (GMT+0300)]
...


<groovyConfig>

<groovyConfig> provides the following options:

<!-- Specifying multiple <classpaths>, <verbose> and <verboseBinding> -->
<configuration>
    <groovyConfig>
        <classpaths>
            <classpath>${project.basedir}/src/main/scripts</classpath>
            <classpath>${project.basedir}/src/main/groovy</classpath>
        </classpaths>
        <verbose>false</verbose>               <!-- "true" by default -->
        <verboseBinding>true</verboseBinding>  <!-- "false" by default -->
    </groovyConfig>
    <resources>
        ...
    </resources>
</configuration>
 
<!-- Specifying single <classpath> -->
<configuration>
    <groovyConfig>
        <classpath>${project.basedir}/src/main/scripts</classpath>
    </groovyConfig>
    <resources>
        ...
    </resources>
</configuration>


  • <classpaths> or <classpath> allow to use additional Groovy classes by adding locations to snippet classpath.
  • <verbose> controls whether Groovy invocation is verbose and return results are displayed, on by default.
  • <verboseBinding> controls whether Groovy binding is dumped before snippet is invoked, off by default.
  • See "properties-maven-plugin" for creating Maven properties with Groovy. Those properties can later be used as usual variables in Groovy <process> and <filter> snippets.


Additional <configuration> options

As mentioned above, "copy-maven-plugin" only requires some <resources> to get going. But some other configurations can also be applied to alter its default behavior.


<runIf>

<runIf> runs a Groovy snippet to determine if plugin or <resource> should or should not be invoked. Snippet's return value converted to boolean with Boolean.valueOf( String.valueOf( value )).


<!-- Specifying <runIf> for all <resources> -->
<configuration>
    <runIf>{{ ... }}</runIf>
    <resources>
        ...
    </resources>
</configuration>
 
<!-- Specifying <runIf> for specific <resource> -->
<resource>
    <runIf>{{ ... }}</runIf>
    ...
</resource>


Some <runIf> examples:


Code Description
<runIf>{{ something != "none" }}</runIf> Only run if Maven property ${something}, being available as Groovy variable, isn't equal to "none"
<runIf>{{ new File( distDir, "config.xml" ).isFile() }}</runIf> Only run if "${distDir}/config.xml" file is available, assuming you have ${distDir} property set
<runIf>{{ new File( project.basedir, 'src' ).isDirectory() }}</runIf> Only run if "src" directory is available in the base directory
<runIf>{{ compilationSuccess }}</runIf> Only run if "compilationSuccess" is set by another plugin to "true" or true
<runIf>{{ project.basedir.listFiles().any{ it.name.endsWith( '.sln' ) }}}</runIf> Only run if "*.sln" files are available in the base directory


<verbose>

A boolean flags that can be set to control whether files copied, packed or unpacked are displayed. Default value is "true".

<!-- Specifying <verbose> for all <resources> -->
<configuration>
    <verbose>false</verbose>
    <resources>
        ...
    </resources>
</configuration>
 
<!-- Specifying <verbose> for specific <resource> -->
<resource>
    <verbose>false</verbose>
    ...
</resource>


<skipIdentical>

Another boolean flags that can be set per single <resource> or per all <resources>. Default value is "false".

<!-- Specifying <skipIdentical> for all <resources> -->
<configuration>
    <skipIdentical>true</skipIdentical>
    <resources>
       ...
    </resources>
</configuration>
 
<!-- Specifying <skipIdentical> for specific <resource> -->
<resource>
    <skipIdentical>true</skipIdentical>
    ...
</resource>


  • <skipIdentical> attempts to speed up builds and skip copying of files that are already copied, off by default.
  • When this flag is turned on, plugin will analyze each file before copying and if destination file already exists, has identical length and "lastModified" timestamp, no copying will be made. It is useful for copying large number of static resources like "*.html" or image files: once done there's no need to overwrite them again. Note that in order to save time and run very fast, the plugin doesn't do any content-related checks (like MD5 calculation) for <skipIdentical>.
  • Since "mvn clean" takes care of any previously created or copied files, this option is useful for "lightweight" builds, running without "clean".


<failIfNotFound>

Default plugin behavior is to fail if any <resource> doesn't find files to copy, pack, unpack or clean. This can be overridden with <failIfNotFound> boolean flag. Default value is "true".

<!-- Specify <failIfNotFound> for all <resources> -->
<configuration>
    <failIfNotFound>false</failIfNotFound>
    <resources>
        ...
    </resources>
</configuration>
 
<!-- Specify <failIfNotFound> for specific <resource> -->
<resource>
    <failIfNotFound>false</failIfNotFound>
    ...
</resource>


<defaultExcludes>

By default, following files are excluded when copying or archiving files: .settings, .classpath, .project, *.iws, *.iml, *.ipr, *~, #*#, .#*, %*%, ._*, CVS, .cvsignore, RCS, SCCS, vssver.scc, .svn, .arch-ids, .bzr, .MySCMServerInfo, .DS_Store, .metadata, .hg, .git, BitKeeper, .git, ChangeSet, _darcs, .darcsrepo, -darcs-backup, .darcs-temp-mail.

You can control this list or disable it with <defaultExcludes>, taking values of "false" or comma-separated list of exclude patterns.

<!-- Disable default excludes for all <resources> -->
<configuration>
    <defaultExcludes>false</defaultExcludes>
    <resources>
    ...
    </resources>
</configuration>
 
<!-- Disable default excludes for specific <resource> -->
<resource>
    <targetPath>${outputDir}</targetPath>
    <directory>${srcDir}</directory>
    <defaultExcludes>false</defaultExcludes>
    <preservePath>true</preservePath>
</resource>
 
<!-- Specify default excludes for all <resources> - comma separated list of patterns -->
<configuration>
    <defaultExcludes>**/.svn/**, **/.git/**</defaultExcludes>
    <resources>
    ...
    </resources>
</configuration>
 
<!-- Specify default excludes for specific <resource> - comma separated list of patterns -->
<resource>
    <targetPath>${outputDir}</targetPath>
    <directory>${srcDir}</directory>
    <defaultExcludes>.gitignore</defaultExcludes>
    <preservePath>true</preservePath>
</resource>


Tips and Tricks

Comment each <resource>

I found it highly useful to comment each <resource> when there are many of them:

<configuration>
    <resources>
        <!-- ~~~~~~~~~~~~~~~~~~~ -->
        <!-- Copying XML congigs -->
        <!-- ~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            ...
        </resource>
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- Copying third-party libraries -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            ...
        </resource>
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- Copying other dependencies -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            ...
        </resource>
        <!-- ~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- Collecting *.bat files -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            ...
        </resource>
        <!-- ~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- Creating final archive -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            ...
        </resource>
    </resources>
</configuration>

Otherwise, it is very easy to loose the sight of what's going on with too many <resources>.


Group relative <resource> sections together

It also helps organizing similar <resource> section close one to another and document them properly:

<configuration>
    <resources>
        <!-- ~~~~~~~~~~~ -->
        <!-- "ct/var/im" -->
        <!-- ~~~~~~~~~~~ -->
        <resource>
            <targetPath>${project.build.outputDirectory}/ct/var/im</targetPath>
            ...
        </resource>
        <resource>
            <targetPath>${project.build.outputDirectory}/ct/var/im</targetPath>
            ...
        </resource>
        <resource>
            <targetPath>${project.build.outputDirectory}/ct/var/im</targetPath>
            ...
        </resource>
        <!-- ~~~~~~~~ -->
        <!-- "ct/bin" -->
        <!-- ~~~~~~~~ -->
        <resource>
            <targetPath>${project.build.outputDirectory}/ct/bin</targetPath>
            ...
        </resource>
        <resource>
            <targetPath>${project.build.outputDirectory}/ct/bin</targetPath>
            ...
        </resource>
        <!-- ~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- Creating final archive -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            ...
        </resource>
    </resources>
</configuration>


Copy as much as you can within a single <resource>

When there are many files from various folders that should be copied to the same <targetPath> I try to get them all with a single <resource>:

<configuration>
    <resources>
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- Copying all XML configs -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~ -->
        <resource>
            <targetPath>${project.build.outputDirectory}/conf</targetPath>
            <directory>/some/base/dir</directory>
            <includes>
                <include>some/project/dir/**/*.xml</include>
                <include>another/project/dir/**/*.xml</include>
                <include>some/other/project/dir/**/*.xml</include>
                ...
            </includes>
        </resource>
    </resources>
</configuration>

Remember how <preservePath> works, this examples assumes we want all matched XML files end up in the same "${project.build.outputDirectory}/conf" directory, effectively flattening their structures.


Creating new files

Strictly speaking, it is not possible to create new files with "copy-maven-plugin". Having said that, new text files can be created by "copying" any other text file, like "${project.basedir}/pom.xml", and replacing its entire content with the new one:

<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    <file>${project.basedir}/pom.xml</directory>
    <destFileName>fileName.xml</destFileName>
    <replace>
        <to>
             .. New file content goes here ..
        </to>
    </replace>
</resource>


The following examples creates an empty new file:

<resource>
    <targetPath>${project.build.outputDirectory}</targetPath>
    <file>${project.basedir}/pom.xml</directory>
    <destFileName>fileName.xml</destFileName>
    <replace>
        <to><!-- Nothing --></to>
    </replace>
</resource>
Personal tools