Provided Dependencies in Gradle

To this day Gradle is lacking a so called provided scope. This scope is probably best known from Maven which offers it out of the box. It allows to set dependencies which are provided by the runtime environment the program will later run on. Examples for such a runtime environment could be an application server like Wildfly or a distributed computing platform like Hadoop. Dependencies which are classified as provided are still needed to compile, yet you don’t need and don’t want them to ship in your deployable package. You don’t need them because they will be in your target environment anyway. And you don’t want them to ship to avoid conflicts of different versions of the same library.

To be honest, Gradle does not totally lack provided. The war plugin offers providedCompile and providedRuntime. So when you are building a web application you can just use that.

In case you build a jar you need to handcraft a provided scope. There haven been discussions about that already. If you’re interested in progress of that matter you can watch GRADLE-784.

The basis for the following code was a nice article by Mick Brooks. Here we go:

apply plugin: 'java'

repositories {
    mavenCentral()
}

configurations {
    provided
}

sourceSets {
    main {
        compileClasspath += configurations.provided
        runtimeClasspath += configurations.provided
    }
    test {
        compileClasspath += configurations.provided
        runtimeClasspath += configurations.provided
    }
}

dependencies {
     provided '...'
}

The fundamental idea is to create a new configuration and add it to all classpaths of main and test. There are other smaller variations of this code out there. They tend to change the compileClasspath of main only. This will work most of the time. It depends on your needs. For example, when you write integration tests where you need the provided dependencies you have to add them to your tests, too.

But why add it to the runtimeClasspath? Because there are relationships between runtime and compile configurations. The runtime configuration extends compile which means that any dependency in compile also appears in runtime. The two classpath properties use the respective configurations. So compileClasspath uses the compile configuration and runtimeClasspath uses runtime configuration. For short: I consider it good practice to add provided to both compileClasspath and runtimeClasspath because it would naturally happen with dependencies anyway. If your needs differ you can still remove these.

Our favorite build tool is now set up. How about our IDE? I like to generate the correct dependencies from our single source of truth – the build script. For IntelliJ IDEA this is achieved by:

apply plugin: 'idea'

idea {
    module {
        scopes.PROVIDED.plus += [ configurations.provided ]
    }
}
apply plugin: 'java'

repositories {
    mavenCentral()
}

configurations {
    provided
}

sourceSets {
    main {
        compileClasspath += configurations.provided
        runtimeClasspath += configurations.provided
    }
    test {
        compileClasspath += configurations.provided
        runtimeClasspath += configurations.provided
    }
}

dependencies {
     provided '...'
}

apply plugin: 'idea'

idea {
    module {
        scopes.PROVIDED.plus += [ configurations.provided ]
    }
}

Gradle and IntelliJ IDEA – JUnit System Properties

In several unit test case scenarios I had the need to inject system properties to make the tests work. This is done easily with gradle:

test {
    systemProperty "mySystemProperty", "myValue"
}

This is straightforward. To make it a little bit more useful we can extract system properties given to gradle and pass them onto the test like this:

ext {
    testProperties = [
            'javax.net.ssl.keyStore',
            'javax.net.ssl.keyStorePassword',
            'javax.net.ssl.keyStoreType',
            'javax.net.ssl.trustStore',
            'javax.net.ssl.trustStorePassword',
            'javax.net.ssl.trustStoreType'
    ]
}

test {
    project.testProperties.each {
        systemProperty it, System.getProperty(it)
    }
}

This is nice if your build environment defines these properties so you can pass them. Also, there are passwords within and we don’t want to put them into the build in cleartext.

Next thing to do is to incorporate the properties into IntelliJ IDEA. I really like it, when I can execute tests in my IDE and so do others:
http://blog.proxerd.pl/article/setting-system-properties-for-the-default-junit-run-configuration-in-intellij-from-gradle

Basically, we add the idea plugin to our gradle script and hook into the project file generation:

String createVmParameters(List keys) {
    keys.collect { key ->
        def value = System.getProperty(key)
        "-D$key=$value"
    }.join(" ")
}

idea.workspace.iws.withXml { XmlProvider provider ->
    Node node = provider.asNode()
    def runManager = node.component.find { it.'@name' == 'RunManager' }
    def defaultJUnitConf = runManager.configuration.find { it.'@default' == 'true' && it.'@type' == 'JUnit' }
    def vmParametersOption = defaultJUnitConf.option.find { it.'@name' == 'VM_PARAMETERS' }
    vmParametersOption.'@value' = createVmParameters(project.testProperties)
}

This hooks into the generation of the project file and modifies the xml. If all works ok you can generate your project files with a call to gradle idea.

apply plugin: 'java'
apply plugin: 'idea'

ext {
    testProperties = [
            'javax.net.ssl.keyStore',
            'javax.net.ssl.keyStorePassword',
            'javax.net.ssl.keyStoreType',
            'javax.net.ssl.trustStore',
            'javax.net.ssl.trustStorePassword',
            'javax.net.ssl.trustStoreType'
    ]
}

test {
    systemProperty "mySystemProperty", "myValue"
    project.testProperties.each {
        systemProperty it, System.getProperty(it)
    }
}

String createVmParameters(List keys) {
    keys.collect { key ->
        def value = System.getProperty(key)
        "-D$key=$value"
    }.join(" ")
}

idea.workspace.iws.withXml { XmlProvider provider ->
    Node node = provider.asNode()
    def runManager = node.component.find { it.'@name' == 'RunManager' }
    def defaultJUnitConf = runManager.configuration.find { it.'@default' == 'true' && it.'@type' == 'JUnit' }
    def vmParametersOption = defaultJUnitConf.option.find { it.'@name' == 'VM_PARAMETERS' }
    vmParametersOption.'@value' = createVmParameters(project.testProperties)
}