My colleague, Tom Wetjens, wrote a blog post Package-only dependencies in Maven. He showed a Maven solution when we want to include dependencies in the WAR file, which are not used in any other scopes. In this blog post we will see how we solve this in Gradle.

Suppose we use the SLF4J Logging API in our project. We use the API as a compile dependency, because our code uses this API. But in our test runtime we want to use the SLF4J Simple implementation of this API. And in our WAR file we want to include the Logback implementation of the API. The Logback dependency is only needed to be included in the WAR file and shouldn't exist in any other dependency configuration.

We first add the War plugin to our project. The war task uses the runtime dependency configuration to determine which files are added to the WEB-INF/lib directory in our WAR file. We add a new dependency configuration warLib that extends the runtime configuration in our project.

apply plugin: 'war'

repositories.jcenter()

configurations {
    // Create new dependency configuration
    // for dependencies to be added in
    // WAR file.
    warLib.extendsFrom runtime
}

dependencies {
    // API dependency for Slf4j.
    compile 'org.slf4j:slf4j-api:1.7.7'

    testCompile 'junit:junit:4.11'

    // Slf4j implementation used for tests.
    testRuntime 'org.slf4j:slf4j-simple:1.7.7'

    // Slf4j implementation to be packaged
    // in WAR file.
    warLib 'ch.qos.logback:logback-classic:1.1.2'
}

war {
    // Add warLib dependency configuration
    classpath configurations.warLib

    // We remove all duplicate files
    // with this assignment.
    // geFiles() method return a unique
    // set of File objects, removing
    // any duplicates from configurations
    // added by classpath() method.
    classpath = classpath.files
}

We can now run the build task and we get a WAR file with the following contents:

$ gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war
:assemble
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Total time: 6.18 secs
$ jar tvf build/libs/package-only-dep-example.war
     0 Fri Sep 19 05:59:54 CEST 2014 META-INF/
    25 Fri Sep 19 05:59:54 CEST 2014 META-INF/MANIFEST.MF
     0 Fri Sep 19 05:59:54 CEST 2014 WEB-INF/
     0 Fri Sep 19 05:59:54 CEST 2014 WEB-INF/lib/
 29257 Thu Sep 18 14:36:24 CEST 2014 WEB-INF/lib/slf4j-api-1.7.7.jar
270750 Thu Sep 18 14:36:24 CEST 2014 WEB-INF/lib/logback-classic-1.1.2.jar
427729 Thu Sep 18 14:36:26 CEST 2014 WEB-INF/lib/logback-core-1.1.2.jar
   115 Wed Sep 03 09:24:40 CEST 2014 WEB-INF/web.xml

Also when we run the dependencies task we can see how the implementations of the SLF4J API relate to the dependency configurations:

$ gradle dependencies
:dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

compile - Compile classpath for source set 'main'.
\--- org.slf4j:slf4j-api:1.7.7

default - Configuration for default artifacts.
\--- org.slf4j:slf4j-api:1.7.7

providedCompile - Additional compile classpath for libraries that should not be part of the WAR archive.
No dependencies

providedRuntime - Additional runtime classpath for libraries that should not be part of the WAR archive.
No dependencies

runtime - Runtime classpath for source set 'main'.
\--- org.slf4j:slf4j-api:1.7.7

testCompile - Compile classpath for source set 'test'.
+--- org.slf4j:slf4j-api:1.7.7
\--- junit:junit:4.11
     \--- org.hamcrest:hamcrest-core:1.3

testRuntime - Runtime classpath for source set 'test'.
+--- org.slf4j:slf4j-api:1.7.7
+--- junit:junit:4.11
|    \--- org.hamcrest:hamcrest-core:1.3
\--- org.slf4j:slf4j-simple:1.7.7
     \--- org.slf4j:slf4j-api:1.7.7

warLib
+--- org.slf4j:slf4j-api:1.7.7
\--- ch.qos.logback:logback-classic:1.1.2
     +--- ch.qos.logback:logback-core:1.1.2
     \--- org.slf4j:slf4j-api:1.7.6 -> 1.7.7

(\*) - dependencies omitted (listed previously)

BUILD SUCCESSFUL

Total time: 6.274 secs

Code written with Gradle 2.1.

Original article

shadow-left