Dependencies - pford68/gradle-examples GitHub Wiki

Contents

Configurations

In Gradle dependencies are grouped into configurations. A configuration is simply a named set of dependencies1. Configurations have a name, a number of other properties, and they can extend each other. Many Gradle plugins add pre-defined configurations to your project. These configurations function like scopes in Maven--in that they mark dependencies as only being required by tests, e.g. Configurations involve more than that, however.

Java Plug-In Configurations

Name Extends Used By Description
implementation compile - Replaces compile and does not include transitive dependencies. This is the go-to replacement for compile.
api compile - Replaces compile and includes transitive dependencies
compileClasspath compile, api, implementation compileJava Compile classpath, used when compiling source.
compile - compileJava (Deprecated) Compile time dependencies
runtime compile - (Deprecated) Runtime dependencies
annotationProcessor Annotation processors used during compilation.
testImplementation testCompile, implementation Implementation only dependencies for tests.
testCompileClasspath testCompile, testCompileOnly, testImplementation compileTestJava Test compile classpath, used when compiling test sources.
testCompile compile compileTestJava (Deprecated) Additional dependencies for compiling tests.
testRuntime runtime, testCompile test (Deprecated) Additional dependencies for running tests only.
archives - uploadArchives Artifacts (e.g. jars) produced by this project.
default runtime - The default configuration used by a project dependency on this project. Contains the artifacts and dependencies required by this project at runtime.

Compile Vs. Runtime

What's the difference? In the most common case, the artifacts needed at compile time are a subset of those needed at runtime. For example, let's say that a program called "app" uses library foo, and library foo internally uses library bar. Then only foo is needed to compile app, but both foo and bar are needed to run it. This is why by default, everything that you put on Gradle's compile configuration is also visible on its runtime configuration, but the opposite isn't true.

Runtime configuration extends compile configuration. It means, that any dependency added to compile configuration is available in runtime configuration.

compile 'oauth.signpost:signpost-core:1.2.1.2' will be enough to get this artifact in both, runtime and compile.

Provided Scope

The WAR Plugin

The WAR plugin provides the providedCompile scope. You will need "provided" scope largely when creating WARs.

Without the WAR Plugin

Without the WAR plugin, there is a way to get a similar effect. In short, you have to add a configuration and a source set as described here.

// Enabling provided scope
configurations {
   provided
   compile.extendsFrom provided
}

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

Declaring Configurations

List the new configuration(s) in a configurations block. One reason to declare a new configuration might be to dependencies that are not used at compile time, or for test sources used in integration tests but not unit tests.2

configurations {
   seaLife
   allLife.extendsFrom seaLife
   integrationTestCompile.extendsFrom testCompile
}

You can then use the new configurations to configure dependencies:

dependencies {
    compile "org.glassfish.jersey.containers:jersey-container-servlet:$jerseyVersion"
    compile "org.glassfish.jersey.media:jersey-media-json-jackson1:$jerseyVersion"
    integrationTestCompile "com.jayway.restassured:rest-assured:$restAssuredVersion"
    integrationTestCompile "com.jayway.restassured:json-path:$restAssuredVersion"
}

Extending another configuration

When one configuration extendsFrom another, it means that all of the dependencies that are the latter will also belong to the former. If you create an uber configuration and declare that compile extendsFrom it, then all uber dependencies will be on the compile-time classpath, but not vice-versa.

configurations {
    uber
    compile.extendsFrom uber
}

dependencies {
    uber name: 'restConnector', version: '1.0'
    compile 'org.codehaus.groovy:groovy-all:2.4.7'
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5'
}

If were to run the dependencies task for that example, I would see one dependency in the uber configuration, but two in the compile configuration:

compile - Dependencies for source set 'main'.
+--- org.codehaus.groovy:groovy-all:2.4.7
\--- :restConnector:1.0

uber
\--- :restConnector:1.0

If I instead declare that the new configuration extendsFrom compile, the results are reversed: uber then contains all uber and compile dependences, and compile does not contain uber dependencies.

Hence, because runtime extendsFrom compile, runtime contains all runtime and compile dependencies.

runtime - Runtime dependencies for source set 'main'.
+--- org.codehaus.groovy:groovy-all:2.4.7
\--- :restConnector:1.0

These are the compile dependencies (compile + uber) from the example above. They also appear under runtime.

Integration Tests

You want to separate integration tests from unit tests, because the former can take a long time as they grow, thus delaying unit tests, which should be short and quick. Thus, you have to move them out of the test directory if they are there. Thus, you have to create a new sourceSet to point to their new directory; otherwise, they will not be compiled or used by tasks.

Creating this new sourceSet automatically creates two new configurations:

  • <new config name>Compile
  • <new config name>Runtime

These appear when you execute gradle properties. However, at this point, they will have no effect on fetching dependencies for the code in the integrationTest sourceSet.

As mentioned above, you have to execute code like the following in order for the new configuration to have effect in the dependencies block:

configurations {
   integrationTestCompile.extendsFrom testCompile
}

This configuration block says that the integrationTestCompile configuration includes the dependencies and sources from testCompile.

Even then, however, your IDE may not understand the custom configuration, as explained below.

IntelliJ Issues

Even after creating a new custom configuration correctly, IntelliJ may not understand the new configuration and download dependencies in that scope when needed. In the classes using those dependencies, moreover, IntelliJ may indicate that the dependencies cannot be found. The solution is to use the Gradle IDEA Plugin and configure it to add the new configuration to an existing classpath.

    
    // Helping IntelliJ understand a custom integrationTestCompile configuration. 
    idea {
        module {
            scopes.TEST.plus += [ configurations.integrationTestCompile ]
        }
    }

ZIPs As Dependencies

Yes, you can do this by faking an Ivy repository, treating the source of the download as though it were an Ivy repository:

repositories {
    mavenCentral()

    ivy {
        url 'https://artifacts.elastic.co/downloads/'
        layout 'pattern', {
            artifact '/[organisation]/[module]-[revision].[ext]'
        }
    }
}

Then you can do something like this in the dependencies block:

configurations {
    install
}

dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
    compile group: 'org.elasticsearch', name: 'elasticsearch', version: '1.7.2'
    ...
    install group: 'kibana', name: 'kibana', version: "$elasticVersion-linux-x86_64", ext: 'tar.gz' 
    install group: 'elasticsearch', name: 'elasticsearch', version: "$elasticVersion", ext: 'zip'
    install group: 'logstash', name: 'logstash', version: "$elasticVersion", ext: 'zip'
}

Then a simple task can deploy the ZIPs/TARs:

task install(type: Copy){
  onlyIf { outputDir != '' }
  into (outputDir) 
  from project.configurations.install.collect {File f->
  	if (f.name.contains('.tar')){
  	  tarTree(f)
  	} else {
  	  zipTree(f)
  	}
  }
  doLast {
     println 'Install finished...'
     report.execute()
  }
}

References

Notes

  1. https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html
    • A configuration can also include artifacts, but does not by default.
  2. http://zeroturnaround.com/rebellabs/the-correct-way-to-use-integration-tests-in-your-build-process/
  • See the section titled "Why integration tests should never run together with unit tests."