Gradle Ear plugin should not copy resources into .ear root - gradle

I have the following folder structure of my gradle module:
src
main
application
META-INF
application.xml
was.policy
was.webmodule
java
resources
image.bmp
logback.xml
... other files, properties
webapp
My goal is to build an ear archive, which will contain only Tclient.war and META-INF. However, gradle copies all resource files to the ear root.
Gradle documentation about Ear Plugin says that:
The default behavior of the Ear task is to copy the content of
src/main/application to the root of the archive. If your application
directory doesn’t contain a META-INF/application.xml deployment
descriptor then one will be generated for you.
So, It's not really clear why it puts resources into the root. Maybe, it work's like the org.gradle.api.tasks.bundling.Jar task and I should override this behaviour in some way?
Here is my partially build.gradle file:
jar {
description 'Creates tclient.jar'
archiveBaseName = 'tclient'
destinationDirectory = file('src/main/webapp/WEB-INF/lib')
from sourceSets.main.output
include '**/*'
include '**/*.properties'
include '**/*.cmd'
}
ear{
description = "Ear archive for WebSphere server"
archiveBaseName = 'Tclient'
appDirName('src/main/application')
// workaround to exclude classes from ear root
rootSpec.exclude('**/de/**')
rootSpec.exclude('**/org/**')
}
war {
archiveFileName = 'Tclient.war'
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
webInf {
from configurations.natives into 'lib/bin'
}
// do not put compiled classes inside WEB-INF/classes
rootSpec.exclude('**/de/**')
rootSpec.exclude('**/org/**')
rootSpec.exclude('urlrewrite*.dtd')
from(jar) {
into 'WEB-INF/lib'
}
}
dependencies {
// Place .war inside .ear root
deploy files(war)
....
}

The possible workaround is to exclude redundant resources from the rootspec:
ear{
description = "Ear archive for WebSphere server"
archiveBaseName = 'Tclient'
appDirName('src/main/application')
// workaround to exclude classes from ear root
rootSpec.exclude('**/de/**')
rootSpec.exclude('**/org/**')
// workaround
rootSpec.exclude('*.properties')
rootSpec.exclude('*.xml')
rootSpec.exclude('*.dtd')
rootSpec.exclude('*.bmp')
}
However, I'm finding this workaround a little bit ugly and it's a dirty hack.

Related

Gradle - how do I exclude a resource from the executable jar?

