An interesting note from Madis Pink in the ZeroTurnaround RebelLabs blog. If someone wakes you up in the middle of the night and asks: “what feature in Gradle should everyone know?” - with confidence answer that it is buildSrc
. This is a special magic Gradle project inside your repository, available to all build.gradle
files as a library.
The approach described below allows you to write code in the JVM language that is convenient for you, and use the result directly in your build scripts. As a bonus, you can cover unit tests with particularly tricky moments in these scripts. Welcome under the cut!
Connect this feature is simple. Create a directory called buildSrc
in the project root (in the same directory where settings.gradle
):
mkdir buildSrc
Both the java
plugin and the groovy
plugin will be applied to this project at the same time. Therefore, further we simply make the standard directory src/main/java
and begin to add code there.
mkdir -p buildSrc/src/main/java
Now you can synchronize the project with the IDE and create a new Java class in the newly- buildSrc
project:
package myapp.gradle; public class Fun { public static void sayHello() { System.out.println("Hello from buildSrc!"); } }
The last step is left: call the Fun.sayHello
method from some build.gradle
file. Let's do a simple hello
task in the root project and look at the exhaust:
import myapp.gradle.Fun task hello { doLast { Fun.sayHello() } } $ ./gradlew hello > Task :hello Hello from buildSrc! BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
That's all. The sayHello
method is a regular Java method. Because build.gradle
works like a JVM application, you can call it from Groovy like any other Java method.
Reuse of the code is good, but all this garbage from doLast {}
when creating a new task looks disgusting. With the help of buildSrc
we will be able to throw garbage away, creating a special task class instead. Your Gradle Task is just a Java class that:
org.gradle.api.DefaultTask
@TaskAction
annotation. This code is executed immediately after the launch of our task.Let's refactor the Fun.sayHello
method to a separate task:
package myapp.gradle; import org.gradle.api.DefaultTask; import org.gradle.api.tasks.TaskAction; public class HelloTask extends DefaultTask { @TaskAction public void run() { System.out.println("Hello from task " + getPath() + "!"); } }
It is easier to use this task from the Gradle scripts:
import myapp.gradle.HelloTask task hello(type: HelloTask) $ ./gradlew hello > Task :hello Hello from task :hello! BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
In a large project, you have to define dozens of tasks that are embedded in an intricate link graph. In this case, it makes sense to create your own plugin. This plugin should create all the necessary tasks and link them together.
package myapp.gradle; import org.gradle.api.Plugin; import org.gradle.api.Project; public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) { project.getTasks().create("hello", HelloTask.class); } }
Now we can not use the task directly, but connect this special plugin directly to the build file:
apply plugin: myapp.gradle.MyPlugin
And yes, it works!
$ ./gradlew hello > Task :hello Hello from task :hello! BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
We now have a hand-written plugin that can be used in any of the subprojects. Of course, the above example of printing text on the screen is an unimaginably small part of what can be arranged in your project with the help of handwritten plug-ins. If you want to understand the plugins deeper, I highly recommend these reports with the Gradle Summit 2017 :
buildSrc
In the afternoon, the author of this note is a developer in the JRebel for Android project. This is a developer tool that consists of an IDE plugin, a Gradle plugin, an Android application, and a regular Java SE application. To collect all these artifacts, there is a huge Gradle project consisting of 90+ subprojects. All these subprojects have in many ways similar dependencies, for example, commons-io
and slf4j-api
. If you have to collect org.slf4j:slf4j-api:1.7.25
time after time, again and again, it quickly turns into a nudyatinu, according to the type of regular ordering at home. The same is about the game of guessing the correct version of these dependencies.
Usually, it is suggested to use the ext {}
block directly in the project root to manage them. An example is in this response to StackOverflow . The flip side of this approach is that the IDE will fall off auto-completion and code navigation.
We went another way - we defined dependencies as string constants in the Deps.java
file inside buildSrc
. Auto-completion and navigation work with this approach in IDE! Let's see how it looks on the example of a small Android project:
package myapp.gradle; public class Deps { public static final String androidPlugin = "com.android.tools.build:gradle:3.0.0-beta6"; public static final String kotlinVersion = "1.1.50"; public static final String kotlinPlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:" + kotlinVersion; public static final String kotlinRuntime = "org.jetbrains.kotlin:kotlin-stdlib-jre7:" + kotlinVersion; public static final String appCompat = "com.android.support:appcompat-v7:26.1.0"; public static final String constraintLayout = "com.android.support.constraint:constraint-layout:1.0.2"; public static final String junit = "junit:junit:4.12"; }
Now you can refer to them inside any build.gradle
files like this:
When you put the build configuration in buildSrc
, it feels ... a bit wrong or something. But the benefits of this approach far outweigh.
Will you use trick with buildSrc
? Write in the comments! In addition, the author can be obtained via Twitter @madisp and ask all the sore uncomfortable questions. All examples from this post are available on GitHub .
The post is published with the permission of the company ZeroTurnaround and the author - Madis Pink. ZeroTurnaround employees are often present in the conference program committee of the JUG.ru Group, act as speakers at conferences and often come just as guests. We also invite Habr's readers to participate in the JPoint, which will be held on April 6-7, 2018 .
Source: https://habr.com/ru/post/342914/