I am very new Gradle. I just started looking at it a couple of days ago, and I really like it, so I am trying to convert my project to Gradle.
I just ran into my first difficult problem. I can't figure out how to prevent the 'war' plugin from duplicating a lot of the transient dependencies already found in my EAR. For example, let's say that I want to use org.springframework:spring-web
in my WAR. This jar has a lot of transient dependencies, which all get packaged in WEB-INF/lib
. This is a problem, b/c all of these dependencies are already in APP-INF/lib
of my EAR, b/c there are other modules that use them.
What I'd like to do is to tell Gradle that I wish to compile the WAR code with all the dependencies as per usual, but when it comes to packaging the WAR, I'd like to have some control over what goes into WEB-INF/lib
. As far as I can tell, this functionality is not there. Or am I missing something?
I have solved the problem with a hack for now, but I feel that there must be an easier way to do this. I am hoping that someone with more experience can point out where I went wrong here.
This is what I have so far:
apply plugin: 'war'
configurations {
// Use this config to indicate that a module should be packaged inside the WAR w/o its transitive dependencies
//
packageWithoutDependencies
// Use this one when you want to package this module and all of its dependencies in the WAR
//
packageWithDependencies
compile.extendsFrom packageWithoutDependencies
compile.extendsFrom packageWithDependencies
}
dependencies {
packageWithDependencies "org.richfaces:richfaces-bom:$richFacesVersion"
packageWithDependencies "org.richfaces.ui:richfaces-components-ui:$richFacesVersion"
packageWithDependencies "org.richfaces.core:richfaces-core-impl:$richFacesVersion"
packageWithoutDependencies "org.springframework:spring-web:$springVersion"
packageWithoutDependencies "org.springframework.security:spring-security-config:$springSecurityVersion"
packageWithoutDependencies "org.springframework.security:spring-security-taglibs:$springSecurityVersion"
packageWithoutDependencies "org.springframework.security:spring-security-web:$springSecurityVersion"
}
// The approach here is to directly modify the classpath used by the 'war' task. This is necessary
// b/c we want to compile with all the dependencies resolved as per usual, yet when we deploy,
// we only want the jars that belong in the WAR, with most transitive dependencies for those already
// deployed as 3rd party jars in the EAR.
//
task modifyWarClasspath << {
// Get the names of all non-transitive modules we are packaging
//
println " ========= WAR Modules w/o dependencies ========= "
def firstLevelDeps = configurations.packageWithoutDependencies.resolvedConfiguration.firstLevelModuleDependencies
def nonTransientDepNames = firstLevelDeps.collect { it.moduleName }
println nonTransientDepNames
// Get the names of all direct file includes we are packaging
//
println "\n ========= WAR Files w/o dependencies ========= "
def directFileIncludes = configurations.packageWithoutDependencies
.getDependencies().findAll{it.hasProperty('source')}
.source.files*.name.flatten()
println directFileIncludes
// Get the names of all transitive modules we a re packaging
//
println "\n ========= WAR Modules with dependencies ========= "
def recursiveDeps = configurations.packageWithDependencies.resolvedConfiguration
.firstLevelModuleDependencies*.allModuleArtifacts.flatten()
def transientDepNames = recursiveDeps.collect { it.name }
println transientDepNames
// Look through the WAR classpath (runtime - providedCompile), and keep only those
// files that match a module name on either of the 3 lists we made above.
//
println "\n ========= Packaging jars in the WAR ========= "
war.classpath = war.classpath.findAll {file ->
((nonTransientDepNames + transientDepNames + directFileIncludes + 'main').find {directDepName ->
file.name.startsWith(directDepName)
} != null)
}
war.classpath*.name.sort().each { println it }
println "\n\n"
}
// Enable 'packageWithDependencies' and 'packageWithoutDependencies' configs
war {
dependsOn modifyWarClasspath
}
I have a nice simple solution to this now. The key was to make use of a 'provided' scope, and then use it in my WAR dependencies to reference my EAR dependencies. That way the entire WAR classpath has the classpath reachable from the EAR dependencies subtracted from it, so only the web dependencies get packaged in the WAR. Nice and simple.
Here's how:
configurations {
provided {
dependencies.all { dep ->
configurations.default.exclude(group: dep.group, module: dep.name)
}
}
compile.extendsFrom provided
}
And then in your WAR project, you can do:
dependencies {
provided project(':DFIPConfig')
provided project(':DFIPSAPBatch')
provided project(':DFIPDomain')
// etc...
compile project(':DryDockProjects:DryDockWebProjects:CommonWebWCAGJar')
compile project(':DryDockProjects:DryDockWebProjects:CommonParticipantWeb')
// etc...
// Other WAR dependencies as per usual
}