The Shadow Knows Gradle
March 14, 2016 4 Comments
Someone recently complained on Twitter that the so-called Shadow plugin for Gradle, written by the inestimable John Engleman, no longer worked on Gradle 2.11 or 2.12. Commenters were quick to point out that the latest version (1.2.3) of the plugin did work. I thought I’d put together this quick blog post to demonstrate that it works fine.
As a overview, the Shadow plugin creates what’s called a “fat” jar, including all the dependencies, so you can deliver a runnable final product without installing anything other than Java.
I made a trivial Java library project using the Gradle Init plugin, using Spock testing (because I couldn’t help using Spock). Fortunately, this time I remembered to create a folder to hold it in ahead of time.
> md temp/shadow_demo > cd temp/shadow_demo > gradle init --type java-library --test-framework spock
That created a typical Java project, with folders src/main/java
and src/test/groovy
in it, along with a source class called Library.java
and a test called LibraryTest.groovy
.
(First minor complaint: the test case should be called LibrarySpec.groovy
. The compiler doesn’t require you to end your Spock tests with the word Spec
, but it’s a standard idiom by now. Plus, it helps me keep straight which of my tests are Spock tests and which are JUnit tests.)
I then opened the build.gradle
file in the root of the project in order to add the shadow plugin. Here’s how it looked when I started:
/* * This build file was auto generated by running the Gradle 'init' task * by 'kousen' at '3/14/16 5:20 PM' with Gradle 2.12 * * This generated file contains a sample Java project to get you started. * For more details take a look at the Java Quickstart chapter in the Gradle * user guide available at https://docs.gradle.org/2.12/userguide/tutorial_java_projects.html */ // Apply the java plugin to add support for Java apply plugin: 'java' // Apply the groovy plugin to also add support for Groovy (needed for Spock) apply plugin: 'groovy' // In this section you declare where to find the dependencies of your project repositories { // Use 'jcenter' for resolving your dependencies. // You can declare any Maven/Ivy/file repository here. jcenter() } // In this section you declare the dependencies for your production and test code dependencies { // The production code uses the SLF4J logging API at compile time compile 'org.slf4j:slf4j-api:1.7.18' // We use the latest groovy 2.x version for Spock testing compile 'org.codehaus.groovy:groovy-all:2.4.6' // Use the awesome Spock testing and specification framework even with Java testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' testCompile 'junit:junit:4.12' }
(Second minor complaint: the build file adds both the java
and groovy
plugins. That’s redundant. The groovy
plugin already includes all the needed java
functionality.
I removed the comments and cleaned up the build file a bit, adding the shadow plugin.
plugins { id 'groovy' id 'com.github.johnrengelman.shadow' version '1.2.3' } repositories { jcenter() } dependencies { compile 'org.slf4j:slf4j-api:1.7.18' compile 'org.codehaus.groovy:groovy-all:2.4.6' testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' testCompile 'junit:junit:4.12' }
I’m using the newer plugins
block syntax instead of the conventional `buildscript` approach. The page at the Gradle plugins site for the Shadow plugin (https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) shows the details. Note that the section at the bottom of that page labelled “About the new plugin mechanism…” gives additional details on the newer approach.
If you run the gradle tasks
command at this point, you’ll see a lot of output, some of which is relevant for this demo.
> gradle tasks :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Build tasks ----------- assemble - Assembles the outputs of this project. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. classes - Assembles main classes. clean - Deletes the build directory. jar - Assembles a jar archive containing the main classes. testClasses - Assembles test classes. Build Setup tasks ----------------- init - Initializes a new Gradle build. [incubating] wrapper - Generates Gradle wrapper files. [incubating] Documentation tasks ------------------- groovydoc - Generates Groovydoc API documentation for the main source code. javadoc - Generates Javadoc API documentation for the main source code. Help tasks ---------- buildEnvironment - Displays all buildscript dependencies declared in root project 'shadow_demo'. components - Displays the components produced by root project 'shadow_demo'. [incubating] dependencies - Displays all dependencies declared in root project 'shadow_demo'. dependencyInsight - Displays the insight into a specific dependency in root project 'shadow_demo'. help - Displays a help message. model - Displays the configuration model of root project 'shadow_demo'. [incubating] projects - Displays the sub-projects of root project 'shadow_demo'. properties - Displays the properties of root project 'shadow_demo'. tasks - Displays the tasks runnable from root project 'shadow_demo'. Shadow tasks ------------ knows - Do you know who knows? shadowJar - Create a combined JAR of project and runtime dependencies Verification tasks ------------------ check - Runs all checks. test - Runs the unit tests. Rules ----- Pattern: clean<TaskName>: Cleans the output files of a task. Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration. Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration. To see all tasks and more detail, run gradle tasks --all To see more detail about a task, run gradle help --task <task> BUILD SUCCESSFUL Total time: 9.466 secs
Note the existence of the shadowJar
task, which creates the fat jar.
My trivial app doesn’t have a Java class with a main
method in it, however, so creating a shadow jar wouldn’t actually accomplish much. Therefore, after renaming src/main/java
to src/main/groovy
, I added a tiny groovy script called src/main/groovy/demo.groovy
:
println 'What up, World?'
Then I added the application
plugin to the Gradle build and set the main class to my script, giving me the final form of the build file:
plugins { id 'groovy' id 'application' id 'com.github.johnrengelman.shadow' version '1.2.3' } mainClassName = 'demo' repositories { jcenter() } dependencies { compile 'org.slf4j:slf4j-api:1.7.18' compile 'org.codehaus.groovy:groovy-all:2.4.6' testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' testCompile 'junit:junit:4.12' }
The (built in) application
plugin adds the run
and shadowRun
tasks to Gradle. Executing those is easy enough, too:
> gradle run :compileJava UP-TO-DATE :compileGroovy :processResources UP-TO-DATE :classes :run What up, World? BUILD SUCCESSFUL Total time: 2.688 secs > gradle runShadow :compileJava UP-TO-DATE :compileGroovy UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :shadowJar :startShadowScripts :installShadowApp :runShadow What up, World? BUILD SUCCESSFUL Total time: 2.503 secs
The first runs the app without the shadow jar, and the second uses it. Everything looks good. The GitHub page for the shadow plugin describes lots of ways to customize it, but I didn’t need any of that for my demo.
Of course, there’s no way you can talk about the shadow plugin without running the additional task it adds, namely knows
:
> gradle knows :knows No, The Shadow Knows.... . .MMMMMO .M .MMMMMMMMM. MMMM. .MMMMMMMMMMMMMMM. .MMMMMMMMMMMMM .MMMMMMMMMMMM .+MMMMMMMMM,ZMMM. ...7MM8D8MM.ZMMMMM. .. MMZ..MZZNMMMMM .... MMMMMMMZZZ.MMMMMMOOOOOO.. ... 7MMMMMMMMMZZZMIMMMMOOOOOMMMM.. .. .~. .MMMMMOMZZZMZMMOOOOOMMMM MM. .MMMMM ..MMM.7DOMOMOOOOOOOMM MMMMM Z .. MMMMMMM.. . ...MMMMMMMMMOOOOOOMMMMMMMMMM . .MMMMMMM. .MMMMM MMMMMOMOMMMMMMMMMMM MMMMMMMMM .MMM.MMMMMMMMMMMOMMMMMMMMMMM .MMMMMMMM $MMMM MMMMMMMMMMMMMMMMMM MMM MMMMMMNMMMMMMMM M.MMMMMM.MMMMMMMM MMMMMM ..MMMMMMMMMMMMMMMMMMMMMMMMMMMM.MNMMMMMMM . ...MMMMMMMMMMM MMMMMMMMMMMMM.MMMMMMMM. MMMMMMMMMM.MMMMMMMMMMMMMDMMMMMMMM. ..MMMMMMMMMMMMMMMMMMM M,MMMMMMMMMMMMMMMZMMMMM +D , .:DMM.M. MMMMMMM.MMMMMMMMMMMMMMI:MMMMM :MMO . MMMMMMMMMMMMMMMMMMMM.MMMMM8 NMMMN ..MMMMMMMMMMMMMMMMMMMMM MMMMN. .MMMMMMMMMMMMMMMM. MMM7 , . =. MMMMMMMMMMMM.$MM M . MM7 MMMMMMMMM=MI:M8 . MNOM M MMMMMMMMMM. . MMMMMM . +MMBUILD SUCCESSFUL Total time: 1.043 secs
That, of course, is worth the whole demo. 🙂
Of all the places i searched for an answer to my problem, I was shown the light here. The blog was very informative. Thank you so much sir.
where is the Def of task “shadowJar” here ?
I find for a simple groovy script I have to include src.main.groovy into the mainClassName, the compiled classes aren’t copied into the root. strange
This article was very helpful towards building an executable JAR from a single Groovy script that I had. When I did execute the JAR using ‘java -jar …’ I got no main manifest attribute, how do I set the shadow plugin to include that attribute?