Gradle: Tasks Are Code

    In the previous topic, I tried to briefly tell what Gradle is and what ideas it is built on. The concept of Source Sets and the functionality associated with it were also highlighted.

    Now I would like to talk about what Gradle personally hooked me on. We will talk about ways to work with tasks. The task in Gradle is a close analogue of Ant Target. And, so as not to get confused in terms, the task (or task) hereinafter will always mean the Gradle Task. If we are talking about an entity from Ant, then this will be indicated explicitly: Ant task.

    So, tasks in Gradle are created using a special dsl (domain specific language) based on Groovy. And the possibilities that this dsl provides, in my opinion, are almost endless compared to ant or maven.



    Let's start with the traditional Hello World programmer. Suppose we have an empty file build.gradle. We write:

    task hello << {
        println 'Hello world!'
    }
    

    Launch Bingo! But not impressive. Let's try something else:

    >gradle -q hello
    Hello world!


    task upper << {
        String someString = 'mY_nAmE'
        println "Original: " + someString 
        println "Upper case: " + someString.toUpperCase()
        4.times { print "$it " }
    }
    

    We start That is the arbitrary definition on Groovy can be in a task definition . And the tasks themselves are a complete Groovy object. And this means that they have properties and methods that allow them to be controlled. For example, adding new actions. Let's look at a more interesting example. Suppose we have a small java project. Here is its build.gradle:

    >gradle -q upper
    Original: mY_nAmE
    Upper case: MY_NAME
    0 1 2 3







    apply plugin: 'java'
    version = '1.0'
    repositories {
        mavenCentral()
    }
    dependencies {
        compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
        testCompile group: 'junit', name: 'junit', version: '4.7'
    }

    and directory structure

    / projectAlpha
        / src
            / test
            / main
                / java
                    / my
                        / own
                            / code
                        / spring
                            / db
                            / plugin
                            / auth
    


    Nothing complicated: version, Maven Central repository, two dependencies for compilation, several packages. When you run the command, the archive will be collected

    >gradle build

    in the directory . All in full accordance with the agreements. Maven would do the exact same thing. But over time, requirements are known to change. Change the requirements in the example. Suppose we needed to collect Spring-related code into separate archives, assemble a separate jar with unit test classes, and another jar with source codes. In Gradle, this is solved as follows:projectAlpha/build/libsprojectAlpha-1.0.jar



    task sourcesJar(type: Jar) {
        appendix = 'sources'
        from sourceSets.main.allJava
    }
    task testJar(type: Jar) {
        appendix = 'test'
        from sourceSets.test.classes
    }
    jar {
        exclude 'my/spring/**'
    }
    task springDbJar(type: Jar) {
        appendix = 'spring-db'
        from sourceSets.main.classes
        include 'my/spring/db/**'
    }
    task springAuthJar(type: Jar) {
        appendix = 'spring-auth'
        from sourceSets.main.classes
        include 'my/spring/auth/**'
    }
    task springPluginJar(type: Jar) {
        appendix = 'spring-plugin'
        from sourceSets.main.classes
        include 'my/spring/plugin/**'
    }
    

    We start
    >gradle assemble

    And we see: What happened:
    projectAlpha>dir /b build\libs
    projectAlpha-1.0.jar
    projectAlpha-sources-1.0.jar
    projectAlpha-spring-auth-1.0.jar
    projectAlpha-spring-db-1.0.jar
    projectAlpha-spring-plugin-1.0.jar
    projectAlpha-test-1.0.jar



    • We defined two new tasks with the Jar type: sourcesJar and testJar. To describe the contents of the archive, source sets already familiar to you are used. Another attribute is set appendix, which, as you might guess, will be included in the archive name after the version.
    • We changed the default task jar (it is defined in the plugin) so that classes from specific packages do not fall into the main archive.
    • We defined 3 more tasks for assembling three separate archives with modules for Spring. When the task was called, assemblethe build system independently selected all the tasks forming the archives (Zip, Jar ..), and completed them. Having preliminarily processed the dependencies on source sets and compiled the necessary classes, as in the previous article.

    I wonder how to do it in Maven?

    But life does not stand still, and our demands continue to change. One fine morning, the Spring Foundation demanded to add to the manifest every jar that is related to Spring and is published on Habré, the demo attribute with the value habr.ru. It sounds strange, but we still need to implement them. Add:

    tasks.withType(Jar).matching { task -> task.archiveName.contains('spring') }.allObjects { task ->
        task.manifest {
                attributes demo: 'habr.ru'
            }
    }
    

    Run: Please note that many tasks have been marked UP-TO-DATE. This is another highlight of Gradle - incremental assembly. But about her another time. Now, if you are not too lazy and look at the contents of the archive manifests, you can find the necessary Bingo line in the Spring related ones ! But the requirements of the Spring Foundation continue to change. And now you need to put its checksum next to each jar :) Licensed cleanliness is a serious matter, and we are forced to obey. Unfortunately, Gradle does not have native support for the MD5 calculation operation. But it is in Ant. Well, let's use it. Change the last fragment as follows:

    projectAlpha>gradle assemble
    :compileJava UP-TO-DATE
    :processResources UP-TO-DATE
    :classes UP-TO-DATE
    :jar UP-TO-DATE
    :sourcesJar UP-TO-DATE
    :springAuthJar
    :springDbJar
    :springPluginJar
    :compileTestJava UP-TO-DATE
    :processTestResources UP-TO-DATE
    :testClasses UP-TO-DATE
    :testJar UP-TO-DATE
    :assemble




    Manifest-Version: 1.0
    demo: habr.ru





    def allSpringJars = tasks.withType(Jar).matching { task -> task.archiveName.contains('spring') }
    allSpringJars.allObjects { task ->
        configure(task) {
            manifest {
                attributes demo: 'habr.ru'
            }
            doLast {
                ant.checksum(file: archivePath, todir: archivePath.parentFile)
            }
        }
    }
    task springJars(dependsOn: allSpringJars)


    And this time we will collect only the ill-fated spring-related archives: Let's see what happened. Completing the task required minimal effort. Perhaps the requirements used in the example seem artificial and a bit naive, but the tasks that have to be solved in reality are surprisingly diverse. In this article, we were able, with the help of a small amount of Groovy code, to adapt to changing requirements and to perform several tasks that would be difficult to cope with Ant or Maven. Using a flexible programming language instead of xml frees you up and lets you decide how you want to complete your task. To be continued.

    projectAlpha>gradle clean springJars
    :clean
    :compileJava
    :processResources UP-TO-DATE
    :classes
    :springAuthJar
    :springDbJar
    :springPluginJar
    :springJars

    BUILD SUCCESSFUL

    Total time: 5.015 secs



    c:\Work\Gradle\tasksAreCode\projectAlpha>dir /b build\libs
    projectAlpha-spring-auth-1.0.jar
    projectAlpha-spring-auth-1.0.jar.MD5
    projectAlpha-spring-db-1.0.jar
    projectAlpha-spring-db-1.0.jar.MD5
    projectAlpha-spring-plugin-1.0.jar
    projectAlpha-spring-plugin-1.0.jar.MD5








    Also popular now: