Gradle中的文件操作

File遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//File traversal 闭包形式
task copys(type: Copy) {
def dir = new File('app/src/main')
dir.traverse(
//过滤条件
nameFilter: ~/.*\.jar|.*\.java|.*\.c/
) { file ->
println(file.getName())
from file.canonicalPath
//筛选
include '*Activity.java'
into 'app/libs'
}
}

除了使用闭包的形式来遍历指定文件夹外,还可以使用fileTree()来完成同样的操作:

1
2
3
4
5
6
7
8
task tree(type: Copy) {
def backUp = fileTree('app/build/intermediates/classes/debug') {
//筛选后的file全部注入到backUp形成一个集合
include '**/*.class'
exclude '**/R.class'
}
println(backUp.getFiles().size())
}

这样得到的就是filetree对象了,可以使用传统的for each来遍历其中的每一个file,值得一提的是,生成的fileTree依旧可以继续调用matching()来完成进一步的筛选,很有liquit的感觉哈。

1
2
3
4
backUp.matching {
include {'~/.*\\.jar|.*\\.java|.*\\.c/'}
exclude('**/*.class')
}

值得一提的是:在java中我们经常获取一个collection想要打印出集合内的信息,然而在gradle中生成的fileTree对象本身是不具备集合的系列功能的,不过我们可以通过files()来实现把fileTree”转换成”具备collection特性的对象,files(backUp).files->即可获得到同时具备backup的内容又同时具备collection接口的对象了(Set)

###expand

此方法可用作于模版文件拓展,常用在于配置文件的生成:
有配置文件如下:

1
# # Application configuration file # hostname: ${databaseHostname} appVersion: ${version} locale: en_us initialConnections: 10 transferThrottle: 5400 queueTimeout: 30000 buildNumber: ${buildNumber} buildDate: ${date.format("yyyyMMdd'T'HHmmssZ")}

其中携带$符号的参数都是会随着项目的发布动态改变的,我们可以编写一个如下的task

1
2
3
4
5
6
7
8
9
10
11
task autoGenConfig(type: Copy){
from 'sourceDir'//模版配置文件的路径
include 'templateConfig.properties'//模板配置文件的文件名
into 'build/config'
expand([
databaseHostname: 'aaron.db.info',
version: 1.5,
buildNumber: (int)(Math.random()*1000),
date: new Date()
])
}

只要运行上述的task,最终能生成如下的配置文件:

1
# # Application configuration file # hostname: aaron.db.info appVersion: 1.5 locale: en_us initialConnections: 10 transferThrottle: 5400 queueTimeout: 30000 buildNumber: 77 buildDate 20120105T162959-0700

可见expand()函数对于项目版本升级的管理来说提供了很大的便利。

###文本操作利器Filter

相比于expand()函数提供对文本的轻量级的”置换”功能,filter()函数提供了更为强大的功能,遍历指定文本的每一行进行修改,例如需要为每一行文本添加行号的要求:

1
2
3
4
5
6
7
8
9
10
11
task addLineNum(type: Copy){
int num = 0
into 'targetDir'
from 'sourceDir'
include 'target.md'
rename { it - '.md' + '.java' }//修改后的文件名后缀给覆盖掉
filter{ line->
num++
String.valueOf(num) + line
}
}

###class文件打包jar

1
2
3
4
5
6
7
8
9
10
task buildJar(type: Jar, dependsOn: ['build']) { //build后才有class文件
archiveName = 'my-lib.jar'
from('app/build/intermediates/classes/debug') //class文件的路径
from(project.zipTree("libs/xxx-x.x.x.jar"))//这个同样是把第三方jar文件作为source
destinationDir = file('app/libs') //destinationDir is file not string
// destinationDir = 'app/build/libs' // error
//排除指定class
exclude('com/cool/boy/ugrowthview/BuildConfig.class')
include('com/cool/boy/ugrowthview/*.class')
}

对于gradle的文件系统来说有一个Lazy Files的概念,例如通过fileTree()的形式来获取一个FileRoot方便后续的遍历,正常的思路是只要调用了这个方法就立马对path里面的file进行收集从而形成一个immutable的list对象,而在Gradle中,却因为整个构建过程分为了三个阶段如果一个path下的文件是必须经历了Execution阶段后才能操作的话就麻烦了,所以引入了lazy file的概念,尤其是在操作.class文件的时候,必须要经历build结束后才能有class文件。所以在gradle中的文件集合中的实际内容只有在构建执行到某一个特别的时间点后才会被填充的。

ps: 对于Gradle构建过程来说,分为了三个阶段:

* Initialization: 此阶段确定哪些项目会加入到构建队列中,并且为每一个项目创建一个Project对象出来.
* Configuration: 根据提供的构建脚本生成对应的task对象,引入了configuration on demand的概念(选择性的生成task对象,对于多项目构建系统中减少编译脚本时间是蛮有用的,通过给项目解藕实现的)
* Execution: 根据configuration生成的task来确定需要用到哪些task,然后去执行选中的task,具体被选中的task是根据执行command line时传入的参数来确认的。