当前位置:   article > 正文

Gradle编译的那些事——从Groovy到Gradle插件开发_groovy-gradle-plugin

groovy-gradle-plugin

1.Android项目中的一些build.gradle分析

  1. apply plugin: 'com.android.application'
  2. apply from: 'test.gradle'
  3. allprojects {
  4. repositories {
  5. google()
  6. jcenter()
  7. }
  8. }

最初的疑问

想这几句gradle的设置是什么含义?其中apply是什么,做了什么?

allprojects、repositories这些语句设置了什么,为什么这样写?

apply实际上时一个方法,它接收一个闭包作为参数,参数plugin:"java"实际上是一个map的结构,键值对。

repository这段代码实际上也是一个方法的调用,参数是一段闭包。这是gradle编译脚本中的特殊语法。

我们可以查看一下build.gradle脚本的代码:

 

 

从源码可以看到: 

  1. void allprojects(groovy.lang.Closure closure);
  2. void afterEvaluate(groovy.lang.Closure closure);
  3. void dependencies(groovy.lang.Closure closure);
  4. void buildscript(groovy.lang.Closure closure);

在Project这个接口类中定义了很多的方法,比如allprojects,他们几乎都拥有一个Closure类型的参数。

刚开始接触到gradle时候,一直以为这个gradel.build文件就是这样的固定写法。后来了解了一些编译的知识才明白,gradle文件也是一种特定语言编写的的文件。而这种语言不常见,而Closure就是它的特殊定义之一。

Closure是什么类型?这里先要了解一些gradle默认编写的语言。

Groovy语言

Gradle默认是由groovy语言来编写的,了解groovy的人大概知道,groovy是一种面相对象的语言,因此和java、kotlin有很多相似的地方。

 groovy官网:The Apache Groovy programming language 

Groovy中一些特别的点

1.闭包

简单的理解,闭包就是一段由大括号括起来的代码块,它有一个类型声明:groovy.lang.Closure closure。通常我们可以定义一段代码块作为参数传递给一个参数是Closure类型的方法调用。

  1. /**
  2. * 定义一个clause
  3. */
  4. def ageClosure = {
  5. int age = 20
  6. while (age<100){
  7. age++
  8. }
  9. }
  10. /**
  11. * 带参数的Cosure
  12. */
  13. def ageClosure = {
  14. param->
  15. int age = 20
  16. while (age<100){
  17. age++
  18. }
  19. println "$param--$age"
  20. }
  21. //不同的调用closure方式
  22. def printAge(Closure closure){
  23. println "==================================="
  24. closure.call("这是一个参数")
  25. println "==================================="
  26. }
  27. //直接作为一个方法调用
  28. ageClosure("text")
  29. ageClosure()
  30. //作为参数
  31. printAge(ageClosure)

闭包看起来和kotlin的一些语法比较像,有什么区别:

  1. //kotlin
  2. @Test
  3. fun main(){
  4. var list = mutableListOf<String>()
  5. list.apply {
  6. add("java")
  7. add("kotlin")
  8. add("html")
  9. }
  10. list.forEach {
  11. println(it)
  12. }
  13. }
  14. //groovy文件中的代码
  15. List<String> list = new ArrayList<>()
  16. list.add("java")
  17. list.add("kotlin")
  18. list.add("groovy")
  19. list.add("python")
  20. /**
  21. * 普通groovy List的forEach方法
  22. */
  23. list.forEach{
  24. println(it)
  25. }

可以看到对于一些语法kotlin和groovy的使用几乎一样。groovy的闭包使用和kotlin的语法也很像:

  1. /**
  2. * 闭包在方法中的使用,和kotlin语法类似
  3. */
  4. def targetFile = new File("file.txt")
  5. targetFile.eachLine {
  6. println(it)
  7. }

代码从一个file.txt文件按行读取文件,然后传递一个闭包,在闭包中打印出每行字符。这里的eachLine方法和前面的kotlin的语法相似,但是参数不同:kotlin的forEach方法

  1. public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
  2. for (element in this) action(element)
  3. }

参数是一个返回Unit的代码块,而groovy的eachLine方法参数是一个Closure闭包。

2.省略

Groovy语言由于其特性,可以做一些简化的省略,比如省略方法的括号,省略参数的引号。

比如apply()方法。实际上是:

void apply(Map<String, ?> options);

我们常用的println()方法,可以省略括号。

  1. tasks.register("hi"){
  2. it.doLast{
  3. println("hellp groovy ~")
  4. println "hello groovy !!"
  5. }
  6. }

 代码闭包的形式,再加上省略,以及固定的一些写法,可能是刚开始接触build.gradle难理解的几个原因。

2.Gradle是什么,它是如何执行的?

关于gradle官方有他的介绍:What is Gradle?

