ControlTier Inc. > Open.ControlTier
 
Font size:      

Interfacing a build

Overview

Dependending on the age and size of your organization you may need to support a number of build processes. Indeed, each build may even use its own build tool. Each build tool has its own way of being configured, and its own way of being invoked (including any required command parameters).

Assorted build tools

This document walks you through an example that shows you how to encapsulate your build tool so it can be incorporated into the build and deployment process.

This document assumes you have familiarized yourself with basic concepts and have setup ProjectBuilder.

Note
For users looking for a ready solution for Java build tools see the Elements Java Server Library.

Introducing Builders

Before diving right in, it is helpful to start by first introducing one of the framework base library modules, Builder. A Builder interfaces with an underlying build tool to coordinate the build life cycle for a given application component or components. A Builder provides a consistent interface to the rest of the framework, and hides the variation of build tool functionality and control. A Builder defines a set of standard commands that are used to define the life cycle the framework expects, and also defines a set of attributes needed to parameterize the calls to the underlying tools.

The following illustration shows a user calling the runBuildScript command on SimpleAntBuilder, which in turn, invokes the command ant -f build.xml. SimpleAntBuilder is a Builder, and as such, possesses the command, runBuildScript, one of several standard commands available in any Builder.

Meta builder concept

As a subtype of Builder, SimpleAntBuilder can override generic Builder commands. To be a special Builder interfacing Ant, SimpleAntBuilder overrides the runBuildScript command to use its own implementation.

As mentioned earlier, the role of a Builder is to coordinate the build life cycle for application components. This not only includes invoking a build tool (which may drive a great deal of actions such as analysis, doc generation, unit testing, etc.), but before that checks out source code (perhaps a specific branch or tag), and after the build tool has completed, takes the build artifacts and puts them into a repository for later deployment. All these actions are driven from a command workflow, Build.

Builder workflow

Builders also have a relationship to another framework base type, Package. A Package represents any kind of build artifact that will be used during deployment. Because application architecture determines the format of the release archive, the Package type, provides a set of hook commands that subtypes can override in order to create and extract the archive. The Package command provides a number of standard commands that do not need to be overriden, as well. These commands take care of putting an artifact into the repository, registering it, and retrieving it. Builders, in effect, are producers of Packages.

Builder type

Preparing simple's application source

In our example, we will automate a very trivial build process for our "simple" project. Of course, your existing applications will already be in a source repository and have an existing build procedure. The next steps will resemble what one would need to do for a brand new application source tree.

Create a source tree

All the application source files will reside in the src/ subdirectory of simple:

mkdir -p $HOME/simple/src
mkdir -p $HOME/simple/src/jsp
mkdir -p $HOME/simple/src/WEB-INF
	

Our example will build a trivial webapp.There will be two main source files: a JSP and a web.xml.

Edit $HOME/simple/src/jsp/index.jsp

<html>
  <head>
    <title>Simple</title>
  </head>
  <body>
    <p>Hello!  The time is now <%= new java.util.Date() %></p>
  </body>
</html>
	

Edit $HOME/simple/src/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
  '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'
  'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
  <servlet>
    <servlet-name>Home Page</servlet-name>
    <jsp-file>/index.jsp</jsp-file>
  </servlet>
</web-app>
	

Create a build.xml

Our example application build needs an Ant build file to build and package the release artifact.

Edit $HOME/simple/src/build.xml with the following content:

<project name="simple" default="all">
  <target name="all">
    <mkdir dir="${targetdir}"/>
    <war destfile="${targetdir}/simple-${buildstamp}.war" webxml="${basedir}/WEB-INF/web.xml">
  <fileset dir="${basedir}/jsp"/>		
    </war>	    
  </target>
</project>
	

Later you'll see that the Builder commands will set the embedded properties: basedir, targetdir and buildstamp.

Create a CVS repo

The source files will be maintained in a local CVS source code repository. Run the following commands to create the instance and import the files:

export CVSROOT=$HOME/simple/cvsroot; # local CVS repo path
cvs -d $CVSROOT init;                # create the repository
cd $HOME/simple/src;                 # change directory and import the sources
cvs import -m "Importing Simple example" src tutorial start; 
cd ..; rm -r $HOME/simple/src;       # remove the original copy.
cvs co src;                          # check out the src dir
	
Note
If the CVS binary is not available on your machine, download and install the CVS package. Several binaries for different OS and architectures, as well as source code can be found at www.nongnu.org/cvs. Once installed, be sure to add it to your PATH.

Noteworthy settings

Reviewing the steps taken in preparing the simple application's source tree a few noteworthy details should be highlighted:

