Chapter 16. Working With Files 跟文件工作

    使用 ) 方法来定位相对于 project 目录相关的文件。

    Example 16.1. Locating files

    build.gradle

    您可以把任何对象传递给 file() 方法,而它将尝试将其转换为一个绝对路径的 File 对象。通常情况下,你会传给它一个 String 或 File 的实例。如果这个路径是一个绝对路径,它会用于构构一个 File 实例。否则,会通过先计算所提供的路径相对于项目目录的相对路径来构造 File 实例。这个 file() 方法也可以识别 URL,例如是 file:/some/path.xml。

    这是把一些用户提供的值转换为一个相对路径的 File 对象的有用方法。由于 file() 方法总是去计算所提供的路径相对于项目目录的路径,最好是使用 new File(somePath),因为它是一个固定的路径,而不会因为用户运行 Gradle 的具体工作目录而改变。

    16.2. File collections 文件集合

    一个文件集合简单的说就是一组文件。它通过 FileCollection 接口来表示。Gradle API 中的许多对象都实现了此接口。比如,章节 就实现了 FileCollection 这一接口。

    使用 Project.files()) 方法是获取一个 FileCollection 实例的其中一个方法。你可以向这个方法传入任意个对象,而它们会被转换为一组 File 对象。这个 files() 方法接受任何类型的对象作为其参数。根据里对 file() 方法的描述,它的结果会被计算为相对于项目目录的相对路径。你也可以将集合,迭代变量,map 和数组传递给 files() 方法。它们会被展开,并且内容会转换为 File 实例。

    Example 16.2. Creating a file collection

    build.gradle

    1. new File('src/file2.txt'),
    2. ['src/file3.txt', 'src/file4.txt'])

    一个文件集合是可迭代的,并且可以使用 as 操作符转换为其他类型的对象集合。您还可以使用 + 运算符把两个文件集合相加,或使用 - 运算符减去一个文件集合。这里是一些使用文件集合的例子

    Example 16.3. Using a file collection

    build.gradle

    1. // Iterate over the files in the collection
    2. collection.each {File file ->
    3. println file.name
    4. }
    5. // Convert the collection to various types
    6. Set set = collection.files
    7. Set set2 = collection as Set
    8. List list = collection as List
    9. String path = collection.asPath
    10. File file = collection.singleFile
    11. File file2 = collection as File
    12. // Add and subtract collections
    13. def union = collection + files('src/file3.txt')
    14. def different = collection - files('src/file3.txt')

    你也可以向 files() 方法传一个闭包或一个 Callable 实例。它会在查询集合内容,并且它的返回值被转换为一组文件实例时被调用。这个闭包或Callable 实例的返回值可以是 files() 方法所支持的任何类型的对象。这是 “实现” FileCollection 接口的简单方法。

    Example 16.4. Implementing a file collection

    build.gradle

    1. task list << {
    2. File srcDir
    3. // Create a file collection using a closure
    4. collection = files { srcDir.listFiles() }
    5. srcDir = file('src')
    6. println "Contents of $srcDir.name"
    7. collection.collect { relativePath(it) }.sort().each { println it }
    8. srcDir = file('src2')
    9. println "Contents of $srcDir.name"
    10. collection.collect { relativePath(it) }.sort().each { println it }
    11. }

    执行 gradle -q list

    1. > gradle -q list
    2. Contents of src
    3. src/dir1
    4. src/file1.txt
    5. Contents of src2
    6. src2/dir1
    7. src2/dir2

    你可以向 files() 传入一些其他类型的对象:

    FileCollection

    它们会被展开,并且内容会被包含在文件集合内。

    Task

    任务的输出文件会被包含在文件集合内。

    TaskOutputs

    TaskOutputs 的输出文件会被包含在文件集合内。

    要注意的一个地方是,一个文件集合的内容是缓计算的,它只在需要的时候才计算。这意味着您可以,比如创建一个 FileCollection 对象而里面的文件会在以后才创建,比方说在一些任务中才创建。

    文件树是按层次结构排序的文件集合。例如,文件树可能表示一个目录树或 ZIP 文件的内容。它通过 FileTree 接口表示。FileTree 接口继承自FileCollection,所以你可以用对待文件集合一样的方式来对待文件树。Gradle 中的几个对象都实现了 FileTree 接口,例如 source sets。

    使用 Project.fileTree() 方法是获取一个 FileTree 实例的其中一种方法。它将定义一个基目录创建 FileTree 对象,并可以选择加上一些 Ant 风格的包含与排除模式

    Example 16.5. Creating a file tree

    build.gradle

    1. // Create a file tree with a base directory
    2. FileTree tree = fileTree(dir: 'src/main')
    3. // Add include and exclude patterns to the tree
    4. tree.include '**/*.java'
    5. tree.exclude '**/Abstract*'
    6. // Create a tree using path
    7. tree = fileTree('src').include('**/*.java')
    8. // Create a tree using closure
    9. tree = fileTree('src') {
    10. include '**/*.java'
    11. }
    12. // Create a tree using a map
    13. tree = fileTree(dir: 'src', include: '**/*.java')
    14. tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])

    你可以像使用一个文件集合的方式一样来使用一个文件树。你也可以使用 Ant 风格的模式来访问文件树的内容或选择一个子树:

    build.gradle

    1. // Iterate over the contents of a tree
    2. tree.each {File file ->
    3. println file
    4. }
    5. // Filter a tree
    6. FileTree filtered = tree.matching {
    7. include 'org/gradle/api/**'
    8. }
    9. // Add trees together
    10. FileTree sum = tree + fileTree(dir: 'src/test')
    11. // Visit the elements of the tree
    12. tree.visit {element ->
    13. println "$element.relativePath => $element.file"

    16.4. Using the contents of an archive as a file tree 使用归档文件的内容作为文件树

    您可以使用档案的内容,如 ZIP 或者 TAR 文件,作为一个文件树。你可以通过使用 Project.zipTree() 或 Project.tarTree() 方法来实现这一过程。这些方法返回一个 FileTree 实例,您可以像使用任何其他文件树或文件集合一样使用它。例如,您可以用它来通过复制内容扩大归档,或把一些档案合并到另一个归档文件中。

    Example 16.7. Using an archive as a file tree

    build.gradle

    1. // Create a ZIP file tree using path
    2. FileTree zip = zipTree('someFile.zip')
    3. // Create a TAR file tree using path
    4. FileTree tar = tarTree('someFile.tar')
    5. //tar tree attempts to guess the compression based on the file extension
    6. //however if you must specify the compression explicitly you can:
    7. FileTree someTar = tarTree(resources.gzip('someTar.ext'))

    Gradle 中的许多对象都有一个接受一组输入文件的属性。例如, JavaCompile 任务有一个 source 属性,定义了要编译的源代码文件。你可以使用上面所示的 files() 方法所支持的任意类型的对象设置此属性。这意味着您可以通过如 File、String、 集合、 FileCollection对象,或甚至是一个闭包来设置此属性。这里有一些例子:

    Example 16.8. Specifying a set of files

    build.gradle

    通常情况下,有一个与属性相同名称的方法,可以追加这个文件集合。再者,这个方法接受 files() 方法所支持的任何类型的参数。

    Example 16.9. Specifying a set of files

    build.gradle

    1. compile {
    2. // Add some source directories use String paths
    3. source 'src/main/java', 'src/main/groovy'
    4. // Add a source directory using a File object
    5. source file('../shared/java')
    6. // Add some source directories using a closure
    7. source { file('src/test/').listFiles() }
    8. }

    16.6. Copying files 拷贝文件

    你可以使用 Copy 任务来复制文件。复制任务非常灵活,并允许您进行,比如筛选要复制的文件的内容,或映射文件的名称。

    若要使用 Copy 任务,您必须提供用于复制的源文件和目标目录。您还可以在复制文件的时候指定如何转换文件。你可以使用一个复制规范来做这些。一个复制规范通过 CopySpec 接口来表示。Copy 任务实现了此接口。你可以使用 CopySpec.from() 方法指定源文件,使用 CopySpec.into() 方法使用目标目录。

    Example 16.10. Copying files using the copy task

    build.gradle

    1. task copyTask(type: Copy) {
    2. from 'src/main/webapp'
    3. into 'build/explodedWar'
    4. }

    from() 方法接受和 files() 方法一样的任何参数。当参数解析为一个目录时,该目录下的所有文件(不包含目录本身) 都会递归复制到目标目录。当参数解析为一个文件时,该文件会复制到目标目录中。当参数解析为一个不存在的文件时,参数会被忽略。如果参数是一个任务,那么任务的输出文件 (即该任务创建的文件)会被复制,并且该任务会自动添加为Copy任务的依赖项。 into() 方法接受和 files() 方法一样的任何参数。这里是另一个示例:

    Example 16.11. Specifying copy task source files and destination directory

    build.gradle

    1. task anotherCopyTask(type: Copy) {
    2. // Copy everything under src/main/webapp
    3. from 'src/main/webapp'
    4. // Copy a single file
    5. from 'src/staging/index.html'
    6. // Copy the output of a task
    7. from copyTask
    8. // Copy the output of a task using Task outputs explicitly.
    9. from copyTaskWithPatterns.outputs
    10. // Copy the contents of a Zip file
    11. from zipTree('src/main/assets.zip')
    12. // Determine the destination directory later
    13. into { getDestDir() }
    14. }

    您可以使用 Ant 风格的包含或排除模式,或使用一个闭包,来选择要复制的文件:

    Example 16.12. Selecting the files to copy

    build.gradle

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

    此外,你也可以使用 Project.copy() 方法来复制文件。它是与任务一样的工作方式,尽管它有一些主要的限制。首先, copy() 不能进行增量操作(见15.9章节,”跳过处于最新状态的任务”)。

    Example 16.13. Copying files using the copy() method without up-to-date check

    build.gradle

    1. task copyMethod << {
    2. copy {
    3. from 'src/main/webapp'
    4. into 'build/explodedWar'
    5. include '**/*.html'
    6. include '**/*.jsp'
    7. }
    8. }

    第二,当一个任务用作复制源(即作为 from() 的参数)的时候,copy()方法不能建立任务依赖性,因为它是一个方法,而不是一个任务。因此,如果您在任务的 action 里面使用 copy() 方法,必须显式声明所有的输入和输出以得到正确的行为。

    Example 16.14. Copying files using the copy() method with up-to-date check

    build.gradle

    1. task copyMethodWithExplicitDependencies{
    2. inputs.file copyTask
    3. outputs.dir 'some-dir' // up-to-date check for outputs
    4. doLast{
    5. copy {
    6. // Copy the output of copyTask
    7. from copyTask
    8. into 'some-dir'
    9. }
    10. }
    11. }

    在可能的情况下,最好是使用 Copy 任务,因为它支持增量构建和任务依赖关系推理,而不需要你额外付出。copy() 方法可以作为一个任务执行的部分来复制文件。即,这个 copy() 方法旨在用于自定义任务 (见)中,需要文件复制作为其一部分功能的时候。在这种情况下,自定义任务应充分声明与复制操作有关的输入/输出。

    Example 16.15. Renaming files as they are copied

    build.gradle

    1. task rename(type: Copy) {
    2. from 'src/main/webapp'
    3. into 'build/explodedWar'
    4. // Use a closure to map the file name
    5. rename { String fileName ->
    6. fileName.replace('-staging-', '')
    7. }
    8. // Use a regular expression to map the file name
    9. rename '(.+)-staging-(.+)', '$1$2'
    10. }

    build.gradle

    在源文件中,“expand” 和 “filter” 操作查找的 “token” ,被格式化成“@tokenName@” (名称是 “tokenName”)

    复制规范用来组织一个层次结构。一个复制规范继承其目标路径,包含模式,排除模式,复制操作,名称映射和过滤器。

    Example 16.17. Nested copy specs

    build.gradle

    1. task nestedSpecs(type: Copy) {
    2. into 'build/explodedWar'
    3. exclude '**/*staging*'
    4. from('src/dist') {
    5. include '**/*.html'
    6. }
    7. into('libs') {
    8. from configurations.runtime
    9. }
    10. }

    Sync 任务继承了 Copy 任务。当它执行时,它会将源文件复制到目标目录中,然后从目标目录移除所有不是它复制的文件。这可以用来做一些事情,比如安装你的应用程序、 创建你的归档文件的 exploded 副本,或维护项目的依赖项的副本。

    这里是一个例子,维护在 build/libs 目录中的项目运行时依赖的副本。

    Example 16.18. Using the Sync task to copy dependencies

    build.gradle

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

    16.8. Creating archives 创建归档文件

    一个项目可以有你所想要的一样多的 JAR 文件。您也可以将 WAR、 ZIP 和TAG 文件添加到您的项目。使用各种归档任务可以创建以下的归档文件: Zip, Tar, Jar, War, and Ear. 他们的工作方式都一样,所以让我们看看如何创建一个 ZIP 文件。

    Example 16.19. Creating a ZIP archive

    build.gradle

    1. apply plugin: 'java'
    2. task zip(type: Zip) {
    3. from 'src/dist'
    4. into('libs') {
    5. from configurations.runtime
    6. }
    7. }

    归档任务与 Copy 任务的工作方式一样,并且实现了相同的 CopySpec 接口。像使用 Copy 任务一样,你需要使用 from() 的方法指定输入的文件,并可以选择是否通过 into() 方法指定最终在存档中的位置。您可以通过一个复制规范来筛选文件的内容、 重命名文件和进行其他你可以做的事情。

    生成的归档的默认名称是 projectName-version.type。举个例子:

    Example 16.20. Creation of ZIP archive

    build.gradle

    1. apply plugin: 'java'
    2. version = 1.0
    3. task myZip(type: Zip) {
    4. from 'somedir'
    5. }
    6. println myZip.archiveName
    7. println relativePath(myZip.destinationDir)
    8. println relativePath(myZip.archivePath)

    执行 gradle -q myZip

    1. > gradle -q myZip
    2. zipProject-1.0.zip
    3. build/distributions
    4. build/distributions/zipProject-1.0.zip

    它添加了一个名称为 myZip 的 ZIP 归档任务,产生 ZIP 文件 zipProject 1.0.zip 。区分归档任务的名称和归档任务生成的归档文件的名称是很重要的。归档的默认名称可以通过项目属性 archivesBaseName 来更改。还可以在以后的任何时候更改归档文件的名称。

    这里有很多你可以在归档任务中设置的属性。它们在以下的中列出。你可以,比方说,更改归档文件的名称:

    Example 16.21. Configuration of archive task - custom archive name

    build.gradle

    1. apply plugin: 'java'
    2. version = 1.0
    3. task myZip(type: Zip) {
    4. from 'somedir'
    5. baseName = 'customName'
    6. }
    7. println myZip.archiveName

    执行 gradle -q myZip

    1. > gradle -q myZip
    2. customName-1.0.zip

    您可以进一步自定义存档名称:

    Example 16.22. Configuration of archive task - appendix & classifier

    build.gradle

    执行 gradle -q myZip

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

    Table 16.1. Archive tasks - naming properties














































    你可以使用Project.copySpec())方法在归档之间共享内容。

    你经常会想要发布一个归档文件,这样就可从另一个项目中使用它。这一过程在会讲到