深入探索Gradle,Task是如何关联起来的?
本文作者
作者:近地小行星
链接:
https://juejin.cn/post/7241492186919239717
本文由作者授权发布。
Task的创建
create register
tasks.create('hello') {
doLast {
println 'greeting'
}
}
tasks.register('hello') {
doLast {
println 'greeting'
}
}
懒加载
create和register的区别
如果是create方式,那Task就会被立即创建,这其实隐含了一个问题--被创建的Task可能并不会被运行,例如在我们想要运行compileJava这个task,build script在eval过程中,将test相关的Task也都创建了。
例如使用register替代create。
可以参考官方文档task_configuration_avoidance。
https://docs.gradle.org/current/userguide/task_configuration_avoidance.html#sec:task_configuration_avoidance_migration_guidelines
Lazy Properties
Provider Property
interface CompileExtension {
Property<String> getClasspath()
}
abstract class Compile extends DefaultTask {
@Input
abstract Property<String> getJdkVersion()
@Input
abstract Property<String> getClasspath()
}
project.extensions.create('compile', CompileExtension)
def a = tasks.register('a', Compile) {
classpath = compile.classpath
jdkVersion = '11'
doLast {
println classpath.get()
println jdkVersion.get()
}
}
compile {
classpath = 'src/main/java'
}
./gradlew a
输出
src/main/java
11
RegularFileProperty
DirectoryProperty
ListProperty
SetProperty
MapProperty
更多内容请参考官方文档lazy_configuration。
https://docs.gradle.org/current/userguide/lazy_configuration.html
NamedDomainObjectCollection
它还有一个namer方法需要重写,这个作用就是用来给添加进来的元素进行命名的。
整体流程
task依赖的resolve task执行顺序的确定
以D作为entry task
D依赖C
C依赖B和A
B依赖A
Task Relationship
task inputs依赖
dependsOn
finalizedBy
mustRunAfter
shoulRunAfter
Task inputs
abstract class A extends DefaultTask {
@OutputFile
abstract RegularFileProperty getOutputFile()
}
def a = tasks.register('a', A) {
outputFile = layout.buildDirectory.file('build/a')
}
tasks.register('b') {
inputs.property('a.outputFile', a.flatMap { it.outputFile })
doLast {
println inputs.properties['a.outputFile']
}
}
def a = tasks.register('a') {
outputs.files('build/a')
}
tasks.register('b') {
inputs.files(a)
}
finalizedBy
def c = tasks.regsiter('c')
tasks.regsiter('d') {
finalizedBy c
}
mustRunAfter/shouldRunAfter
def c = tasks.regsiter('c')
tasks.regsiter('d') {
mustRunAfter c
}
Task Dependency Resolve
ExecutionPlan
public void addEntryTasks(Collection<? extends Task> tasks) {
LinkedList<Node> queue = new LinkedList<>(tasks);
discoverNodeRelationships(queue);
}
private void discoverNodeRelationships(LinkedList<Node> queue) {
Set<Node> visiting = new HashSet<>();
while (!queue.isEmpty()) {
Node node = queue.getFirst();
if (visiting.add(node)) {
node.resolveDependencies(dependencyResolver);
for (Node successor : node.getDependencySuccessors()) {
if (!visiting.contains(successor)) {
queue.addFirst(successor);
}
}
} else {
queue.removeFirst();
visiting.remove(node);
for (Node finalizer : node.getFinalizers()) {
finalizers.add(finalizer);
if (!visiting.contains(finalizer)) {
queue.addFirst(finalizer);
}
}
}
}
}
CachingDirectedGraphWalker
findValues 查找从start node可达的nodes findCycles 查找图中存在的环
熟悉强连通图算法Tarjan's strongly connected components algorithm - Wikipedia的同学应该知道它可以用来查找图中的环,强连通的概念本身就是节点间俩俩都能互达,而在有向无环图中是不可能存在的,所以是对算法进行了修改,以便可以找到依赖节点。
http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
调用DefaultTaskDependency.visitDependencies去resolve task的依赖。 调用WorkDependencyResolver将 Task 转化为LocalTaskNode。
依赖resolve
visitDependencies
Task
def a = tasks.create('a')
tasks.register('b') {
dependsOn a
}
Provider
def a = tasks.register('a')
tasks.register('b') {
dependsOn a
}
TaskDependencyContainer
input analysis
概念:
Simple values
基本类型,字符串等实现了Serializable的类型。Filesystem types
File,或者用Project.file()等gradle文件操作生成的对象。Dependency resolution results
依赖裁决的结果,实质上也是文件。Nested values
以上类型的嵌套组合。
作用:
inputs/outputs相关的依赖分析 Incremental Build中up-to-date check
如何给属性标注注解
Input 用以标注一个普通类型。 InputFiles 用以标注是一个输入的文件相关类型。 Nested 用以标注潜套类型。 OutputFiles 用以标注是一个输出的文件相关类型。 Internal 用以标注一个属性是内部使用。
...
等等,具体参考task_input_output_annotations。
https://docs.gradle.org/current/userguide/incremental_build.html#sec:task_input_output_annotations
@Internal这个注解值得多说一句。
例如上面提到的编译时可用最大内存。source files,target jvm version的改变都会影响到class文件的编译结果,但是运行时可用最大内存对编译结果无影响。这种和输入输出无关的属性,对Incremental Build缓存结果不产生影响的结果,可以用这个进行标注。
这也表明@Input,@InputFiles等这些注解标注的属性是对缓存结果有影响的。
class SimpleTask extends DefaultTask {
@Input String inputString
@InputFiles File inputFiles
@OutputFiles Set<File> outputFiles
@Internal Object internal
}
通过给属性加注解的方式。 调用inputs的api添加。
abstract class Compile extends DefaultTask {
@Input
abstract Property<String> getClasspath()
}
tasks.register('compile', Compile) {
classpath = 'src/main'// 1. 属性注解方式
inputs.property('name', 'compile')// 2. inputs添加属性
inputs.files(project.files('libs'))// 3. inputs添加文件
}
gradle如何分析inputs建立的依赖
def e = tasks.register('e', CustomTask) {
inputs.property('prop1', a.flatMap { it.outputFile })
inputs.files(b)
prop2 = c.flatMap { it.outputFile }
prop3 = d.files
}
prop1通过inputs.property的方式依赖task a,a.flatMap返回的是Provider保存了task a的信息,task a本身也是Provider,gradle通过反射调用Task属性的getter的方式可以拿到task a,将其作为依赖。 inputs.files直接依赖了task b,inputs.files(b)实际上是对task b的outputs文件的依赖,和FileCollection处理一致。 prop2依赖了task c,处理方式同prop1。 prop3依赖了task d,d.files返回的是FileCollection,在创建时也保存了task d的信息。
Project依赖导致的Task依赖
plugins {
id 'java'
}
plugins {
id 'java'
}
dependencies {
implementation(project(':libA'))
}
执行顺序
判断队列是否为空。 如果为空则结束,保存结果的set的顺序就是排序结果。 如果不为空,取队列中第一个node。 node是否已经存在结果set中了,存在的话直接移除队列中的node,重复步骤1。 node状态是否为“搜索中”,如果搜索过node则将其保存到结果的set中,并移除队列中的node,重复步骤1,否则标记当前node。 node的直接依赖结点successors。 如果node的successors中存在状态为“搜索中”,那么表示DAG图有环,进行报错提示。 将node的successors全部添加到队列中,回到步骤1判断队列是否为空。
void processNodeQueue() {
while (!queue.isEmpty()) {
final Node node = queue.peekFirst();
if (result.contains(node)) {
queue.removeFirst();
visitingNodes.remove(node);
continue;
}
if (visitingNodes.put(node)) {
ListIterator<Node> insertPoint = queue.listIterator();
for (Node successor : node.getAllSuccessors()) {
if (visitingNodes.containsEntry(successor)) {
onOrderingCycle(successor, node);
}
insertPoint.add(successor);
}
} else {
queue.removeFirst();
visitingNodes.remove(node);
result.add(node);
}
}
}
finalizedBy引入的依赖,会被加到对应Task的刚好后面一个,例如 a.finalizedBy(b) c.dependsOn(a) 那么b会位于queue中a,c的中间,也就保证了执行的顺序。 如果Task是由mustRunAfter/shouldRunAfter添加的,且没有其他强依赖的方式引用到,是不会被加到结果中的。 成环的判断那里,如果是由于shouldRunAfter造成的会忽略掉。 entry nodes可以是多个,处理多个entry nodes时,每个entry nodes会对应一个segment将不同的node区分开来。
参考文档
Authoring Tasks
https://docs.gradle.org/current/userguide/more_about_tasks.html
Incremental build
https://docs.gradle.org/current/userguide/incremental_build.html
Developing Custom Gradle. Task Types
https://docs.gradle.org/current/userguide/custom_tasks.html
Lazy Configuration
https://docs.gradle.org/current/userguide/lazy_configuration.html
Task Configuration Avoidance
https://docs.gradle.org/current/userguide/task_configuration_avoidance.html
Developing Parallel Tasks using the Worker API
https://docs.gradle.org/current/userguide/worker_api.html
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!