Greetings,
I am in the process of converting a set of Ant build scripts to Gradle, and have run into an issue creating a WAR file – In this case, I need to first include/compile a Java class file that has been generated by another Java program.
This is a multi-project build for a GWT application. The Java source code generator exists in a different project that the WAR project is dependent upon. However, the nature of the code generator is such that it needs access to both the classpath of the project it is contained in as well as the classpath of the WAR project.
For the morbidly curious, the code generator uses Java reflection to dynamically create a list of all other Java classes in our project(s) that implement Serializable. That list of classes is then written out to a new Java class which looks like…
public class GenericRemoteServicesHandlerImpl implements GenericRemoteServicesHandler {
private static final List<Class<?>> handlers = new ArrayList<Class<?>>();
static {
handlers.add(com.acme.gwt.client.bean.BooleanFieldValue.class);
handlers.add(com.acme.gwt.client.bean.BooleanOrNull.class);
handlers.add(com.acme.gwt.client.bean.CacheStatus.class);
handlers.add(com.acme.gwt.client.bean.ContentSearch.class);
//... about a thousand more adds; that's why we generate this "handler" class. ;)
The list of serializeable classes is obtained from both our “core” project as well as the WAR project where this class needs to be compiled and included in the archive.
The build script for our “core” project (which includes our source code generator)…
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'maven-publish'
apply from: "$rootProject.projectDir/eclipse-gwt-config.gradle"
sourceSets {
main {
java {
srcDirs = ['src', 'src-defdb', 'test']
}
resources {
srcDirs = ['testdata']
}
}
}
dependencies {
compile project(':AcmeGwt')
compile project(':AcmeDefaultDB')
compile 'com.google.zxing:core:1.7'
//... other compile/runtime dependencies omitted for brevity...
}
//JAR task and publishing closures are actually defined in our
//root project build script; I'd be happy to provide that as well
//if necessary.
Build script for our WAR project…
apply plugin: 'war'
apply plugin: 'maven'
apply plugin: 'maven-publish'
apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin'
apply from: "$rootProject.projectDir/eclipse-gwt-config.gradle"
sourceSets {
main {
java {
srcDirs = ['src', 'test']
}
}
genRpcSrc {
java {
srcDirs = ['../AcmeCore/src']
include '**/GenericRemoteServicesJsonHandlerBuilder.java'
}
}
}
dependencies {
compile project(':AcmeCore')
compile 'com.jcraft:jsch:0.1.53'
//... other compile/runtime dependencies omitted for brevity...
}
task genRmtHandlerCpJar(type: Jar) {
dependsOn configurations.runtime
appendix = 'launcher'
doFirst {
manifest {
attributes "Class-Path": configurations.runtime.files.collect { File file-> file.name }.join(" ")
}
}
}
task genSource(type: JavaExec, dependsOn: genRmtHandlerCpJar) {
classpath = files(genRmtHandlerCpJar.archivePath)
main = 'com.acme.server.service.GenericRemoteServicesJsonHandlerBuilder'
args "/${buildDir}/generated-sources/GenericRemoteServicesHandlerImpl.java"
args 'false'
doFirst {
(new File("/${buildDir}/generated-sources")).mkdirs()
}
}
/*compileJava {
dependsOn genSource
}*/
war {
from 'war' //i.e., additional files from the 'war' subdirectory
//all other configuration defined in the root project
}
My current problem is just getting everything that’s needed on the classpath for the JavaExec task…
Error: Could not find or load main class com.acme.server.service.GenericRemoteServicesJsonHandlerBuilder.
Initially I was dealing with a classpath-too-long for Windows error. I worked around that by creating a “launcher” or classpath-only JAR. However, I still need to get our core project JAR (where our code generator lives) along with any additional dependencies needed by the generator itself (e.g., slf4j and reflection utilities). Also needed on the JavaExec classpath is the WAR project’s classpath – again, when the code generator executes, it needs both projects in order to correctly find all serializeable classes.
Finally, after the code generator runs, the resulting implementation class still needs to be compiled itself, so it can be included in the WAR archive.
I’m still relatively new to Gradle, and I’ve spent several days trying to figure this latest issue out. I know exactly what I want to do, but I still haven’t fully wrapped my brain around all of the magic that Gradle is doing under the covers.
I also tried creating a separate project for this particular task, but that only worked for the core project – I couldn’t figure out how to “look-ahead” so to speak into the separate WAR projects. (I’m just using one example by the way; we actually have 4 different WAR projects with customizations for different clients/customers; so I need a repeatable solution.) Once I can get this solved, I’d like to pull the code out into a separate Gradle script that I can then just apply to each WAR project. My other thought is that perhaps I need to create an actual plugin for something like this, but I haven’t really found any good, non-trivial tutorials on how to do that – at least, nothing that is similar to what I’m trying to retrieve.
Any assistance will be greatly appreciated.
Thank you!
Lee