I have a "production" logback configuration file logback.xml under src/main/resources... but that directory also contains the "testing" logback configuration file logback-test.xml (which logback looks for first).
When creating an executable jar I want to delete the "testing" xml file.
I tried this
jar {
manifest {
// PS this is the correct line for Shadow Plugin...
attributes 'Class-Path': '/libs/a.jar'
attributes 'Main-Class': 'core.MyMainClass'
}
exclude("**/resources/*test*")
}
and I tried this
jar {
manifest {
attributes 'Class-Path': '/libs/a.jar'
attributes 'Main-Class': 'core.MyMainClass'
}
doLast {
exclude("**/resources/*test*")
}
}
... what am I doing wrong?
later
I find here that I was probably making life difficult for myself in putting these xmls under /src/main/resources ... so I created a new directory under src, /logback, and put the files in there instead. I added this to the classpath (as logback says that's where it looks for these files) by doing this:
test {
classpath += files( 'src/logback' )
}
Interestingly, as well as meaning that logging during testing happens OK, this is enough to get the resulting executable jar to use logback OK when run.
Unfortunately, configuring the "shadowJar" task like this
shadowJar {
baseName = 'DocumentIndexer'
classifier = null
version = project.version
exclude("logback/*test*")
}
or configuring "jar" task like this:
jar {
manifest {
attributes 'Class-Path': '/libs/a.jar'
attributes 'Main-Class': 'core.ConsoleIOHandler'
}
exclude("logback/*test*")
}
... just refuses to work: the file logback-test.xml is still there in the jar.
I got the answer from the forums at gradle.org.
The basic answer is that the "test" logback config file should go under src/test/resources and the "production" config file should go under src/main/resources. This way the former will be excluded from the jar.
The answerer also said the "resources" is one of the roots from which relative paths are specified.
configurations {
provided
compile.extendsFrom provided
}
dependencies {
provided 'WHATEVER' // Packages you don't need to add to jar
provided 'Other WHATEVER' // Packages you don't need to add to jar
shadow 'OTHER' // Packages you need to add to jar
shadow 'Another OTHER' // Packages you need to add to jar
}
shadowJar {
configurations = [project.configurations.shadow] // ***
}
as mentioned here
line *** is the way to tell shadow what dependencies to include in jar

Copy resources from a dependency jar or war file in Gradle

We have multiple war files in different projects, say A and B, sharing common resources like images. The common resources are placed in a war module in a separate project E. And this war file is added as dependency in all the war modules in projects A and B. Currently we are using maven resource plugins to copy these common resources to the root of A and B modules.
How can we do the same action using Gradle?
I am trying this with below configuration, but the files are not copied in to the generated war file. They are only copied to build/libs folder.
configurations {
commonWebResources
}
task extractApi(type: Copy) {
print 'File : ' + configurations.commonWebResources.singleFile
from zipTree(configurations.commonWebResources.singleFile)
into file("${project.buildDir}/libs/")
}
compileJava.dependsOn(extractApi)
When I have common resources like images that I want a build task to generate, I do the following:
sourceSets {
main {
resources.srcDirs += "src-gen/main/resources"
}
}
// (include your configurations block and extractApi task here)
processResources.dependsOn extractApi
task cleanGen << {
file('src-gen/main/resources').delete()
}
clean.dependsOn cleanGen

Gradle ShadowJar with Other SourceSet Resources

I am using the ShadowJar Gradle plugin to build a Jar containing all of the source files in the src/main/java directory and other Jar files in a lib directory and it is working fine. What I need is another ShadowJar task, a devShadowJar task, that will instead of pulling in a JSON file in the src/main/resources folder, it will pull in a JSON file in the src/dev/resources folder.
I added this to the build.gradle file to define the dev source set:
sourceSets {
dev
}
But now I am not sure how to create a devShadowJar task to use the dev JSON resource instead of the JSON resource file located in src/main/resources.
try playing around this:
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
task devShadowJar(type: ShadowJar) {
zip64 true
from './build/classes/java/main'
from project.configurations.compile
from './src/dev/resources' // or wherever the resources and up under ./build
}

Unable to run repacked spring boot jar caused by "Unable to open nested entry"

I am setting up a build pipeline for a spring boot project.
It has three stages so far:
build: compile-->unit test-->archive the jar
deploy acceptance test: repack the jar for acc environment (replacing datasource.properties etc)
deploy uat test: repack the jar for uat environment (replacing datasource.properties etc)
I don't want to build the jar from scratch for different environments as it wastes time and potentially has risk of building inconsistent artifacts.
For traditional war project, I just extract the war, replace the config files and repack. But this time with spring boot, somehow it does not work. When I run the repacked jar, it reports
java.lang.IllegalStateException: Unable to open nested entry 'lib/antlr-2.7.7.jar'. It has been compressed and nested jar files must be stored without compression. Please check the mechanism used to create your executable jar file
at org.springframework.boot.loader.jar.JarFile.createJarFileFromFileEntry(JarFile.java:378)
at org.springframework.boot.loader.jar.JarFile.createJarFileFromEntry(JarFile.java:355)
at org.springframework.boot.loader.jar.JarFile.getNestedJarFile(JarFile.java:341)
at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchive(JarFileArchive.java:108)
at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchives(JarFileArchive.java:92)
at org.springframework.boot.loader.ExecutableArchiveLauncher.getClassPathArchives(ExecutableArchiveLauncher.java:68)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:60)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:45)
I extracted the origin jar and the repacked jar and don't find differences with lib folder.
task extractArtifact() {
doLast {
def outputDirName = "${buildDir}/tmp/under_config"
def outputDir = file(outputDirName)
assert outputDir.deleteDir() // cleanup workspace
def zipFile = file("${buildDir}/libs/${getArtifactName()}")
copy {
from zipTree(zipFile)
into outputDir
}
copy {
from file("${buildDir}/env")
into file("${buildDir}/tmp/under_config")
}
}
}
task repackConfiguredArtifact(type: Zip, dependsOn: extractArtifact) {
archiveName = "${getArtifactName()}"
destinationDir = file("${buildDir}/libs/${getEnv()}")
from file("${buildDir}/tmp/under_config")
}
Does anyone have an idea?
Or how do you guys config the jar for different environment (without re-compile the binary).
You shoud add -0 to store only; use no ZIP compression
$jar -cvf0m yourproject.jar META-INF/MANIFEST.MF .
There is another soulution:
Set the active Spring profiles
$java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar
You can use application-${profile}.properties to specify profile-specific values.
I have a solution after looking up the spring-boot reference.
turn default spring boot repackage off since I need to repack it anyway.
extract the traditional jar and copy the config files
Use jar type task to repack it
Use BootRepackage type task to assemble a spring-boot jar.
here is the code:
bootRepackage {
enabled = false
}
task extractArtifact() {
doLast {
def outputDirName = "${buildDir}/tmp/under_config"
def outputDir = file(outputDirName)
assert outputDir.deleteDir() // cleanup workspace
def zipFile = file("${buildDir}/libs/${getArtifactName()}")
copy {
from zipTree(zipFile)
into outputDir
}
copy {
from file("${buildDir}/env")
into file("${buildDir}/tmp/under_config")
}
assert zipFile.delete()
}
}
task clientJar(type: Jar, dependsOn: extractArtifact) {
archiveName = "${getArtifactName()}"
from file("${buildDir}/tmp/under_config")
}
task repackConfiguredArtifact(type: BootRepackage, dependsOn: clientJar) {
withJarTask = clientJar
}
I found that if you had a directory called 'resources/lib' the spring-boot executable JAR would assume the contents were zipped and throw the above exception. Renaming to 'resources/static' worked for me.

