Skip to content

Template developers guide

Tommy Barker edited this page Aug 2, 2013 · 16 revisions

Lazybones alone is just a tool that doesn't do much. It needs a solid selection of template packages to make it a compelling solution for developers' day-to-day needs. That's why we try to make it as easy as possible to develop and publish templates. This guide explains how to go about both steps.

Getting started

The first thing you want to do is set up a build for your templates, because creating and publishing a template involves several steps:

  1. Create the directory structure and files for the template
  2. Add VERSION and README files
  3. Package the template structure into a zip, filtering out .retain and VERSION files
  4. Publish the zip file to a repository (only Bintray supported at the moment)

Fortunately this is dead easy because you can use Lazybones to set a project up for you! Simply run

lazybones create lazybones-project my-lzb-templates

and you'll get a simple Gradle build file with a directory into which you put your templates. The next step is to create the template.

Creating a template

Lazybones templates are simple zip files containing a directory structure and a bunch of files. How you create that zip file is up to you, but we're going to use the build that was created for us. It handles both the packaging and publishing of templates, so we don't have to worry about the details.

So let's create a new template Java project with just a Gradle build file and some standard directories. First, create the directory templates/simple-java/. This is where our template files are going to go. Inside this new directory, add these files:

  • README.md - a text file that contains information about the template
  • VERSION - a text file containing the current version number of the template
  • build.gradle - the build file for building this new project
  • src/main/java/.retain
  • src/main/resources/.retain
  • src/test/java/.retain
  • src/test/resources/.retain

The .retain files allow us to include empty directories in both a git repository and the template zip. The build simply excludes .retain files when packaging the template while maintaining the directory structure. Note that the .retain files can be empty, so a simple touch src/main/java/.retain is sufficient.

The build.gradle file is part of this template project and just contains:

apply plugin: "java"

group = "org.example"
version = "0.1"

repositories {
    mavenCentral()
}

dependencies {
}

The VERSION file is required by the build, because that's how the build knows what the current version of the template is. Just put any version string into the file:

1.0-SNAPSHOT

No quotes. No markup. Just the version text. Note that the build excludes this file from the template zip - the version is included in the filename of the zip.

Finally, README.md contains some information about the template. Remember that this is displayed immediately after a new project is created from the template, so it should offer some guidance on what the template provides and what steps to take next with the new project. Add this to the file:

Simple Java project template
------------------------------

You have just created a basic Java application. It provides a standard
project structure and a basic Gradle build. Simply add your source files
to `src/main/java`, your test cases to `src/test/java` and then you will
be able to build your project with

    gradle build
    gradle compile
    gradle test

Don't forget to add any extra JAR dependencies to `build.gradle`!

Although the README is not required, you really should include one. It doesn't have to be Markdown either or have a file extension. We just happen to like the Markdown syntax and the way that GitHub handles files with an md extension.

We could simply leave the template as it is, but wouldn't it be great if the user could set the group ID and version for the project at creation time? That would mean parameterising the group and version in the build file. Not a problem: we can add a post-install script.

Creating a post-install script

Post-install scripts are executed immediately after a template is unpacked into the new project directory and just before the README is displayed. They are straight Groovy scripts with access to just the core Groovy classes, plus Groovy's SimpleTemplateEngine and Apache Commons IO (for making file manipulation easier).

The script also has access to all the public and protected methods and properties defined in the LazybonesScript class. Of particular interest are the ask() and processTemplates() methods.

ask() allows the script to request input from a user, such as 'y' or 'n' for whether to include a particular feature or not. Even better, the user can provide the input on the command line, bypassing the input requests all together.

processTemplates() makes it easy to parameterise any of the files in your template using Groovy syntax. It basically runs the source file through Groovy's SimpleTemplateEngine to produce the resulting file. So if we want to allow the user to specify the project's group ID and version at install time, we modify build.gradle slightly:

apply plugin: "java"

group = "${group}"
version = "${version}"

repositories {
    mavenCentral()
}

dependencies {
}

and then add a post-install script, lazybones.groovy, in the root of the template:

def props = [:]
props.group = ask("Define value for 'group' [org.example]: ", "group", "org.example")
props.version = ask("Define value for 'version' [0.1]: ", "version", "0.1")

processTemplates "build.gradle", props

Sorted! And if the user wants to bypass the two asks, he or she can provide values for the requested properties on the command line:

lazybones create simple-java my-java-app -Pgroup=uk.co.cacoethes -Pversion=1.0-SNAPSHOT

