Capacity is limited, unavoidable error, please understand!

The book continues:

  • Gradle Pit Climbing Guide – Introduction
  • Gradle Pit Crawl Guide – Grammar and concepts first
  • Gradle Pit Crawl Guide – Understand plugins, Tasks, and build processes

We have seen Plugin, Task and Gradle construction stages. Gradle has three built-in objects: Gradle, Setting and Project. Gradle has three built-in objects: Setting and Project. Gradle has three built-in objects: Setting and Project

Gradle Core model

Gradle has three built-in objects: Gradle, Setting, and Project. Gradle has three built-in objects: Gradle, Setting, and Project.

How do I know which object is behind the setting script? There are only three Gradle objects: Init script for Gradle, setting script for Settings, build script for Project. To understand the relationship between these three, you need to look at the Gradle life cycle

The Gradle build tool allows us to write the build logic using.gradle scripts, but at compile time the script files are programmed into Java objects for execution

  • init.gradleAfter the script is compiled, Gradle objects are generated
  • settings.gradleWhen the script is compiled, the Setting object is generated
  • build.gradleAfter the script is compiled, the Project object is generated

Gradle, Setting, and Project are all interfaces in the source code. In fact, the objects we obtain are implementation classes. Here we only look at the interface definition function can understand a lot of things. Specific implementation classes should be implemented by imported plug-ins, such as Android, Java build plug-ins

Let’s take a look at some of the annoying DSL code blocks in the script file. All of the DSL code blocks in the script come from Gradle itself, except for those from plug-ins

app build.gradle –>

