Gradle学习记录007 用Gradle操作文件 part3-part文件

学习如何使用Gradle操作文件,第三部分。该学习记录基于Gradle官方网站资料。本篇参考链接如下:

https://docs.gradle.org/current/userguide/working_with_files.html

11.文件目录深度拷贝

用Gradle拷贝文件的基本步骤如下

  • 定义一个类型为Copy的任务
  • 指定拷贝对象文件或目录
  • 指定拷贝目标文件夹

其中,指定对象文件或目录使用from方法。指定目标文件夹使用into方法。它们都是CopySpec接口的方法。可以接受所有能被files方法接受的参数。

task anotherCopyTask (type: Copy) {
// 拷贝 src/main/webapp 下的内容
from 'src/main/webapp'
// 拷贝单个文件
from 'src/staging/index.html'
// 拷贝copyTask任务的输出
from copyTask
// 拷贝copyTaskWithPatterns的输出, 这里显示指定了outputs
from copyTaskWithPatterns.outputs
// 拷贝一个zip压缩包内的文件树
from zipTree('src/main/assets.zip')
// 使用闭包来实现Lazy模式指定拷贝目标文件夹
into { getDestDir() }
}

文件过滤

之前我们遇到过使用include和exlude来过滤文件集合。他们也是CopySpec接口的方法。

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

当指定的include和exclude重叠的时候exclude会发生作用,把文件或者文件夹排除在拷贝对象之外。

重命名文件

学习记录005 part2里有重命名的示例。值得提到的是,Gradle里的rename方法可以使用标准java的Pattern类来实现正则表达式。使用这个方法的注意点:

  • 第一个参数是斜杠包含的字符串是,rename必须有括号
	rename(/(.+)-staging-(.+)/, '$1$2')
  • 第二个参数最好用单引号包裹,但如果需要用反斜杠转义$符号的时候,则需要用双引号比如"\$1\$2"
  • 第二个参数可以为null,表明不需要更改文件名称
  • rename会对每一个拷贝对象都做重命名处理, 所以需要考虑效率问题。

过滤文件内容

// 引入方法2使用的ant类
import org.apache.tools.ant.filters.FixCrLfFilter
import org.apache.tools.ant.filters.ReplaceTokens
task filter(type: Copy) {
from "$buildDir/sourceFiles"
into "$buildDir/destination"

// 方法1
expand(copyright: '2009', version: '2.3.1')
expand(project.properties)
// 方法2,使用filter方法和ant
// 将所有的换行替换为\r\n
filter(FixCrLfFilter)
// 将ant的token替换
filter(ReplaceTokens, tokens: [copyright: '2019', version: '5.2.1'])

// 删除拷贝对象文件里以-开头的行
filter { String line ->
line.startsWith('-') ? null : line
}
// 将拷贝对象文件的每一行都用[]括起来
filter { String line ->
"[$line]"
}
filteringCharset = 'UTF-8'
}

任务执行前的拷贝对象文件

copyright is ${copyright}

-empty line

version is ${version}

-empty line

copyright is @copyright@

-empty line

version is @version@

任务执行后的拷贝文件

[copyright is 2009]

[version is 2.3.1]

[copyright is 2019]

[version is 5.2.1]

活用CopySpec接口

  • 在工程内统一定义,令所有任务都可以使用
CopySpec webAssetsSpec = copySpec {
from 'src/main/webapp'
include '**/*.html', '**/*.webp', '**/*.webp'
rename '(.+)-staging(.+)', '$1$2'
}
task copyAssets (type: Copy) {
into "$buildDir/inPlaceApp"
with webAssetsSpec
}
task distApp(type: Zip) {
archiveFileName = 'my-app-dist.zip'
destinationDirectory = file("$buildDir/dists")
// 任务内定义的相同属性,会覆盖工程定义
from appClasses
with webAssetsSpec
}

下面的示例不使用CopySpec接口,实现相同功能

