10

Gradle 创建 Task 的写法不是 Groovy 的标准语法吧?

 4 years ago
source link: https://www.bennyhuo.com/2021/04/12/gradle-task-syntax/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Gradle 创建 Task 的写法不是 Groovy 的标准语法吧?

发表于 2021-04-12 | 阅读次数: 17
本文字数: 5.1k | 阅读时长 ≈ 8 分钟

任务名居然是以标识符的形式写出来的,你们难道没有觉得奇怪吗?

本文配套视频在已在B站上传,感谢大家的关注和支持!

你一定写过这样的代码来创建一个 Task:

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

它定义了一个叫做 “clean” 的任务,这个任务的类型是 Delete。

其中 Delete 是一个类的名字,这是 Groovy 的语法,相当于 Delete.class。这个还好,至少人家语法上支持这样做。

后面的 { … } 有 Kotlin 经验的小伙伴们自然也不会觉得陌生,这肯定是接收一个 Lambda (在 Groovy 当中就是 Closure)作为参数,里面的 delete rootProject.buildDir 则等价于 delete(rootProject.buildDir),这也是 Groovy 的语法,在 Groovy 当中只要不引起歧义,函数的调用是可以去掉括号的,类似的例子有很多:

dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
...
}

这里的 classpath 也是如此。

这都很容易理解。那么问题来了,task clean(...){ ... } 这是个什么语法?我们定义一个名叫 “clean” 的任务,这个任务名不应该是一个字符串字面量吗,但现在按照 Groovy 的语法,它应该等价于 task(clean(...){ ... }) ,这个 clean 看上去其实是个方法名,而不是一个常量。

如果大家跟我一样一开始就绞尽脑汁地去研究这个玩意究竟是什么 Groovy 语法,那你从一开始就错了。这个答案直到我们在翻阅 Gradle 源码的时候,看到有一个叫做 TaskDefinitionScriptTransformer 的类,这个类在 Gradle 脚本编译运行的第二个阶段时被调用,它和其他几个类似的 Transformer 一样,作用就是对源代码的语法树做了一些转换。

大家在 Gradle 源码当中找到这个类之后就会发现,注释已经写的非常清晰了,例如:

if (args.getExpression(0) instanceof MapExpression && args.getExpression(1) instanceof VariableExpression) {
// Matches: task <name-value-pairs>, <identifier>, <arg>?
// Map to: task(<name-value-pairs>, '<identifier>', <arg>?)
transformVariableExpression(call, 1);
} else if (args.getExpression(0) instanceof VariableExpression) {
// Matches: task <identifier>, <arg>?
transformVariableExpression(call, 0);
}

通过注释我们可以看到,task 实际上是被当做函数来调用的,我们也确实可以在 Project 当中找到它的定义:

image-20210411072516707

这个映射实际上就是给 identifier 加了个引号,变成字符串字面量。注意到 transformVariableExpression(call, 1); 的第二个参数 1 对应的就是 <identifier>,第二个分支里面的位置则是 0。

这个方法的实现也很显而易见:

private void transformVariableExpression(MethodCallExpression call, int index) {
ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
//拿到 identifier 对应的表达式
VariableExpression arg = (VariableExpression) args.getExpression(index);
if (!isDynamicVar(arg)) {
return;
}

// Matches: task args?, <identifier>, args? or task(args?, <identifier>, args?)
// Map to: task(args?, '<identifier>', args?)
String taskName = arg.getText(); // 表达式的内容就是任务名
call.setMethod(new ConstantExpression("task"));
// 创建一个以任务名为内容的字符串字面量
args.getExpressions().set(index, new ConstantExpression(taskName));
}

除了这个转换以外,还有很多其他的情况,现在我们的问题是文章一开始提到的 task clean(...){ ... }应当属于那种转换?属于嵌套方法调用的转换。前面我们已经分析到这个写法其实可以等价于 task(clean(...){ ... }),对应的转换在 maybeTransformNestedMethodCall 方法当中给出了实现,我们摘录一部分给大家了解一下:

private boolean maybeTransformNestedMethodCall(MethodCallExpression nestedMethod, MethodCallExpression target) {
...
// Matches: task <identifier> <arg-list> | task <string> <arg-list>
// Map to: task("<identifier>", <arg-list>) | task(<string>, <arg-list>)
Expression taskName = nestedMethod.getMethod();
Expression mapArg = null;
List<Expression> extraArgs = Collections.emptyList();

if (nestedMethod.getArguments() instanceof TupleExpression) {
TupleExpression nestedArgs = (TupleExpression) nestedMethod.getArguments();
if (nestedArgs.getExpressions().size() == 2 && nestedArgs.getExpression(0) instanceof MapExpression && nestedArgs.getExpression(1) instanceof ClosureExpression) {
// Matches: task <identifier>(<options-map>) <closure>
mapArg = nestedArgs.getExpression(0);
extraArgs = nestedArgs.getExpressions().subList(1, nestedArgs.getExpressions().size());
} else {
...
}
}

target.setMethod(new ConstantExpression("task"));
ArgumentListExpression args = (ArgumentListExpression) target.getArguments();
args.getExpressions().clear();
// 如果有 map 参数,放到第一个
if (mapArg != null) {
args.addExpression(mapArg);
}
// 注意,taskName 被当做参数传入
args.addExpression(taskName);
// 剩下的参数
for (Expression extraArg : extraArgs) {
args.addExpression(extraArg);
}
return true;
}