Gradle dependency destination on non-jar config file

I can create a dependency to something other than a jar file like this:
dependencies {
compile files("../other-project/config.txt")
}
The above works fine, except that config.txt ends up in the WEB-INF/lib folder of my war file. Instead I need it to be in WEB-INF/classes in the war file, and in src/main/resources for jettyRun.
How can I control where the dependency ends up? Or am I going about this the wrong way?
I can also solve this with a copy task, but this really is a dependency in that I don't need the file updated unless it changes. An unconditional copy would work, but I'd rather do this the right way.
The war task (as configured by the war plugin) puts dependencies into WEB-INF/lib, the web project's own code/resources into WEB-INF/classes, and web app content (which by default goes into src/main/webapp) into WEB-INF. Other content can be added by explicitly configuring the war task. For example:
war {
into("WEB-INF/classes") {
from "../other-project/config.txt"
}
}
One way to make this work with embedded Jetty (though maybe not the most convenient during development) is to use jettyRunWar instead of jettyRun. Another solution that comes to mind, particularly if the content to be added resides in its own directory, is to declare that directory as an additional resource directory of the web project (sourceSets.main.resources.srcDir "../other-project/someResourceDir"). This is in fact an alternative to configuring the war task. If the web project already has a dependency on the other project, you could instead configure an additional resource directory for that project.
Let's say you have configured a multi-project build with the following directory and file structure:
/combined-war
/main-project
/src
/webapp
/WEB-INF
web.xml
build.gradle
/other-project
/resources
/WEB-INF
/classes
config.txt
build.gradle
build.gradle
In order to allow jettyRun to combine the contents of the webapp directory from main-project with the contents of the resources directory in other-project you need to add a workaround to your build.gradle of main-project (I've adapted the one posted by the user siasia on gist).
Adding the same directory content to the war file is quite simple and is documented in the Gradle User Guide and and the DSL reference.
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'jetty'
import org.gradle.api.plugins.jetty.internal.JettyPluginWebAppContext
def newResourceCollection(File... resources) {
def script = '''
import org.mortbay.resource.ResourceCollection
new ResourceCollection(resources)
'''
def shell = new GroovyShell(JettyPluginWebAppContext.class.classLoader)
shell.setProperty("resources", resources as String[])
return shell.evaluate(script)
}
jettyRun.doFirst {
jettyRun.webAppConfig = new JettyPluginWebAppContext()
jettyRun.webAppConfig.baseResource = newResourceCollection(
// list the folders that should be combined
file(webAppDirName),
file("${project(':other-project').projectDir}/resources")
)
}
war {
from("${project(':other-project').projectDir}/resources")
}
Whenever you execute gradle jettyRun a new ResourceCollection is created that combines the given directories. Per default Jetty locks (at least on Windows) all the files it's serving. So, in case you want to edit those files while Jetty is running take a look at the following solutions.
Update
Since other-project in this case is not another Gradle project the two tasks in build.gradle should look like that:
jettyRun.doFirst {
jettyRun.webAppConfig = new JettyPluginWebAppContext()
jettyRun.webAppConfig.baseResource = newResourceCollection(
file(webAppDirName),
file("$projectDir/../other-project/resources")
)
}
war {
from("$projectDir/../other-project/resources")
}
I'm not aware of any solution that adds only one file (e.g. config.txt). You'll always have to add a complete directory.
As I mentioned above, it's simple enough to do an unconditional copy that solves the problem. Again, not the question I originally asked. But here's my solution that works for both war and jettyRun tasks:
processResources.doFirst {
copy {
from '../other-project/config.txt'
into 'src/main/resources'
}
}

Resources