ext.kotlin_version = "1.4.10"
buildscript {
    ext.kotlin_version = "1.4.10"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "Com. Android. Tools. Build: gradle: 4.0.1." "
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"}}allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
Copy the code

Each DSL in the script corresponds to a method in the Gradle core model object, buildScript {… }, repositories {… }, dependencies {… }, allprojects {… }, task {… } these can find the corresponding method, and we look at the source code with me, without much depth, just look at the interface design

Build. Gradle is a DSL code block that you wrote in your build.gradle script. You can also come and see the source code of Gradle project, CTRL +F12 once it is clear, the annotation can also be written, AS there is a translation plug-in, we should use the translation to see the official explanation

In fact, I did not expect to see the interface declaration to clarify the situation, thanks to Gradle team from the introduction to Gradle video: Gradle development team

The official production, however, is different. It is much more in-depth than blogs, documents, and other educational videos. If you haven’t watched it yet, you are strongly advised to have a look. Open horizon, a little experience to use the taste of the source code, I don’t know if you get to ヾ(≧O≦) “ao ~

Hook function

Gradle Hook functions also have a lifecycle. Just as we register our Application listeners, Gradle allows us to exert our influence during the construction phase

This part of the content is very complicated, I also read a lot of articles, after careful consideration before deciding to write this content from this Angle

Group Hook functions from different perspectives

Gradle provides a lot of Hook functions, it is a bit confusing, anyway, I just read this feeling. There are different design considerations and perspectives among these Hook functions. Based on this, we try to group these Hook functions for memory and understanding

Here we are going to combine the Gradle core model mentioned above ヾ(´∀ ‘o)+ knowledge is always connected

1. From the perspective of Gradle core model:

  • Both Gradle and Settings are global objects, and the hook functions provided by them naturally play a role in the build process
  • A Project object is for a single Project, and there may be multiple Project objects during the build process. The functions provided by a natural Project object can only be used for that Project
  • The Root Project object is special. There are both hook functions that can be set globally and hook functions that only work on themselves. The familiar allProject() is one of these functions that can be set globally. Generally, hook global Settings are not written in the script, but in the Settings script

2. From the perspective of function naming:

  • EvaluatedWords represent individual executions during the build phase.gradleThe script
    • beforeEvaluatedWhat do YOU do before executing the script
    • afterEvaluatedWhat do YOU do after executing the script
  • ProjectEvaluatedA word, but “Project” means a lot more than “Evaluated”, which is every module.gradleThe script. A Project is the entire building process of a module or can be understood as the Project object itself. The hook function looks almost the same, but still need to understand the difference between these two words
    • beforeProjectWhat do you do before executing the project
    • afterProjectWhat do you do after executing the project

3. From the perspective of script execution:

  • We are in.gradleA hook function written in a script must take effect only after the script is executedbeforeEvaluatedDoes that make sense to you? Now that the script is executed, it’s too late to define what should be executed before the script is executed. Should you try to write the function before the script is executed

4. From the perspective of the build process:

  • InitializationGradle and settings.gradle scripts are executed during initialization. The init.gradle script is not recommended because of the wide range of influences and who knows when it will cause unknown problems. We write hook functions in settings.gradle scripts
  • ConfigurationCompilation phase –> Subproject scripts generally only write hook functions whose scope of influence is only in this project. But pay attention to the flow of hook functions, such as writing beforeProfile in subproject scripts is pure bullshit. What’s the use of writing this hook when the script is running? Therefore, it is important to note that the hook must be written in the right place, and the location of the hook function should be considered in the context of the entire build process
  • ExecutionExecution phase — we can’t write hook functions at this stage, and even if we can, what can we do? How to execute the build task has already been decided in the previous section, and we can’t exert any influence at this stage

5. From the perspective of function design: Hook functions are divided into two types

  • One is a single function hook function, such as beforeProfile, of which there are many
  • AddLisenter () is one of these hook functions, which can add various listener functions. Most single-function hook functions can be replaced with a listener. In addition, the Listener can set and operate all projects through traversal

6. From the perspective of the function’s target:

  • One that monitors the execution of scripts, like BeforeProfile
  • One that specifically listens to the execution of a project, such as beforeProject, gradle.allproject () can operate on allProject objects
  • A special listening task execution, such as gradle. TaskGraph. WhenReady ()

The Hook function initializes a node from a Gradle 3 large object

Because the whole process is very long, Gradle 3 large objects are not created at the beginning of the process, but are created step by step during the build process. If we use this object before, we will get an error, so we need to know which hook function to use Gradle 3 large objects from

To put it another way — we write a script, but the actual code is dynamically compiled into Java objects for execution, and we have to consider whether the objects are already initialized when we use them in the script

Gradle object:

Gradle is created after the script is executed, so we can use gradle objects as much as we want in Settings. Gradle script. This is why settings.gradle scripts are used for global hook Settings

2. Setting object:

gradle.settingsEvaluated { .... Settings object is ready to use}Copy the code

Settings. after the gradle script is executed, the Setting object is ready for use

3. Project Object:

gradle.projectsLoaded {
    .... projectObject is ready to use}Copy the code

Settings. gradle creates Project objects for all of its subprojects. In projectsLoaded(), we get all of them

In fact, in each script, the corresponding core model object can be used, the script is running, the object must have been created. One thing to be careful about is using objects in scripts that are outside the scope of this script

Settings. Hook functions that gradle scripts can use

Method comments and logs are viewed together

include ':libs'
include ':app'

buildscript{... }println("settings...")

// Setting is called before the script executes
gradle.beforeSettings {
    // This is obviously useless
    println("gradle.beforeSettings...")}// Setting is called before compiling the project
gradle.beforeProject {
    // This is obviously useless
    println("gradle.beforeProject...")}// Setting is invoked after the script is executed
gradle.settingsEvaluated {
    println("gradle.settingsEvaluated...")}// Setting is called after the project is compiled
gradle.afterProject {
    println("gradle.afterProject...")}// after all project scripts are executed
gradle.projectsEvaluated {
    println("gradle.projectsEvaluated...")}// called when the compile phase starts
gradle.projectsLoaded {
    println("gradle.projectsLoaded...")}// called when the build ends
gradle.buildFinished {
    println("gradle.buildFinished...")}// Set all project scripts
gradle.allprojects(new Action<Project>() {
    @Override
    void execute(Project project) {
        // Set beforeEvaluate here to work
        project.beforeEvaluate {
            println("gradle.allprojects.beforeEvaluate...")}project.afterEvaluate {
            println("gradle.allprojects.afterEvaluate...")}}})// When the Task flow chart is calculated during compilation
gradle.taskGraph.whenReady {
    println("gradle.taskGraph.whenReady...")}Copy the code

Run logs:

settings... gradle.settingsEvaluated... gradle.projectsLoaded... > Configure project: --> Execute the root script gradle.beforeproject... gradle.allprojects.beforeEvaluate... root build.gradle... gradle.afterProject... gradle.allprojects.afterEvaluate... root_project.afterEvaluate... > Configure Project :app --> Execute app shell engineering script gradle.beforeProject... gradle.allprojects.beforeEvaluate... app build.gradle... gradle.afterProject... gradle.allprojects.afterEvaluate... > Configure project :libs --> Execute the libs subproject script gradle.beforeProject... gradle.allprojects.beforeEvaluate... libs build.gradle... gradle.afterProject... gradle.allprojects.afterEvaluate... gradle.projectsEvaluated... gradle.taskGraph.whenReady... > Task: prepareKotlinBuildScriptModel the UP - TO - DATE -- > all of Task execution, omitted here gradle. BuildFinished... BUILD SUCCESSFUL in 2sCopy the code

Subproject build.gradle script can use Hook functions

Settings. Gradle scripts can write anything here. It is too late to add a hook to the global project. Even if the hook is set, the script will only take effect after the script is set

Can write hook is actually these two, log output to see the above line

project.beforeEvaluate {
    // This is useless
    println("project.beforeEvaluate...")}project.afterEvaluate {
    println("root_project.afterEvaluate...")}Copy the code

Hook function of type Listener

Gradle Listener is also recommended to be written in settings. Gradle script, which is suitable for this location. Root build. Gradle can barely write, and several hook functions will not work because the hook point has passed

Gradle adds a Listener in a very flexible way. The addListener() function takes an Object as an argument and supports many types of listeners

1) addBuildListener ()

