文件操作

大多数构建工作需要操作文件,Gradle 增加了一些API帮助您处理这些工作。

Locating files

使用 Project.file() 方法能够相对项目目录定位一个文件

例 16.1. 定位文件

build.gradle

// 使用一个相对路径

File configFile = file('src/config.xml')

// 使用一个绝对路径

configFile = file(configFile.absolutePath)

// 使用一个项目路径的文件对象

configFile = file(new File('src/config.xml'))`

file() 方法接收任何形式的对象参数.它会将参数值转换为一个绝对文件对象,一般情况下,你可以传递一个 String 或者一个 File 实例.如果传递的路径是个绝对路径,它会被直接构造为一个文件实例.否则,会被构造为项目目录加上传递的目录的文件对象.另外,file()函数也能识别URL,例如 file:/some/path.xml.

这个方法非常有用,它将参数值转换为一个绝对路径文件.所以请尽量使用 new File(somePath) , 因为 file() 总是相对于当前项目路径计算传递的路径,然后加以矫正.因为当前工作区间目录依赖于用户以何种方式运行 Gradle.

文件集合

文件集合表示一组文件,Gradle 使用 FileCollection 接口表示文件集合, Gradle API 中的许多项目都实现了这个接口,例如 dependency configurations .

获取 FileCollection 实例的一种方法是使用 Project.files() 方法.你可以传递任何数量的对象参数,这个方法能将你传递的对象集合转换为一组文件对象.files() 方法接收任何类型对象参数.每一个 file() 方法都依赖于项目目录(在第 15 章,第一小节中介绍).files()方法也接收 collections, iterables, mapsarrays 类型参数.这些参数的内容会被解析,然后被转换为文件对象.

例 15.2 创建文件集合

build.gradle

FileCollection collection = files('src/file1.txt',
                                  new File('src/file2.txt'),
                                  ['src/file3.txt', 'src/file4.txt'])

文件集合可以被迭代器,使用迭代操作能够将其转换为其他的一些类型.你可以使用 + 操作将两个文件集合合并,使用 - 操作能够对一个文件集合做减法.下面一些例子介绍如何操作文件集合.

例 15.3 使用文件集合

build.gradle

// 对文件集合进行迭代
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
File file2 = collection as File

// 增加和减少文件集合
def union = collection + files('src/file3.txt')
def different = collection - files('src/file3.txt')

你也可以向 files() 方法专递一个闭合或者可回调的实例参数.当查询集合的内容时就会调用它,然后将返回值转换为一些文件实例.返回值可以是 files() 方法支持的任何类型的对象.下面有个简单的例子来演示实现 FileCollection 接口

例 15.4 实现一个文件集合

build.gradle

task list << {
    File srcDir

    // 使用闭合创建一个文件集合
    collection = files { srcDir.listFiles() }

    srcDir = file('src')
    println "Contents of $srcDir.name"
    collection.collect { relativePath(it) }.sort().each { println it }

    srcDir = file('src2')
    println "Contents of $srcDir.name"
    collection.collect { relativePath(it) }.sort().each { println it }
}

使用 gradle -q list 输出结果

> gradle -q list
Contents of src
src/dir1
src/file1.txt
Contents of src2
src2/dir1
src2/dir2

另外, files() 方法也接收其他类型的参数:

FileCollection

内容损坏的文件包含在文件集合中.

Task

任务的输出文件包含在文件集合中.

TaskOutputs

TaskOutputs 的输出文件包含在文件集合中

值得注意的是当有需要时文件集合的内容会被被惰性处理,就比如一些任务在需要的时候会创建一个 FileCollecion 代表的文件集合.

文件树

文件树就是一个按照层次结构分布的文件集合,例如,一个文件树可以代表一个目录树结构或者一个 ZIP 压缩文件的内容.它被抽象为 FileTree 结构,FileTree 继承自 FileCollection,所以你可以像处理文件集合一样处理文件树, Gradle 有些对象实现了FileTree 接口,例如 源集合. 使用 Project.fileTree() 方法可以得到 FileTree 的实例,它会创建一个基于基准目录的对象,然后视需要使用一些 Ant-style 的包含和去除规则.

例 15.5 创建文件树 build.gradle

/以一个基准目录创建一个文件树
FileTree tree = fileTree(dir: 'src/main')

// 添加包含和排除规则
tree.include '**/*.java'
tree.exclude '**/Abstract*'

// 使用路径创建一个树
tree = fileTree('src').include('**/*.java')

// 使用闭合创建一个数
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-style 规则选择一个子树。

例 15.6 使用文件树 build.gradle

// 遍历文件树
tree.each {File file ->
    println file
}

// 过滤文件树
FileTree filtered = tree.matching {
    include 'org/gradle/api/**'
}

// 合并文件树A
FileTree sum = tree + fileTree(dir: 'src/test')

// 访问文件数的元素
tree.visit {element ->
    println "$element.relativePath => $element.file"
}

使用一个归档文件的内容作为文件树

你可以使用 ZIP 或者 TAR 等压缩文件的内容作为文件树, Project.zipTree()Project.tarTree() 方法返回一个 FileTree 实例, 你可以像使用其他文件树或者文件集合一样使用它.例如,你可以使用它去扩展一个压缩文档或者合并一些压缩文档.

例 15.7 使用压缩文档作为文件树 build.gradle

// 使用路径创建一个 ZIP 文件
FileTree zip = zipTree('someFile.zip')

// 使用路径创建一个 TAR 文件
FileTree tar = tarTree('someFile.tar')

//tar tree 能够根据文件扩展名得到压缩方式,如果你想明确的指定压缩方式,你可以使用下面方法
FileTree someTar = tarTree(resources.gzip('someTar.ext'))

指定一组输入文件

Gradle 中有一些对象的某些属性可以接收一组输入文件.例如,JavaComplile 任务有一个 source 属性,它定义了编译的源文件,你可以设置这个属性的值,只要 files() 方法支持. 这意味着你可以使用 File , String , collection , FileCollection 甚至是使用一个闭合去设置属性的值.

例 15.8 指定文件

build.gradle

//使用一个 File 对象设置源目录
compile {
    source = file('src/main/java')
}

//使用一个字符路径设置源目录
compile {
    source = 'src/main/java'
}

// 使用一个集合设置多个源目录
compile {
    source = ['src/main/java', '../shared/java']
}

// 使用 FileCollection 或者 FileTree 设置源目录
compile {
    source = fileTree(dir: 'src/main/java').matching { include 'org/gradle/api/**' }
}

// 使用一个闭合设置源目录
compile {
    source = {
        // Use the contents of each zip file in the src dir
        file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }
    }
}

Usually, there is a method with the same name as the property, which appends to the set of files. Again, this method accepts any of the types supported by the files() method.

通常情况下,会有一个方法名和属性名相同的方法能够附加一组文件,这个方法接收 files() 方法支持的任何类型的值.

例 15.9 指定文件

build.gradle

compile {
    // 使用字符路径添加源目录
    source 'src/main/java', 'src/main/groovy'

    // 使用 File 对象添加源目录
    source file('../shared/java')

    // 使用闭合添加源目录
    source { file('src/test/').listFiles() }
}

复制文件

你可以使用复制任务( Copy )去复制文件. 复制任务扩展性很强,能够过滤复制文件的内容, 映射文件名.

使用复制任务时需要提供想要复制的源文件和一个目标目录,如果你要指定文件被复制时的转换方式,可以使用 复制规则. 复制规则被 CopySpec 接口抽象,复制任务实现了这个接口. 使用 CopySpec.from() 方法指定源文件.使用 CopySpec.into() 方法指定目标目录.

例 15.10. 使用复制任务复制文件

build.gradle

task copyTask(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
}

from() 方法接收任何 files() 方法支持的参数. 当参数被解析为一个目录时,在这个目录下的任何文件都会被递归地复制到目标目录(但不是目录本身).当一个参数解析为一个文件时,该文件被复制到目标目录中.当参数被解析为一个不存在的文件时,这个参数就会忽略.如果这个参数是一个任务,任务的输出文件(这个任务创建的文件)会被复制,然后这个任务会被自动添加为复制任务的依赖.

例 15.11 指定复制任务的源文件和目标目录

build.gradle

task anotherCopyTask(type: Copy) {
    // 复制 src/main/webapp 目录下的所有文件
    from 'src/main/webapp'
    // 复制一个单独文件
    from 'src/staging/index.html'
    // 复制一个任务输出的文件
    from copyTask
    // 显式使用任务的 outputs 属性复制任务的输出文件
    from copyTaskWithPatterns.outputs
    // 复制一个 ZIP 压缩文件的内容
    from zipTree('src/main/assets.zip')
    // 最后指定目标目录
    into { getDestDir() }
}

你可以使用Ant-style 规则或者一个闭合选择要复制的文件.

例 15.12 选择要复制文件

build.gradle

task copyTaskWithPatterns(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    include '**/*.html'
    include '**/*.jsp'
    exclude { details -> details.file.name.endsWith('.html') &&
                         details.file.text.contains('staging') }
}

你也可以使用 Project.copy() 方法复制文件,它的工作方式有一些限制,首先该方法不是增量的,请参考 第 14.9节 跳过最新的任务.第二,当一个任务被用作复制源时(例如 from() 方法的参数), copy() 方法不能够实现任务依赖,因为它是一个普通的方法不是一个任务.因此,如果你使用 copy()方法作为一个任务的一部分功能,你需要显式的声明所有的输入和输出以确保获得正确的结果.

例 15.13 不使用最新检查方式下用 copy() 方法复制文件

build.gradle

task copyMethod << {
    copy {
        from 'src/main/webapp'
        into 'build/explodedWar'
        include '**/*.html'
        include '**/*.jsp'
    }
}

例 15.14 使用最新的检查方式下用 copy() 方法复制文件

build.gradle

task copyMethodWithExplicitDependencies{

     // 对输入做最新检查,添加 copyTask 作为依赖
    inputs.file copyTask
    outputs.dir 'some-dir' //对输出做最新检查
    doLast{
        copy {
            // 复制 copyTask 的输出
            from copyTask
            into 'some-dir'
        }
    }
}

建议尽可能的使用复制任务,因为它支持增量化的构建和任务依赖推理,而不需要去额外的费力处理这些.不过 copy() 方法可以用作复制任务实现的一部分.即该 方法被在自定义复制任务中使用,请参考 第60章 编写自定义任务.在这样的场景下,自定义任务应该充分声明与复制操作相关的输入/输出。

15.6.1 重命名文件

例 15.15 在复制时重命名文件

build.gradle

task rename(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    // 使用一个闭合映射文件名
    rename { String fileName ->
        fileName.replace('-staging-', '')
    }
    // 使用正则表达式映射文件名
    rename '(.+)-staging-(.+)', '$1$2'
    rename(/(.+)-staging-(.+)/, '$1$2')
}

15.6.2 过滤文件

例 15.16 在复制时过滤文件

build.gradle

import org.apache.tools.ant.filters.FixCrLfFilter
import org.apache.tools.ant.filters.ReplaceTokens

task filter(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    // 在文件中替代属性标记
    expand(copyright: '2009', version: '2.3.1')
    expand(project.properties)
    // 使用 Ant 提供的过滤器
    filter(FixCrLfFilter)
    filter(ReplaceTokens, tokens: [copyright: '2009', version: '2.3.1'])
    // 用一个闭合来过滤每一行
    filter { String line ->
        "[$line]"
    }
    // 使用闭合来删除行
    filter { String line ->
        line.startsWith('-') ? null : line
    }
}

在源文件中扩展和过滤操作都会查找的某个标志 token,如果它的名字是 tokenName , 它的格式应该类似于 @tokenName@.

15.6.3 使用 CopySpec 类

复制规范来自于层次结构,一个复制规范继承其目标路径,包括模式,排除模式,复制操作,名称映射和过滤器.

例 15.17. 嵌套复制规范

build.gradle

task nestedSpecs(type: Copy) {
    into 'build/explodedWar'
    exclude '**/*staging*'
    from('src/dist') {
        include '**/*.html'
    }
    into('libs') {
        from configurations.runtime
    }
}


使用同步任务

同步任务 ( Sync ) 任务继承自复制任务 ( Copy ) , 当它执行时,它会复制源文件到目标目录中,然后从目标目录中的删除所有非复制的文件,这种方式非常有用,比如安装一个应用,创建一个文档的副本,或者维护项目的依赖关系副本.

下面有一个例子,维护 build/libs 目录下项目在运行时的依赖

例 15.7 使用 Sync 任务复制依赖关系

build.gradle

task libs(type: Sync) {
    from configurations.runtime
    into "$buildDir/libs"
}

创建归档文件

一个项目可以有很多 JAR 文件,你可以向项目中添加 WAR , ZIP 和 TAR 文档,使用归档任务可以创建这些文档: Zip , Tar , Jar , War 和Ear. 它门都以同样的机制工作.

例 15.19 创建一个 ZIP 文档

build.gradle

apply plugin: 'java'

task zip(type: Zip) {
    from 'src/dist'
    into('libs') {
        from configurations.runtime
    }
}

所有的归档任务的工作机制和复制任务相同,它们都实现了 CopySpec 接口,和 Copy 任务一样,使用 from() 方法指定输入文件,可以选择性的使用 into() 方法指定什么时候结束.你还可以过滤文件内容,重命名文件等等,就如同使用复制规则一样.

重命名文档

projectName-vsersion.type 格式可以被用来生成文档名,例如:

例 15.20 创建压缩文档

build.gradle

apply plugin: 'java'

version = 1.0

task myZip(type: Zip) {
    from 'somedir'
}

println myZip.archiveName
println relativePath(myZip.destinationDir)
println relativePath(myZip.archivePath)

使用 gradle -q myZip 命令进行输出:

> gradle -q myZip
zipProject-1.0.zip
build/distributions
build/distributions/zipProject-1.0.zip

上面例子使用一个名为 myZip的 Zip 归档任务生成名称为 zipProject-1.0.zip 的 ZIP 文档,区分文档任务名和最终生成的文档名非常的重要

可以通过设置项目属性 archivesBaseName 的值来 修改生成文档的默认名.当然,文档的名称也可以通过其他方法随时更改.

归档任务中有些属性可以配置,如表 15.1 所示:

表 15.1 归档任务-属性名

属性名 类型 默认值 描述
archiveName String baseName-appendix-version-classifier.extension,如果其中任何一个都是空的,则不添加名称 归档文件的基本文件名
archivePath File destinationDir/archiveName 生成归档文件的绝对路径。
destinationDir File 取决于文档类型, JAR 和 WAR 使用project.buildDir/distributions. project.buildDir/libraries.ZIP 和 TAR 归档文件的目录
baseName String project.name 归档文件名的基础部分。
appendix String null 归档文件名的附加部分。
version String project.version 归档文件名的版本部分。
classifier String null 归档文件名的分类部分
extension String 取决于文档类型和压缩类型: zip, jar, war, tar, tgz 或者 tbz2. 归档文件的扩展名

例 15.21 配置归档文件-自定义文档名

build.gradle

apply plugin: 'java'
version = 1.0

task myZip(type: Zip) {
    from 'somedir'
    baseName = 'customName'
}

println myZip.archiveName

使用 gradle -q myZip 命令进行输出:

> gradle -q myZip
customName-1.0.zip

更多配置:

例 15.22 配置归档任务- 附加其他后缀

build.gradle

apply plugin: 'java'
archivesBaseName = 'gradle'
version = 1.0

task myZip(type: Zip) {
    appendix = 'wrapper'
    classifier = 'src'
    from 'somedir'
}

println myZip.archiveName

使用 gradle -q myZip 命令进行输出:

> gradle -q myZip
gradle-wrapper-1.0-src.zip

多个文档共享内容

你可以使用 Project.copySpec() 在不用归档文件间共享内容. 如果你想发布一个文档,然后在另一个项目中使用,这部分将在第 53 章 发布文档 中讲述.