The Gradle Plugin is an important tool for changing code at compile time and a core part of our precision testing.

Official website town building:

Docs.gradle.org/current/use…

Developer.android.com/studio/buil…

Gradle Plugins exist in three forms:

  • In the build script: write directly to the project’s current build.gradle
  • BuildSrc: The buildSrc folder at the project root, which is a system-reserved directory where you can run the plug-in code without referencing the plug-in package
  • Standalone projects: Similar to modules, compiled separately for use as jars

create

Gradle init is an example of how to create a template project. Gradle Init is an example of how to create a template project.

Execute the command in the appropriate directory, as shown below.

➜ qdplugin/Users/xuyisheng/Downloads/gradle - 6.5 / bin/gradle init Select type of project to generate: 1: basic 2: application 3: library 4: Gradle plugin Enter selection (default: basic) [1..4] 4 Select implementation language: 1: Groovy 2: Java 3: Kotlin Enter selection (default: Java) [1..3] 1 Select build script DSL: 1: Groovy 2: Kotlin Enter selection (default: Groovy) [1..2] 1 Project name (default: qdplugin): Source package (default: qdplugin): > Task :init Get more help with your project: https://guides.gradle.org?q=Plugin%20Development BUILD SUCCESSFUL in 12s 2 actionable tasks: 2 executedCopy the code

Gradle automatically creates the required files for us.

The new Gradle plugin structure is different from the previous one. The new Gradle plugin does not need the resources directory to declare the plugin’s entry meta-info file. Instead, it is written directly in build. Gradle, and so on.

gradlePlugin {
    // Define the plugin
    plugins {
        coverage {
            id = 'com.yw.coverage'
            implementationClass = 'com.yw.coverage.Coverage_pluginPlugin'
        }
    }
}
Copy the code

In order to avoid some weird problems at compile time, it is recommended that you add instructions specifying Java8 compilation.

SourceCompatibility = 1.8 targetCompatibility = 1.8Copy the code

release

The first two ways to use the Gradle Plugin are not to publish the Plugin. You can use the Plugin directly, but in most cases, you will create the buildSrc directory in the root of the project, and then use Gradle Init to generate the required files for the Plugin. After development, you can migrate the Plugin to a separate project.

In buildSrc, you can directly participate in compilation and debugging without publishing to the App every time, but after the plug-in is stable, you can make plug-in integration and management more convenient through a separate plug-in project.

Generally, we use the local Maven libraries to debug plugins. Gradle’s Maven-Publish plugin allows us to easily publish plugins to the local Maven libraries.

First, introduce plug-ins:

plugins {
    id 'java-gradle-plugin'
    id 'java'
    id 'maven-publish'
    id 'groovy'
    id 'maven'
}
Copy the code

Using MavenLocal, publish the plugin in: /Users/ usernames /.m2/repository.

Go ahead and create the release script as follows:

publishing { publications { coverage(MavenPublication) { groupId = 'com.yw.coverage' artifactId = 'coverage' version = From components. Java}}}Copy the code

Among them:

  • Coverage: The task name can be specified at will
  • GroupId, artifactId, and Version: These three things make up the reference ID, which is used in the root directory of build.gradle.

Independent plug-in project, you need to perform the publish task, found in Gradle label card publishCoveragePublicationToMavenLocal such a task, release the plugin to MavenLocal, compilation can be used successfully.

use

In the Gradle file at the root of the project using the plug-in, specify access to mavenLocal, and make a reference to the plug-in using groupId, artifactId, and version, as shown below.

Buildscript {ext.kotlin_version = "2.4.21" repositories {Google () mavenCentral() mavenLocal()} dependencies { Classpath 'com. Android. Tools. Build: gradle: 4.4.1' classpath "org. Jetbrains. Kotlin: kotlin - gradle - plugin: $kotlin_version" The classpath "com. Yw. Coverage: coverage: 0.0.1" / / NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }Copy the code

In the main Module, reference the plug-in as follows.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.yw.coverage'
}
Copy the code

The ID, in this case, is the id defined in the plugin configuration for the plug-in.

gradlePlugin {
    // Define the plugin
    plugins {
        coverage {
            id = 'com.yw.coverage'
            implementationClass = 'com.yw.coverage.Coverage_pluginPlugin'
        }
    }
}
Copy the code

After the sync project, you can find the new task in the Task, which is registered in the code (generated by default).

public class Coverage_pluginPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register a task
        project.tasks.register("coverage") {
            doLast {
                println("Hello from plugin 'com.yw.coverage'")
            }
        }
    }
}
Copy the code

Pluginimplementationclass implements Plugin, implements Plugin, implements Plugin, implements Plugin, implements Plugin, implements Plugin, implements Plugin, implements Plugin, implements Plugin, implements Plugin

Compatible with

Gradle is easy to use, but the API changes very frequently, and the compatibility is not very good, so you often find some scripts on the Internet, may not be able to execute in your environment, so through the official documentation to check the latest manual, is the most stable way.

Transform

Transform is the core of the Gradle Plugin.

The Gradle Plugin provides a process pipeline before dex packaging a class during compilation. There are many official tasks that are also implemented based on the Transform. Customizing Gradle Plugin and making code changes with the Transform is a common way to intervene in the compilation process.

One of the simplest transformations is shown below.