Function can replace some global set hook function, inside the method is some of the above hook function repetition

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println("gradle.addBuildListener.buildStarted...")
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println("gradle.addBuildListener.settingsEvaluated...")
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println("gradle.addBuildListener.projectsLoaded...")
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println("gradle.addBuildListener.projectsEvaluated...")
    }

    @Override
    void buildFinished(BuildResult result) {
        println("gradle.addBuildListener.buildFinished...")}})Copy the code

2) TaskExecutionGraphListener

Can monitor the execution of all Task functions

gradle.addListener(new TaskExecutionGraphListener() {
    @Override
    void graphPopulated(TaskExecutionGraph graph) {
        println("gradle.addListener.hasTask..."+graph.hasTask(":test"))
        graph.getAllTasks().each { 
            it.doLast {
                // ...
            }
            it.doFirst {
                // ...}}}})Copy the code

3) TaskExecutionListener

Add hooks before and after Task execution

gradle.addListener(object : TaskExecutionListener {
    override fun beforeExecute(task: Task){... }override fun afterExecute(task: Task, state: TaskState){... }})Copy the code

4) TaskActionListener

Add hooks before and after action execution

gradle.addListener(object : TaskActionListener {
    override fun beforeActions(task: Task){... }override fun afterActions(task: Task){... }})Copy the code

5) addTaskExecutionGraphListener

Effect with whenReady

gradle.getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
    @Override
    void graphPopulated(TaskExecutionGraph graph) {}})Copy the code

6) DependencyResolutionListener

Gradle makes dependency decisions after all subproject scripts have been executed. The beforeResolve/afterResolve methods are a pair of hooks that Gradle sets separately for this work

gradle.addListener(object : DependencyResolutionListener {
    override fun beforeResolve(dependencies: ResolvableDependencies){... }override fun afterResolve(dependencies: ResolvableDependencies){... }})Copy the code

Some functions use caution points

1. Determine whether to add: to the specified Task

I can’t find the specified Task, and I can’t find the custom Task in the flow chart if it is not called during the construction process

task test {
    println("task--test...")
    doLast {
        println("task--test.doLast...")
    }
}