mapArg 是否为 null,对应了 task 方法的两个重载版本:

Task task(String name, Closure configureClosure);
Task task(Map<String, ?> args, String name, Closure configureClosure);

这么来看,文章开头提到的创建任务的写法,实际上相当于:

task(type: Delete, "clean") {
delete rootProject.buildDir
}

其他类似的 Transformer 大家可以自行分析。


C 语言是所有程序员应当认真掌握的基础语言,不管你是 Java 还是 Python 开发者,欢迎大家关注我的新课 《C 语言系统精讲》:

扫描二维码或者点击链接《C 语言系统精讲》即可进入课程

program_in_c.png


Kotlin 协程对大多数初学者来讲都是一个噩梦,即便是有经验的开发者,对于协程的理解也仍然是懵懵懂懂。如果大家有同样的问题,不妨阅读一下我的新书《深入理解 Kotlin 协程》,彻底搞懂 Kotlin 协程最难的知识点:

扫描二维码或者点击链接《深入理解 Kotlin 协程》购买本书

understanding_kotlin_coroutines.png


如果大家想要快速上手 Kotlin 或者想要全面深入地学习 Kotlin 的相关知识,可以关注我基于 Kotlin 1.3.50 全新制作的入门课程:

扫描二维码或者点击链接《Kotlin 入门到精通》即可进入课程

exported_qrcode_image_256.png


Android 工程师也可以关注下《破解Android高级面试》,这门课涉及内容均非浅尝辄止,除知识点讲解外更注重培养高级工程师意识:

扫描二维码或者点击链接《破解Android高级面试》即可进入课程

15520936284634.jpg


Recommend

  • 52
    • www.jianshu.com 6 years ago
    • Cache

    Gradle 之语言基础 Groovy - 简书

  • 20
    • www.cnblogs.com 5 years ago
    • Cache

    Gradle+Groovy提高篇

    创建自定义任务 打开 build.gradle 文件,并在末尾添加以下内容: println "1" task howdy { println "2" doLast { println "Howdy" } } println "3"...

  • 10
    • blog.csdn.net 4 years ago
    • Cache

    Gradle之Groovy

    作为一名Android开发者,每天使用AndroidStudio,对于项目中build.gradle文件肯定不陌生,里面有各种各样的配置。对于一些常用的配置我们肯定烂熟于心,不过有时候去看一些大厂的代码的时候,经常会发现他们的项目中有很多的gradle的代码,我们往往因为不了解这...

  • 5
    • www.androidchina.net 4 years ago
    • Cache

    Gradle入门:创建二进制发布版本

    在创建了一个实用的应用程序之后,我们可能想将其与他人分享。其中一种方式就是创建一个可以从网站上下载的二进制文件。 这篇教程描述了如何创建一个二进制发布版本,满足以下需求: 二进制发布一定不能使用所谓的“fat jar”方式。换句话说,...

  • 6

    Vue第二波ref语法提案来袭 这次会进入到标准吗?其实之前Vue3做过好多次语法糖的提案,最经典的莫过于<script setup>提案。但一开始这个提案夹杂着...

  • 7

    Vue3 Ref 语法糖,告别 .value 的写法近期,Vue3 提了一个 Ref Sugar 的 RF...

  • 4

    Go 作为一种编译型语言,经常用于实现后台服务的开发。由于 Go 初始的开发大佬都是 C 的老牌使用者,因此 Go 中保留了不少 C 的编程习惯和思想,这对 C/C++ 和 PHP 开发者来说非常有吸引力。作为编译型语言的特性,也让 Go 在多协程环境下的性能有不俗的表现。

  • 7
    • zhiqiang.org 3 years ago
    • Cache

    BASH 基础语法和常用写法

    1、变量和引用变量 # 注意等号前面不能有空格! var1= 'var1' var2= 200 echo ${var1} ${var2} var3= "$var1, ${var2}" 2、字符串 # 单引号是原样输出,不支持转义符。 var4= '${var3}' #...

  • 3
    • blog.51cto.com 2 years ago
    • Cache

    Gradle语法之再解闭包

    闭包中有三个属性,this,owner,delegete。可在闭包中直接获取。 this:代表定义当前闭包的类。 如果是内部类,就是只想内部类名。 owner:代表定义当前闭包的类或者闭包。 ...

  • 6

    本篇内容为Groovy学习第32篇,学习Groovy语法中的提升与强制转换相关知识点。(Promotion和coercion)学习在Groovy中的各种数据类型的各种强制转换和类型变换。如果不了解Groovy中的数据时如何进行转换的,那么可以学习一下本篇内容,应该能够给你一些...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK