Gradle增量编译二增删修改文件查找

前言:增量编译本质上需要解决两个问题,第一个是增删修改文件的检测,第二个是文件的依赖关系检索,其中增删修改文件的检测又是整个增量编译的基础,是最核心最关键的环节,本文将会给大家介绍下怎么通过Gradle去检测增删修改文件

目前Gradle最新版本是5.4,在5.4版本以前Gradle通过IncrementalTaskInputs这个API来检测增删修改文件的,但是由于这个API存在着一些弊端5.4之后已经被标记为deprecated了,后面可能还会删除掉此接口,所以这里我们只介绍新的API方法。5.4之后Gradle提供了 InputChanges @Incremental @SkipWhenEmpty 这几个API来实现增删修改文件的检测能力,下面介绍下如何去使用这些API。

首先写一个自定义Task,Task的Action Method必须有一个类型为InputChanges的参数,Gradle会通过这个参数来告诉用户文件是增加了还是被删除了还是被修改了,除此以外Task必须通过@Incremental来定义至少一个输入参数,通常是个目录名称等可以通过RegularFileProperty, DirectoryProperty 又或者 ConfigurableFileCollection来指定
。当Task Action被执行的时候,我们就可以通过InputChanges提供的getFileChanges() 方法来获取到哪些文件是被修改过了,说了这么多了,是不是有种一脸懵逼的感觉了,下面我们还是来写一些简单的例子来演示下Incremental Task的使用吧。

abstract class IncrementalTask extends DefaultTask {
    @Incremental
    @PathSensitive(PathSensitivity.NAME_ONLY)
    @InputDirectory
    abstract DirectoryProperty getInputDir()

    @OutputDirectory
    abstract DirectoryProperty getOutputDir()

    @TaskAction
    void taskAction(InputChanges inputChanges) {
        println(inputChanges.incremental
                    ? 'Executing incrementally'
                    : 'Executing non-incrementally'
                )
        inputChanges.getFileChanges(inputDir).each { change ->
            //跳过目录
            if (change.fileType == FileType.DIRECTORY) return
            println "${change.changeType}: ${change.normalizedPath}"
            //outDir查找这个文件.
            def targetFile = outputDir.file(change.normalizedPath).get().asFile
            //如果文件是被移出了outDir也对应的移出下.
            if (change.changeType == ChangeType.REMOVED) {
                targetFile.delete()
            } else {
                //把file写到outDir下面
                targetFile.text = change.file.text
            }
        }
    }
}

IncrementalTask定义了两个属性,一个是inputDir 另外一个是outputDir,taskAction是Task被执行的时候回调的方法,这个Task很简单,接收一个inputDir参数,并且把inputDir下面的所有文件写到对应的outDir目录下面,代码里面我也给出了注释了,下面我们再实现下这个抽象的接口(Tips: Gradle里面的type就类似于java的extends,譬如我们常见的task collectApks(type: Copy) 可以理解为继承下Copy接口,然后重写了from to来实现一些拷贝的功能)

task fileIncrementalTask(type: IncrementalTask) {
    inputDir = file('inputs')
    outputDir = file("$buildDir/outputs")
}

我们的workspace目录结构大致如下

├── build.gradle
└── inputs
    ├── 1.txt
    ├── 2.txt
    └── 3.txt

最后我们执行这个task看下输出结果

