Search

Dark theme | Light theme
Showing posts with label GradleGoodness:BuildScript. Show all posts
Showing posts with label GradleGoodness:BuildScript. Show all posts

May 11, 2016

Gradle Goodness: Get Property Value With findProperty

Gradle 2.13 added a new method to get a property value: findProperty. This method will return a property value if the property exists or null if the property cannot be found. Gradle also has the property method to return a property value, but this method will throw an exception if the property is missing. With the new findProperty method and the Groovy elvis operator (?:) we can try to get a property value and if not found return a default value.

In the following example we have a task that tries to print the value of the properties sampleOld and sampleNew. We use the findProperty for sampleNew and the property method for sampleOld:

// File: build.gradle
task resolveProperties << {
    println "sampleOld -> ${project.hasProperty('sampleOld') ? project.property('sampleOld') : 'default value for sampleOld'}"
    println "sampleNew -> ${project.findProperty('sampleNew') ?: 'default value for sampleNew'}"
}

First run the task and not set the project properties sampleOld and sampleNew:

$ gradle -q resolveProperties
sampleOld -> default value for sampleOld
sampleNew -> default value for sampleNew
$

Next we use the -P command line option to set a value for the properties:

$ gradle -q -PsampleOld="Value sampleOld" -PsampleNew="Value sampleNew" resolveProperties
sampleOld -> Value sampleOld
sampleNew -> Value sampleNew
$

Written with Gradle 2.13.

February 19, 2016

Gradle Goodness: Build Script Using Java Syntax

A Gradle build script is actually a Groovy script. The Gradle API uses Groovy a lot so we can have a nice DSL to define our builds. But we can also use Java code in a Groovy script. The compiler that compiles the build script understands Java code as well as the Groovy code. Sometimes I hear from people new to Gradle that they have difficulty understanding the DSL. I thought it would be a fun exercise to write a very simple Gradle build script using Java syntax.

Most notable is that we invoke the getProject method to get a reference to org.grade.api.Project. In the Gradle DSL we could use the simpler Groovy property reference project or just leave it out, because all method invocations in the build script are delegated to Project.

// Apply Java plugin.
getProject().getPluginManager().apply("java");

// Set repositories.
final RepositoryHandler repositories = getProject().getRepositories();
repositories.jcenter();

// Set dependencies.
final DependencyHandler dependencies = getProject().getDependencies();
dependencies.add("compile", "org.slf4j:slf4j-api:1.7.14");
dependencies.add("testCompile", "junit:junit:4.12");

// Add a new task.
final TaskContainer tasks = getProject().getTasks();
final Task helloWorldTask = tasks.create("helloWorld");
helloWorldTask.doFirst(new Action() {
    void execute(Object task) {
        System.out.println("Running " + task.getName());
    }
});


/* Equivalent to following Gradle DSL:

apply plugin: 'java'

repositories {
    jcenter()
}

dependencies {
    compile 'org.slf4j:slf4j-api:1.7.14'
    testCompile 'junit:junit:4.12'
}

task helloWorld << { task ->
    println "Running $task.name"
}
*/

Written with Gradle 2.11.

April 22, 2015

Gradle Goodness: Use Git Commit Id in Build Script

The nice thing about Gradle is that we can use Java libraries in our build script. This way we can add extra functionality to our build script in an easy way. We must use the classpath dependency configuration for our build script to include the library. For example we can include the library Grgit, which provides an easy way to interact with Git from Java or Groovy code. This library is also the basis for the Gradle Git plugin.

In the next example build file we add the Grgit library to our build script classpath. Then we use the open method of the Grgit class. From the returned object we invoke the head to get the commit id identified as id. With the abbreviatedId property we get the shorter version of the Git commit id. The build file also includes the application plugin. We customize the applicationDistribution CopySpec from the plugin and expand the properties in a VERSION file. This way our distribution always includes a plain text file VERSION with the Git commit id of the code.

buildscript {

    repositories {
        jcenter()
    }

    dependencies {
        // Add dependency for build script,
        // so we can access Git from our
        // build script.
        classpath 'org.ajoberstar:grgit:1.1.0'
    }

}

apply plugin: 'java'
apply plugin: 'application'

ext {
    // Open the Git repository in the current directory.
    git = org.ajoberstar.grgit.Grgit.open(file('.'))

    // Get commit id of HEAD.
    revision = git.head().id
    // Alternative is using abbreviatedId of head() method.
    // revision = git.head().abbreviatedId
}

// Use abbreviatedId commit id in the version.
version = "2.0.1.${git.head().abbreviatedId}"

// application plugin extension properties.
mainClassName = 'sample.Hello'
applicationName = 'sample'

// Customize applicationDistribution
// CopySpec from application plugin extension.
applicationDistribution.with {
    from('src/dist') {
        include 'VERSION'
        expand(
            buildDate: new Date(), 
            // Use revision with Git commit id:
            revision : revision,
            version  : project.version,
            appName  : applicationName)
    }
}

// Contents for src/dist/VERSION:
/*
Version: ${version}
Revision: ${revision}
Build-date: ${buildDate.format('dd-MM-yyyy HH:mm:ss')}
Application-name: ${appName}
*/

assemble.dependsOn installDist

When we run the build task for our project we get the following contents in our VERSION file:

Version: 2.0.1.e2ab261
Revision: e2ab2614011ff4be18c03e4dc1f86ab9ec565e6c
Build-date: 22-04-2015 13:53:31
Application-name: sample

Written with Gradle 2.3.

November 24, 2014

Gradle Goodness: Using and Working with Gradle Version

To get the current Gradle version we can use the gradleVersion property of the Gradle object. This returns a string value we can use for displaying the values. If we want to compare Gradle versions we can use the GradleVersion object. With this class we can get the current version, but we can also compare Gradle versions. This can be useful in our build scripts if we have functionality based on a Gradle version.

In the following build file we first have a task that uses the gradleVersion of Gradle. Then inside the task we use the static method current of the GradleVersion class. We get an GradleVersion instance and we display different properties from this instance. In the task compareGradleVersion we create a GradleVersion instance with the static version method. We compare multiple GradleVersion objects and have different functionality based on the Gradle version.

task printGradleVersion << {
    // Get current Gradle version as object.
    final GradleVersion gradleVersion = GradleVersion.current()

    // Print different properties.
    println "Your Gradle version is ${gradleVersion.version}"
    println "Base version: ${gradleVersion.baseVersion}"
    println "Build time  : ${gradleVersion.buildTime}"
    println "Build number: ${gradleVersion.buildNumber}"
    println "Commit id   : ${gradleVersion.revision}"
    println "Next major  : ${gradleVersion.nextMajor}"
    println "Snapshot?   : ${gradleVersion.snapshot}"
}

task compareGradleVersion << {
    // Current Gradle version.
    final GradleVersion gradleVersion = GradleVersion.current()

    // Gradle version 2.1 as GradleVersion object.
    final GradleVersion gradle2_1 = GradleVersion.version('2.1')

    // Compare versions.
    if (gradleVersion > gradle2_1) {
        println "Your Gradle version is newer than 2.1"
    } else if (gradleVersion == gradle2_1) {
        println "Your Gradle version is 2.1"
    } else {
        println "Your Gradle version is older than 2.1"
    }
}

When we run the tasks we get the following output:

$ gradle -q printGradleVersion
Gradle version is 2.2

Your Gradle version is 2.2
Base version: Gradle 2.2
Build time  : 2014-11-10 13:31:44 UTC
Build number: none
Commit id   : aab8521f1fd9a3484cac18123a72bcfdeb7006ec
Next major  : Gradle 3.0
Snapshot?   : false
$ gradle -q compareGradleVersion
Your Gradle version is newer than 2.1
$

Thanks to John Engelman who showed me this class on a pull request for the Gradle Grails plugin.

Written with Gradle 2.2.

May 29, 2013

Gradle Goodness: Extending DSL

Gradle already has a powerful DSL, but Gradle wouldn't be Gradle if we couldn't extend the DSL ourselves. Maybe we have our own naming conventions in our company or we have a special problem domain we want to express in a Gradle build script. We can use the ExtensionContainer, available via project.extensions, to add new concepts to our build scripts. In the Standardizing your enterprise build environment webinar by Luke Daley some examples are shown on how to extend the DSL. Also in the samples folder of the Gradle distribution are examples on how to create a custom DSL.

