Goal
The purpose of this blog post is to demonstrate how to generalize a Maven project that suits a specific need into a re-usable archetype, how to publish this archetype to a Maven repository and how to use it as the starting point for similar projects.
tl;dr
The artifact that is the end result of this tutorial can be used for creating a boilerplate Tycho plug-in project hierarchy with minimal configuration. This generated project will contain a simple Eclipse UI plug-in, an integration-test fragment, a feature that contains the binary plug-in, a feature that contains the source of the plug-in and its test fragment, and an update site for publishing both features.
Generate Archetype From Project
Since we are moving from the specific to the general, we begin by cloning the save-actions-extensions project from github.
git clone git://github.com/timezra/save-actions-extensions.git
Since we need only a few of the components from the save-actions-extensions project, we can prune out the extra. We must also rename the features, plugins and update-site directories to match the artifactIds in their respective poms along with the module declarations in the root pom. This directory structure follows the Eclipse convention, but archetype generation will be easier if we make sure that the artifactIds and directory names match. We will also rename the individual plugins and features along with their artifactIds so that there is a consistent name prefix for all projects. Again, this convention will help when generating the archetype.
The pruned project
save-actions-extensions
.
|____pom.xml
|____README
| timezra.eclipse.save_actions_extensions.features
| |____pom.xml
| | timezra.eclipse.save_actions_extensions.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| | timezra.eclipse.save_actions_extensions.source.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| timezra.eclipse.save_actions_extensions.plugins
| |____pom.xml
| | timezra.eclipse.save_actions_extensions.plugin
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | main
| | | | | java
| | | | | | timezra
| | | | | | | eclipse
| | | | | | | | save_actions_extensions
| | | | | | | | | plugin
| | | | | | | | | |____Activator.java
| | timezra.eclipse.save_actions_extensions.plugin.tests
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | test
| | | | | java
| | | | | | timezra
| | | | | | | eclipse
| | | | | | | | save_actions_extensions
| | | | | | | | | plugin
| | | | | | | | | |____ActivatorTest.java
| timezra.eclipse.save_actions_extensions.update-site
| |____index.html
| |____pom.xml
| |____site.xml
| | web
| | |____site.css
| | |____site.xsl
.
|____pom.xml
|____README
| timezra.eclipse.save_actions_extensions.features
| |____pom.xml
| | timezra.eclipse.save_actions_extensions.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| | timezra.eclipse.save_actions_extensions.source.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| timezra.eclipse.save_actions_extensions.plugins
| |____pom.xml
| | timezra.eclipse.save_actions_extensions.plugin
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | main
| | | | | java
| | | | | | timezra
| | | | | | | eclipse
| | | | | | | | save_actions_extensions
| | | | | | | | | plugin
| | | | | | | | | |____Activator.java
| | timezra.eclipse.save_actions_extensions.plugin.tests
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | test
| | | | | java
| | | | | | timezra
| | | | | | | eclipse
| | | | | | | | save_actions_extensions
| | | | | | | | | plugin
| | | | | | | | | |____ActivatorTest.java
| timezra.eclipse.save_actions_extensions.update-site
| |____index.html
| |____pom.xml
| |____site.xml
| | web
| | |____site.css
| | |____site.xsl
We are now ready to generate, install and test the archetype from maven.
NB: even when specifying filtered extensions, not all files (here README, MANIFEST.MF and site.xsl) are turned into velocity templates, and the hard-coded artifactId references in some files are not all replaced with parameters.
mvn archetype:create-from-project -Darchetype.filteredExtensions=java,html,xsl,properties,xml,MF
cd target/generated-sources/archetype/
find ./src -type f \( -name "*.MF" -o -name "*.xsl" \) -print0 |xargs -0 perl -pi -e 's/\$/\${symbol_dollar}/g'
find ./src -type f \( -name "*.MF" -o -name "*.xsl" \) -exec perl -pi -e "print qq(#set( \\\$symbol_pound = '#' )\n#set( \\\$symbol_dollar = '\\$' )\n#set( \\\$symbol_escape = '\\\' )\n) if $. == 1" {} \;
find ./src -type f -print0 |xargs -0 perl -pi -e 's/timezra\.eclipse\.save_actions_extensions/\$\{rootArtifactId}/g'
find ./src -type f -print0 |xargs -0 perl -pi -e 's/\$\{groupId}\.save_actions_extensions/\$\{rootArtifactId}/g'
find ./src -name archetype-metadata.xml -exec perl -pi -e 'undef $/; $_=<>; s/\s*\n\s*//g; s~README~README~g;s~META-INF~META-INF~g;s~web \*\*/\*\.xsl ~web **/*.xsl web ~g' {} \;
mvn clean install
cd /path/to/tmp/archetype
mvn archetype:generate -DarchetypeCatalog=local
# Choose the new archetype (here local -> timezra.eclipse:timezra.eclipse.save_actions_extensions-archetype)
# Define a groupId (e.g., my.company)
# Define an artifactId (e.g., my.company.do_something)
# Define a version (e.g., 1.0.0-SNAPSHOT)
# Define a package (e.g., my.company.do_something.plugin)
cd my.company.do_something
mvn verify
cd target/generated-sources/archetype/
find ./src -type f \( -name "*.MF" -o -name "*.xsl" \) -print0 |xargs -0 perl -pi -e 's/\$/\${symbol_dollar}/g'
find ./src -type f \( -name "*.MF" -o -name "*.xsl" \) -exec perl -pi -e "print qq(#set( \\\$symbol_pound = '#' )\n#set( \\\$symbol_dollar = '\\$' )\n#set( \\\$symbol_escape = '\\\' )\n) if $. == 1" {} \;
find ./src -type f -print0 |xargs -0 perl -pi -e 's/timezra\.eclipse\.save_actions_extensions/\$\{rootArtifactId}/g'
find ./src -type f -print0 |xargs -0 perl -pi -e 's/\$\{groupId}\.save_actions_extensions/\$\{rootArtifactId}/g'
find ./src -name archetype-metadata.xml -exec perl -pi -e 'undef $/; $_=<>; s/\s*\n\s*//g; s~
mvn clean install
cd /path/to/tmp/archetype
mvn archetype:generate -DarchetypeCatalog=local
# Choose the new archetype (here local -> timezra.eclipse:timezra.eclipse.save_actions_extensions-archetype)
# Define a groupId (e.g., my.company)
# Define an artifactId (e.g., my.company.do_something)
# Define a version (e.g., 1.0.0-SNAPSHOT)
# Define a package (e.g., my.company.do_something.plugin)
cd my.company.do_something
mvn verify
After running this sequence of commands, we should see the eclipse workbench open and successfully run the canary integration test for our test project.
The generated project structure
my.company.do_something
.
|____pom.xml
|____README
| my.company.do_something.features
| |____pom.xml
| | my.company.do_something.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| | my.company.do_something.source.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| my.company.do_something.plugins
| |____pom.xml
| | my.company.do_something.plugin
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | main
| | | | | java
| | | | | | my
| | | | | | | company
| | | | | | | | do_something
| | | | | | | | | plugin
| | | | | | | | | |____Activator.java
| | my.company.do_something.plugin.tests
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | test
| | | | | java
| | | | | | my
| | | | | | | company
| | | | | | | | do_something
| | | | | | | | | plugin
| | | | | | | | | |____ActivatorTest.java
| my.company.do_something.update-site
| |____index.html
| |____pom.xml
| |____site.xml
| | web
| | |____site.css
| | |____site.xsl
.
|____pom.xml
|____README
| my.company.do_something.features
| |____pom.xml
| | my.company.do_something.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| | my.company.do_something.source.feature
| | |____build.properties
| | |____feature.properties
| | |____feature.xml
| | |____license.html
| | |____pom.xml
| my.company.do_something.plugins
| |____pom.xml
| | my.company.do_something.plugin
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | main
| | | | | java
| | | | | | my
| | | | | | | company
| | | | | | | | do_something
| | | | | | | | | plugin
| | | | | | | | | |____Activator.java
| | my.company.do_something.plugin.tests
| | |____build.properties
| | |____plugin.properties
| | |____pom.xml
| | | META-INF
| | | |____MANIFEST.MF
| | | src
| | | | test
| | | | | java
| | | | | | my
| | | | | | | company
| | | | | | | | do_something
| | | | | | | | | plugin
| | | | | | | | | |____ActivatorTest.java
| my.company.do_something.update-site
| |____index.html
| |____pom.xml
| |____site.xml
| | web
| | |____site.css
| | |____site.xsl
Dynamic Directories
On closer comparison of the directory tree from the original project and the generated project, you might wonder how Maven knows to name folders based on the artifactIds.
If we look at the generated archetype, we will see a parameterized directory structure.
Parameterized directories are in red
archetype
.
|____pom.xml
| src
| | main
| | | resources
| | | | archetype-resources
| | | | |____pom.xml
| | | | |____README
| | | | | __rootArtifactId__.features
| | | | | |____pom.xml
| | | | | | __rootArtifactId__.feature
| | | | | | |____build.properties
| | | | | | |____feature.properties
| | | | | | |____feature.xml
| | | | | | |____license.html
| | | | | | |____pom.xml
| | | | | | __rootArtifactId__.source.feature
| | | | | | |____build.properties
| | | | | | |____feature.properties
| | | | | | |____feature.xml
| | | | | | |____license.html
| | | | | | |____pom.xml
| | | | | __rootArtifactId__.plugins
| | | | | |____pom.xml
| | | | | | __rootArtifactId__.plugin
| | | | | | |____build.properties
| | | | | | |____plugin.properties
| | | | | | |____pom.xml
| | | | | | | META-INF
| | | | | | | |____MANIFEST.MF
| | | | | | | src
| | | | | | | | main
| | | | | | | | | java
| | | | | | | | | |____Activator.java
| | | | | | __rootArtifactId__.plugin.tests
| | | | | | |____build.properties
| | | | | | |____plugin.properties
| | | | | | |____pom.xml
| | | | | | | META-INF
| | | | | | | |____MANIFEST.MF
| | | | | | | src
| | | | | | | | test
| | | | | | | | | java
| | | | | | | | | |____ActivatorTest.java
| | | | | __rootArtifactId__.update-site
| | | | | |____index.html
| | | | | |____pom.xml
| | | | | |____site.xml
| | | | | | web
| | | | | | |____site.css
| | | | | | |____site.xsl
| | | | META-INF
| | | | | maven
| | | | | |____archetype-metadata.xml
| | test
| | | resources
| | | | projects
| | | | | basic
| | | | | |____archetype.properties
| | | | | |____goal.txt
.
|____pom.xml
| src
| | main
| | | resources
| | | | archetype-resources
| | | | |____pom.xml
| | | | |____README
| | | | | __rootArtifactId__.features
| | | | | |____pom.xml
| | | | | | __rootArtifactId__.feature
| | | | | | |____build.properties
| | | | | | |____feature.properties
| | | | | | |____feature.xml
| | | | | | |____license.html
| | | | | | |____pom.xml
| | | | | | __rootArtifactId__.source.feature
| | | | | | |____build.properties
| | | | | | |____feature.properties
| | | | | | |____feature.xml
| | | | | | |____license.html
| | | | | | |____pom.xml
| | | | | __rootArtifactId__.plugins
| | | | | |____pom.xml
| | | | | | __rootArtifactId__.plugin
| | | | | | |____build.properties
| | | | | | |____plugin.properties
| | | | | | |____pom.xml
| | | | | | | META-INF
| | | | | | | |____MANIFEST.MF
| | | | | | | src
| | | | | | | | main
| | | | | | | | | java
| | | | | | | | | |____Activator.java
| | | | | | __rootArtifactId__.plugin.tests
| | | | | | |____build.properties
| | | | | | |____plugin.properties
| | | | | | |____pom.xml
| | | | | | | META-INF
| | | | | | | |____MANIFEST.MF
| | | | | | | src
| | | | | | | | test
| | | | | | | | | java
| | | | | | | | | |____ActivatorTest.java
| | | | | __rootArtifactId__.update-site
| | | | | |____index.html
| | | | | |____pom.xml
| | | | | |____site.xml
| | | | | | web
| | | | | | |____site.css
| | | | | | |____site.xsl
| | | | META-INF
| | | | | maven
| | | | | |____archetype-metadata.xml
| | test
| | | resources
| | | | projects
| | | | | basic
| | | | | |____archetype.properties
| | | | | |____goal.txt
You might also notice that the Activator.java and ActivatorTest.java are contained in the root of their src/main/java and src/test/java folders, respectively, in the archetype resources; however, in the generated project, the files are in the proper subdirectories with corresponding package declarations. In the generated archetype-metadata.xml, the contents of these source directories are filtered and packaged.
src/main/resources/META-INF/maven/archetype-metadata.xml
<archetype-descriptor ....>
....
<modules>
<module id="${rootArtifactId}.plugins" ....>
<modules>
<module id="${rootArtifactId}.plugin" ....>
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/main/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
....
</fileSets>
</module>
<module id="${rootArtifactId}.plugin.tests" ....>
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/test/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
....
</fileSets>
</module>
</modules>
</module>
....
</modules>
</archetype-descriptor>
....
<modules>
<module id="${rootArtifactId}.plugins" ....>
<modules>
<module id="${rootArtifactId}.plugin" ....>
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/main/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
....
</fileSets>
</module>
<module id="${rootArtifactId}.plugin.tests" ....>
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/test/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
....
</fileSets>
</module>
</modules>
</module>
....
</modules>
</archetype-descriptor>
Testing
After customizing our resources and adding any additional required properties, we can integration-test the archetype using a golden template project contained in the src/test/projects/basic/reference directory. For this example, if the generated my.company.do_something project above exactly meets our needs, then we can simply copy the entire contents of this directory to the src/test/projects/basic/reference folder and update the archetype.properties to match the properties we used for project generation.
src/test/resources/projects/basic/archetype.properties
groupId=my.company
artifactId=my.company.do_something
version=1.0.0-SNAPSHOT
package=my.company.do_something.plugin
artifactId=my.company.do_something
version=1.0.0-SNAPSHOT
package=my.company.do_something.plugin
During the integration-test phase, Maven will generate a sample project from the archetype with these properties and will compare the results with the golden template.
mvn verify
NB: use of this type of golden template can be very brittle, and any changes made to files in the src/main/resources/archetype-resources directory must be reflected by equivalent changes in the src/test/resources/projects/basic/reference directory.
Publish to a Maven Repository
Now that our Maven archetype is tested and ready for publication, we can publish it, either to an internal repository, to the sonatype repository, which will synch our artifact with Maven central, or to a private repository, which in our case can simply be github pages.
We want to partition our repository into snapshots and releases, and we will need archetype catalogs in a subfolder of each.
We can deploy our archetype to a folder that contains the gh-pages branch of a github project called maven, which will contain all our published maven artifacts.
mvn -DaltDeploymentRepository=snapshot-repo::default::file:/path/to/git_maven_repository/snapshots clean deploy
Since we have installed the archetype, we already have a ~/.m2/archetype-catalog.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<archetype-catalog
xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0 http://maven.apache.org/xsd/archetype-catalog-1.0.0.xsd"
xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<archetypes>
<archetype>
<groupId>timezra.eclipse</groupId>
<artifactId>timezra.eclipse.save_actions_extensions-archetype</artifactId>
<version>1.0.0-SNAPSHOT</version>
<description>Parent project for the timezra.eclipse.save_actions_extensions project set</description>
</archetype>
</archetypes>
</archetype-catalog>
<archetype-catalog
xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0 http://maven.apache.org/xsd/archetype-catalog-1.0.0.xsd"
xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<archetypes>
<archetype>
<groupId>timezra.eclipse</groupId>
<artifactId>timezra.eclipse.save_actions_extensions-archetype</artifactId>
<version>1.0.0-SNAPSHOT</version>
<description>Parent project for the timezra.eclipse.save_actions_extensions project set</description>
</archetype>
</archetypes>
</archetype-catalog>
The Maven repository
github_maven_repository
.
| releases
| snapshots
| | archetypes
| | |____archetype-catalog.xml
| | timezra
| | | eclipse
| | | | timezra.eclipse.save_actions_extensions-archetype
| | | | |____maven-metadata.xml
| | | | |____maven-metadata.xml.md5
| | | | |____maven-metadata.xml.sha1
| | | | | 1.0.0-SNAPSHOT
| | | | | |____maven-metadata.xml
| | | | | |____maven-metadata.xml.md5
| | | | | |____maven-metadata.xml.sha1
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.jar
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.jar.md5
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.jar.sha1
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.pom
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.pom.md5
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.pom.sha1
.
| releases
| snapshots
| | archetypes
| | |____archetype-catalog.xml
| | timezra
| | | eclipse
| | | | timezra.eclipse.save_actions_extensions-archetype
| | | | |____maven-metadata.xml
| | | | |____maven-metadata.xml.md5
| | | | |____maven-metadata.xml.sha1
| | | | | 1.0.0-SNAPSHOT
| | | | | |____maven-metadata.xml
| | | | | |____maven-metadata.xml.md5
| | | | | |____maven-metadata.xml.sha1
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.jar
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.jar.md5
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.jar.sha1
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.pom
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.pom.md5
| | | | | |____timezra.eclipse.save_actions_extensions-archetype-1.0.0-20111020.005931-1.pom.sha1
Use the Archetype From the Repository
After our artifacts have been deployed to a Maven repository, we can generate a project from the deployed archetype in a way similar to how we generated the project from the archetype installed locally above.
Generating from an artifact in a gh-pages repository
mvn archetype:generate -DarchetypeCatalog=http://your_github_username.github.com/maven/snapshots/archetypes
Conclusion
This tutorial is a continuation of a previous post, in which we created a set of Tycho-enabled Eclipse projects. The archetype we created from this set of projects abstracts the boilerplate and conventions into a re-usable component that will make setting up similar projects even faster in the future.
The end result of this effort is the tycho_new_plugin_project.