Page History

Gradle

Mark George edited this page on 1 Mar

Clone this wiki locally

Contents

Pass System.in to Application

run {
     standardInput = System.in
}

Dump JVM and Gradle Details

In case you are sure what versions of things are being used:

tasks.register("info") { 
    description = "Show version and location information for Java, Gradle, and project.";
    doLast { 
        println "Java Version: " + org.gradle.internal.jvm.Jvm.current();
        println "Java Home: " + org.gradle.internal.jvm.Jvm.current().getJavaHome();
        println "Gradle Version: ${gradle.gradleVersion}";
        println "Gradle Home: ${gradle.gradleHomeDir}";
        println "Gradle User Home: ${gradle.gradleUserHomeDir}";
        println "Project Location: ${projectDir}";
        println "Project Build Location: ${buildDir}";

    }
}

Run using:

gradle jvm

Working With Multiple Source Sets

All source sets are broken into two sub-trees — one called java which can only contain Java code, and one called resources for everything else.

There are a couple of default source sets — main for the main code and test for the unit test code.

To add a new source set:

sourceSets { 
    // create a new source set
    shinyStuff {
        java {
            srcDir 'path to source dir'
        }
    }
}

New source sets will automatically get the default java and resources sub-trees unless they are explicitly excluded:

sourceSets {
    web {
        resources {
            srcDirs = ['public']
        }
        java{
            srcDirs = []
        }
    }
}

This will prevent the java tree from being used for this source set, and also prevent the default resource tree from being used in addition to the new public one.