In other words, you get non-interactive creation of projects from templates.

Once the template is ready, it's time to try it out and publish it.

Packaging, installing and publishing

There are three steps to publishing a template, each of which can be accomplished with a simple task provided by the build:

  • packaging - zipping up the template directory
  • installing - putting the template package into the local Lazybones template cache
  • publishing - making the template package available publicly in a Bintray repository

The relevant Gradle tasks are:

  • packageTemplate<Name>
  • packageAllTemplates
  • installTemplate<Name>
  • installAllTemplates
  • publishTemplate<Name>
  • publishAllTemplates

The packaging tasks aren't often used, so we'll skip over those right now. But installing the templates in your local cache is important so that you can easily test them before publication. You can do this on a per-template basis, or simply install all the templates in your templates directory.

So if you want execute a task for a particular template, what is <Name> in the above tasks? It's derived from the name of the template, which comes from the directory name. In our case, the template name is simple-java. To use this name in the Gradle tasks, we simply camel-case it: SimpleJava. Of course, this means your directories should use hyphenated notation rather than camel-case.

Installing the simple-java template in the local cache then becomes a simple case of

./gradlew installTemplateSimpleJava

which can then be tested with

lazybones create simple-java 1.0-SNAPSHOT my-java-app -Pgroup=uk.co.cacoethes -Pversion=0.1

Note that you have to specify the version of the template to install, otherwise Lazybones will look up the latest version online and either say the template doesn't exist, or use whatever the latest version is (not your development version).

Once you're happy with the template, you can publish it to a Bintray repository. To do that, you have to configure the build. If you have a look at build.gradle, you'll see this section (+ some comments):

lazybones {
    repositoryUrl = "https://api.bintray.com/content/<account>/<templates-repo>"
    // ...
    repositoryUsername = "your_bintray_username"
    repositoryApiKey = "your_bintray_api_key"
}

As you can see, the URL, username, and API key properties need to be set up properly. You can hard-code the repository URL in the build file (just replace <account> and <templates-repo> appropriately), but the username and API key shouldn't be included in a file that will probably go into version control.

Instead, create a gradle.properties file in the root of the project (not the root of the simple-java template, but the overall project directory) and put the following into it:

bintrayUsername=someone
bintrayApiKey=sfhakfh2948th9ghagh4gh30948g93hg

Of course, put your username and key in there! Then update build.gradle with:

lazybones {
    repositoryUrl = "..."
    repositoryUsername = project.bintrayUsername
    repositoryApiKey = project.bintrayApiKey
}

So now the repository credentials are being initialised from the (non-SCM-controlled) properties file.

Before you can successfully publish to Bintray, you of course have to have an account. But you also have to set up both a repository and a package. The publish* tasks cannot create packages in Bintray because that's not allowed at this moment in time. Once you've done that through the Bintray web interface though, you can run

./gradlew publishTemplateSimpleJava

to make it available to all and sundry! You can also send an inclusion request for your package to pledbrook/lazybones-templates. If accepted, your template will automatically appear in the lazybones list command.

That's it for the getting started guide. You've created a template, tested it, and finally published it to Bintray. For the rest of the guide we'll look at the template creation in more detail.

Post install script in-depth

The lazybones.groovy post install script is a generic groovy script with a few extra helper methods:

  • ask(String message, defaultValue = null) - asks the user a question and returns their answer, or defaultValue if no answer is provided

  • ask(String message, String propertyName, defaultValue = null) - works similarily to the ask above, but allows grabbing variables from the command line as well based on the propertyName.

  • processTemplates(String filePattern, Map substitutionVariables) - use ant pattern matching to find files and filter their contents in place using Groovy's SimpleTemplateEngine.

  • hasFeature(String featureName) - checks if the script has access to a feature, hasFeature("ask") or hasFeature("fileFilter") would both return true

Here is a very simple example lazybones.groovy script that asks the user for a couple of values and uses those to populate parameters in the template's build file:

def params = [:]
params["groupId"] = ask("What is the group ID for this project?")
params["version"] = ask("What is the project's initial version?", "version", "0.1")

processTemplates("*.gradle", params)
processTemplates("pom.xml", params)

The main Gradle build file might then look like this:

apply plugin: "groovy"

<% if (group) { %>group = "${group}"<% } %>
version = "${version}"

The ${} expressions are executed as Groovy expressions and they have access to any variables in the parameter map passed to processTemplates(). Scriptlets, i.e. code inside <% %> delimiters, allow for more complex logic.

Clone this wiki locally