gradle.addListener(new TaskExecutionGraphListener() {
    @Override
    void graphPopulated(TaskExecutionGraph graph) {
        println("gradle.addListener.hasTask..."+graph.hasTask(":test"))
        graph.getAllTasks().each { 
            it.doLast {
                // ...
            }
            it.doFirst {
                // ...}}}})Copy the code

All hook flow charts

Figure from the nuggets brochure, generally in the picture, we finally combined with this picture to understand

Meaning of Hook functions

You have to set an execution time for it, so the Hook is a good choice

Project API

The Gradle API is one of the most common and annoying apis for building Gradle objects

1. getAllprojects()

This is the same as gradle.allprojects ()

// index = 0 is Root buils.gradle
project.getAllprojects().eachWithIndex { Project project.int index ->
    if (index == 0) println("index 0 :root project") else println("index $index : $project.name")}Copy the code

2. getSubprojects()

Get all subprojects

project.getSubprojects().eachWithIndex { Project project.int index ->
    println("index $index : $project.name")}Copy the code

3. getParent()

The root project object gets NULL

project.getParent()
Copy the code

4. getRootProject()

project.getRootProject()
Copy the code

5. project()

Gradle objects also have a find method, but this is the first time I have seen it

project("app") { Project project ->
    apply plugin: 'com.android.application'
}
Copy the code

6. allprojects() / subprojects

We all know that subprojects don’t operate projects

7. plugins

if (project.plugins.hasPlugin("com.android.library")) {
	apply from: '.. /publishToMaven.gradle'
}
Copy the code

Ext extended attributes

ext{… } This DSL snippet is also a method provided by the Project object. ext{… } everyone is familiar with it, are used to do global parameters, dependency configuration. ext{… } is a block of code provided by Gradle that lets us define the global variables we need

1. Define the ext

Normally we write ext{… in the root project script. }

// root build.gradle

ext {
    tag = "BB"
    age = 2
}
Copy the code

2. Use ext

Pay attention to these two ways of using the automatic prompt link, many people complain that it is difficult to use without the code prompt

// app build.gradle

// Mode 1: Use sync directly. An automatic message is displayed after sync
println( "name = $tag" )
println( "age = $age" )

// Method 1: Use the rootProject object, the automatic message will be displayed after the rebuild
println( "name = ${rootProject.ext.tag}" )
println( "age = ${rootProject.ext.age}" )
Copy the code

Step 3 Use 1 – Abstract common configuration scripts

Ext {… } Some people say that we should split the root script, so we write a special ext{… }, this script is usually called: config.gradle, and then apple from this directory import script can be used

ext{… } this is compiled as a member property of the Project object, ext{… } we usually write a Map to configure some properties

// config.gradle

ext {

	// It is recommended to write a map for different DSL configuration blocks, which is convenient to find
    android = [
            compileSdkVersion: 29,
            applicationId    : "com.bloodcrown.myapplication22",
            minSdkVersion    : 21,
            targetSdkVersion : 29,
            versionCode      : 1,
            versionName      : "1.0"]}Copy the code
// root builf.gradle

apply from: this.file("config.gradle")

buildscript {
    ext.kotlin_version = "1.4.10"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "Com. Android. Tools. Build: gradle: 4.0.1." "
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"}}Copy the code
// app build.gradle

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}}Copy the code

Step 2: Abstract the subproject script base class

This is not hard to understand, if our project has multiple subprojects, each subproject in the repeated script configuration is also a nuisance to write, especially in the case of temporary change is very annoying, we should continue the Idea of Java phase objects: one change, use everywhere

Gradle, which is called “base_build.gradle”. You can import the subproject script from apple

// base_build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'}}dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx. Core: the core - KTX: 1.3.2'
    implementation 'androidx. Appcompat: appcompat: 1.2.0'
    implementation 'androidx. Constraintlayout: constraintlayout: 2.0.4'
    testImplementation 'junit: junit: 4.12'
    androidTestImplementation 'androidx. Test. Ext: junit: 1.1.2'
    androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0'
}
Copy the code
// app build.gradle

apply from: rootProject.file("base_build.gradle")

dependencies {
    implementation fileTree(dir: "libs".include: ["*.jar"])
    implementation 'com. Google. Android. Material: material: 1.0.0'
    implementation 'androidx. Navigation: navigation - fragments - KTX: 2.1.0'
    implementation 'androidx. Navigation: navigation - UI - KTX: 2.1.0'
}
Copy the code

Here is:

5. Advanced use of 3 – use all generations to write line after line

Maybe some projects have too many dependencies, so there is an ext{… }, and then in the script to iterate over the way to add dependencies, convenient is easy, but look a little unaccustomed to

ext{
    dependencies= [...].  annotationd_ependencies = [...] }dependencies.each { k, v -> implementation v }
annotationd_ependencies.each { k, v -> implementation v } 
Copy the code

Well ~ this kind of thinking is not quite acceptable, we see the demand

6. ext{… } can also write code

Out of writing some configuration parameters, we can write some code, look at an example:

ext {
  versionName = rootProject.ext.android.versionName
  versionCode = rootProject.ext.android.versionCode
  versionInfo = 'Version 2 of the App, with some of the most basic core features.'
  destFile = file('releases.xml')
  
  if(destFile ! =null && !destFile.exists()) {
    destFile.createNewFile()
  }
}

this.project.afterEvaluate { project ->
  def buildTask = project.tasks.findByName("build")
  doLast {
    buildTask.doLast {
      writeTask.execute()
    }
  }
}

taskwriteTask {...... }Copy the code
(project.tasks.findByName(chmodTask.name) as Task).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))
(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))
Copy the code