Source sets do not see each other by default (one can't use code from another). To make this happen:

sourceSets {
    shinyStuff  // this is all we need if source dir is in the root of the project and named shinyStuff

    main {
        // make compiled shinyStuff visible to main
        compileClasspath += shinyStuff.output
        runtimeClasspath += shinyStuff.output

        // make shinyStuff dependencies also visible to main source set
        compileClasspath += shinyStuff.compileClasspath
    }
}

Dependencies are tied to source sets and prefixed with the source set name:

dependencies {
    // dependencies for shinyStuff
    shinyStuffImplementation '...'
    shinyStuffImplementation '...'

    // dependencies for main source set
    implementation '...'
    implementation '...'
}

Alternately you can nest the dependencies with the source sets, and you don't need to prefix anymore:

sourceSets {
    shinyStuff {
        dependencies {
            implementation '...'
        }
    }
}

Note : Only resources in the main source set are processed by Gradle — resources in other source sets will not be included in the build output or the generated JAR file by default. Add the following to make this happen:

processResources {
    // resources outside of the default 'main' sourceSet are not processed by default
    from(project.sourceSets.web.resources) {
        include "**/*.*"
    }
}

If you just need an additional resources directory, then you can add a directory to the existing main source set as follows:

sourceSets {
    main {
        resources {
            srcDirs += ['static']
        }
    }
}

Dependency Version Number Wildcards

Sometimes it is handy to just get the latest versions of everything (saves us from having to look up the version numbers manually). You can use the '+' operator as a version wild-card:

dependencies {
    def dorisVer='+'  // use latest version of doris
    def borisVer='1+'  // use latest 1.x version of boris

    implementation group: 'com.some.where', name: 'doris', version: dorisVer
    implementation group: 'com.who.cares', name: 'boris', version: borisVer
}

Copy Dependencies

Sometimes it is handy to have actual copies of the JAR files somewhere. The following task will copy the JARs for declared dependencies into the libs folder:

task copyLibs(type: Copy) {
    project.configurations.implementation.setCanBeResolved(true)
    from configurations.implementation
    into 'libs'
}

Building Generated Code

If you are using a task that generates code, then you will probably want the standard build to run the generate task before the compileJava task. The can be useful for resolving chicken and egg build problems in addition to removing additional steps that need to be performed to build the project:

compileJava.dependsOn tasks.<taskNameGoesHere>

This goes at the root level.

The tasks prefix is there to resolve problems where you are using plugins that have extensions and tasks that have the same name (such as the OpenAPI Generator Gradle plugin).

Source/Target Versions

If you are building with a later version of Java, but want to build binaries that run on older versions:

compileJava {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}

Fat JAR

Create a JAR that includes the dependencies. This does't always work since some dependencies may be sealed, or otherwise protected. The contents of the individual JAR files will be extracted and added to the final JAR along side the classes for your project. This results in a single self-contained JAR file.

task fatJAR(type: Jar) {

    project.configurations.implementation.setCanBeResolved(true)

    manifest.from jar.manifest

    baseName = 'fat'

    from {
        configurations.implementation.collect {
            duplicatesStrategy = 'exclude'
            it.isDirectory() ? it : zipTree(it)
        }
    }

    destinationDir = file('dist')

    with jar
}

Adding Dependencies to Manifest Classpath

As an alternative to creating a fat JAR, the dependencies can be added to the manifest's classpath. This produces a JAR that is runnable without the need for a script that sets the classpath.

jar {
    project.configurations.implementation.setCanBeResolved(true)

    manifest {
        attributes (
            'Main-Class': mainClassName,
            'Class-Path':  configurations.implementation.collect { 'lib/' + it.name }.join(' ')              
        )
    }
}

This code with assumes the dependencies are in a subdirectory named libs relative to the primary JAR.

Managing Source Set Directories

Being able to create and remove a project's source set directories on the fly is very handy, and it is annoying that Gradle doesn't have built-in tasks for this already.

Creating missing source folders:

task createMissingSourceDirs {
    group = "Directories"
    description = "Create all of the missing source set directories for this project."
    doFirst {
        sourceSets.each { def sourceRoot ->
            sourceRoot.allSource.srcDirTrees.each { def sourceDir ->
                if(!sourceDir.dir.exists()) {
                    println "Creating ${sourceDir}"
                    mkdir sourceDir.dir
                }
            }
        }
    }
}

Removing empty source folders:

task deleteEmptySourceDirs {
    group = "Directories"
    description = "Delete all empty source set directories."
    doFirst {
        sourceSets.each { def sourceRoot ->
            sourceRoot.allSource.srcDirTrees.each { def sourceDir ->
                if(sourceDir.dir.exists() && sourceDir.dir.isDirectory() && sourceDir.dir.list().length == 0) {
                    println "Removing empty ${sourceDir}"
                    sourceDir.dir.delete()
                }
            }
        }
    }
}

Preventing Tests from Running

You can sometimes end up with chicken and egg situations if the tests are run as part of the standard build (which is the default). In this case you can stop this by adding the following to the root of the build.gradle:

test.onlyIf { project.gradle.startParameter.taskNames.contains("test") }

This will stop the tests from being executed unless you explicitly run the test task.

Parallel Execution of JUnit Tests

Note that this will likely cause non-deterministic test results unless you have actually created your module under test to handle concurrency. This is good for demonstrating non-determinism due to poorly implemented concurrency though.

test {
    useJUnitPlatform()
    
    systemProperty("junit.jupiter.execution.parallel.enabled", true)
    systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
}

Opening project directories

If you are using an IDE or editor that lacks an "open project in file manager" feature then you can add this via a Gradle task:

task openProjectFolder {
    group = "Directories"
    description = "Open the project root in the system file manager."
    doFirst {
        println('Opening: ' + file(projectDir))
        java.awt.Desktop.getDesktop().open(file(projectDir));
    }
}

Generating .gitignore

def gitIgnored="""
.gradle
.classpath
.project
build
bin
dist
*.zip
*.tgz
*.class
.DS_Store
"""

task createGitIgnore {
    group = "Git"
    description = "Create the project's .gitignore file."
    doLast {
        def file = new File(projectDir, ".gitignore")
        if ( !file.exists() ) {
            println('Creating .gitignore')
            file.text = gitIgnored
        } else {
            println('.gitignore already exists')
        }
    }
}

Making NetBeans + Web Application + Embedded Jetty + Gradle play nicely

If you have a project that NetBeans recognises as a web application (has the typical JEE web project layout) then NetBeans will run the war task when you run the project. Since the embedded server is launched from a main method, this is not very useful. We want the normal run task to be called when the project is run so that the embedded server is started via the main method.

This is fixable via the build.gradle by altering the dependency chain:

  1. We are going to hijack the war task to make it call run, so we need a replacement task for building the WAR file. This is only necessary if we want to produce a WAR file — if we don't need a WAR file then we don't need this task since we only really care about the exploded WAR directory for deploying to Jetty:

    tasks.register("buildWar") {
       dependsOn war;
    }
  2. Make the war task call run so that the embedded server will be started when you run the project. Only allow the war task itself to be executed if the starting task was buildWar. Note that the run dependency will still be executed when tasks other than buildWar trigger the task — the dependsOn tasks aren't affected by the onlyIf.

    war {
      onlyIf { project.gradle.startParameter.taskNames.contains("buildWar") };
      dependsOn run;
    }
  3. Add a task that will copy the exploded WAR into the Jetty deploy location:

    tasks.register("deployWar", Sync) {
       into "${buildDir}/deploy";
       with war;
    }
  4. Make the run task call deployWar so that the application is deployed to Jetty prior to the embedded server being launched. We use the onlyIf to ensure that we only run the embedded server if the war or run tasks are the starting tasks (since both buildWar and build call war which calls run):

    run {
       onlyIf { project.gradle.startParameter.taskNames.intersect(["war","run"]) };
       dependsOn deployWar;
    }

The buildWar task can be used to produce the WAR file. The war task will now call run which in turn deploys the exploded JAR.

To make the Run File feature of NetBeans work with this configuration, you need to modify the build action for the run.single task so that it calls deployWar before calling runSingle:

  1. Open the project properties and select Build > Build Actions.

  2. Select run.single in the Configure Action combo box.

  3. In the Arguments text box, find where the runSingle task is being called and add deployWar just before it.

This adds a gradle.properties file to the project with the above change.