Gradle Tips #2-syntax,
Gradle Tips #2-syntax
In my first blog, I explained the different stages of tasks and tasks in the construction process. After writing this article, I realized that I should give a more detailed description of Gradle. It is important to understand the syntax, so that we will not be confused when encountering complicated build scripts. This article will explain some syntactic things.
Syntax
Gradle scripts are written in Groovy. Groovy syntax is a bit like Java. I hope you can accept it.
If you are familiar with Groovy, skip this section.
There is a very important concept in Groovy that you need to understand-Closure (Closure)
Closures
Closure is the key to understanding Gradle. Closure is a separate code block. It can receive parameters, return values, or assign values to variables. Similar to the Callable interface in Java and Future, it is also like a function pointer. You can understand it easily...
The key is that this code will be executed when you call it, rather than when you create it. Let's look at a Closure example:
def myClosure = { println 'Hello world!' }//execute our closuremyClosure()#output: Hello world!
The following is a Closure of the receiving parameter:
def myClosure = {String str -> println str }//execute our closuremyClosure('Hello world!')#output: Hello world!
If Closure only receives one parameter, you can use it to reference this parameter:
def myClosure = {println it }//execute our closuremyClosure('Hello world!')#output: Hello world!
Closure that receives multiple parameters:
def myClosure = {String str, int num -> println "$str : $num" }//execute our closuremyClosure('my string', 21)#output: my string : 21
In addition, the parameter type is optional. The preceding example can be abbreviated as follows:
def myClosure = {str, num -> println "$str : $num" }//execute our closuremyClosure('my string', 21)#output: my string : 21
The cool thing is that the variables in the current context can be used in Closure. By default, the current context is the class where closure was created:
def myVar = 'Hello World!'def myClosure = {println myVar}myClosure()#output: Hello world!
Another cool point is that the context of closure can be changed through Closure # setDelegate (). This feature is very useful:
def myClosure = {println myVar} //I'm referencing myVar from MyClass classMyClass m = new MyClass()myClosure.setDelegate(m)myClosure()class MyClass { def myVar = 'Hello from MyClass!'}#output: Hello from MyClass!
As you can see in the lock, myVar does not exist when creating closure. There is no problem because myVar exists in the context of closure when we execute closure. In this example. Because I changed its context to m before executing closure, myVar exists.
Pass closure as a parameter
The benefit of closure is that it can be passed to different methods, which can help us decouple the execution logic. The previous example shows how to pass closure to a class instance. The following describes how to receive closure as a parameter:
1. Only one parameter is received, and the parameter is the closure method: myMethod (myClosure)
2. If the method only receives one parameter, the brackets can be omitted: myMethod myClosure
3. You can use the inline closure: myMethod {println 'Hello world '}
4. Method for receiving two parameters: myMethod (arg1, myClosure)
5. Like 4, the singular closure is inline: myMethod (arg1, {println 'Hello world '})
6. If the last parameter is closure, it can be taken out from parentheses: myMethod (arg1) {println 'Hello world '}
Here I just want to remind you whether the 3 and 6 statements seem familiar to you?
Gradle
Now that we know the basic syntax, how can we use it in the Gradle script? Let's take a look at the following example:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' }}allprojects { repositories { jcenter() }}
Having understood the Groovy syntax, is the above example quite understandable?
The first is a buildscript method, which receives a closure:
def buildscript(Closure closure)
The allprojects method also receives a closure parameter:
def allprojects(Closure closure)
Others are similar...
It seems much easier now, but I still don't understand it. Where are these methods defined? The answer is Project.
Project
This is a key to understanding the Gradle script.
The statement block at the top layer of the build script will be delegated to the Project instance.
This shows that the Project is exactly where I want to find it.
Search for the buildscript method on the document page of the Project, and find the buildscript {} script block (script block). And so on. What is the script block? According to the document:
Script block is a method that only receives closure as a parameter.
Continue to read the buildscript documentation, which describes Delegates to: ScriptHandler from buildscript. That is to say, the closure passed to the buildscript method, and the final execution context is ScriptHandler. In the preceding example, closure passed to buildscript calls the repositories (closure) and dependencies (closure) methods. Since closure is delegated to ScriptHandler, we will go to ScriptHandler to find the dependencies method.
Void dependencies (Closure configureClosure) is found. According to the document, dependencies are used to configure the script dependency. Dependencies is finally delegated to DependencyHandler.
I have seen how widely used Gradles is. Understanding delegation is important.
Script blocks
By default, many script blocks are pre-defined in the Project, but the Gradle plug-in allows us to define new script blocks!
This means that if you send some {…} at the top layer of the build script {...}, However, you cannot find the script blocks or method in the Gradle document. In most cases, this is a script block defined in the plug-in.
Android Script block
Let's take a look at the default Android app/build. gradle file:
apply plugin: 'com.android.application'android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.trickyandroid.testapp" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}
As we can see, it seems like there shocould be android method which accepts Closure as a parameter. but if we try to search for such method in Project documentation-we won't find any. and the reason for that is simple-there is no such method :)
If you look closely to the build script-you can see that before we execute android method-we apply com. android. application plugin! And that's the answer! Android application plugin extends Project object with android script block (which is simply a method which accepts Closure and delegates it to AppExtension class1 ).
We can see that there is an android method that receives a closure parameter. However, if we search the Project document, we cannot find this method. The reason is simple. This is not the method defined in the Project.
Check the build script carefully. We can see that we used the com. android. application plug-in before using the android method. The Android application plug-in extends the Project object and adds the android Script block.
Where can I find the Android plug-in documentation? Android Tools website or here
If we open the AppExtension document, we can find the method used in build script. attributes:
1. compileSdkVersion 22. If we search for compileSdkVersion, this attribute will be found. Here we assign "22" to this attribute ".
2. buildToolsVersion is similar to 1
3. defaultConfig-a script block will be delegated to the ProductFlavor class for execution.
4. Miscellaneous
Now we can understand the Gradle script syntax and know how to search for documents.
Exercise
Let's configure something for the trainer.
In AppExtension, I found a script block testOptions, which delegates the closure parameter to the TestOptions class. According to the document, the TestOptions class has two attributes: reportDir and resultsDir. ReportDir is used to save the test report. Let's change this attribute.
android {...... testOptions { reportDir "$rootDir/test_reports" }}
Here, I used the rootDir attribute defined in the Project, which is the root directory of the Project.
Now, if I execute./gradlew connectedCheck, the test report will be saved to the [rootProject]/test_reports directory.
Do not try this modification in a real project. All build outputs should be in the build directory so as not to pollute your project directory structure.