学习如何使用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', '**/*.png', '**/*.jpg' 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', '**/*.png', '**/*.jpg' } 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)
task nestedSpecs(type: Copy) { // 主目标文件夹 into "$buildDir/explodedWar" exclude '**/*staging*' // 过滤html,png,jpg文件从src/dist拷贝到explodedWar from('src/dist') { // 子指定 include '**/*.html', '**/*.png', '**/*.jpg' } // 从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 }