Setting Example Description
SCM connection $HOME/simple/cvsroot path to the source in the source code repository. (used by scmCheckout)
SCM module src module to checkout of the repository (used by scmCheckout)
source base directory $HOME/simple/src directory where checked out source resides (used by scmCheckout and runBuildScript)
build file path src/build.xml the build file to invoke (used by runBuildScript)

Each of these setting details will be recorded in the type defintion declared in the next section.

A Builder for Ant

With a bit of familiarity with Builders and Packages, we are ready to proceed with actually defining a simple Builder. This section will show you how to make SimpleAntBuilder, a Builder subtype that will interface with Ant.

The class diagram below shows the inheritance hierarchy of the SimpleAntBuilder type. It inherits a number of commands from its super types but will override runBuildScript.

SimpleAntBuilder inheritance

There is a typical type development cycle used when introducing a new type. The process is broken down into several routine steps:

  1. Define the type
  2. Define an instance of the type
  3. Test a type command
  4. Repeat

Two tools will be used during the process. ProjectBuilder to create model artifacts and a text editor to edit type.xml, the type definition file.

Define the SimpleAntBuilder Type

Create the type

Use ProjectBuilder's create-type command to initialize the SimpleAntBuilder type. You can run create-type without a full set of arguments and it will prompt you for the remaining needed input (input is in bold):

ctl -p default -m ProjectBuilder -c create-type -- -supertype Builder
Name of type: 
SimpleAntBuilder
Description of type: 
A simple Builder to interface with Ant
Directory where module files will be created:  [/ctier/ctl/src]
/ctier/ctl/src
Creating module definition files in directory: /ctier/ctl/src ...
Initializing type module from template dir: /ctier/ctl/depots/default/modules/ProjectBuilder/templates/boilerplate ...
Created dir: /ctier/ctl/src/modules/SimpleAntBuilder
Copying 1 file to /ctier/ctl/src/modules/SimpleAntBuilder
Creating directory structure...
Created dir: /ctier/ctl/src/modules/SimpleAntBuilder/bin
Created dir: /ctier/ctl/src/modules/SimpleAntBuilder/commands
Created dir: /ctier/ctl/src/modules/SimpleAntBuilder/objects
Created dir: /ctier/ctl/src/modules/SimpleAntBuilder/templates
Copying /ctier/ctl/depots/default/modules/ProjectBuilder/templates/types/Managed-Entity to /ctier/ctl/src/modules/SimpleAntBuilder ...
Initializing type module from template dir: /ctier/ctl/depots/default/modules/ProjectBuilder/templates/types/Managed-Entity ...
Copying 2 files to /ctier/ctl/src/modules/SimpleAntBuilder
Define commands and attributes in this file: /ctier/ctl/src/modules/SimpleAntBuilder/type.xml

A new directory called "SimpleAntBuilder" will be created in the basedir $CTIER_ROOT/ctl/src and populated with various files and subdirectories. The last line refers you to the type.xml file that contains the initial type definition for SimpleAntBuilder. A snippet is shown below:

    <type
        name="SimpleAntBuilder"
        role="concrete"
        uniqueInstances="true">

        <!-- description of the Type -->
        <description>A simple Builder to interface with Ant</description>

        <!-- supertype/typereference: defines the supertype of the current Type -->
        <supertype>
            <typereference name="Builder"/>
        </supertype>
	...
    

Open the $CTIER_ROOT/ctl/src/modules/SimpleAntBuilder/type.xml file in your favorite text editor.

Note
In the next two steps, you will replace parts of the type.xml file with content specified in this document. The surest method to get the desired result is to replace the entire needed section. Also, be sure you are not pasting text inside a commented section.

Define attribute defaults

Attribute defaults are used to provide default values used to parameterize commands. These appear as entity.attribute.<attribute-name> properties and these properties are often used to default command option values. Cut and paste the following fragment into the type.xml replacing the <attributes> ... </attributes> content already there. Ensure that you have made the content a sub-element of <type>.

<attributes>
  <attribute name="basedir" type-property="deployment-basedir"/>
  <attribute name="targetdir" type-property="deployment-install-root"/>
  <attribute-default name="scmBinding" value="cvs"/>
  <attribute-default name="scmLabel" value=""/>
  <attribute-default name="scmConnection" value="${user.home}/simple/cvsroot"/>
  <attribute-default name="scmModule" value="src"/>
  <attribute-default name="importMin" value="1"/>
  <attribute-default name="importMax" value="1"/>
  <attribute-default name="packageExtension" value="war"/>
  <attribute-default name="packageFilebase" value="simple"/>
  <attribute-default name="packageInstallroot" value="${user.home}/simple/tomcat/webapps"/>
  <attribute-default name="packageType" value="war"/>
  <attribute-default name="buildFile" value="${user.home}/simple/src/build.xml"/>
  <attribute-default name="buildTarget" value="all"/>