Let's first create a simple DSL extension. We first define a new class CommonDependencies with methods to define dependencies in a Java project. We want to use these methods with descriptive names in our build scripts. To add the class we use the create() method of the ExtensionContainer. The first argument is a name that needs to be unique within the build. The name can be used together with a configuration block in the script to invoke methods on the class we pass as the second argument. Finally we can pass constructor arguments for the class as last arguments of the create() method.

/**
 * Class for DSL extension. A default repository is added
 * to the project. The use<name>() methods add 
 * dependencies to the project.
 */
class CommonDependencies {
    /** Reference to project, so we can set dependencies/repositories */
    final Project project

    CommonDependencies(final Project project) {
        this.project = project

        // Set mavenCentral() repository for project.
        project.repositories {
            mavenCentral()
        }
    }

    /**
     * Define Spock for testCompile dependency 
     * @param version Version of Spock dependency with default 0.7-groovy-2.0
     */
    void useSpock(final String version = '0.7-groovy-2.0') {
        project.dependencies {
            testCompile "org.spockframework:spock-core:$version"
        }
    }

    /**
     * Define Spring for compile dependency 
     * @param version Version of Spring dependency with default 3.2.3.RELEASE
     */
    void useSpring(final String version = '3.2.3.RELEASE') {
        project.dependencies {
            compile "org.springframework:spring-core:$version"
        }
    }

}

// Add DSL extension 'commonDependencies' with class CommonDependencies 
// passing project as constructor argument.
project.extensions.create('commonDependencies', CommonDependencies, project)

apply plugin: 'java'

// Use new DSL extension. Notice we can use configuration closures just
// like we are used to with other Gradle DSL methods.
commonDependencies {
    useSpock()
    useSpring '3.1.4.RELEASE'
}

// We can still use the Java plugin dependencies configuration.
dependencies {
    compile 'joda-time:joda-time:2.1'
}

We can invoke the dependencies task from the command-line and we see all dependencies are resolved correctly:

$ gradle dependencies
...
compile - Compile classpath for source set 'main'.
+--- org.springframework:spring-core:3.1.4.RELEASE
|    +--- org.springframework:spring-asm:3.1.4.RELEASE
|    \--- commons-logging:commons-logging:1.1.1
\--- joda-time:joda-time:2.1
...
testCompile - Compile classpath for source set 'test'.
+--- org.springframework:spring-core:3.1.4.RELEASE
|    +--- org.springframework:spring-asm:3.1.4.RELEASE
|    \--- commons-logging:commons-logging:1.1.1
+--- joda-time:joda-time:2.1
\--- org.spockframework:spock-core:0.7-groovy-2.0
     +--- junit:junit-dep:4.10
     |    \--- org.hamcrest:hamcrest-core:1.1 -> 1.3
     +--- org.codehaus.groovy:groovy-all:2.0.5
     \--- org.hamcrest:hamcrest-core:1.3
...

We can also use a plugin to extend the Gradle DSL. In the plugin code we use the same project.extensions.create() method so it is more transparent for the user. We only have to apply the plugin to a project and we can use the extra DSL methods in the build script. Let's create a simple plugin that will extend the DSL with the concept of a book and chapters. The following build script shows what we can do after we have applied the plugin:

apply plugin: 'book'

book {
    title 'Groovy Goodness Notebook'
    chapter project(':chapter1')
    chapter project(':chapter2')
}

To achieve this we first create the following directory structure with files:

+ sample
  + buildSrc
     + src/main/groovy/com/mrhaki/gradle
       + Book.groovy
       + BookPlugin.groovy
     + src/main/resources/META-INF/gradle-plugins
       + book.properties
  + book
    + build.gradle
  + chapter1/src/html
    + index.html
  + chapter2/src/html
    + index.html
  + settings.gradle

The Book class will be added as DSL extension. The class has a method to set the title property and a method to add chapters which are Gradle project objects.

// File: buildSrc/src/main/groovy/com/mrhaki/gradle/Book.groovy
package com.mrhaki.gradle

import org.gradle.api.*

class Book {
    String title
    List<Project> chapters = []

    void title(final String title) {
        this.title = title
    } 

    void chapter(final Project chapter) {
        chapters << chapter
    }
}

