内容简介:Gradle学习(十六)——文件操作
构建的大部分工作是基于文件的,Gradle提供了一些api和概念来帮助你来进行操作
检索文件
你可以通过以项目目录为baseDir的相对路径来检索文件:
//相对路径 File configFile = file 'src/config.cson' //绝对路径 configFile = file configFile.absolutePath //使用相对路径的文件对象 configFile = file new File('src/config.cson') //使用相对路径的java.nio.file.Path对象 configFile = file Paths.get('src', 'config.cson') //使用绝对路径的java.nio.file.Path对象 configFile = file Paths.get(System.getProperty('user.home')).resolve('global-config.cson')
你可以传任意对象给 file()
方法,都会被转为一个绝对路径的 File
对象,通常传入的是String,File或者Path的实例。如果路径的是绝对地址,那么就会直接创建绝对路径的文件,如果路径是相对路径,那么就将项目目录作为baseDir来转换成绝对路径的文件。还要注意一点, file()
方法不会接收标准的URL,比如 file:/some/path.xml
要注意的一点是 new File(somePath)
是基于当前工作目录的,而 file()
是基于项目目录的,具体怎么选择看你自己了。
File collections
file collection
仅仅是一组文件集合,它的代表是 FileCollection
接口,Gradle的API中的很多对象都实现了这个接口,比如dependency configurations。
可以通过 Project.files(java.lang.Object[])
方法来获取 FileCollection
实例,方法可以传入任意数量的对象,然后把这些对象转换成一组 File
对象。 files()
可以接受任意类型的参数,就单个参数来说类似于 file()
方法,除此之外你可以传集合,迭代器,map和数组,它们都会解包成多个的 File
文件。
FileCollection fileCollection = files('src/file1.txt', new File('src/file2.txt'), ['src/file3.txt', 'src/file4.txt'], Paths.get('src', 'file5.txt'))
file collection
是可迭代的,并且可以用 as
操作符,你也可以通过 +
操作符添加文件,或者用 -
操作符来移除文件,如下面的例子所示:
//迭代fileCollection fileCollection.each { // println it.name } //把fileCollection转化为其他类型 Set set = fileCollection.files Set set1 = fileCollection as Set List list = fileCollection as List String path = fileCollection.asPath File file=fileCollection.singleFile File file1=fileCollection as File //添加或者移除fileCollection元素 fileCollection -= files('src/file4.txt') fileCollection += files('src/file4.txt')
你还可以给 files()
方法传个闭包或者 Callable
的实例,当这个集合的上下文被调用时,闭包和Callable的返回值就会转换为一组File实例,它的返回值可以是任何可以传给 files()
的类型。
task listSrc { doLast { File srcDir fileCollection = files { srcDir.listFiles() } srcDir = file('src') println "contents of $srcDir.name" fileCollection.collect { relativePath(it) }.sort().each { println it } srcDir = file('src2') println "contents of $srcDir.name" fileCollection.collect { relativePath(it) }.sort().each { println it } } }
然后执行任务
± % gradle listSrc -q !1846 contents of src src/config.cson src/file1.txt src/file2.txt src/file3.txt src/file4.txt src/file5.txt contents of src2 src2/dir1
还有一些其他的你可以传给 files()
的类型:
-
FileCollection
把FileCollection解包成多个File实例并且把它们包含到fileCollection
中 -
Task
任务的输出文件列表被包含到fileCollection
中 -
TaskOutputs
TaskOutputs
的输出文件被包含到fileCollection
中
一定要注意的一点是 fileCollection
是惰性求值的,意味着你可以先定义一个 fileCollection
,然后在集合中的文件在将来使用的时候再进行创建并使用。
File trees
file tree
是层级结构排列的文件集合。比如,它可以表示目录树也可以表示ZIP文件的内容。它的代表是 FileTree
接口,这个接口是从 FileCollection
接口继承的,因此你可以像使用 FileCollection
那样使用它,Gradle中的很多对象都实现了这个接口,比如 source sets
获得 FileTree
实例的一种方式就是通过 Project.fileTree(java.util.Map)
方法,它可以使用一个baseDir创建 FileTree
,还可以使用ant模式 include
和 exclude
进行操作。
//使用一个baseDir来创建fileTree FileTree tree = fileTree(dir: 'src') tree.include '**/*.cson' tree.exclude '**/*.txt' //使用path创建fileTree tree = fileTree('src').include('**/*.cson') //使用闭包创建fileTree tree = fileTree('src') { include '**/*.txt' } //使用map创建fileTree tree = fileTree dir: 'src', include: '**/*.cson' tree = fileTree dir: 'src', includes: ['**/*.cson', '**/*.txt'] tree = fileTree dir: 'src', include: '**/*.cson', exclude: '**/*.txt'
你可以像操作 file collection
一样操作 file tree
,你还可以通过visit访问它,或者通过模式来选择它的子层 file tree
task treeView { doLast { tree = fileTree('src2') //tree的迭代 tree.each { println it } //通过pattern找到子层的tree def subtree = tree.matching { include '**/*.txt' } //合并两个tree tree+=fileTree 'src' //访问tree的元素 tree.visit { println "$it.relativePath => $it.file" } } }
注意, file tree
会默认 exclude
掉一些文件:
**/*~ **/#*# **/.#* **/%*% **/._* **/CVS **/CVS/** **/.cvsignore **/SCCS **/SCCS/** **/vssver.scc **/.svn **/.svn/** **/.DS_Store **/.git **/.git/** **/.gitattributes **/.gitignore **/.gitmodules **/.hg **/.hg/** **/.hgignore **/.hgsub **/.hgsubstate **/.hgtags **/.bzr **/.bzr/** **/.bzrignore
用归档文件的内容创建fileTree
你可以使用归档文件创建fileTree,比如zip,tar文件,分别对应 Project.zipTree(java.lang.Object)
和 Project.tarTree(java.lang.Object)
,这两个方法将返回的 FileTree
实例可以像其他 file tree
和 file collection
那样使用,比如你可以通过复制 FileTree
的内容来实现解压,还可以合并归档文件。
task archiveView { doLast { //使用zip文件创建FileTree tree = zipTree('archive/CineGIF.zip') //使用tar文件创建FileTree tree = tarTree('someFile.tar') //有时候你需要先将gz先解压成tar格式 tree=tarTree(resources.gzip('archive/tools.tar.gz')) } }
指定输入文件集合
在Gradle中有很多对象都有可以接收一组输入的属性。比如 JavaCompile
任务的 source
属性,它定义了可以需要被编译的源文件。你可以使用上面的不同参数 file()
方法那样来设置这个属性的值,这意味着你可以使用的参数有文件,字符串,FileCollection,还有闭包。
还有一个和属性同名的方法 source
一样也可以使用,和 files()
方法一样使用
使用属性指定
//使用文件 compileJava { source = file('src/main/java2') } //使用字符串 compileJava { source = 'src/main/java2' } //使用集合 compileJava { source = ['src/main/java2', 'src2'] } //使用FileCollection或者FileTree compileJava { source = fileTree('src/main/').matching { include 'java2/*' } } //使用闭包 compileJava { source = { file('build').listFiles().findAll { it.name.endsWith('zip') }.collect { zipTree(it) } } }
使用方法指定
compileJava { source 'src/main/java2', 'src/main/groovy' source 'src/main/java2' source fileTree('src/main/').matching { include 'java2/*' } }
复制文件
你可以使用 Copy
任务来复制文件,而且非常灵活,你可以使用过滤器来过滤需要复制的文件,映射文件的名字。
为了使用 Copy
任务,你需要指定需要复制的一组文件,并且指定需要复制到的目录,你还可以指定在复制文件时如何进行转换,你可以使用Copy规范来实现他们,Copy规范的代表是 CopySpec
接口, Copy
任务实现了这个接口,你可以使用 CopySpec.from(java.lang.Object[])
方法指定源文件集合,可以用 CopySpec.into(java.lang.Object)
指定输出目录。
task copyTask(type: Copy) { from 'src/main/java2' into 'dst' }
from()
接受的参数种类和 files()
一样,当参数是一个目录是,则目录下的所有文件(不包括本目录)将递归的复制到目标目录。当参数是一个文件时,那么这个文件将会被复制到目标目录,如果参数对应的文件不存在,那么参数将会被忽略掉,当参数是个任务,那么该任务的输出文件将会被拷贝到目标目录,并且这个任务将会自动增加为当前任务的依赖。 into()
接受的参数种类和 files()
一样.示例如下:
task anotherCopyTask(type: Copy) { //递归目录所有文件 from 'src2/dir1' //单个文件 from 'src2/bb.txt' //任务的输出文件 from copyTask //任务输出的文件 from copyTask.outputs def destdir //闭包惰性求值 into { destdir } destdir = file 'dist2' }
你还可以通过ant格式的include模式和exclude模式来选择需要拷贝的文件
task copyTaskWithPatterns(type: Copy) { from 'src' into 'dist2' include '**/*.txt' include '**/*.java' exclude { it.file.name.startsWith('file') && it.file.text.contains('label') } }
你还可以使用工程的 Project.copy(org.gradle.api.Action)
方法来拷贝文件,和使用Copy任务的方式差不多的,但是最大的区别就是 copy()
方法不支持增量构建
task copyMethod { doLast { copy { from 'src' into 'dist2' include '**/*.txt' include '**/*.java' exclude { it.file.name.startsWith('file') && it.file.text.contains('label') } } } }
还有一个主要的区别就是 copy()
方法中 from()
的参数是Task对象时,无法被自动添加依赖,因为它是个方法而不是一个任务。这种情况下只能手动的添加输入输出,支持增量构建的同时支持任务依赖的推断,上一章也讲过了任务依赖推断就是增量构建的福利,如下:
task copyMethod2 { inputs.files copyTask outputs.dir 'dist3' doLast { copy { from copyTask into 'dist3' } } }
最好就是使用Copy任务,因为它在不需要进行额外操作的情况下就可以支持增量构建和任务依赖推断。而 copy()
方法使用的场景仅仅是在自定义任务的时候作为任务的一部分存在,在这种场景下,记得要声明任务的输入输出,以便支持增量构建和任务依赖推断。
文件重命名
task renameTask(type: Copy) { from 'src' into 'dist4' //用闭包来映射 rename { it.replace('file', 'fileDist') } //用正则 rename 'file(.+)', 'fileDist$1' }
过滤文件
task filter(type: Copy) { from 'src' into 'dist5' expand copyright: '2018', version: '1.0.0' expand project.properties filter { "[$it]" } filter { it.startsWith('//') ? null : it } filteringCharset = 'UTF-8' }
使用 expand()
方法将会将文件中的 ${tokenName}
格式的符号进行替换,这个特性要注意使用,防止和源文件冲突。
filteringCharset
可以指定源文件和目标文件的文件编码格式。
CopySpec类
CopySpec形成了一个层次结构,Copy任务的规范都是从这个类继承来的。
task nestedSpecs(type: Copy) { from('src2') { exclude 'dir1' } exclude '**/*.java' into 'dist7' into('dist6') { from 'src' } }
使用 Sync
任务
Sync
任务是继承是 Copy
任务的,执行的时候会讲源文件拷贝到目标目录,然后把目标目录不是 Sync
任务复制的删除掉,相当于scp和rsync的区别,比较常用的场景有安装程序,解压归档文件,备份项目的依赖。下面的代码就是备份项目依赖的例子:
task libs(type: Sync) { from configurations.runtime into "$buildDir/libs" }
文件归档
项目中常常可以见到很多jar归档文件,你也可以自己给项目添加WAR,ZIP或者TAR归档文件,归档文件可以由不同的归档任务创建:War,Zip,Tar,Jar还有Ear,他们的方式都差不多,我们以ZIP为例子:
apply plugin: 'java' task zipTask(type: Zip) { from 'src' into('libs') { from configurations.runtime } doLast { zipTree("$buildDir/distributions/workingWithFiles.zip").each { println it } } }
java插件为Zip任务增加了一些默认值,这就是你为什么没有看到 into()
方法的原因,只看到个嵌套的 CopySpec
的 into()
方法,归档任务的工作方式和Copy任务一样。像使用Copy任务一样使用它即可。
归档命名
projectName-version.type
是Gradle归档任务的默认格式,例如:
apply plugin: 'java' version = '1.1.0' task myZip(type: Zip) { from 'src' println myZip.archiveName println relativePath(myZip.destinationDir) println relativePath(myZip.archivePath) }
执行任务:
± % gradle myZip -q workingWithFiles-1.1.0.zip build/distributions build/distributions/workingWithFiles-1.1.0.zip
可以看到格式是workingWithFiles-1.1.0.zip,归档的名字可以通过 archivesBaseName
更改。
apply plugin: 'java' version = '1.1.0' task myZip1(type: Zip) { from 'src' baseName 'sweetop' doLast { println myZip1.archiveName } }
然后执行任务:
% gradle myZip1 -q sweetop-1.1.0.zip
还可以进一步的自定义归档文件的名字:
apply plugin: 'java' version = '1.1.0' archivesBaseName = 'gradle' task myZip2(type: Zip) { from 'src' appendix = 'init' classifier = 'src' doLast { println myZip2.archiveName } }
执行任务:
± % gradle myZip2 -q gradle-init-1.1.0-src.zip
归档任务的属性表:
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
archiveName | String |
baseName-appendix-version-classifier.extension
,如果属性是null将不会增加到最终的归档文件的名字 |
生成的归档文件的名字 |
archivePath | File |
destinationDir/archiveName
|
归档文件的绝对路径 |
destinationDir | File |
取决于文件类型,如果是jar或者war,那么在 project.buildDir/libraries
,如果是zip或者tar,那么就是 project.buildDir/distributions
|
归档任务所指向的输出目录 |
baseName | String |
project.name
|
归档文件的baseName位置 |
appendix | String | null | 归档文件的appendix位置 |
version | String |
project.version
|
归档文件的version位置 |
classifier | String | null | 归档文件的classifier位置 |
extension | String | 归档类型:zip,jar,war,tar,tgz,tbz2 | 归档文件的扩展名 |
你可以使用 Project.copySpec(org.gradle.api.Action)
在多个归档任务之间共享内容
可重现的归档过程
有时候你要确保相同代码在不同机器上生成的归档文件从字节这一层都是一样的,这无疑是个极大的挑战,因为不同机器上归档时候的文件顺序都可能不一样,但Gradle为你实现了这一点,只需要添加如下设置即可:
tasks.withType(AbstractArchiveTask) { preserveFileTimestamps = false reproducibleFileOrder = true }
属性文件
在 java 的开发过程中,属性文件会经常见到,Gradle提供了在构建过程中创建属性文件的方法,就是通过 WriteProperties
任务。
WriteProperties
还修复了 Properties.store()
的bug,这个bug会对增量构建造成影响。标准的java会每次都生成一个唯一的属性文件,即使很多时候属性值根本没有改变,因为在注释中包含了时间戳,Gradle的 WriteProperties
任务则不同,它生成的每个属性文件从字节码上都是相同的,做到这样主要是进行了三个调整:
- 没有时间戳的注释增加到属性文件中
-
换行符是依赖于系统的,当然你可以自定义,默认是
\n
- 所有属性按字母列表存储
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。