总结来说就是几个字:Gradle是高性能的、可编程的、基于JVM的、可扩展的编译框架。

1.在Gradle之前的代码编译工具Ant和maven

Ant构建项目

Ant是早期Apache开发的用于构建java项目的构建工具,它是用xml来编写编译配置文件的,内部内置了很多预定义的任务,方便Java开发着编译,部署项目。

一个Hello world Java项目使用ant 编译项目的配置文件build.xml

  1. <?xml version = "1.0"?>
  2. <project name = "fax" basedir = "." default = "build">
  3. <property name = "src.dir" value = "src"/>
  4. <property name = "web.dir" value = "war"/>
  5. <property name = "build.dir" value = "${web.dir}/WEB-INF/classes"/>
  6. <property name = "name" value = "fax"/>
  7. <path id = "master-classpath">
  8. <fileset dir = "${web.dir}/WEB-INF/lib">
  9. <include name = "*.jar"/>
  10. </fileset>
  11. <pathelement path = "${build.dir}"/>
  12. </path>
  13. <target name = "build" description = "Compile source tree java files">
  14. <mkdir dir = "${build.dir}"/>
  15. <javac destdir = "${build.dir}" source = "1.5" target = "1.5">
  16. <src path = "${src.dir}"/>
  17. <classpath refid = "master-classpath"/>
  18. </javac>
  19. </target>
  20. <target name = "clean" description = "Clean output directories">
  21. <delete>
  22. <fileset dir = "${build.dir}">
  23. <include name = "**/*.class"/>
  24. </fileset>
  25. </delete>
  26. </target>
  27. </project>

其中:

src.dir指的是可以找到java源文件的项目的源文件夹

build.dir是指项目编译的输出文件夹

web.dir是指项目的Web源文件夹,您可以在其中找到JSP,web.xml,css,javascript和其他Web相关文件

有一个name是clean的target和build的target,从名字可以看出这是用来clean和build操作的。

Ant参考 Ant - 介绍_学习Apache Ant|WIKI教程 

Maven 构建项目

maven是Apache中的一个编译和管理工具,主要对Java项目进行构建和管理。maven能够帮助开发人员完成项目的构建、文档生成、依赖、发布、分发等操作。

使用命令创建一个基于maven编译的项目的pom.xml

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.companyname.bank</groupId>
  5. <artifactId>consumerBanking</artifactId>
  6. <packaging>jar</packaging>
  7. <version>1.0-SNAPSHOT</version>
  8. <name>consumerBanking</name>
  9. <url>http://maven.apache.org</url>
  10. <dependencies>
  11. <dependency>
  12. <groupId>junit</groupId>
  13. <artifactId>junit</artifactId>
  14. <version>3.8.1</version>
  15. <scope>test</scope>
  16. </dependency>
  17. </dependencies>
  18. </project>

从pom. xml代码中,可知 Maven 已经添加了 JUnit 作为测试框架。

执行mvn clean package 可以完成 clean 并打包生成一个jar

网上一个典型的maven构建的生命周期图:

https://www.runoob.com/wp-content/uploads/2018/09/maven-package-build-phase.png

maven中引入第三方依赖:

通过给pom.xml中添加一个dependencies节点

  1. <dependencies>
  2. <!-- 在这里添加你的依赖 -->
  3. <dependencies>
  4. <dependency>
  5. <groupId>com.companyname.common-lib</groupId><!-- 库名称,也可以自定义 -->
  6. <artifactId>common-lib</artifactId><!-- 库名称,也可以自定义 -->
  7. <version>1.0.0</version><!--版本号-->
  8. </dependency>
  9. <dependencies>
  10. </dependencies>

maven 参考 Maven 教程 | 菜鸟教程 

Ant、maven、Gradle的区别和联系

Ant是最早的构建工具之一,它可以定义不同的task来处理任务,然后执行命令完成编译。但是用户发现ant有一个很大的缺陷,不能管理依赖,对于一些第三方的库,在使用的时候都需要手动拷贝到lib目录,这个操作很烦且容易出错。

后来为了解决依赖管理的问题,Apache提出了maven,maven最大的改进在于提出了仓库的概念。开发者可以把所有的依赖都放到仓库中,在项目的管理文件中,我们只需要标注需要什么包,什么版本,maven就自动去对一个的仓库寻找,并且打包到项目中来。对应了pom.xml文件中dependency节点。maven改进了ant的target,增加了编译的生命周期等一些新概念。

