-
Notifications
You must be signed in to change notification settings - Fork 5
The Structure of centipede archetype
This page explains the structure of centipede archetype so that you can modify it if you need to or use the knowledge behind it to build other archetype plugins.
The most complex thing going on in the archetype plugin is that filtering is happening at three different points in the process, which causes things to be shuffled into different directories at different times and also requiring careful attention payed to escaping.
The build process for archetypes is typically pretty simple. A template for the project that will be created by the archetype is created in target/classes/archetype-resources
and a configuration file that controls the archetype generation process goes to target/classes/META-INF/maven/archetype-metadata.xml
.
For better and worse, we want the version number to be synchronized between centipede
and centipede-archetype
. In version 99.0 I hadn't paid attention to this, so the system incorrectly built a dependency on the 99.0-SNAPSHOT
version of centipede
into the pom.xml
file. Although large systems become unmaintainable when version synchronization is forced between a large projects, the centipede system is small and the addition of a manual step in the release process will certainly lead to errors and suffering.
This filtering is enabled by the following maven snippets in the pom.xml
for the archetype project:
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/filtered</directory>
</resource>
<resource>
<filtering>false</filtering>
<directory>${basedir}/src/main/resources</directory>
</resource>
</resources>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<escapeString>\</escapeString>
</configuration>
</plugin>
</plugins>
Note that the pom.xml
template for the generated project is put in the src\main\filtered
directory. Normally we wouldn't need to explicitly declare the maven-resources-plugin
, however, we need to do so in this case because we want to enable the backslash as an escape character. This lets us write ${version}
in the pom.xml
template to refer to the version of the archetype project and write \${version}
to have {$version}
written into the POM template which will later be interpolated with the version of the archetype project. Note that the maven-resources-plugin
uses it's own system of string interpolation which looks a bit like Velocity but is nowhere near is full-featured.
All of the other files that go into the archetype JAR are in the src/main/resources
directory and these are copied unfiltered into the project.
The bad news is that filtering is done differently upon archetype generation (what happens when you do mvn archetype:generate
), but the good news is that this filtering is done with Velocity, which is really powerful.
The archetype generation process is controlled by this code from the target/classes/META-INF/maven/archetype-metadata.xml
<fileSets>
<fileSet filtered="true" packaged="true">
<directory>src/main/java</directory>
</fileSet>
<fileSet filtered="true" packaged="true">
<directory>src/main/resources</directory>
</fileSet>
<fileSet filtered="true" packaged="false">
<directory>src/main/scripts</directory>
</fileSet>
<fileSet filtered="true" packaged="true">
<directory>src/test/java</directory>
</fileSet>
<fileSet filtered="true" packaged="false">
<directory>src/main/unpackaged-resources</directory>
</fileSet>
<fileSet filtered="true" packaged="true">
<directory>src/main/filtered</directory>
</fileSet>
</fileSets>
Note that the archetype generation system is only capable of renaming a directory in one sense. If you set packaged="true"
the system will install files into a package under the directory. That is, if the package of your generated project is com.example.myProject
, then the contents of src/test/java
get copied to src/test/java/com/example/myProject
. This is what you want for Java source code, but this is unacceptable for the log4j.properties
file, which will only get found by Log4J
if it is installed in the root of the classpath. I would have liked to have been able to just stick this in the resources
directory, but I couldn't do that, because it would have been relocated. Instead, I put it in the unpackaged-resources
directory of the generated project and rely on a cantrip in the generated POM (described later) to have it installed in the right place.
Some answers to interesting problems that come up can be found in the src/main/resources/archetype-resources/src/main/scripts/path.sh
file, which looks like
#!/bin/sh
#set( $symbol_dollar = '$' )
#set( $slashedGroup = $groupId.replace(".","/") )
alias ${artifactId}="java -jar $HOME/.m2/repository/${slashedGroup}/${artifactId}/${symbol_dollar}{project.version}/${artifactId}-${symbol_dollar}{project.version}-onejar.jar"
we define the {$symbol_dollar} symbol so that we can escape ${project.version}
so it will be properly filtered in the last step. The definition of $slashedGroup
solves a problem that turns up a lot with this sort of system, which is turning a package path into a directory path. A definition that works for Windows is in the corresponding path.ps1
file, and looks like
#set( $slashedGroup = $groupId.replace('.','\\') )
Finally some filtering happens when you do mvn install
on the generated project.
This is controlled by the following code in the template POM:
<resources>
<resource>
<filtering>true</filtering>
<directory>\${basedir}/src/main/filtered</directory>
</resource>
<resource>
<filtering>false</filtering>
<directory>\${basedir}/src/main/resources</directory>
</resource>
<resource>
<filtering>false</filtering>
<directory>\${basedir}/src/main/unpackaged-resources</directory>
</resource>
<resource>
<filtering>true</filtering>
<targetPath>..</targetPath>
<directory>\${basedir}/src/main/scripts</directory>
</resource>
</resources>
The src\main\filtered
directory contains a special properties file which is used to encode the version number of the package into the JAR:
#set( $symbol_dollar = '$' )
${package}.version=${symbol_dollar}{project.version}
Note that in the above set of processing, we substitute in the ${package} name. (Since the directory path is hard-wired, we might as well hard wire the package name.) This gets rewritten at archetype generation time to
com.example.myProject.version=${project.version}
which in turn picks up the version number when you build the generated project. Note that the configuration in the POM above causes the output of filtered
, resources
, and unpackaged-resources
to all be copied to the classpath, which merges the version.properties
file and the log4j.properties
with all of the other resource files when you really build your project.
The above configuration also causes the scripts, after the last round of filtering, to be written directly into the target
directory and not be packaged up in the JAR.