Preface
Instructions in this guide also refer to the Template Testing Companion for testing templates during development.
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:
-
Create the directory structure and files for the template
-
Add VERSION, DESCRIPTION, and README files
-
Package the template structure into a zip, filtering out .retain,
VERSION
, andDESCRIPTION
files -
Publish the zip file to a repository by copying the manifest file and zip files to a file or http(s) URL.
Fortunately this is very easy because you can use Skeletal to set a project up for you! Simply run
skeletal create lazybones-project my-lzb-templates
and you’ll get top-level my-lzb-templates
directory which contains a simple Gradle build file, gradle wrapper, a README.md, and a templates
directory into which you put your templates.
my-lzb-templates |-- gradle | `-- wrapper | |-- gradle-wrapper.jar | `-- gradle-wrapper.properties |-- templates (1) |-- build.gradle |-- gradlew |-- gradlew.bat `-- README.md
1 | Directory for project templates |
The build.gradle
file applies the Skeletal Gradle Plugin which provides the packaging and publishing commands and some properties needed for publishing.
plugins {
id "net.codebuilders.lazybones-templates" version "1.6.2" (1)
}
lazybones {
packageExclude "**/*.swp", "**/*.swo", "**/.gradle" (2)
templateOwner = "<owner of templates in this project>" (3)
}
1 | Apply the Skeletal Gradle Plugin |
2 | Ant-style pattern for excluding files |
3 | Owner used for published 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.
To get a project layout to begin with, we will use the gradle init command
to create a simple Java application with Spock
tests and a Gradle build. We are using a template naming
convention of app type-language-test type-build tool
to differentiate
between templates, but you can use whatever convention that best suites your needs.
Create a new directory templates/simple-java-spock-gradle
.
from a shell in this directory:
gradle init
-
Select type of project to generate:
application
-
Select implementation language:
Java
-
Split functionality across multiple subprojects?:
no
-
Select build script DSL:
Groovy
-
Generate build using new APIs and behavior:
no
-
Select test framework:
Spock
-
Project name:
simple-java-spock-gradle
-
Source package:
org.example
|-- app (2) | |-- src | | |-- main | | | |-- java | | | | `-- org | | | | `-- example | | | | `-- App.java (3) | | | `-- resources | | `-- test | | |-- groovy | | | `-- org | | | `-- example | | | `-- AppTest.groovy (4) | | `-- resources | `-- build.gradle (5) |-- gradle | `-- wrapper | |-- gradle-wrapper.jar | `-- gradle-wrapper.properties |-- .gitattributes |-- .gitignore |-- gradlew |-- gradlew.bat `-- settings.gradle (1)
Gradle multi-project build layout with:
1 | settings file with sub-projects listed |
2 | app sub-project |
3 | sample application |
4 | sample test |
5 | app sub-project build file |
In simple-java-spock-gradle
create these files. See below for information on
their contents and also reference our sample in lazybones-templates/templates/simple-java-spock-gradle
since they may be more complete than the basic content shown here.
-
README.md - a text file that contains information about the template.
-
VERSION - a text file containing the current version number of the template.
-
DESCRIPTION - a text file containing the description of the template
Adding an empty .retain
file in a template allows 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. Since the .retain
files can be empty, a simple
touch src/main/java/.retain
is sufficient.
Add empty .retain
files as shown here:
-
app/src/main/resources/.retain
-
app/src/test/resources/.retain
The app/build.gradle
file is part of this template project and contains:
plugins { (1)
id 'groovy'
id 'application'
}
repositories {
mavenCentral() (2)
}
dependencies { (3)
testImplementation 'org.codehaus.groovy:groovy:3.0.10'
testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0'
testImplementation 'junit:junit:4.13.2'
implementation 'com.google.guava:guava:31.0.1-jre'
}
application {
mainClass = 'org.example.App' (4)
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
1 | Apply the Groovy and the Java application plugins |
2 | Use the Maven Central repository for dependencies |
3 | Dependencies |
4 | Specify the main class |
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:
0.1.0
No quotes. No markup. Just the version text. Note that the build excludes this file from the template zip as the version is included in the zip file’s name.
The DESCRIPTION
file is required by the publishing for the description shown
during the skeletal list
command. Just put a description string into the file:
A simple Java application template
As with the VERSION
file, no quotes or markup, just text.
Finally, README.md
contains some information about the template. 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 Spock Gradle Application Project -------------------------------------------- You have just created a simple Java application. There is a standard project structure for source code and tests. Simply add your source files to `app/src/main/java`, your test cases to `app/src/test/groovy` and see below for running your application. == Using the project: 1. Add any dependencies to build.gradle. 2. Add logic to App.java == Building the Extension - Build archives for distribution: ``` ./gradlew assemble ``` - Build an install directory with a runnable project unpacked: ``` ./gradlew installDist ```
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 didn’t include it here, but we often include a graphical view of the
directory structure in the initial README.md
created using the tree command.
This makes it easy for the user of the template to see where everything is
without hunting through the directories.
tree -a --dirsfirst --charset nwildner
You can see the full README.md
in simple-java-spock-gradle/README.md.
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 parameterizing 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).
Every script has access to the following properties:
-
projectDir
- aFile
instance representing the root directory of the new project. Treat this as read-only. -
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 Skeletal 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 lazybones*
properties have been retained for compatability with existing
Lazybones templates but now refer to the corresponding Skeletal versions.
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 parameterize 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:
plugins {
id 'groovy'
id 'application'
}
group = "${project_group}" (1)
version = "${project_version}" (2)
repositories {
mavenCentral()
}
...
1 | project_group variable |
2 | project_version variable |
and then add a post-install script, lazybones.groovy
, in the root of the template:
Map props = [:] (1)
props.project_group = ask("Define value for 'group' [org.example]: ", "org.example", "group") (2)
props.project_version = ask("Define value for 'version' [0.1.0]: ", "0.1.0", "version") (3)
processTemplates('app/build.gradle', props) (4)
1 | Property Map to store properties used to process template files |
2 | Set project_group using ask() |
3 | Set project_version using ask() |
4 | Use the template engine to process app/build.gradle using stored properties |
The first parameter to ask() is the user prompt message. The second is a default value to use if the user hits enter without providing a value. The third is a property name in the script binding that if provided on the command line will be used instead of prompting the user for one.
To try the template, see installing the template to cache.
Passing parameters to the script binding looks like -P<param>=<value>
From a directory to create the test project in, create the test project:
skeletal create simple-java-spock-gradle 0.1.0 my-java-app -Pgroup=net.codebuilders -Pversion=1.0-SNAPSHOT
If you provide them all, you get non-interactive creation of projects from templates.
Since we ran the build file through processTemplates
, If you look in your
new my-java-app/app/build.gradle
you should see group and version updated:
...
group = "net.codebuilders"
version = "1.0-SNAPSHOT"
...
Before continuing cleanup the cache and test project.
Another useful method available to post-install scripts is transformText()
.
It’s common for scripts to convert strings between camel case (for class names
perhaps), lower-case hyphenated (for directory names), and other forms. The
transformText()
method allows you to do just that:
import uk.co.cacoethes.util.NameType
def className = "MyClass"
def directoryForClass = transformText(className, from: NameType.CAMEL_CASE, to: NameType.HYPHENATED)
new File(directoryForClass).mkdirs()
The from
and to
arguments are both required and must be one of the NameType
enum values: CAMEL_CASE
("MyClass"), PROPERTY
("myClass"),
HYPHENATED
("my-class"), or NATURAL
("My Class")
We will use this later in the final script to guess the project class name from the project directory and the project name from that prior to the ask() prompts.
Before we get back to the lazybones.groovy
script let’s take care of a
directory issue. When we created the project files from gradle init
we used
a package org.example
which added those as org/example
subdirectories
under src/main/java
and src/test/groovy
and where the App.java
and
AppTest.groovy
are respectively. We’re going to use the script to create new
package directories at creation, so we can remove them from our template by
moving the two classes up to src/main/java
and src/test/groovy
and deleting
the org/example
directories.
Edit App.java
and change the package and class name like shown:
package ${project_package}; (1)
public class ${project_class_name} { (2)
public String getGreeting() {
return "Hello World!";
}
public static void main(String[] args) {
System.out.println(new ${project_class_name}().getGreeting()); (2)
}
}
1 | project_package variable |
2 | project_class_name variable |
We like to use the Spock naming convention of *Spec instead of *Test, so we will
rename AppTest.groovy
to AppSpec.groovy
and then edit as shown:
package ${project_package}
import spock.lang.Specification
class ${project_class_name}Spec extends Specification {
def "application has a greeting"() {
setup:
def app = new ${project_class_name}()
when:
def result = app.greeting
then:
result != null
}
}
Now that the project package and class name are variables, we need to edit
build.gradle
and use variables for the main class. We will also add
settings for the jar archive basename and application name which is the
command that gets ran. These would have both defaulted to app
since that
is the subproject we are working in.
...
jar {
archiveBaseName = '${project_name}' (1)
}
application {
mainClass = '${project_package}.${project_class_name}' (2)
applicationName = '${project_name}' (1)
}
...
1 | project_name variable |
2 | project_package and project_class_name variables |
The same for settings.gradle
:
rootProject.name = '${project_name}' (1)
1 | project_name variable |
Now edit the lazybones.groovy
like this:
import uk.co.cacoethes.util.NameType
import org.apache.commons.io.FileUtils
Map props = [:]
(1)
if (projectDir.name =~ /\-/) {
props.project_class_name = transformText(projectDir.name, from: NameType.HYPHENATED, to: NameType.CAMEL_CASE)
} else {
props.project_class_name = transformText(projectDir.name, from: NameType.PROPERTY, to: NameType.CAMEL_CASE)
}
(2)
props.project_name = transformText(props.project_class_name, from: NameType.CAMEL_CASE, to: NameType.HYPHENATED)
(3)
props.project_group = ask("Define value for 'group' [org.example]: ", "org.example", "group")
props.project_name = ask("Define value for 'artifactId' [" + props.project_name + "]: ", props.project_name , "artifactId")
props.project_version = ask("Define value for 'version' [0.1.0]: ", "0.1.0", "version")
props.project_package = ask("Define value for 'package' [" + props.project_group + "]: ", props.project_group, "package")
props.project_class_name = ask("Define value for 'className' [" + props.project_class_name + "]: ", props.project_class_name, "className").capitalize()
props.project_property_name = transformText(props.project_class_name, from: NameType.CAMEL_CASE, to: NameType.PROPERTY)
props.project_capitalized_name = props.project_property_name.capitalize()
String packagePath = props.project_package.replace('.' as char, '/' as char)
props.package_path = packagePath
(4)
processTemplates('README.md', props)
processTemplates('app/build.gradle', props)
processTemplates('settings.gradle', props)
processTemplates('gradle.properties', props)
processTemplates('app/src/main/java/*.java', props)
processTemplates('app/src/test/groovy/*.groovy', props)
File mainSources = new File(projectDir, 'app/src/main/java')
File testSources = new File(projectDir, 'app/src/test/groovy')
File mainSourcesPath = new File(mainSources, packagePath)
mainSourcesPath.mkdirs()
File testSourcesPath = new File(testSources, packagePath)
testSourcesPath.mkdirs()
def renameFile = { File from, String path ->
if (from.file) {
File to = new File(path)
to.parentFile.mkdirs()
FileUtils.moveFile(from, to)
}
}
(5)
mainSources.eachFile { File file ->
renameFile(file, mainSourcesPath.absolutePath + '/' + file.name)
}
testSources.eachFile { File file ->
renameFile(file, testSourcesPath.absolutePath + '/' + props.project_capitalized_name + file.name)
}
renameFile(new File(mainSourcesPath, 'App.java'), mainSourcesPath.absolutePath + '/' + props.project_class_name + ".java")
renameFile(new File(testSourcesPath, 'AppSpec.java'), testSourcesPath.absolutePath + '/' + props.project_class_name + "Spec" + ".groovy")
1 | In the first if/else statement we make an educated guess about the project class
name based on the directory given to create and use transformText() to make
it CAMEL_CASE . |
2 | Then we use the class name and transformText() again to make a HYPHENATED
project name. |
3 | Then we use these guesses as defaults when asking the user for their values next. This pattern continues until we have all the information we need. |
4 | Then we use processTemplates() on all the files that have variables to replace. |
5 | Finally, we rename our sources to move them into the package directory structure and then rename the application class and test class. |
Install into Cache and Test
This is now covered in the Template Testing Companion document. To test, install the template again, create the test project per the instructions, build and run the distribution and then cleanup the cache and test project.
Once the template is ready, it’s time to publish it.
Packaging, Installing and Publishing
There are three options to use publishing a template, each of which can be accomplished with a simple task provided by the Skeletal Gradle Plugin:
-
packaging - zipping up the template directory (no manifest created)
-
installing - putting the template package directly into the local Skeletal template cache
-
publishing - making the template package and a manifest file to place in a simple URL repository.
Publishing takes care of the packaging and creates a manifest file in one task. The first time a template is used from a repository it is also added the cache so the install step isn’t required unless you don’t plan on using a remote repository or unless you are doing template development testing.
The relevant Gradle tasks are:
-
packageTemplate<Name>
-
packageAllTemplates
-
installTemplate<Name>
-
installAllTemplates
-
publishTemplate<Name>
-
publishAllTemplates
The packaging tasks aren’t often used by themselves, 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 from your templates
directory.
If you want to execute a task for a particular template, the <Name>
in
the above tasks is derived from the name of the template, which comes from
the directory name. In our case, the template name is simple-java-spock-gradle
.
To use this name in the Gradle tasks, we simply camel-case it:
SimpleJavaSpockGradle
. Of course, this means your directories should use
hyphenated notation rather than camel-case.
If the rules for converting between camel-case and hyphenated forms don’t suit your template name, for example if you separate numbers with hyphens ('javaee-7'), then you can use hyphens in the task name:
./gradlew packageTemplate-javaee-7
Once you’re happy with the template, you can publish it for a simple URL
repository. To do that, you have to configure the build. If you have a look at
my-lzb-templates/build.gradle
, you’ll see this section:
lazybones {
...
templateOwner = "Skeletal Project"
}
templateOwner
is used in the manifest file as the template owner or creator.
This owner is used for all templates published from this lazybones project.
To publish the template and create the manifest file:
./gradlew publishTemplateSimpleJavaSpockGradle
This will create the zip archive and a skeletal-manifest.txt
file. This manifest
is a simple CSV formatted text file with an entry for each template published.
name,version,owner,description simple-java-spock-gradle-template,1.0,Skeletal Project,A simple Java Spock Gradle project template
See the Skeletal Gradle Plugin for instructions on applying the plugin to your Gradle build, task usage, and advanced configuration.
Fine-tuning the Packaging
The packaging process is by default rather dumb. It will include all files and directories in the target template directory except for a few hard-coded exceptions (the DESCRIPTION, VERSION, and .retain files for example). That leaves a lot of scope for accidentally including temporary files in the package! To help you avoid that, the plugin allows you to specify a set of extra exclusions using Apache Ant-style patterns.
lazybones {
packageExclude "**/*.swp", ".gradle", "build"
....
}
These exclusions apply to all templates. If you want template-specific exclusions, then use the following syntax:
lazybones {
template("simple-java-spock-gradle") { // Template (directory) name
packageExclude "**/*.swp", ".settings"
}
}
Note that the template-specific settings completely override the global ones, so if you want the global ones to apply you will need to repeat them in the template-specific list.
Another potential issue when packaging templates is with file and directory permissions. Lazybones attempts to retain the permissions it finds in the template directory, but these may not be correct on Windows. To compensate for that, the plugin allows you to specify file permissions in the template configuration:
lazybones {
fileMode "755", "gradlew", "**/*.sh"
}
The first argument is the Unix-style permission as a string (such as "600"
, "755"
and so on), and the rest are a list of Ant-style patterns representing the files and directories that the permission string should apply to. You can have multiple fileMode()
entries, although ideally you should only have one per file mode.
As with package exclusions, you can also specify file modes on a per-template basis:
lazybones {
template("simple-java") {
fileMode "600", "secret.properties"
fileMode "755, "gradlew", "**/*.sh"
}
}
Again, the template-specific settings replace the global ones for that particular template.
That’s it for the getting started guide. You’ve created a template, tested it, and finally published it for a URL repository. For the rest of the guide we’ll look at the template creation in more detail.
Simple URL Repositories
In a break from the original Lazybones project and their use of Bintray for
repositories, Skeletal uses what we call a Simple URL Repository
which can be
any file:
or http(s):
URL location that contains templates and a
skelatal-manifest.txt
file that your computer has read-access to.
To create a repository just copy the manifest file and project template
archives to a local directory, file share, or webserver and use file:
or http(s)
type repository URLs to access them. Configuring Skeletal to
use additional repositories is covered in the Application Users Guide.
Template engines
The processTemplates()
method available to post-install scripts allows you to
generate files based on templates. By default, any files that match the pattern
passed to processTemplates()
are treated as Groovy templates that can be
processed by SimpleTemplateEngine
and those source files are replaced by the processed versions. That’s not the
end of the story though.
Skeletal allows you to use any template engine that implements Groovy’s
TemplateEngine,
meaning that your source templates could be Mustache, Apache Velocity, or anything
else. Of course, not every template engine has a Groovy implementation, but it’s
often trivial to create an adapter TemplateEngine
implementation.
The original Lazybones version of this guide contained examples of using their Handlebars Template Engine but the JAR is no longer available so the remainder of this section is TBD until it can be replaced.
Subtemplates
It’s very easy to add subtemplate support to your project templates. The key points to understand are:
-
Subtemplates are similar to project templates but packaged inside a project template zip.
-
A subtemplate can be included in multiple project templates.
-
Subtemplates only take effect when the user runs the
skeletal generate
command.
Let’s say you want to add a subtemplate for generating Spock test classes in a
project created from the simple-java-spock-gradle
template we introduced
earlier. Your starting point is to create a new directory for the subtemplate:
templates/subtmpl-simple-spock-test
Note that although the subtemplate will be going inside the simple-java-spock-gradle
template, its directory is at the same level as templates/simple-java-spock-gradle
.
The key is to give the directory name as 'subtmpl-' prefix, as this is what
tells the build that it’s a subtemplate, resulting in
subtmpl-simple-spock-test
being excluded from the *AllTemplates
tasks.
The contents of a subtemplate source directory look a little like a normal project template, except you are unlikely to include as many files and the README is unnecessary. In this case, we want:
-
VERSION - the file containing the current version of the subtemplate
-
lazybones.groovy - the post-install script
-
Spec.groovy.gtpl - the template source file for test classes
Each of these files behaves in the same way as in a project template, but there are a few slight differences. Consider the template source file for tests:
package ${pkg}
import spock.lang.Specification
class ${cls} extends Specification {
def "application has a greeting"() {
given: // setup
def app = new ${parentClassName}()
when: // stimulus
def result = app.greeting
then: // response
result != null
}
}
This references several properties: pkg
, cls
, and parentClassName
. Where
do these parameters come from? We need to look into the post-install script
lazybones.groovy
, to find out:
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import static org.apache.commons.io.FilenameUtils.concat
Map props = [:]
// Pass in parameters from the project template
String parentPackage = parentParams.package (2) (3)
props.parentClassName = parentParams.className (2) (3)
props.pkg = ask("Define value 'package' [" + parentPackage + "]: ", parentPackage, "package") (1)
props.cls = ask("Define value for 'class' name: [SimpleSpec]", "SimpleSpec", "class").capitalize() (1)
processTemplates("Spec.groovy", props)
String pkgPath = props.pkg.replace('.' as char, '/' as char)
String filename = props.cls.capitalize() + ".groovy"
File destFile = new File(projectDir, concat(concat("app/src/test/groovy", pkgPath), filename))
destFile.parentFile.mkdirs()
FileUtils.moveFile(new File(templateDir, "Spec.groovy"), destFile) (4)
println "Created new Spock Test ${FilenameUtils.normalize(destFile.path)}"
1 | As you can see, the pkg and cls properties are mapped from the return
values of two ask() calls. This is standard post-install script behavior. |
2 | The interesting properties, parentPackage and parentClassName , are mapped from
something new: the parentParams map. This contains any named parameters used
by the parent project template, i.e. simple-java-spock-gradle in this case.
Because of this, parentParams only exists for subtemplates. |
3 | Only parentClassName is used in the template so it is in the prop map where
parentPackage is a property only used within the script. |
4 | Another novel aspect of the post-install script is the reference to a
templateDir property in addition to projectDir . This is because
subtemplates are not unpacked directly in the project directory. Instead,
Lazybones unpacks them into the project’s .lazybones directory. templateDir
points to the location of the unpacked subtemplate, whereas projectDir still
points to the root directory of the project created from
simple-java-spock-gradle . So your subtemplate post-install script will
typically want to copy or move files from templateDir to projectDir . The
Commons IO classes that all post-install scripts have access to are ideal for
this. |
With all the subtemplates files in place, all you need to do is tell the build
that the simple-java project template should include the entity subtemplate.
So open up the build file and add this line to the lazybones
block:
lazybones {
...
template "simple-java-spock-gradle" includes "simple-spock-test"
}
Note how the name of the subtemplate excludes the 'subtmpl-' prefix. Now when
you package the simple-java project template, the entity subtemplate will be
included in it, ready for use with Lazybones' generate
command.
If you want to include multiple subtemplates, just pass extra arguments to includes()
:
lazybones {
...
template "simple-java-spock-gradle" includes "simple-spock-test", "controller", "repository"
}
There is one final option available to template authors. What if you want to package the simple-spock-test, controller, and repository template files into a single subtemplate package? How would the user be able to specify which type of class they want to generate? The answer is through template qualifiers.
Let’s say you have an 'artifact' subtemplate that includes Spec.groovy.gtpl, Controller.groovy.gtpl, etc. The user can run the generate
command like this to determine which artifact type to use:
skeletal generate artifact::controller
The ::
separates the subtemplate name, 'artifact', from the qualifier,
'controller'. In your post-install script, you can access the qualifiers through
a tmplQualifiers
property:
def artifactTemplate
if (tmplQualifiers) {
artifactTemplate = tmplQualifiers[0].capitalize() + ".groovy.gtpl"
}
else {
artifactTemplate = ask("Which type of artifact do you want to generate? ", null, "type")
}
// ... process the corresponding template file.
The user can even pass extra qualifiers simply by separating them with ::
skeletal generate artifact::controller::org.example::Book
This is why tmplQualifiers
is a list. It retains the order that the qualifiers are specified on the command line.
Note qualifiers should not be used for general parameterization such as packages and class names. Think carefully before supporting more than a single qualifier.
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, 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("processTemplates")
would both return true
You can get a complete list of the available methods from the LazybonesScript class.
Here is a very simple example lazybones.groovy
script that asks the user for
a couple of values and uses those to populate properties in the template’s build
file:
def props = [:]
props["groupId"] = ask("What is the group ID for this project?")
props["version"] = ask("What is the project's initial version?", "0.1", "version")
processTemplates("*.gradle", props)
processTemplates("pom.xml", props)
The main Gradle build file might then look like this:
plugins {
id 'groovy'
id 'application'
}
<% if (group) { %> (2)
group = "${project_group}"
<% } %>
version = "${project_version}" (1)
1 | The ${} expressions are executed as Groovy expressions, and they have access
to any variables in the property map passed to processTemplates() . |
2 | Scriptlets, i.e. code inside <% %> delimiters, allow for more complex logic. |