maven已经可以满足大部分工程的构建,但是由于maven也是xml文件配置的,语法不够简单,不能实现随意的编程设置,并且maven不支持自定义任务,只能以插件的方式工作。Gradle在maven基础上出现了,它充分利用了maven的资源,继承了maven的中央仓库。比起xml的写法,gradle使用面相对象的语言来编程脚本,可以像编写其他程序一样来编写编译工具。后来有一些开发工具开始集成gradle作为编译工具,更进一步简化了gradle脚本的使用,创建项目会帮开发者自动创建基本的脚本代码。而我们如果想添加一个第三方依赖,只需要在build.gradle的dependencies方法中添加一条引用的字符串,IDE就能自定帮我们从指定的仓库中下载。这就是我们现在使用的工具了。

2.Gradle的编译流程

1.初始化阶段

gradle支持单项目和多项目的构建,在初始化阶段,gradle决定哪些项目将会参与构建,并为每个项目创建Project对象实例。同时在初始化阶段会更加settting.gradle文件创建一个Setting对象,来配置多项目的Project。

2.配置阶段

在配置阶段,gradle会分别在每个Project上执行build.gradle,并配置Project对象。

 3.执行阶段

在执行阶段,gradle会判断配置阶段创建的哪些Task将要执行,然后执行选中的Task。

响应构建生命周期

在gradle的执行生命周期过程,可以给build.gradle添加一些方法从而添加我们自己的处理流程,如:afterEvaluate()方法来给一个添加一个closure,这个方法添加一个listener监听一个项目的build的过程,在build之后就会执行。

  1. afterEvaluate {
  2. def assemble = project.tasks.findByName("assemble")
  3. println("task-name:"+assemble+"---1enable:${assemble.enabled}")
  4. assemble.setEnabled(false)
  5. println("task-name:"+assemble+"---2enable:${assemble.enabled}")
  6. println("size:"+set.size())
  7. println("ext--name==${ext.name}")
  8. }

Build Lifecycle 构建流程,生命周期

3.Gradle中一些基本的类

Gradle类

        Gradle是基础类,通过project对象的getGradle可以获取到对象,它提供了几个访问项目的方法,以及一些添加回调的方法。比如:allprojects(action)、afterProject。

Project类

Project是使用Gradle最主要的类之一,我们每个build.gradle文件在编译的过程都会被组装成一个project对象实例。提供了基础的访问项目的方法,我们常用的

buildscript、dependencies都是它的方法。

Task类

Task代表了gradle系统中的一个原子操作,比如编译class文件或者生成一个doc文件。没一个Task都属于一个project,我们可以使用一些简单的方法创建task对象。

  1. task myTask
  2. task myTask { configure closure }
  3. task myTask(type: SomeType)
  4. task myTask(type: SomeType) { configure closure }

其中task关键字,实际上时一个方法

Gradle系统提供的一些基础的Task操作:assemble、delete、clean、build

        https://docs.gradle.org/current/userguide/base_plugin.html#sec:base_tasks

Task之间是可以相互依赖的,某个task的执行可能要依赖于某个task的结果。

        截取官网gradle介绍的两个不同的gradle任务,他们的Task依赖关系图:

 Task Action

每个Task由一些列的有序的Action组成,当一个task执行的时候会调用action的execute执行。我们在build.gradle文件中一般可以通过

Task.doFirst(org.gradle.api.Action) or Task.doLast(org.gradle.api.Action)添加一个Action来执行我们的代码逻辑。

Setting类

Setting官方定义:声明实例化和配置参与构建的项目实例的层次结构所需的配置。我们通常使用Setting的两个功能:

1.组织多moudle项目的项目结构,让gradle编译时区别是单项目构建还是多项目构建。

include ':flutter_xxx'

2.设置一些属性

除了此接口的属性之外,settting对象还为设置脚本提供了一些额外的只读属性。包括:buildCacheextensionspluginsrootProject等。

3.设置Gradle

Gradle提供了一些通过属性和方法设置编译环境的方式。这些设置能够改变编译的参数,根据我们的需求优化编译效果,提高编译速度等。

Gradle属性

我们在版本控制中保存一些设置比如 JVM 内存配置和 Java 主目录位置,以便整个团队在一致的环境中工作。我们只需要在gradle.properties文件中添加一些属性即可。

  1. gradle.properties中的一些属性设置,有一些是Android组件自定义的。
  2. # 这里是 robolectric 单元测试读取资源用的
  3. android.enableUnitTestBinaryResources=true
  4. base.dir=../Base/base
  5. structure.dir=../Structure/structure
  6. account.dir=../Account/account
  7. common.dir=../Common/common
  8. ad.dir=../Ad/ad

 Gradle提供的其他一些设置

org.gradle.caching=(true,false)

当设置为 true 时,Gradle 将在可能的情况下重用任何先前构建的任务输出,从而使构建速度更快

org.gradle.caching.debug=(true,false)

设置为 true 时,每个任务的单个输入属性哈希值和构建缓存键都会记录在控制台上

org.gradle.daemon=(true,false)

当设置为 true 时,Gradle 守护程序用于运行构建。默认为真