</attributes>
	

Define the runBuildScript command

The initial type.xml produced by create-type has a number of comments that show command definition examples. To define a command, you use the <command> tag. Cut and paste the following fragment into the type.xml replacing the <commands>... </commands> content already there. Ensure that you have made the content a sub-element of <type>.

<commands>
    <command name="runBuildScript"
                description="invokes the Ant build"
                command-type="Command"
                daemonized="false" >
        <!-- ** for unix use "sh" -->
           <execution-string>sh</execution-string>
           <argument-string>ant -Dbasedir=${opts.basedir} -Dbuildstamp=${opts.buildstamp} -Dtargetdir=${opts.targetdir} -f ${opts.buildfile} ${opts.target}</argument-string>
        <!-- ** for windows use "cmd.exe" -->
           <!--execution-string>cmd.exe</execution-string-->

         <opts>
        <opt parameter="basedir" description="directory where build resources reside" 
           required="false" property="opts.basedir" type="string" 
               defaultproperty="entity.attribute.basedir"/>
        <opt parameter="targetdir" description="directory build artifacts will be written" 
           required="false" property="opts.targetdir" type="string" 
           defaultproperty="entity.attribute.targetdir"/>
        <opt parameter="buildstamp" description="build identifier" 
           required="false" property="opts.buildstamp" type="string" 
           defaultproperty="entity.attribute.buildstamp"/>
        <opt parameter="buildfile" description="build file to execute" 
           required="false" property="opts.buildfile" type="string" 
           defaultproperty="entity.attribute.buildFile"/>
        <opt parameter="target" description="build target to evaluate" 
           required="false" property="opts.target" type="string" 
           defaultproperty="entity.attribute.buildTarget"/>
        </opts>
    </command>
</commands>

The XML above, defines the runBuildScript command as a bash script, supplying the execution-string and argument-string parameters. Several command options are also defined. These tags support passing command line arguments. It turns out that these are the same command options defined by the Builder base type so we are showing how SimpleAntBuilder is maintaining the same command syntax.

The example script code shown above uses Ant property references of the form, opts.<param-name>. The CTL task, get-opts, parses the command line and sets these properties. These properties can then be referenced by the command.

Note
If Ant is not installed you can make use of the install CTL provides. Set your PATH variable with the following steps:
1. source $CTL_BASE/etc/profile; # this sets ANT_HOME
2. PATH=$PATH:$ANT_HOME/bin

Build the type

Use the ProjectBuilder's build-type command to build the SimpleAntBuilder type. Specify the optional arguments -upload and -deploy to also upload the model artifact to Workbench, and then deploy it back to your working context.

ctl -p default -m ProjectBuilder -c build-type -- -type SimpleAntBuilder -upload -deploy
Base directory where module source files reside [/ctier/ctl/src]
/ctier/ctl/src
Building type using the buildmodule.xml via classloader
converting type.xml for module: SimpleAntBuilder
packaging module: SimpleAntBuilder
Building jar: /ctier/ctl/target/SimpleAntBuilder-1.jar
uploading SimpleAntBuilder module jar in /ctier/ctl/target to server ...
processing files in directory: /ctier/ctl/target
Uploading jar: /ctier/ctl/target/SimpleAntBuilder-1.jar to server: localhost ...
deploying new build of SimpleAntBuilder module to local installation ...
Getting: http://localhost:8080/webdav/default/publish/modules/SimpleAntBuilder-head.jar
To: /home/tutorial/ctl_base/var/SimpleAntBuilder-head.jar
Created dir: /home/tutorial/ctl_base/depots/simple/modules/SimpleAntBuilder
Expanding: /home/tutorial/ctl_base/var/SimpleAntBuilder-head.jar into
 /home/tutorial/ctl_base/depots/simple/modules/SimpleAntBuilder
Getting: http://localhost:8080/webdav/default/publish/modules/Builder-3.jar
To: /home/tutorial/ctl_base/var/Builder-3.jar
Expanding: /home/tutorial/ctl_base/var/Builder-3.jar into 
 /home/tutorial/ctl_base/depots/simple/modules/Builder
	

The SimpleAntBuilder type has now been loaded into the server and is now part of the greater project model. If you like, go to Workbench and view the type there. If you click on the "Commands" tab, you should see the runBuildScript command defined.

Screenshot

Define the SimpleAntBuilder Object

With the SimpleAntBuilder type defined, all that is left before we can test it, is to create an instance of it. To do that save the XML content below into a file and load it via the load-objects command.

File listing: simpleAntbuilder.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project PUBLIC "-//ControlTier Software Inc.//DTD Project Document 1.0//EN" 
    "project.dtd">
<project>
  <deployment 
      name="tutorial" type="SimpleAntBuilder"
      description="A simple Builder to interface with Ant"
      basedir="${user.home}/simple/src"
      installRoot="${user.home}/simple/target"      
      >
     <!--
      **
      ** References where the builder is hosted: 
      **       (replace localhost with the name of your node)
      -->
    <referrers replace="false">
      <resource type="Node" name="localhost"/>
    </referrers>
   </deployment>
</project>	
	

Load the data

ctl -p default -m ProjectBuilder -c load-objects -- \
	  -filename simpleAntbuilder.xml
	

Install the builder

ctl-depot -a install
"Install" command running for object: (SimpleAntBuilder) tutorial

The load-objects command takes the input from the XML file and registers an instance of SimpleAntBuilder in the project model. The ctl-depot command deploys it to the CTL instance. You can run the Get-Properties command to look at the information about the new object:

ctl -p default -t SimpleAntBuilder -o tutorial -c Get-Properties -- -print

[MULTI_LINE]
# tutorial [SimpleAntBuilder] #

A simple Builder to interface with Ant

## Attributes ##

*  basedir: "/Users/ctier/simple/src"
*  buildFile: "/Users/ctier/simple/src/build.xml"
*  buildTarget: "all"
*  importMax: "1"
*  importMin: "1"
*  packageExtension: "war"
*  packageFilebase: "simple"
*  packageInstallroot: "/Users/ctier/simple/tomcat/webapps"
*  packageType: "war"
*  scmBinding: "cvs"
*  scmConnection: "/Users/ctier/simple/cvsroot"
*  scmLabel: ""
*  scmModule: "src"
*  targetdir: "/Users/ctier/simple/target"

## Dependencies ##

### Parent Dependencies ###
1.   strongbad [Node]

### Child Dependencies ###

- - -
[/MULTI_LINE]
	

If you go to Workbench, and navigate back to the SimpleAntBuilder type, you will see the new object in the "Objects" tab.

Screenshot

The SimpleAntBuilder object is now ready for use.

Test the runBuildScript command

The SimpleAntBuilder type has been defined and an instance of it has been created and deployed to your working CTL project. You are ready to try the command. Run it with arguments like so:

ctl -p default -t SimpleAntBuilder -o tutorial -c runBuildScript -- -buildstamp 123
Buildfile: /Users/ctier/simple/src/build.xml

all:
      [war] Building war: /Users/ctier/simple/target/simple-123.war

BUILD SUCCESSFUL

As defined, runBuildScript invokes the ant -f build.xml command string, passing in the basedir, targetdir and buildstamp. The trivial build results in the creation of a simple release archive, a war file: $HOME/simple/target/simple-123.war.

Note
Remember you need both ant and cvs executables in your PATH.

The Build life cycle command

The runBuildScript command is just one part of the over all build life cycle, implemented in the standard Builder workflow, Build.

To drive the whole build cycle, run the Builder's Build workflow command:

ctl -p default -t SimpleAntBuilder -o tutorial -c Build -- -buildstamp 123
Start: "Run the build cycle." commands: scmCheckout,setBuildstamp,runBuildScript,repoImport
...
begin workflow command (1/4) -> "scmCheckout -buildstamp 123"
cvs checkout: Updating src
...
begin workflow command (2/4) -> "setBuildstamp -buildstamp 123" ...
...
begin workflow command (3/4) -> "runBuildScript -buildstamp 123" ...
...
begin workflow command (4/4) -> "repoImport -buildstamp 123" ...
...
Puted 1 file to http://localhost:8080/jackrabbit/repository/workbench/pkgs/default/war/wars/simple-123.war
...
Completed: execution time: 13.656 sec

Build begins by running the scmCheckout command which runs the CVS checkout of simple's src directory. It then runs the runBuildScript command you ran directly earlier. After that completes, Build runs repoImport. The repoImport command looks in the target directory for matching artifacts and then puts them into the repository, recording information about the package in the model. Go to the toplevel "Packages" tab in Workbench and navigate to the "war" type to view a new instance created by the Builder.

Next step: Deployment

With a Builder subtype created, to invoke simple's build process, the next step is to deploy the built artifact.