Introduction
In this article we're going to try to give you a concise but
complete view of how Krypton works and how it could fit into your
development environment.
Krypton is a build tool that is driven from a command-line
interface. IDE bindings and other related tools are also planned.
Krypton is tested under Windows and Linux and should run just about
anywhere that a Java 1.4 compatible JDK exists.
Separating the What from the How
Krypton's approach is to separate the artifacts that are built,
from the production processes that build them. The
what from the how. This
separation turns the build definition into a purely declarative
description of the artifacts, while the work of actually producing
those artifacts is encapsulated in plugins that are called
Producers. So a Krypton build contains absolutely no build logic,
just descriptions of artifacts.
A Krypton build is configured by writing a krypton.xml file, which
is typically placed in the root of your projekt. This descriptor
file contains defintions of:
- All the artifacts that can be built from your projekt
- Meta data bundles (containing names, version numbers,
environmental settings etc) that should be retrieved and made
available to the build
- Operations that can be performed against completed artifacts
("upload", "publishtomaven", etc)
Example Build Definition
Let's take a look at a small example projekt built by Krypton.
Note that Krypton doesn't mandate a particular directory layout,
although
template projekt layouts are provided. The directory
layout for our example projekt looks like this:
All our sources are placed under the "source" directory. We have
two sources: coreimportclasses which contains a
couple of properties files, and corejava which
contains the Java source code for our program. Main.java uses
classes from the Log4J and NekoHtml libraries, so when we build our
projekt we'll need to link in the JAR's for those libraries.
So, what we want our build to do is:
- Retrieve log4j and nekohtml JAR's from a Maven repository
- Compile our Java code into classes
- Build a JAR file containing our compiled classes, as well as
the files in our coreimportclasses source.
Here's the krypton.xml file for our projekt. It defines meta data
bundles, artifacts and operations. For clarity, we've simplified
this example to exclude default values, but we'll talk about
defaults later.
<krypton name="My Projekt">
<metadatabundles>
<metadatabundle type="Properties">
<setting name="filename"
value="../../environment.properties" />
<metadatabundle>
<metadatabundle type="Properties">
<setting name="filename"
value="build.properties" />
<metadatabundle>
</metadatabundles>
<artifacts>
<artifact name="log4j_jar"
type="SimpleMaven1RepoArtifact">
<setting name="artifactId" value="log4j" />
<setting name="version" value="1.2.8" />
<setting name="type" value="jar" />
</artifact>
<artifact name="nekohtml_jar"
type="SimpleMaven1RepoArtifact">
<setting name="artifactId" value="nekohtml" />
<setting name="version" value="0.9.4" />
<setting name="type" value="jar" />
</artifact>
<artifact name="coreclasstree" type="Classtree">
<dependency name="java">
<item type="source" name="corejava" />
</dependency>
<dependency name="compilejars">
<item type="artifact" name="log4j_jar" />
<item type="artifact" name="nekohtml_jar" />
</dependency>
</artifact>
<artifact name="corejar" type="Jar">
<setting name="jarFilename"
value="${component.version.name}.jar" />
<dependency name="classtree">
<item type="source" name="coreimportclasses" />
<item type="artifact" name="coreclasstree" />
</dependency>
<operation name="publish" type="PublishToMaven">
<setting name="repository"
value="${environment.repository.local}" />
<setting name="artifactId"
value="${component.name}" />
<setting name="version"
value="${component.version}" />
<setting name="type" value="jar" />
</operation>
</artifact>
</artifacts>
</krypton>
Artifacts and Producers
This krypton.xml file defines four artifacts:
log4j_jar and nekohtml_jar
These are both "built" by the SimpleMaven1RepoArtifact producer. In
fact this producer doesn't create the artifacts but retrieves them
from a local or remote Maven 1 repository, and makes them available
to the build.
Most producers have settings, which enable you to supply details
about the artifact being produced. In the case of the
SimpleMaven1RepoArtifact producer it accepts settings that describe
the JAR to be retrieved.
coreclasstree
This artifact is built by the Classtree producer which takes some
Java source code and some JAR files, and compiles the supplied
source code against the supplied JAR files. The Classtree producer
doesn't have any settings, but it does have dependencies. These are
like inputs to the producer through which raw materials are provided
for use during production. You can see that in Krypton,
dependencies have a particular role. Classtree has the "java" and
"compilejars" dependencies.
Note that when specifying items to satisfy a dependency, you can
choose to link in sources, artifacts or a mixture of both. This is
an important part of Krypton's model – sources are treated as
dependencies, just like intermediate artifacts are.
Note also that many items can be supplied for one dependency. In
our example, we have wired the two JARs previously retrieved from a
Maven repository into the "compilejars" dependency.
corejar
The final artifact in our example is the corejar artifact which is
produced by the Jar producer. This producer takes a setting which
is used to specify the filename of the JAR that will be produced.
It also takes a classtree dependency through which the "classes"
that should be JAR'd up are provided. In this case we have wired in
our coreimportclasses source, as well as the coreclasstree artifact
that we produced earlier.
Running the Build
To build an artifact, we use the kr command-line. We can ask
Krypton to build any artifact in our projekt, and it will build all
of the intermediate artifacts leading up to the one we asked for,
and finally will build our requested artifact. You can also specify
multiple artifacts in one command.
For example, any of the following commands would be valid for our
projekt:
> kr log4j_jar
> kr log4j_jar nekohtml_jar
> kr coreclasstree
> kr corejar
Krypton creates a "target" output directory with subdirectories for
each artifact. The completed artifacts are placed in their own
subdirectories.
Note: The current build of Krypton places artifacts in
target/artifacts. The final release of Krypton 2 will leave
artifacts in the target directory as shown in this diagram.
Operations and Executors
You might have noticed that our corejar artifact also has an
operation defined against it called "publish".
<artifact name="corejar" type="Jar">
...
<operation name="publish" type="PublishToMaven">
<setting name="repository"
value="${environment.repository.local}" />
<setting name="artifactId"
value="${component.name}" />
<setting name="version"
value="${component.version}" />
<setting name="type" value="jar" />
</operation>
</artifact>
This operation uses the PublishToMaven Executor. As with artifacts,
operations may have settings that you can use to configure them.
Operations can be executed through the command-line interface by
appending the operation name to the artifact name, separated by a
colon character. So to execute our publish operation, we would
issue the command:
> kr corejar:publish.
Krypton will make sure the artifact is built and up to date, and
will then execute the publish operation against it. If the artifact
production fails, the operation won't be executed. Many
operations can be executed against a single artifact in one command.
For example:
> kr corejar:publishtomaven corejar:uploadtodistrosite
Krypton will only build corejar once, and will then execute all of
the requested operations against that built artifact.
Metadata Bundles and Collectors
Going back to krypton.xml, you'll see that the first things defined
in our projekt are Metadata Bundles. These are collections of
name-value pairs that can be retrieved from external sources, and
then applied to artifacts. There are different types of Collectors
that can pull in meta data bundles. In our project we have used the
Properties collector to retrieve values from two properties files.
In the corejar artifact, you can see we have applied meta data
values in a couple of places:
Using meta data collectors, you can pull in values from descriptor
files, or even other sources such as databases. You can also
externalize and centralize values that are used many times in your
build or are common to multiple projekts into properties files or
other formats.
Default Values
We glossed over the use of default values before to make our initial
look at a krypton.xml file clearer. Where you need to use the same
setting values on many entities, default values allow you to set values
in one place that will be applied across many artifacts, operations or
metadata bundles.
Krypton supports Java and Ant as implementation languages for
plugins. Almost all of the Krypton Standard Library is implemented
as Ant plugins. Ant plugins are all implemented on a common Java
class which has two settings which we didn't mention before: antHome
and javaHome. The unabbreviated version of our coreclasstree
artifact defintion would look like this:
<artifact name="coreclasstree" type="Classtree">
<setting name="javaHome"
value="${environment.java.home}" />
<setting name="antHome"
value="${environment.ant.home}" />
<dependency name="java">
<item type="source" name="corejava" />
</dependency>
<dependency name="compilejars">
<item type="artifact" name="log4j_jar" />
<item type="artifact" name="nekohtml_jar" />
</dependency>
</artifact>
As all our artifacts are built by Ant-based producers, these
settings need to be provided every time. We could plugin values
from a metadata bundle, but we'd still have to repeatedly set those
settings for every artifact and also for our "publish" operation as
it too is based on an Ant plugin.
The default values feature enables you to set values once which will
then be automatically applied to many entites. Default values can
be specified per producer, executor or collector, so you could for
example tell all Classtree artifacts to use the same setting values.
Default values can also be specified per plugin class. Krypton will
then apply those default settings to all entities based on that
plugin class.
The full set of defaults we would have used in our example projekt
looks like this. Note that we are using metadata values for the
javaHome and antHome settings.
<defaults>
<set type="class" name="AntCollector">
<setting name="javaHome"
value="${environment.java.home}" />
<setting name="antHome"
value="${environment.ant.home}" />
</set>
<set type="class" name="AntProducer">
<setting name="javaHome"
value="${environment.java.home}" />
<setting name="antHome"
value="${environment.ant.home}" />
</set>
<set type="class"
name="com.x1seven.krypton.plugin.AntExecutor">
<setting name="javaHome"
value="${environment.java.home}" />
<setting name="antHome"
value="${environment.ant.home}" />
</set>
<set type="producer" name="SimpleMaven1RepoArtifact">
<setting name="remoteRepository"
value="http://www.ibiblio.com/maven/" />
<setting name="localRepository"
value="E:/wrk/x17_lib_mavenrepository" />
</set>
</defaults>
Relationship to Ant, NAnt and Maven
Krypton can be used on new projekts, in parallel with Ant, Nant or
Maven, or as a replacement for those tools. Krypton imposes no
restrictions on the directory layout of your projekt, so it can be
adapted to fit any existing structure.
Krypton is able to retrieve metadata from external sources,
including properties files or XML files you may already be using to
drive your Ant / NAnt build, or Maven POM files. This allows
Krypton to be used to complement your existing build processes with
additional build functionality.
Krypton also has a special relationship to Ant in that all of the
plugins in the Krypton Standard Library are implemented as miniature
Ant scripts. Krypton plugins can be written in Java, or Ant, or
other languages by writing an adapter. We believe that Ant is an
excellent tool and scripting language that effectively unifies
almost every build-related tool you could want to use. By
supporting Ant as a plugin scripting language, Krypton enables you
to use your existing Ant knowledge and the plethora of Ant tasks
available to write Krypton plugins.
Further Reading
|