Search

Sunday 27 October 2013

GitHub: integrating project into entire GitHub infrastructure

GitHub provides quite good set of toolsets which can be used to support full development cycle and prepare infrastructure to all necessary output artifacts. Recently, I've described samples how to provide integration with GitHub issue tracker and to wrap it with Maven plugin. So, actually, I have dedicated Maven plugin project which I can use as a separate project. In order to make it as much self-contained as it's needed we should:

  1. Set up new project in the GitHub which includes:
    1. New repository setup
    2. Build script preparation
    3. Release settings
  2. Prepare project documentation generation
  3. Integration with CI
So, at the end, all we have to do is to work with our IDE on adding new code or making some changes to existing modules. Everything else is supported by GitHub infrastructure as soon as we push our changes into remote repository.

Not bad at the beginning. So, let's get started.

Set up new project

New repository setup

New GitHub repository creation is clearly described in help pages. In particular, here.

Once it's cloned locally it can be integrated with the development environment. In my case it's Eclipse and all necessary settings for it can be found here.

Then we can create new Maven project in eclipse to fill in all the sources we have. Here is the example how to Create a New Maven Project in Eclipse.

OK. After all above manipulations I have a project like this where I put all necessary sources for the plugin I'm going to create project for.

Build script preparation

All build logic is defined in the Maven pom.xml file. All nodes and attributes are defined to support the following:

So, base pom.xml file looks like (mouse over each section to see the explanation):
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.github.mkolisnyk</groupId>
 <artifactId>sirius-maven-plugin</artifactId>
 <name>sirius-maven-plugin</name>

 <version>1.1-SNAPSHOT</version>
 <packaging>maven-plugin</packaging>

 <licenses>
  <license>
   <name>The Apache Software License, Version 2.0</name>
   <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
   <distribution>repo</distribution>
  </license>
 </licenses>
 <developers>
  <developer>
   <id>mkolisnyk</id>
   <name>mkolisnyk</name>
   <email>kolesnik.nickolay@gmail.com</email>
  </developer>
 </developers>

 <parent>
  <groupId>org.sonatype.oss</groupId>
  <artifactId>oss-parent</artifactId>
  <version>7</version>
 </parent>
 
 <scm>
  <connection>scm:git:https://github.com/mkolisnyk/sirius-maven-plugins.git</connection>
  <developerConnection>scm:git:https://github.com/mkolisnyk/sirius-maven-plugins.git</developerConnection>
  <url>https://github.com/mkolisnyk/sirius-maven-plugins.git</url>
  <tag>v1.0</tag>
 </scm>
 
 <issueManagement>
  <system>GitHub</system>
  <url>https://github.com/mkolisnyk/sirius-maven-plugins/issues</url>
 </issueManagement>
 
 
 <prerequisites>
  <maven>2.0</maven>
 </prerequisites>
 
 
 <inceptionYear>2013</inceptionYear>
 
 <build>
  
  <sourceDirectory>src/main/java</sourceDirectory>
  <testSourceDirectory>src/test/java</testSourceDirectory>
  
  <resources>
   <resource>
    <directory>src</directory>
    <excludes>
     <exclude>**/*.java</exclude>
    </excludes>
   </resource>
   <resource>
    <directory>target/dependency</directory>
    <excludes>
     <exclude>**/*.java</exclude>
    </excludes>
   </resource>
  </resources>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-plugin-plugin</artifactId>
    <version>3.2</version>
    <configuration>
     <!-- see http://jira.codehaus.org/browse/MNG-5346 -->
     <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
    </configuration>
    <executions>
     <execution>
      <id>mojo-descriptor</id>
      <goals>
       <goal>descriptor</goal>
      </goals>
     </execution>
     <!-- if you want to generate help goal -->
     <execution>
      <id>help-goal</id>
      <goals>
       <goal>helpmojo</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.7</source>
     <target>1.7</target>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.3.1</version>
    <configuration>
     <file>target/${project.artifactId}-${project.version}.jar</file>
     <groupId>${project.groupId}</groupId>
     <artifactId>${project.artifactId}</artifactId>
     <version>${project.version}</version>
     <packaging>maven-plugin</packaging>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <excludes>
      <exclude>*</exclude>
      <exclude>com/thoughtworks/**/*</exclude>
      <exclude>freemarker/**/*</exclude>
      <exclude>ftl/**/*</exclude>
      <exclude>i18n/**/*</exclude>
      <exclude>style/**/*</exclude>
      <exclude>junit/**/*</exclude>
      <exclude>licenses/**/*</exclude>
      <!-- <exclude>META-INF/maven/**/*</exclude> -->
      <exclude>org/codehaus/**/*</exclude>
      <exclude>org/apache/velocity/**/*</exclude>
      <exclude>org/hamcrest/**/*</exclude>
      <exclude>org/jbehave/**/*</exclude>
      <exclude>org/junit/**/*</exclude>
      <exclude>org/testng/**/*</exclude>
      <exclude>org/xmlpull/**/*</exclude>
      <exclude>stories/**/*</exclude>
      <exclude>style/**/*</exclude>
      <exclude>tests/**/*</exclude>
     </excludes>
     <archive>
      <manifest>
       <addClasspath>true</addClasspath>
       <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
       <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
       <!-- <addExtensions>true</addExtensions> -->
       <classpathLayoutType>repository</classpathLayoutType>
       <mainClass>sirius.utils.retriever.Program</mainClass>
      </manifest>
     </archive>
    </configuration>
   </plugin>
   <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
     <descriptorRefs>
      <descriptorRef>-deps</descriptorRef>
     </descriptorRefs>
    </configuration>
   </plugin>
   <plugin>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
     <execution>
      <id>unpack-dependencies</id>
      <phase>generate-resources</phase>
      <goals>
       <goal>unpack-dependencies</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>2.4</version>
   </plugin>
  </plugins>
  <pluginManagement>
   <plugins>
    <!--This plugin's configuration is used to store Eclipse m2e settings 
     only. It has no influence on the Maven build itself. -->
    <plugin>
     <groupId>org.eclipse.m2e</groupId>
     <artifactId>lifecycle-mapping</artifactId>
     <version>1.0.0</version>
     <configuration>
      <lifecycleMappingMetadata>
       <pluginExecutions>
        <pluginExecution>
         <pluginExecutionFilter>
          <groupId>
           org.apache.maven.plugins
          </groupId>
          <artifactId>
           maven-dependency-plugin
          </artifactId>
          <versionRange>
           [2.1,)
          </versionRange>
          <goals>
           <goal>
            unpack-dependencies
           </goal>
          </goals>
         </pluginExecutionFilter>
         <action>
          <ignore />
         </action>
        </pluginExecution>
       </pluginExecutions>
      </lifecycleMappingMetadata>
     </configuration>
    </plugin>
   </plugins>
  </pluginManagement>
 </build>
 <dependencies>
  <dependency>
   <groupId>org.kohsuke</groupId>
   <artifactId>github-api</artifactId>
   <version>1.41</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven.plugin-tools</groupId>
   <artifactId>maven-plugin-annotations</artifactId>
   <version>3.2</version>
   <!-- annotations are not needed for plugin execution so you can remove 
    this dependency for execution with using provided scope -->
   <scope>provided</scope>
  </dependency>
  <!-- generated help mojo has a dependency to plexus-utils -->
  <dependency>
   <groupId>org.codehaus.plexus</groupId>
   <artifactId>plexus-utils</artifactId>
   <version>3.0.1</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-plugin-api</artifactId>
   <version>2.0</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven.reporting</groupId>
   <artifactId>maven-reporting-api</artifactId>
   <version>2.0.8</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven.reporting</groupId>
   <artifactId>maven-reporting-impl</artifactId>
   <version>2.0.4.3</version>
  </dependency>
  <dependency>
   <groupId>net.masterthought</groupId>
   <artifactId>cucumber-reporting</artifactId>
   <version>0.0.21</version>
  </dependency>
 </dependencies>
</project>  

Release settings

In order to make the entire project be prepared to the release we should make additional settings. For this purpose we should update maven-release-plugin section as follows:

   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>2.4</version>
    <configuration>
     <tagNameFormat>v@{project.version}</tagNameFormat>
     <preparationGoals>package install:install-file</preparationGoals>
    </configuration>
   </plugin>
These configuration sections define actions to be performed in order to make release process done and the template which will be used for tagging during release process.

Prepare project documentation generation

Documentation is an essential part of any software product. Without it the user will have some difficulties in using the software. Also, documentation contains some usefult references information.

If you take a look at any Maven plugin documentation you should see that the documentation structure is pretty similar and fits some common template. That's true and all this stuff is generated with Maven site plugin during Maven site goal phase.

Additionally GitHub provides the abilities to create project site where all necessary documentation is stored. This functionality is implemented by GitHub Pages functionality. The idea is pretty simple: each GitHub project may have special branch named gh-pages which can be used as the storage for project site content. GitHub Pages functionality takes the gh-pages branch content and uses that as the site content.

Adding Maven reports

During build process there're many various activities which produce some reporting artifacts. That can be various static analysis tools like PMD, CPD, Check-Style, Code Coverage analysis etc. Also, there're some additional generators like Javadoc. All those reports may be triggered during site generation process. Maven pom.xml file has dedicated section for that. It's reporting section where all reports to be generated are defined. For our project it looks like:

        <reporting>
                <plugins>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-pmd-plugin</artifactId>
                                <version>3.0.1</version>
                                <configuration>
                                        <excludes>
                                                <exclude>**/plugin/**/*.java</exclude>
                                        </excludes>
                                        <excludeRoots>
                                                <excludeRoot>target/generated-sources</excludeRoot>
                                        </excludeRoots>
                                </configuration>
                        </plugin>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-jxr-plugin</artifactId>
                                <version>2.3</version>
                        </plugin>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-plugin-plugin</artifactId>
                                <version>3.2</version>
                        </plugin>
                </plugins>
        </reporting>
In this example we're adding 3 reporting items:
  1. maven-pmd-plugin - generates static code analysis report
  2. maven-jxr-plugin - used for generating XRef resources (actually, source code with links to some specific lines)
  3. maven-plugin-plugin - generates plugin information
After adding these entries and running the following command:
mvn site
we'll get project site generated under the target/site directory which will contain the set of base project reports (most of the information is takes from the informational fields) + additional reports based on data in the reporting section of pom.xml.

Prepare project site structure

But the default set of project documentation pages is usually not enough. E.g. the About page is mainly built based on value in the description field in the pom.xml file. So, you can't put too much information in it. Additionally, we may like to have some usage samples or some other descriptive reports. So, we should configure:

  • The entire set of pages to use
  • Some custom static pages
For this purpose we should create src/site folder in our project. General information can be found at the Maven Guide for Creating a site. Mainly, we should build folder structure like:
+- src/
   +- site/
      +- apt/
      |  +- index.apt
      |
      +- fml/
      |  +- general.fml
      |  +- faq.fml
      |
      +- site.xml
Where:
  • site.xml - the XML file containing the template for the entire site structure
  • apt/ - the folder containing documentation files in the APT format which is another text markup format. During site generation each apt file is converted to appropriate HTML file with the same name.
  • fml/ - the folder containing files in FML format which is XML based format
There're some other formats available but I've described main ones which I'm going to use. The templates and samples for these pages can be found at the Guide to the Plugin Documentation Standard document.

I'm not going to describe all the files but I'll pay more attention to the site.xml file which contains the following structure for current project:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <body>
    <menu name="Overview">
      <item name="Introduction" href="index.html"/>
      <item name="Goals" href="plugin-info.html"/>
      <item name="Usage" href="usage.html"/>
      <item name="FAQ" href="faq.html"/>
    </menu>
 
    <menu name="Examples">
      <item name="description1" href="examples/example1.html"/>
      <item name="description2" href="examples/example2.html"/>
    </menu>
    
    <menu ref="reports"/>
    
  </body>
</project>
There're 2 sections which require attention here:
  1. Section marked with yellow contains custom menu items which reference to static pages generated during site generation process.
  2. Section marked with red contains reference to the area where all basic reports should be placed. If you remove this section the Maven site plugin will still produce all reports but they will not appear in the left navigation menu.

Additional Custom reports

There may be some reports or artifacts which may be produced outside the tasks defined in the pom.xml inside the reporting section. E.g. in addition to any other reports I'd like to have GitHub change history report. It can be provided with the site-maven-plugin. So, I just have to add the following entry into the build section:

   <plugin>
    <groupId>com.github.danielflower.mavenplugins</groupId>
    <artifactId>maven-gitlog-plugin</artifactId>
    <version>1.5.0</version>
    <configuration>
     <reportTitle>Changelog for ${project.name} version
      ${project.version}</reportTitle>
     <verbose>true</verbose>
     <outputDirectory>target/site/changelog</outputDirectory>
     <generatePlainTextChangeLog>true</generatePlainTextChangeLog>
     <plainTextChangeLogFilename>changelog-${project.version}.txt</plainTextChangeLogFilename>
     <generateSimpleHTMLChangeLog>true</generateSimpleHTMLChangeLog>
     <markdownChangeLogFilename>changelog-${project.version}.md</markdownChangeLogFilename>
     <generateMarkdownChangeLog>true</generateMarkdownChangeLog>
     <simpleHTMLChangeLogFilename>changelog.html</simpleHTMLChangeLogFilename>
     <generateHTMLTableOnlyChangeLog>true</generateHTMLTableOnlyChangeLog>
     <htmlTableOnlyChangeLogFilename>changelog-tableonly.html</htmlTableOnlyChangeLogFilename>
     <issueManagementSystem>GitHub issue tracker</issueManagementSystem>
     <issueManagementUrl>https://github.com/mkolisnyk/sirius-maven-plugins/issues</issueManagementUrl>
     <fullGitMessage>true</fullGitMessage>
     <dateFormat>yyyy-MM-dd HH:mm:ss Z</dateFormat>
    </configuration>
    <executions>
     <execution>
      <goals>
       <goal>generate</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
