Wednesday, October 19, 2011

Tycho Archetype

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



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**/*.xslweb~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



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



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



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>



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



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>



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



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.

No comments: