I have a large project that I'm helping migrate to Grails 3 and Gradle from Grails 2.5. In the short term, we are moving much of the business code into framework-agnostic gradle modules and then importing them to grails 2.5, eventually planning to move to a fully grails-3 build.
The migration approach plan is to:
Create gradle modules and move most of the code into these.
Have grails execute the gradle build
Gradle puts the result of each module's build into the /lib directory
Grails finds them on the classpath and uses them
I don't specify the dependencies in BuildConfig.groovy
, I simply use the classes in the grails code and everything turns out to be there by the compile phase of app startup.
This works fantastically locally. I can start with a fresh checkout of the project from source control, execute grails run-app --refresh-dependencies
(or test-app), and see the result of my build. I've found that
--refresh-dependenciesis a magic grails voodoo that tells grails that it might find some classes it needs in its
lib/` directory.
This does not work on Jenkins. I am using the Jenkins Gradle plugin. Jenkins can't resolve my (single, POC) class:
[groovyc] /mnt/jenkins/workspace/Java8/Steps/MyApp-Grails-2.5.2-Java8-Pull-Request-Unit-Tests
/src/groovy/com/myapp/package/SomeServiceWithADependency.groovy:
10: unable to resolve class com.myapp.module.util.MyUtil
[groovyc] @ line 10, column 1.
[groovyc] com.myapp.module.util.MyUtil
[groovyc] ^
Here's what I've tried:
I've checked the box for Jenkins to add the --refresh-dependencies
flag to all build cycle commands
There are 3 other jars in the lib/
directory, checked into git source control. Grails finds those without a problem.
I've added a print statement to the end of the gradle module build to tell me what's in the /lib
directory, and I both the dependencies gradle is generating and the ones checked into source control. They seem to be there and the path matches the expected workspace path.
Any ideas? Hoping someone on planet earth has dealt with a similar problem
We solved the issue. The build was working locally because of gradle's caches and was failing remotely because Jenkins was working with a fresh repository.
Given the requrirements of starting with a clean pull of the git repo, executing a single grails command that would build the gradle modules and pull in the dependencies, our solution was to use the gradlew
shell script to build gradle first, failing if the gradle build failed:
(exec ./modules/gradlew -p modules build jar publishToMavenLocal); gradlew_return_code=$?
if [ "$gradlew_return_code" -eq "0" ]; then
echo "Gradle build task success!"
else
echo "Gradle build task failed with return code $gradlew_return_code; aborting."
exit -1
fi
Downsides are:
All developers working on the project must use ./grailsw
(instead of grails
) to execute any grails tasks, and all jenkins builds have accordingly been modified.
Any transitive dependencies inside any module must also be added to BuildConfig.groovy
so that grails puts them on the classpath at runtime. Failure to add a dependency in BuildConfig.groovy
will not throw a compile-time error but will blow up with no class def at runtime.
However the benefits of a modularized build, especially the discipline it is already bringing to code organization and the comforts of knowing a big part of our application code is framework-agnostic, are well worth these inconveniences.
Hope this helps someone else out there.