So, it should generate the target/site/changelog.html file containing change history. It would be usefult to add this report to Maven site. So, for this purpose I'm just adding the following entry to the site.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <body>
    <menu name="Overview">
      <item name="Introduction" href="index.html"/>
      <item name="Goals" href="plugin-info.html"/>
      <item name="Usage" href="usage.html"/>
      <item name="FAQ" href="faq.html"/>
      <item name="Change Log" href="changelog/changelog.html"/>
    </menu>
 
    <menu name="Examples">
      <item name="description1" href="examples/example1.html"/>
      <item name="description2" href="examples/example2.html"/>
    </menu>
    
    <menu ref="reports"/>
    
  </body>
</project>
So, this is the way how to add another reference to the report from Maven site.

Export to GH-Pages branch

After the above steps we just have our site deployed locally and located under target/site directory. But in order to share it we should commit it into gh-pages branch. For this purpose we can use site-maven-plugin from GitHub Maven Plugins repository. We should add the following entry under build section:

   <plugin>
    <groupId>com.github.github</groupId>
    <artifactId>site-maven-plugin</artifactId>
    <version>0.8</version>
    <configuration>
     <message>Creating site for ${project.version}</message>
     <oauth2Token>MyAUTH2Token</oauth2Token>
     <repositoryName>sirius-maven-plugins</repositoryName>
     <repositoryOwner>mkolisnyk</repositoryOwner>
    </configuration>
    <executions>
     <execution>
      <goals>
       <goal>site</goal>
      </goals>
      <phase>site</phase>
     </execution>
    </executions>
   </plugin>
So, after this if we run
mvn site
we'll have our Maven site generated and committed into GH-pages branch of the repository we define.

Integration with CI

Integrating with Travis

Travis is another free hosted continuous integration system which can be used to build our GitHub projects as soon as we push some changes into the remote repository. The integration with GitHub is set quite easily and it is described in the Travis Getting Started page.

The only thing left after we set up the integration is to define the build file. For this purpose we should create _travis.yml file with the following content:

language: java
install: mvn install site
notifications:
  email: false
Generally all we have to define here is just the programming language and command line to initiate the build. So, from now each time we push our changes to the repository the Travis build will be triggered and the following command will be performed:
mvn install site

Filtering out release related commits

We're almost done except one annoing and at the same time serious thing: when we perform the release the Maven switches version number and commits changes. But Travis is targeted to version changes as well. So, it means that during release process the Travis will perform at least 3 false runs. In order to prevent that we should mark the commit somehow to say that we don't need CI build after those changes.

According to How to skip a build we should provide the commit with the following text:

[ci skip]
So, we should make Maven commit release changes with this text in the commit message. And this can be set in the release plugin settings. So, we should update pom.xml entry like this:
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>2.4</version>
    <configuration>
     <tagNameFormat>v@{project.version}</tagNameFormat>
     <preparationGoals>package install:install-file</preparationGoals>
     <scmCommentPrefix>#42'[ci skip]'</scmCommentPrefix>
    </configuration>
   </plugin>
Now we're safe from false CI builds.

Bells and whistles: adding build status badge

From time to time we may notice various badges on the GitHub pages indicating the build status. It's just information but it's something nice to have on the page. Once we have Travis project active we always can request the build status badge. E.g. in order to display that in the GitHub README.md page we should add the following text:

[![Build Status](https://travis-ci.org/mkolisnyk/sirius-maven-plugins.png)](https://travis-ci.org/mkolisnyk/sirius-maven-plugins)
This will display the badge like that:

Conclusion

That was an example showing how to create GitHub Java project and make all necessary settings to provide all necessary output artifacts automatically. So, actually, all we have to do is to work with our IDE and push changes to the repository. Everything else is prepared and generated using GitHub infrastructure.

No comments:

Post a Comment