Gradle学习记录006 用Gradle操作文件 part2-part文件

学习如何使用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')
  • 文件树

文件树就是带有目录阶层结构的文件集合,且必须有一个根目录。

如图展示了文件树和文件集合的不同。

Gradle学习记录006 用Gradle操作文件 part2

文件树是带有目录结构的文件集合,需要把文件数转为扁平的文件集合时,可以使用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的内容有以上添加的所有文件
}

推荐阅读