[nls:gradle nls$ gradle -q fileIncrementalTask
Executing non-incrementally
ADDED: 1.txt
ADDED: 2.txt
ADDED: 3.txt
[nls:gradle nls$ 

第一行打印出了Executing non-incrementally了,InputChanges的incremental属性是个boolean类型,当为true的时候可以理解为有文件被修改了,当为false的时候调用inputChanges.getFileChanges,所有文件会被告知为ADDED(文件不一定真的被ADDED进来 这里可以简单理解为没有发生增量,所有文件均被视为有被修改)更多关于incremental属性的说明可以参考官方文档incremental 。接着后面的inputChanges.getFileChanges是用来获取哪些文件有被修改过的,1.txt 2.txt 3.txt 都是新增加的文件,被打印出来了,然后我们再次执行下Task看下输出结果

[nls:gradle nls$ gradle  fileIncrementalTask

BUILD SUCCESSFUL in 0s
1 actionable task: 1 up-to-date
[nls:gradle nls$ 

这次的执行没有任何的输出了,因为我们没有修改过任何的文件,我们尝试着修改下1.txt 的文件内容然后再次执行Task

[nls:gradle nls$ gradle -q fileIncrementalTask
Executing incrementally
MODIFIED: 1.txt
[nls:gradle nls$ 

可以看见这次的执行,Gradle检测出来了 1.txt文件内容被修改了,现在我们尝试着增加一个 4.txt 文件 然后再执行下Task

[nls:gradle nls$ gradle -q fileIncrementalTask
Executing incrementally
ADDED: 4.txt
[nls:gradle nls$ 

增加的4.txt文件也被检测出来了,现在再试下删除掉2.txt 文件然后再执行下Task

[nls:gradle nls$ gradle -q fileIncrementalTask
Executing incrementally
REMOVED: 2.txt
[nls:gradle nls$ 

同样的2.txt 文件的删除操作也是可以被检测到了

总结下:首次进行增量Task构建的时候,肯定会先进行一次全量的构建(因为Gradle需要把状态记录下来,首次构建的时候由于没有前一次的构建记录所以肯定会执行全量的构建),再次执行构建的时候Gradle没检测到任何文件被修改的记录,所以没有任何的输出了,当我们修改了文件的内容的时候,被修改的文件就会被检测出来,同样的当我们增加或者删除一个文件的时候,增删的文件照样是可以被Gradle检测出来,Gradle内部就是通过这种方式去检测构建项目的增删修改文件的。

理想情况下,所有增删修改都能正常被检测出来,那么有没有非正常的情况吗?能这样提问的当然是有的了,下面再说说几种会影响到增量构建的情况。

  • 没有前一次的构建历史记录(上面已经提到过了)
  • 前一次的构建跟这次的构建Gradle的版本不一致
  • upToDateWhen 被设置为false了
  • 跟上一次构建对比,某个输入属性发生了变化
  • 跟上一次构建对比,某个non-incremental输入属性发生了变化
  • 一个或者多个输出文件发生了变化

有发生以上的这些情况,调用inputChanges.getFileChanges时,Gradle把所有input file标记为ADDED并且返回(就是说增量失效的意思了)。下面我们一一来验证下。(1、2、4这两种情况就不去验证了)

跟上一次构建对比,某个non-incremental输入属性发生了变化

还是用上面的例子,我们增加一个non-incremental的属性inputDir1,如下

abstract class IncrementalTask extends DefaultTask {
    @Incremental
    @PathSensitive(PathSensitivity.NAME_ONLY)
    @InputDirectory
    abstract DirectoryProperty getInputDir()

    @InputDirectory
    abstract DirectoryProperty getInputDir1()
    //省略下面的代码了
}

task fileIncrementalTask(type: IncrementalTask) {
    inputDir = file('inputs')
    inputDir1 = file('inputs1')
    outputDir = file("$buildDir/outputs")
}

然后再增加一个inputs1目录,结构如下

├── build.gradle
└── inputs
    ├── 1.txt
    ├── 2.txt
    └── 3.txt
└── inputs1
    ├── 1.txt

我们先执行一遍Task,再次执行的时候Gradle没有任何输出,因为没有检查到任何修改内容

[nls:gradle nls$ gradle  fileIncrementalTask

BUILD SUCCESSFUL in 0s
1 actionable task: 1 up-to-date
[nls:gradle nls$ 

当我们每次修改inputs1下面的1.txt 的时候,再去执行下Task

[nls:gradle nls$ gradle -q fileIncrementalTask
Executing non-incrementally
ADDED: 1.txt
ADDED: 3.txt
ADDED: 4.txt
[nls:gradle nls$ 

可以清楚得看得到,虽然这里我们的inputs1参数只是作为传参,没有参与Task的任何逻辑处理或运算,但是一旦inputs1的内容被修改了,将会影响到整个Task的Incremental判断。

upToDateWhen 被设置为false了

我们把task的upToDateWhen强制设置为false,然后多次执行Task观察下结果,如下

task fileIncrementalTask(type: IncrementalTask) {
    inputDir = file('inputs')
    inputDir1 = file('inputs1')
    outputDir = file("$buildDir/outputs")
    outputs.upToDateWhen {
        false
    }   
}
[nls:gradle nls$ gradle -q fileIncrementalTask
Executing non-incrementally
ADDED: 1.txt
ADDED: 3.txt
ADDED: 4.txt
[nls:gradle nls$ gradle -q fileIncrementalTask
Executing non-incrementally
ADDED: 1.txt
ADDED: 3.txt
ADDED: 4.txt
[nls:gradle nls$ 

可以看到,不管妳执行多少次Task,Incremental都是失效的。

一个或者多个输出文件发生了变化

这个就比较简单了,我们只要修改下outputDir下面的文件内容,再执行下Task

[nls:gradle nls$ gradle fileIncrementalTask

BUILD SUCCESSFUL in 0s
1 actionable task: 1 up-to-date
[nls:gradle nls$ gradle -q fileIncrementalTask
Executing non-incrementally
ADDED: 1.txt
ADDED: 3.txt
ADDED: 4.txt
[nls:gradle nls$ 

当我们增加或者删除outputDir下面的文件时,也是会影响到Incremental的正常执行,但是增加一个文件的时候不会受到影响

章结:本章节介绍了Gradle的文件增删修改检测实现以及用法(其原理部分较为复杂,后面我会单独开文章来讲解),Gradle内部也是通过这种方式了检测构建项目中代码文件的变化状态情况,下一节Gradle增量编译系列第三课,我讲会从代码层出发,给大家讲解下,Gradle在检测到增量文件时,又是做了哪些处理,Gradle又是如何去检索项目中代码文件的依赖关系的,下节再见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容