Showing posts with label Gradle. Show all posts
Showing posts with label Gradle. Show all posts

Thursday, May 5, 2011

A Gradle Templates plugin

I'm almost done with my Gradle Templates plugin. All the information on how to use it can be found on it's wiki page. All that's really needed now is to finish up the Scala support.

I'm pretty happy with the result. The plugin supports all the built in Gradle plugins: Java, Groovy, War (webapp), and soon Scala. It also has tasks for starting your own standalone Gradle Plugin.

It's my hope that this plugin will be helpful to others getting started with the Gradle build system.

A Quick Start:
To start using the plugin you need to have Gradle, and Bazaar VCS installed.

Then you install the plugin by downloading the source and running the 'installPlugin' task.

bzr branch lp:gradle-templates

Or if you prefer Git, there is a github mirror.

Then run the installPlugin task in the newly checked out project.

gradle installPlugin

Usually, I do all my development work in a directory under my home directory like so:
/home/elberry/development/projects
So to use the 'create*Project' tasks, I've created a build.gradle file under my 'projects' directory, which simply contains the following:
apply plugin: "templates"
This provides me with the following "Template" tasks (running 'gradle tasks').
Template tasks
--------------
createGradlePlugin - Creates a new Gradle Plugin project in a new directory named after your project.
createGroovyClass - Creates a new Groovy class in the current project.
createGroovyProject - Creates a new Gradle Groovy project in a new directory named after your project.
createJavaClass - Creates a new Java class in the current project.
createJavaProject - Creates a new Gradle Java project in a new directory named after your project.
createScalaClass - Creates a new Scala class in the current project.
createScalaProject - Creates a new Gradle Scala project in a new directory named after your project.
createWebappProject - Creates a new Gradle Webapp project in a new directory named after your project.
initGradlePlugin - Initializes a new Gradle Plugin project in the current directory.
initGroovyProject - Initializes a new Gradle Groovy project in the current directory.
initJavaProject - Initializes a new Gradle Java project in the current directory.
initScalaProject - Initializes a new Gradle Scala project in the current directory.
initWebappProject - Initializes a new Gradle Webapp project in the current directory.

As explained in the wiki, the 'create*Project' tasks are intended for creating new projects, and new directories. The 'init*Project' tasks are intended to be used within a previously created directory where you want to set this directory up as a new Gradle project.

So, to get started with your own webapp, you can now run 'gradle createWebappProject'.

Answer a few basic questions:
[503] gradle createWebappProject
> Building > :createWebappProject
??> Project Name: TestWebappProject

??> Use Jetty Plugin? (Y|n)[n] y
:createWebappProject

BUILD SUCCESSFUL

Total time: 13.207 secs

And you're done. You can 'cd' into your new project and run 'gradle jettyRun' and have your webapp up and running on port 8080 in no time.

Cheers, and happy coding. :)
Eric

Saturday, April 9, 2011

Gradle, Groovy, and MethodMissing

I've created a few of these, but I think my latest is the cleanest implementation I've done yet. I've created a new ProjectTemplate builder for laying out new projects from scratch. Why, you ask? Because I seem to be always creating new projects and importing them into my IDE. Or, I use the IDE to create them and then wind up with a bunch of IDE files stuck in source control. Personally, I prefer the former.

Normally creating the initial project structure isn't that big of a deal, it's just a few simple *nix commands really:
mkdir -p MyProject/src/
cd MyProject/src
mkdir -p main/java main/resources test/java test/resources

I could just create a small bash script to handle this for me, but I'd have to change it around all the time to handle the project name, or if I wanted to use Groovy instead of Java, etc... I could also just make the bash script handle user input, but I'd still want to set the project up with some sort of build system, and it'd be cool if the build system could do this for me.

As I've been a fan of Gradle for a while now, I wanted to see if I could do something to get Gradle to do this for me. I know Maven can do this already with archetypes, but as I said, I wanted Gradle to be able to do this for me.

Without getting into the Gradle bit too much just yet, because I want to do that properly, and it's a whole other ball of wax, I'll focus on the Groovy bits first.