def webAssetPatterns = {
include '**/*.html', '**/*.webp', '**/*.webp'
}
task copyAppAssets(type: Copy) {
into "$buildDir/inPlaceApp"
// webAssetPatterns作为参数传递给from方法
// 同样,这种方式也适用于into,rename等方法
from 'src/main/webapp', webAssetPatterns
}
task archiveDistAssets(type: Zip) {
archiveFileName = 'distribution-assets.zip'
destinationDirectory = file("$buildDir/dists")
// webAssetPatterns作为参数传递给from方法
// 同样,这种方式也适用于into,rename等方法
from 'distResources', webAssetPatterns
}
  • 当需要实现如图的需求时,可以使用子指定(child spesification)

Gradle学习记录007 用Gradle操作文件 part3

task nestedSpecs(type: Copy) {
// 主目标文件夹
into "$buildDir/explodedWar"
exclude '**/*staging*'

// 过滤html,png,jpg文件从src/dist拷贝到explodedWar
from('src/dist') {
// 子指定
include '**/*.html', '**/*.webp', '**/*.webp'
}
// 从sourceSets输出的class文件拷贝到explodedWar/WEB-INF/classes
from(sourceSets.main.output) {
// 子指定。主目标文件夹下的路径explodedWar/WEB-INF/classes
into 'WEB-INF/classes'
}
// 从runtimeClasspath将类库拷贝到
// 主目标文件夹下的路径explodedWar/WEB-INF/lib
into('WEB-INF/lib') {
// 子指定
from configurations.runtimeClasspath
}
}

使用自定义任务拷贝文件

有时候希望在自定义的任务内,实现一些拷贝功能。可以不使用Copy类型,而是使用Project.copy方法。

task copyMethod {
doLast {
copy {
from 'src/main/webapp'
into "$buildDir/explodedWar"
include '**/*.html'
include '**/*.jsp'
}
}
}

需要注意,copy方法不是增量模式的。需要显示标记它的输入(inputs)和输出(outputs)来进行up-to-date检查。

task copyMethodWithExplicitDependencies {
// 对输入进行up-to-date 检查
// 将copyTask作为当前任务的依赖
inputs.files copyTask
// 对输出进行up-to-date 检查
outputs.dir 'some-dir'
doLast{
copy {
// 拷贝copyTask任务的输出作为copy方法的输入
from copyTask
into 'some-dir'
}
}
}

镜像目录或文件集合

Syns类型是Copy的子类。它会把文件拷贝到目标文件夹, 并且删除目标文件夹中的其他内容。

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

gradle也提供了Project.sync方法,以用于自定义任务内部使用。

12.深入了解归档

归档,就是压缩文件。在Gradle里得到了很好的支持。

与之前的学习相同的部分不再赘述。这里学习如下几点。

归档文件的命名规则

归档文件的命名规则不是硬性规则,但是一般情况下最好遵守。规则如下:

[projectName]-[version].[type]

plugins {
id 'base'
}
version = 1.0
task myZip(type: Zip) {
from 'somedir'
doLast {
println archiveFileName.get()
// zip默认保存的目录build/distributions/
println relativePath(destinationDirectory)
println relativePath(archiveFile)
}
}

得到的zip文件名字为zipProject-1.0.zip。默认保存在zipProject/build/distributions下面

如果需要指定文件名与保存目录, 可以使用archiveFileName和destinationDirectory属性。

另外可以使用如下的属性, 来指定文件名字的各个部位

[archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension]

比如

version = 1.0
task myCustomZip(type: Zip) {
archiveBaseName = 'customName'
from 'somedir'
doLast {
println archiveFileName.get()
}
}

输出的文件名字是customName-1.0.zip

还可以使用archivesBaseName属性来全局指定所有压缩文件的名字。

plugins {
id 'base'
}
version = 1.0
archivesBaseName = "gradle"
task myZip(type: Zip) {
from 'somedir'
}
task myOtherZip(type: Zip) {
archiveAppendix = 'wrapper'
archiveClassifier = 'src'
from 'somedir'
}
task echoNames {
doLast {
println "Project name: ${project.name}"
println myZip.archiveFileName.get()
println myOtherZip.archiveFileName.get()
}
}

输出如下

$ gradle -q echoNames

Project name: zipProject

gradle-1.0.zip

gradle-wrapper-1.0-src.zip

关于压缩文件的属性,可以参照 AbstractArchiveTask的API

13.可再生的归档

使用下面的方法,可以在不同的环境中生成完全一样的归档文件。笔者暂时不知道什么情况下会用到这个功能。

tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}

推荐阅读