Next we create the BookPlugin class. The plugin will add the Book class as DSL extension. But we also create a task aggregate that will visit each chapter that is defined and then copies the content from the scr/html folder in the chapter project to the aggregate folder in the build folder. Finally we add a dist task that will simply archive the contents of the aggregated files.

// File: buildSrc/src/main/groovy/com/mrhaki/gradle/BookPlugin.groovy
package com.mrhaki.gradle

import org.gradle.api.*
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.Zip

class BookPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.configure(project) {
            apply plugin: 'base'

            def book = project.extensions.create 'book', Book

            afterEvaluate {
                // Create task in afterEvaluate, so chapter projects
                // are resolved, otherwise chapters is empty.
                tasks.create(name: 'aggregate') {

                    // Skip task if no chapters are defined.
                    onlyIf { !book.chapters.empty }

                    doFirst {
                        // Copy content in src/html of 'book' directory.
                        copy {
                            from file('src/html')
                            into file("${buildDir}/aggregate")
                        }

                        // Copy content in src/html of chapter directories.
                        book.chapters.each { chapterProject ->
                            copy {
                                from chapterProject.file('src/html')
                                into file("${buildDir}/aggregate/${chapterProject.name}")
                            }
                        }
                    }
                }
            }

            tasks.create(name: 'dist', dependsOn: 'aggregate', type: Zip) {
                from file("${buildDir}/aggregate")
            }
        }        
    }
}

We create the file book.properties to tell Gradle about our new plugin:

# File: buildSrc/src/main/resources/META-INF/gradle-plugins/book.properties
implementation-class=com.mrhaki.gradle.BookPlugin

Our plugin is finished, so we can add a book project and some chapter projects. In the settings.gradle file we define an inclusion for these directories:

// File: settings.gradle
include 'chapter1'
include 'chapter2'
include 'book'

In the chapter directories we can add some sample content in the src/html directories. And in the book folder we create the following build.gradle file:

// File: book/build.gradle
apply plugin: 'book'

book {
    title 'Groovy Goodness Notebook'
    chapter project(':chapter1')
    chapter project(':chapter2')
}

Now from the book folder we can run the aggregate and dist tasks. The end result is that all files from the chapter src/html folder are in the build/aggregate folder. And in the build/distributions folder we have the file book.zip containing the files.

Code written with Gradle 1.6.

September 18, 2012

Gradle Goodness: Using Objects for Version

One of the great things of Gradle is that the build scripts are code. We can use all the features of the Groovy language, we can refactor our build scripts to make them more maintainable, we can use variables and properties to define values and much more, just like our application code. In this post we see how we can create a class to define a version in our build script.

To set the version of a Gradle project we only have to assign a value to the version property. Normally we use a String value, but we can also assign an object. Gradle will use the toString() method of the object to get the String value for a version.

In the following build script we define a new class Version in our build script. We create an instance of the class and assign it to the version property. With the task printVersion we can see the value of the version property:

version = new Version(major: 2, minor: 1, revision: 14)

task printVersion {
    doFirst {
        println "Project version is $version"
    }
}

defaultTasks 'printVersion'

class Version {
    int major, minor, revision

    String toString() {
        "$major.$minor.$revision"
    }
}

When we execute the task from the command-line we see the following output:

$ gradle -q printVersion
Project version is 2.1.14

We can even extend the class definition and include a boolean property release. If the value is true the version stays the same, but if it is false the String value -SNAPHSOT is appended to the version. In the following example build file we check if a release task is part of the Gradle build execution and if so the release property of the Version object is set to true:

version = new Version(major: 2, minor: 1, revision: 14)

gradle.taskGraph.whenReady { taskGraph ->
    // Set version as release version
    // when release task is to be executed.
    version.release = taskGraph.hasTask(release)
}

task printVersion {
    doFirst {
        println "Project version is $version"
    }
}

task release {
    doFirst {
        println "Releasing application $version"
    }
}

defaultTasks 'printVersion'

class Version {
    int major, minor, revision
    boolean release

    String toString() {
        "$major.$minor.$revision${release ? '' : '-SNAPSHOT'}"
    }
}

When we invoke the release and printVersion tasks together or just the printVersion task we get the following output:

$ gradle -q printVersion release
Project version is 2.1.14
Releasing application 2.1.14
$ gradle -q
Project version is 2.1.14-SNAPSHOT