So, the goal is to have a small-ish Gradle build script that'll let me create a directory structure for a new Groovy project. A pretty standard directory structure looks like this:
[project root]/
   src/
      main/
         groovy/
         resources/
      test/
         groovy/
         resources/
Let's start with a basic Gradle build script: build.gradle
task "create-groovy-project" << {
   String projectName = System.console().readLine("> Project Name: ")
   println "Creating directory structure for new Groovy project: ${projectName}"
}

Running 'gradle create-groovy-project' now results in us being asked for our new project's name, and then it being echoed back at us.

Now for a little Groovy fun. Here's a shortened version of my ProjectTemplate class. This should just be pasted into the top of your build.gradle file.
class ProjectTemplate {
   
   private File parent
   
   private ProjectTemplate() {}
   
   static void root(String path, Closure closure) {
      new ProjectTemplate().d(path, closure)
   }

   void d(String name, Closure closure = {}) {
      File oldParent = parent
      if (parent) {
         parent = new File(parent, name)
      } else {
         parent = new File(name)
      }
      parent.mkdirs()
      closure.delegate = this
      closure()
      parent = oldParent
   }
   void f(Map args = [:], String name) {
      File file
      if (parent) {
         file = new File(parent, name)
      } else {
         file = new File(name)
      }
      file.exists() ?: file.createNewFile()
      if (args.content) {
         def content = args.content.stripIndent()
         if (args.append) {
            file.append(content)
         } else {
            file.text = content
         }
      }
   }
}
Nothing too spiffy about it. You use it by calling the static 'root' method and then declaring your directories and files. Let's add it to our 'create-groovy-project' task:
task "create-groovy-project" << {
   String projectName = System.console().readLine("> Project Name: ")
   println "Creating directory structure for new Groovy project: ${projectName}"
   ProjectTemplate.root(projectName) {
      d("src") {
         d("main") {
            d("groovy")
            d("resources")
         }
         d("test") {
            d("groovy")
            d("resources")
         }
      }
      f("LICENSE.txt", content: "// LICENSE GOES HERE")
      f("README.txt", content: "// What is this project all about, and what's needed to build it?")
   }
}
Running 'gradle create-groovy-project' now, and giving the name "MyProject" results in a new directory called "MyProject" which contains a directory structure like:
MyProject/
   src/
      main/
         groovy/
         resources/
      test/
         groovy/
         resources/
   LICENSE.txt
   README.txt
Ok, so far, so good. We can now create basic directory structures for a new Groovy project, and it wouldn't be too hard at all to create an 'create-java-project', or 'create-scala-project' task now to create directory structures for those types of projects.

But the ProjectTemplate usage is still a little noisy for me. We can get rid of the parenthesis, but it's still not enough.

Let's add a methodMissing method to the ProjectTemplate class to make things cleaner:
def methodMissing(String name, def args) {
      if (args) {
         def arg = args[0]
         if (arg instanceof Closure) {
            d(name, arg)
         } else if (arg instanceof Map) {
            f(arg, name)
         } else if (arg instanceof String || arg instanceof GString) {
            f([content: arg], name)
         } else {
            println "Couldn't figure out what to do. name: ${name}, arg: ${arg}, type: ${arg.getClass()}"
         }
      }
   }
What does this buy us? Well, now we don't need to specify the method name anymore. That's right kiddies, no more d's or f's. :)

We can change our usage to this now:
ProjectTemplate.root(projectName) {
      "src" {
         "main" {
            "groovy" {}
            "resources" {}
         }
         "test" {
            "groovy" {}
            "resources" {}
         }
      }
      "LICENSE.txt" "// LICENSE GOES HERE"
      "README.txt" "// What is this project all about, and what's needed to build it?"
   }
This is a lot cleaner, and easier to read to me.

I am actually working on a Gradle plugin which I hope to make available very soon, but for now It's hosted at LaunchPad. 

You can download the source using Bazaar:
bzr branch lp:gradle-templates
I've uploaded the complete build.gradle file on TellurianRing.com (1).

I'll blog more about my Gradle plugin endeavors as they happen.

Cheers,
Eric

(1) build.gradle (right click and save as)