学习如何使用Gradle操作文件,第二部分。该学习记录基于Gradle官方网站资料。本篇参考链接如下:
https://docs.gradle.org/current/userguide/working_with_files.html
10.文件目录深度
操作文件,必须首先知道文件路径。就是path。Gradle利用java的标准类File,而且引入了API来操作文件路径集合。
- hard coding的文件路径
ext {
// 固定字符串
destination= "$buildDir/archives"
}
task packageClasses(type: Zip) {
archiveAppendix = "classes"
destinationDirectory = file(destination)
from "classes"
}
- 单一文件与目录
Gradle提供了file方法来取得单一文件的路径。可以指定相对或者绝对路径。相对路径,以工程的根目录为参照。注意不要使用new File(Path),因为它会建立一个相对于当前工作路径的相对路径。当前工作路径和工程根目录有可能不相同。
// 利用相对路径。字符串作为参数。
File configFile = file('src/config.xml')
// 利用绝对路径。Path类的对象作为参数
configFile = file(configFile.absolutePath)
// 利用new File建立了一个相对于当前工作路径的相对路径。File类的对象作为参数
configFile = file(new File('src/config.xml'))
// 利用使用了相对路径的java.nio.file.Path对象
configFile = file(Paths.get('src', 'config.xml'))
// 利用使用了绝对路径的java.nio.file.Path对象
configFile = file(Paths.get(System.getProperty('user.home')).resolve('global-config.xml'))
多工程构建的情况下,file()方法会把路径都转换成相对于当前工程的相对路径。如果想取得根工程的路径需要使用Project.getRootDir()。
// Project.getRootDir()在脚本中可用$rootDir代替
File configFile = file("$rootDir/shared/config.xml")
- 文件集合(file collection)
文件集合就是一组实现了FileColletion接口的路径。文件之间没有先后顺序,也不相互干扰。所以不同文件夹的文件可以组成同一个文件集合。最常用的操作文件集合的方法是ProjectLayout.files(java.lang.Object...)。它返回一个FileCollection实例。这个方法非常灵活,它的参数可以是字符串,文件,文件集合,甚至可以是任务(前提是这个任务声明了outputs)。
FileCollection collection = layout.files('src/file1.txt', // 字符串参数
new File('src/file2.txt'), // 文件参数 ←实际中不要使用new File
['src/file3.csv', 'src/file4.csv'], // 文件集合
Paths.get('src', 'file5.txt')) // 利用java.nio.file.Path对象
文件集合有一些比较重要的属性
① 可以通过Lazy模式构建。Lazy模式就是在任务执行的阶段进行构建。
下面的示例展示了Lazy模式
task list {
doLast {
File srcDir
// 使用闭包构建文件集合,是Lazy模式的要点。
// 闭包要返回一个能被files()方法接收的类型。
// 比如List<File>, String, FileCollection
// layout在脚本中可代替ProjectLayout.files(java.lang.Object...)
FileCollection collection = layout.files { srcDir.listFiles() }
// 打印sourceFiles内文件的相对路径
srcDir = file("$buildDir/sourceFiles")
println "Contents of $srcDir.name"
collection.collect { relativePath(it) }.sort().each { println it }
// 打印resources内文件的相对路径
srcDir = file("$buildDir/resources")
println "Contents of $srcDir.name"
collection.collect { relativePath(it) }.sort().each { println it }
}
}
输出:
$ gradle -q list
F:\97_example\projectA
Contents of sourceFiles
build\sourceFiles\file1.txt
build\sourceFiles\file2.txt
build\sourceFiles\file3.pdf
build\sourceFiles\folder1
build\sourceFiles\javaws.jar
build\sourceFiles\jce.jar
Contents of resources
build\resources\docs
build\resources\file3.pdf
build\resources\folder1
② 可遍历
task list2 {
doLast {
File srcDir = file("$buildDir/sourceFiles")
// 使用闭包构建文件集合
FileCollection collection = layout.files { srcDir.listFiles() }
// 循环文件集合中的文件,循环变量为File类型。
collection.each { File file ->
println file.name
}
// 把文件集合转换为其他类型的对象
Set set = collection.files
Set set2 = collection as Set
List list = collection as List
String path = collection.asPath
// 文件集合中只有一个文件的时候可以执行成功
// File file = collection.singleFile
}
}
③ 可过滤
FileCollection textFiles = collection.filter { File f ->
f.name.endsWith(".txt")
}
④ 可合并或拆分
// 文件集合内容的增加和减少
def union = collection + layout.files('src/file2.txt')
def difference = collection - layout.files('src/file2.txt')
- 文件树
文件树就是带有目录阶层结构的文件集合,且必须有一个根目录。
如图展示了文件树和文件集合的不同。
文件树是带有目录结构的文件集合,需要把文件数转为扁平的文件集合时,可以使用FileTree.getFiles()方法。
建立一个FileTree
// 建立一个带有外层目录的文件树
ConfigurableFileTree tree = fileTree(dir: 'src/main')
// 设定需要包含和需要除外的文件
tree.include '**/*.java'
tree.exclude '**/Abstract*'
// 通过闭包来建立文件树
tree = fileTree('src') {
include '**/*.java'
}
// 使用map来建立文件树
tree = fileTree(dir: 'src', include: '**/*.java')
tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')
可以通过ant任务来设定一些默认除外的文件
task forcedCopy (type: Copy) {
into "$buildDir/inPlaceApp"
from 'src/main/webapp'
// 除外git自动生成的文件
doFirst {
ant.defaultexcludes remove: "**/.git"
ant.defaultexcludes remove: "**/.git/**"
ant.defaultexcludes remove: "**/*~"
}
doLast {
ant.defaultexcludes default: true
}
}
访问文件树
task useFileTree() {
// 首先建立一个文件树
ConfigurableFileTree tree = fileTree(dir: 'input')
println "1. Files in input folder"
// 遍历文件数打印文件路径
tree.each {File file ->
println " $file"
}
// 设置过滤器,将非java文件和以Abstract开头的java文件除外
FileTree filtered = tree.matching {
include '**/*.java'
exclude '**/Abstract*'
}
println "2. Java files in input folder without filename starts with Abstract"
filtered.each {File file ->
println " $file"
}
println "3. Files in input and output folder"
// 将两个文件树合并
FileTree sum = tree + fileTree(dir: 'output')
// 使用visit来访问树种每一个元素
sum.visit {element ->
println " $element.relativePath => $element.file"
}
}
输出:
$ gradle --rerun-tasks useFileTree
> Configure project :
1. Files in input folder
C:\study\gradle\test\input\AbstractClass.java
C:\study\gradle\test\input\file1.txt
C:\study\gradle\test\input\file2.txt
C:\study\gradle\test\input\file4.txt
C:\study\gradle\test\input\NormalClass.java
2. Java files in input folder without filename starts with Abstract
C:\study\gradle\test\input\NormalClass.java
3. Files in input and output folder
AbstractClass.java => C:\study\gradle\test\input\AbstractClass.java
file1.txt => C:\study\gradle\test\input\file1.txt
file2.txt => C:\study\gradle\test\input\file2.txt
file4.txt => C:\study\gradle\test\input\file4.txt
NormalClass.java => C:\study\gradle\test\input\NormalClass.java
file1.txt => C:\study\gradle\test\output\file1.txt
file2.txt => C:\study\gradle\test\output\file2.txt
file4.txt => C:\study\gradle\test\output\file4.txt
> Task :useFileTree UP-TO-DATE
BUILD SUCCESSFUL in 2s
上面的结果有个很奇怪的地方。使用了--rerun-tasks参数,但是useFileTree还是被标记为UP-TO-DATE,好像任务在配置阶段就被执行了。
调查发现,直接写在任务中(不在doLast等的闭包中)的println会在配置阶段被执行。需要后续学习展开。
利用归档文件(zip,jar,tar)作为文件树
// 利用zip文件来建立文件树。 Jar文件也可以用这个方法
FileTree zip = zipTree('someFile.zip')
// 利用tar文件来建立文件树
FileTree tar = tarTree('someFile.tar')
// tar文件树试图通过扩展名自动匹配压缩包的类型
// 如果需要显示指定压缩文件的话可以用如下方法
FileTree someTar = tarTree(resources.gzip('someTar.ext'))
文件集合的隐式转换
有很多类型具有可以接受文件集合的属性,原则上所有能被files方法接受的类型,比如File, String, collection, FileCollection,甚至闭包,都可以被这个属性所接受。
它们会被隐式转换为文件集合。
task compile(type: JavaCompile) {
// 利用file方法
source = file('src/main/java')
// 利用字符串
source = 'src/main/java'
// 利用字符串集合
source = ['src/main/java', '../shared/java']
// 利用fileTree
source = fileTree(dir: 'src/main/java').matching { include 'org/gradle/api/**' }
// 利用闭包
source = {
// Use the contents of each zip file in the src dir
file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }
}
}
另外,像JavaCompile类的source这样的属性,会把它的参数添加到既有source里面,而不是替换掉
compile {
// 通过字符串路径添加
source 'src/main/java', 'src/main/groovy'
// 通过file添加
source file('../shared/java')
// 通过闭包添加
source { file('src/test/').listFiles() }
// 这是source的内容有以上添加的所有文件
}