org.gradle.java.home=(path to JDK home)

为 Gradle 构建过程指定 Java 主目录。该值可以设置为 jdk 或 jre 位置,但是,根据您的构建功能,使用 JDK 更安全

org.gradle.jvmargs=(JVM arguments)

指定用于 Gradle 守护程序的 JVM 参数。该设置对于为构建性能配置 JVM 内存设置特别有用,这不会影响 Gradle 客户端 VM 的 JVM 设置

其他设置

设置系统属性、环境变量、以及CI触发执行等,还可以设置通过http代理访问等。

官网设置

Build Environmenticon-default.png?t=LA46https://docs.gradle.org/current/userguide/build_environment.html

4.Gradle插件

apply plugin:java 表示应用了gradle的java插件

apply plugin :MyClass 表示应用指定的class实现的插件。

插件的实现

1.gradle文件插件的实现

通过定义一个如config.gradle脚本文件的方式添加一个脚本插件。这个方式的插件简单方便,但是,这种插件在构建脚本之外不可见,我们不能在定义他的项目外重用。

2.buildSrc方式插件的实现

添加buildSrc moudle的方式,可以让一个项目的多个module使用。

可以将插件的源代码放在 rootProjectDir/buildSrc/src/main/java 目录(或 rootProjectDir/buildSrc/src/main/groovy 或 rootProjectDir/buildSrc/src/main/kotlin)具体取决于我们用哪种语言开发,同时我们在build.gradle中指定资源的位置。Gradle 自动编译和测试插件,这种插件在我们的项目子项目可以使用。

3.独立可发布的插件

创建一个插件,写几行简单的代码,然后使用发布命令发布到本地的maven仓库,通过classpath的方式引入插件,然后通过apply plugin:插件名称 方式使用插件。

通过classpath引入发布的可重用插件

通过将插件添加到构建脚本类路径然后应用插件,可以将已发布为外部 jar 文件的二进制插件添加到项目中。可以使用 buildscript {} 块将外部 jar 添加到构建脚本类路径中,如构建脚本的外部依赖项中所述。

官方示例:加载在指定的插件存储库gradlePluginPortal这个maven地址中的插件。

 代码示例

  1. public class DiguaPlugin :Plugin<Project> {
  2. override fun apply(target: Project) {
  3. target.tasks.forEach {
  4. println("DiguaPlugin--task--${it.name}")
  5. }
  6. target.task("androidReaderTask"){
  7. it.doLast {
  8. println("android reader apply doLast Task")
  9. }
  10. }
  11. target.afterEvaluate {
  12. println("AndroidReader--自定义gradle插件 end")
  13. }
  14. }
  15. }

定义一个插件类,实现了Plugin接口,这个接口只有一个apply方法。

发布插件设置

  1. //引入发布的依赖
  2. dependencies {
  3. implementation gradleApi()
  4. implementation localGroovy()
  5. }
  6. //设置发布组件的Task
  7. uploadArchives {
  8. repositories {
  9. mavenDeployer {
  10. //本地的Maven地址设置为
  11. repository(url: uri('../repo'))
  12. pom.groupId = 'com.digua.android.plugin'
  13. pom.artifactId = 'diguaPlugin'
  14. pom.version = '1.0.6'
  15. }
  16. }
  17. }

在main目录下创建resource目录:

 注意:这里的properties文件的名称就是最后我们apply或者plugins访问插件的名称

 

 最后根据需求编写完成插件后执行对应的uploadArchives的Task就可以发布到对饮的仓库中。

使用组件

 设置maven仓库,然后在classpath中添加gradle插件的路径。调用apply plugin:"pluginName"或者plugins中添加properties文件的名称这样就能访问到插件了。

5.探讨

1.没有Gradle如何编译项目?

2.Gradle还有哪些不常见的特性?

3.Gradle在项目编译中有什么可以改进的地方?

比如可以修改gradle.properties中的设置,根据需要调整编译配置:

比如修改org.gradle.jvmargs = -Xmx1536M,这个可以修改gradle编译VM的内存,影响到编译的速度。因为保活进程会保存每次编译的一些信息,内存大能够保存更多的编译信息,下次编译的时候不需要重新加载文件,直接提高了编译速度。同时也会影响增量编译等。

4.如何能通过修改优化gradle脚本,优化编译速度?

比如我们可以获取所有的Task(./gradlew tasks),然后通过编写条件,过滤掉一些编译不太重要的task,从而加快编译速度。

拓展

Gradle保活进程如何提高编译速度?

The Gradle Daemon

 参考文献

深入理解Android之Gradle_Innost的专栏-CSDN博客_android gradle

Gradle开发快速入门——DSL语法原理与常用API介绍 - Paincker | Paincker

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/445053
推荐阅读
相关标签
  

闽ICP备14008679号