package com.yw.coverage import com.android.build.api.transform.QualifiedContent import com.android.build.api.transform.Transform import com.android.build.api.transform.TransformException import com.android.build.api.transform.TransformInvocation import com.android.build.gradle.internal.pipeline.TransformManager import org.gradle.api.Project public class CoverageInjectTransform extends Transform { private Project project CoverageInjectTransform(Project Project) {this. Project = Project} For example, when the return value is CoverageInjectTransform, Task @override String getName() {return "Coverage"} // Task @override String getName() {return "Coverage" Specifies the type of input the Transform will handle, Usually for a Class type @ Override the Set < QualifiedContent. ContentType > getInputTypes () {return TransformManager. CONTENT_CLASS} / / Specifies the range to which the Transform input file belongs, @override Set<? Super QualifiedContent.scope > getScopes() {return TransformManager.scope_full_project} // Override Boolean isIncremental() {return true} @override void Transform (TransformInvocation) transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation) println("------------------------") } }Copy the code

Note, however, that the above Transform is actually unexecutable because, as we mentioned earlier, a Transform is a pipeline, each Transform is a Gradle Task, and the TaskManager in the compiler concatenates each Transform. The Transform takes the class file compiled by the previous Transform as input, as well as jar and AAR resources, and resource files in the asset directory. After processing, the Transform needs to output these contents to the next Transform as its output. Until all the transforms have been executed.

The user-defined Transform is executed before the system Transform

There are two kinds of Transform, namely “consumer Transform” and “reference Transform”.

  • Consumer Transform: This Transform requires that each JAR, AAR, and class intermediate be copied to the Transform Dest directory. This directory is actually the input directory for the next Transform. In the process of copying the intermediate, that’s when we modify the product. We can make some changes to the bytecode of the JAR, AAR, and class files and pass them to the next Transform

  • Referential Transform: The current Transform can read these inputs, but cannot modify them, and no intermediate output is required to the next Transform

Therefore, the consumer Transform must output the input intermediate to the next Transform or it will not continue compiling.

There are several important concepts in Transform:

  • TransformInput: Transform is the transformation of the input class file into the target bytecode file. TransformInput is the abstraction of these input files. It currently consists of two parts: the DirectoryInput collection and the JarInput collection.

  • DirectoryInput: It represents all the directory structures and the source files in the directory that participate in the compilation of the project as source code and can be used to modify the directory structure of the output file, the target bytecode file.

  • JarInput: it represents all the local or remote JAR packages that participate in the project compilation as jar packages and can be used to dynamically add jar packages.

  • TransformOutputProvider: This represents the output of the Transform. For example, it can be used to get the output path.

For TransformInput, Gradle controls the input files through the following two dimensions.

  • Scope: Filters the source of the input file
  • ContentType: Filters the type of the input file

Scope has the following values:

PROJECT_LOCAL_DEPS: Only local dependencies (local jars) of the PROJECT. PROVIDED_ONLY: Only local or remote dependencies are provided. SUB_PROJECTS: Only SUB_PROJECTS_LOCAL_DEPS: Only the local dependencies (local JARS) of the subproject TESTED_CODE: The code tested by the current variable (including dependencies)Copy the code

The values of ContentType are as follows:

CONTENT_CLASS: class type CONTENT_JARS: JAR CONTENT_RESOURCES: asset CONTENT_NATIVE_LIBS: nativeCopy the code

Any consumable Transform can use Gradle’s API to retrieve the output directory and Copy the intermediate products to the output directory:

// class
def outputDirFile = transformInvocation.outputProvider.getContentLocation(
    directoryInput.name, directoryInput.contentTypes, directoryInput.scopes,
    Format.DIRECTORY
)
FileUtils.copyDirectory(directoryInput.getFile(), outputDirFile);

// jar            
def outputFile = transformInvocation.outputProvider.getContentLocation(
    jarInput.name, jarInput.contentTypes, jarInput.scopes,
    Format.JAR
)
FileUtils.copyFile(jarInput.getFile(), outputFile);
Copy the code

All custom Transform intermediate, can be found under the build/intermediates/transforms (Kotlin files in the build/TMP/Kotlin – classes directory), you can view these intermediate conform to their expectations.

registered

The Transform needs to be registered in the Plugin to take effect, and there are two ways to register it, as shown below.

/ / registered way 1 AppExtension AppExtension = project. Extensions. GetByType (AppExtension) AppExtension. RegisterTransform (new MethodTimeTransform ()) / 2 / / project/registered way. Android. RegisterTransform (new MethodTimeTransform ())Copy the code

Kotlin,

Gradle plug-in experienced Java, Grovvy version changes, ushered in a comprehensive kotlinization of the new wave, the new version of the official Gradle plug-in, have all used Kotlin to write, with Kotlin, we can be very convenient unified code writing environment, with the help of not inferior to Grovvy syntax sugar, Gradle Plugin can be easily written.

To use Gradle in Gradle, we need to make some changes to the script. First, we need to change the build. Gradle script to bledd.gradle.kts, and then put the Kotlin code in the SRC /man/ Kotlin directory. Finally, we need to update the code in the script accordingly. The KTS script is shown below.

Plugins {' java-gradle-plugin 'id(" org.jetbrain-kotlin.jvm ") version "2.3.72"} Repositories {mavenCentral() Google () jcenter() } dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom")) Implementation (" com. Android. Tools. Build: gradle: 4.4.1 ") implementation (" org. Ow2. Asm: asm: 9.1 ")} gradlePlugin {val greeting by plugins.creating { id = "asmtest" implementationClass = "com.yw.asm.MyPlugin" } } java.sourceCompatibility =  JavaVersion.VERSION_1_8 java.targetCompatibility = JavaVersion.VERSION_1_8Copy the code

To make it easier, use Gradle Init to generate the Kotlin version of the plugin default code and Copy it.

Gradle plug-in is the basis of our subsequent bytecode modification, we must be skilled in the development and debugging of the plug-in, so as to avoid the subsequent development of bytecode plug-in encountered various plug-in problems and can not concentrate on the bytecode development.

I’d like to recommend my website Xuyisheng. Top/focus on Android-Kotlin-flutter. Welcome to visit