-
Notifications
You must be signed in to change notification settings - Fork 104
Template developers guide
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.
The first thing you want to do is set up a build for your templates, because creating and publishing a template involves several steps:
- Create the directory structure and files for the template
- Add VERSION and README files
- Package the template structure into a zip, filtering out
.retain
andVERSION
files - 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.
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.
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).
Every script has access to the following properties:
-
targetDir
- aFile
instance (or a string in Lazybones 0.4 and earlier) representing the root directory of the new project. Treat this as read-only. Useas File
if you want to support all versions of Lazybones, not just 0.5 and above. -
fileEncoding
- the encoding used by your template files. You can set this at the start of the script. Defaults to UTF-8. -
lazybonesVersion
- a string representing the version of Lazybones the user is running. -
lazybonesMajorVersion
- a string representing the first number in the version string, e.g. "1" for "1.2.3". -
lazybonesMinorVersion
- a string representing the second number in the version string, e.g. "2" for "1.2.3".
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]: ", "org.example", "group")
props.version = ask("Define value for 'version' [0.1]: ", "0.1", "version")
processTemplates "build.gradle", props
Sorted! And if the user wants to bypass the two ask
s, 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.
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.
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, ordefaultValue
if no answer is provided -
ask(String message, defaultValue, String propertyName)
- works similarly to theask()
above, but allows grabbing variables from the command line as well based on thepropertyName
. -
processTemplates(String filePattern, Map substitutionVariables)
- use ant pattern matching to find files and filter their contents in place using Groovy'sSimpleTemplateEngine
. -
hasFeature(String featureName)
- checks if the script has access to a feature,hasFeature("ask")
orhasFeature("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?", "0.1", "version")
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.