Gradle学习(十六)——文件操作

栏目: Java · 发布时间: 6年前

内容简介:Gradle学习(十六)——文件操作

构建的大部分工作是基于文件的,Gradle提供了一些api和概念来帮助你来进行操作

检索文件

你可以通过以项目目录为baseDir的相对路径来检索文件:

//相对路径
File configFile = file 'src/config.cson'
//绝对路径
configFile = file configFile.absolutePath
//使用相对路径的文件对象
configFile = file new File('src/config.cson')
//使用相对路径的java.nio.file.Path对象
configFile = file Paths.get('src', 'config.cson')
//使用绝对路径的java.nio.file.Path对象
configFile = file Paths.get(System.getProperty('user.home')).resolve('global-config.cson')

你可以传任意对象给 file() 方法,都会被转为一个绝对路径的 File 对象,通常传入的是String,File或者Path的实例。如果路径的是绝对地址,那么就会直接创建绝对路径的文件,如果路径是相对路径,那么就将项目目录作为baseDir来转换成绝对路径的文件。还要注意一点, file() 方法不会接收标准的URL,比如 file:/some/path.xml

要注意的一点是 new File(somePath) 是基于当前工作目录的,而 file() 是基于项目目录的,具体怎么选择看你自己了。

File collections

file collection 仅仅是一组文件集合,它的代表是 FileCollection 接口,Gradle的API中的很多对象都实现了这个接口,比如dependency configurations。

可以通过 Project.files(java.lang.Object[]) 方法来获取 FileCollection 实例,方法可以传入任意数量的对象,然后把这些对象转换成一组 File 对象。 files() 可以接受任意类型的参数,就单个参数来说类似于 file() 方法,除此之外你可以传集合,迭代器,map和数组,它们都会解包成多个的 File 文件。

FileCollection fileCollection = files('src/file1.txt',
        new File('src/file2.txt'),
        ['src/file3.txt', 'src/file4.txt'],
        Paths.get('src', 'file5.txt'))

file collection 是可迭代的,并且可以用 as 操作符,你也可以通过 + 操作符添加文件,或者用 - 操作符来移除文件,如下面的例子所示:

//迭代fileCollection
fileCollection.each {
//    println it.name
}
//把fileCollection转化为其他类型
Set set = fileCollection.files
Set set1 = fileCollection as Set
List list = fileCollection as List
String path = fileCollection.asPath
File file=fileCollection.singleFile
File file1=fileCollection as File
//添加或者移除fileCollection元素
fileCollection -= files('src/file4.txt')
fileCollection += files('src/file4.txt')

你还可以给 files() 方法传个闭包或者 Callable 的实例,当这个集合的上下文被调用时,闭包和Callable的返回值就会转换为一组File实例,它的返回值可以是任何可以传给 files() 的类型。

task listSrc {
    doLast {
        File srcDir
        fileCollection = files {
            srcDir.listFiles()
        }
        srcDir = file('src')
        println "contents of $srcDir.name"
        fileCollection.collect {
            relativePath(it)
        }.sort().each {
            println it
        }
        srcDir = file('src2')
        println "contents of $srcDir.name"
        fileCollection.collect {
            relativePath(it)
        }.sort().each {
            println it
        }
    }
}

然后执行任务

± % gradle listSrc -q                                                                                                           !1846
contents of src
src/config.cson
src/file1.txt
src/file2.txt
src/file3.txt
src/file4.txt
src/file5.txt
contents of src2
src2/dir1

还有一些其他的你可以传给 files() 的类型:

  • FileCollection 把FileCollection解包成多个File实例并且把它们包含到 fileCollection
  • Task 任务的输出文件列表被包含到 fileCollection
  • TaskOutputs TaskOutputs 的输出文件被包含到 fileCollection

一定要注意的一点是 fileCollection 是惰性求值的,意味着你可以先定义一个 fileCollection ,然后在集合中的文件在将来使用的时候再进行创建并使用。

File trees

file tree 是层级结构排列的文件集合。比如,它可以表示目录树也可以表示ZIP文件的内容。它的代表是 FileTree 接口,这个接口是从 FileCollection 接口继承的,因此你可以像使用 FileCollection 那样使用它,Gradle中的很多对象都实现了这个接口,比如 source sets

获得 FileTree 实例的一种方式就是通过 Project.fileTree(java.util.Map) 方法,它可以使用一个baseDir创建 FileTree ,还可以使用ant模式 includeexclude 进行操作。

//使用一个baseDir来创建fileTree
FileTree tree = fileTree(dir: 'src')
tree.include '**/*.cson'
tree.exclude '**/*.txt'
//使用path创建fileTree
tree = fileTree('src').include('**/*.cson')
//使用闭包创建fileTree
tree = fileTree('src') {
    include '**/*.txt'
}
//使用map创建fileTree
tree = fileTree dir: 'src', include: '**/*.cson'
tree = fileTree dir: 'src', includes: ['**/*.cson', '**/*.txt']
tree = fileTree dir: 'src', include: '**/*.cson', exclude: '**/*.txt'

你可以像操作 file collection 一样操作 file tree ,你还可以通过visit访问它,或者通过模式来选择它的子层 file tree

task treeView {
    doLast {
        tree = fileTree('src2')
        //tree的迭代
        tree.each {
            println it
        }
        //通过pattern找到子层的tree
        def subtree = tree.matching {
            include '**/*.txt'
        }
        //合并两个tree
        tree+=fileTree 'src'
        //访问tree的元素
        tree.visit {
            println "$it.relativePath => $it.file"
        }
    }
}

注意, file tree 会默认 exclude 掉一些文件:

**/*~
    **/#*#
    **/.#*
    **/%*%
    **/._*
    **/CVS
    **/CVS/**
    **/.cvsignore
    **/SCCS
    **/SCCS/**
    **/vssver.scc
    **/.svn
    **/.svn/**
    **/.DS_Store
    **/.git
    **/.git/**
    **/.gitattributes
    **/.gitignore
    **/.gitmodules
    **/.hg
    **/.hg/**
    **/.hgignore
    **/.hgsub
    **/.hgsubstate
    **/.hgtags
    **/.bzr
    **/.bzr/**
    **/.bzrignore

用归档文件的内容创建fileTree

你可以使用归档文件创建fileTree,比如zip,tar文件,分别对应 Project.zipTree(java.lang.Object)Project.tarTree(java.lang.Object) ,这两个方法将返回的 FileTree 实例可以像其他 file treefile collection 那样使用,比如你可以通过复制 FileTree 的内容来实现解压,还可以合并归档文件。

task archiveView {
    doLast {
        //使用zip文件创建FileTree
        tree = zipTree('archive/CineGIF.zip')
        //使用tar文件创建FileTree
        tree = tarTree('someFile.tar')
        //有时候你需要先将gz先解压成tar格式
        tree=tarTree(resources.gzip('archive/tools.tar.gz'))
    }
}

指定输入文件集合

在Gradle中有很多对象都有可以接收一组输入的属性。比如 JavaCompile 任务的 source 属性,它定义了可以需要被编译的源文件。你可以使用上面的不同参数 file() 方法那样来设置这个属性的值,这意味着你可以使用的参数有文件,字符串,FileCollection,还有闭包。

还有一个和属性同名的方法 source 一样也可以使用,和 files() 方法一样使用

使用属性指定

//使用文件
compileJava {
    source = file('src/main/java2')
}
//使用字符串
compileJava {
    source = 'src/main/java2'
}

//使用集合
compileJava {
    source = ['src/main/java2', 'src2']
}

//使用FileCollection或者FileTree
compileJava {
    source = fileTree('src/main/').matching {
        include 'java2/*'
    }
}

//使用闭包
compileJava {
    source = {
        file('build').listFiles().findAll {
            it.name.endsWith('zip')
        }.collect { zipTree(it) }
    }
}

使用方法指定

compileJava {
    source 'src/main/java2', 'src/main/groovy'
    source 'src/main/java2'
    source fileTree('src/main/').matching {
        include 'java2/*'
    }
}

复制文件

你可以使用 Copy 任务来复制文件,而且非常灵活,你可以使用过滤器来过滤需要复制的文件,映射文件的名字。

为了使用 Copy 任务,你需要指定需要复制的一组文件,并且指定需要复制到的目录,你还可以指定在复制文件时如何进行转换,你可以使用Copy规范来实现他们,Copy规范的代表是 CopySpec 接口, Copy 任务实现了这个接口,你可以使用 CopySpec.from(java.lang.Object[]) 方法指定源文件集合,可以用 CopySpec.into(java.lang.Object) 指定输出目录。

task copyTask(type: Copy) {
    from 'src/main/java2'
    into 'dst'
}

from() 接受的参数种类和 files() 一样,当参数是一个目录是,则目录下的所有文件(不包括本目录)将递归的复制到目标目录。当参数是一个文件时,那么这个文件将会被复制到目标目录,如果参数对应的文件不存在,那么参数将会被忽略掉,当参数是个任务,那么该任务的输出文件将会被拷贝到目标目录,并且这个任务将会自动增加为当前任务的依赖。 into() 接受的参数种类和 files() 一样.示例如下:

task anotherCopyTask(type: Copy) {
    //递归目录所有文件
    from 'src2/dir1'
    //单个文件
    from 'src2/bb.txt'
    //任务的输出文件
    from copyTask
    //任务输出的文件
    from copyTask.outputs
    def destdir
    //闭包惰性求值
    into {
        destdir
    }
    destdir = file 'dist2'
}

你还可以通过ant格式的include模式和exclude模式来选择需要拷贝的文件

task copyTaskWithPatterns(type: Copy) {
    from 'src'
    into 'dist2'
    include '**/*.txt'
    include '**/*.java'
    exclude {
        it.file.name.startsWith('file') && it.file.text.contains('label')
    }
}

你还可以使用工程的 Project.copy(org.gradle.api.Action) 方法来拷贝文件,和使用Copy任务的方式差不多的,但是最大的区别就是 copy() 方法不支持增量构建

task copyMethod {
    doLast {
        copy {
            from 'src'
            into 'dist2'
            include '**/*.txt'
            include '**/*.java'
            exclude {
                it.file.name.startsWith('file') && it.file.text.contains('label')
            }
        }
    }
}

还有一个主要的区别就是 copy() 方法中 from() 的参数是Task对象时,无法被自动添加依赖,因为它是个方法而不是一个任务。这种情况下只能手动的添加输入输出,支持增量构建的同时支持任务依赖的推断,上一章也讲过了任务依赖推断就是增量构建的福利,如下:

task copyMethod2 {
    inputs.files copyTask
    outputs.dir 'dist3'
    doLast {
        copy {
            from copyTask
            into 'dist3'
        }
    }
}

最好就是使用Copy任务,因为它在不需要进行额外操作的情况下就可以支持增量构建和任务依赖推断。而 copy() 方法使用的场景仅仅是在自定义任务的时候作为任务的一部分存在,在这种场景下,记得要声明任务的输入输出,以便支持增量构建和任务依赖推断。

文件重命名

task renameTask(type: Copy) {
    from 'src'
    into 'dist4'
    //用闭包来映射
    rename {
        it.replace('file', 'fileDist')
    }
    //用正则
    rename 'file(.+)', 'fileDist$1'
}

过滤文件

task filter(type: Copy) {
    from 'src'
    into 'dist5'
    expand copyright: '2018', version: '1.0.0'
    expand project.properties
    filter {
        "[$it]"
    }
    filter {
        it.startsWith('//') ? null : it
    }
    filteringCharset = 'UTF-8'
}

使用 expand() 方法将会将文件中的 ${tokenName} 格式的符号进行替换,这个特性要注意使用,防止和源文件冲突。

filteringCharset 可以指定源文件和目标文件的文件编码格式。

CopySpec类

CopySpec形成了一个层次结构,Copy任务的规范都是从这个类继承来的。

task nestedSpecs(type: Copy) {
    from('src2') {
        exclude 'dir1'
    }
    exclude '**/*.java'
    into 'dist7'
    into('dist6') {
        from 'src'
    }
}

使用 Sync 任务

Sync 任务是继承是 Copy 任务的,执行的时候会讲源文件拷贝到目标目录,然后把目标目录不是 Sync 任务复制的删除掉,相当于scp和rsync的区别,比较常用的场景有安装程序,解压归档文件,备份项目的依赖。下面的代码就是备份项目依赖的例子:

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

文件归档

项目中常常可以见到很多jar归档文件,你也可以自己给项目添加WAR,ZIP或者TAR归档文件,归档文件可以由不同的归档任务创建:War,Zip,Tar,Jar还有Ear,他们的方式都差不多,我们以ZIP为例子:

apply plugin: 'java'

task zipTask(type: Zip) {
    from 'src'
    into('libs') {
        from configurations.runtime
    }
    doLast {
        zipTree("$buildDir/distributions/workingWithFiles.zip").each {
            println it
        }
    }
}

java插件为Zip任务增加了一些默认值,这就是你为什么没有看到 into() 方法的原因,只看到个嵌套的 CopySpecinto() 方法,归档任务的工作方式和Copy任务一样。像使用Copy任务一样使用它即可。

归档命名

projectName-version.type 是Gradle归档任务的默认格式,例如:

apply plugin: 'java'
version = '1.1.0'
task myZip(type: Zip) {
    from 'src'
    println myZip.archiveName
    println relativePath(myZip.destinationDir)
    println relativePath(myZip.archivePath)
}

执行任务:

± % gradle myZip -q  
workingWithFiles-1.1.0.zip
build/distributions
build/distributions/workingWithFiles-1.1.0.zip

可以看到格式是workingWithFiles-1.1.0.zip,归档的名字可以通过 archivesBaseName 更改。

apply plugin: 'java'
version = '1.1.0'
task myZip1(type: Zip) {
    from 'src'
    baseName 'sweetop'
    doLast {
        println myZip1.archiveName
    }
}

然后执行任务:

% gradle myZip1 -q
sweetop-1.1.0.zip

还可以进一步的自定义归档文件的名字:

apply plugin: 'java'
version = '1.1.0'
archivesBaseName = 'gradle'
task myZip2(type: Zip) {
    from 'src'
    appendix = 'init'
    classifier = 'src'
    doLast {
        println myZip2.archiveName
    }
}

执行任务:

± % gradle myZip2 -q
gradle-init-1.1.0-src.zip

归档任务的属性表:

属性名 类型 默认值 描述
archiveName String baseName-appendix-version-classifier.extension ,如果属性是null将不会增加到最终的归档文件的名字 生成的归档文件的名字
archivePath File destinationDir/archiveName 归档文件的绝对路径
destinationDir File 取决于文件类型,如果是jar或者war,那么在 project.buildDir/libraries ,如果是zip或者tar,那么就是 project.buildDir/distributions 归档任务所指向的输出目录
baseName String project.name 归档文件的baseName位置
appendix String null 归档文件的appendix位置
version String project.version 归档文件的version位置
classifier String null 归档文件的classifier位置
extension String 归档类型:zip,jar,war,tar,tgz,tbz2 归档文件的扩展名

你可以使用 Project.copySpec(org.gradle.api.Action) 在多个归档任务之间共享内容

可重现的归档过程

有时候你要确保相同代码在不同机器上生成的归档文件从字节这一层都是一样的,这无疑是个极大的挑战,因为不同机器上归档时候的文件顺序都可能不一样,但Gradle为你实现了这一点,只需要添加如下设置即可:

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

属性文件

java 的开发过程中,属性文件会经常见到,Gradle提供了在构建过程中创建属性文件的方法,就是通过 WriteProperties 任务。

WriteProperties 还修复了 Properties.store() 的bug,这个bug会对增量构建造成影响。标准的java会每次都生成一个唯一的属性文件,即使很多时候属性值根本没有改变,因为在注释中包含了时间戳,Gradle的 WriteProperties 任务则不同,它生成的每个属性文件从字节码上都是相同的,做到这样主要是进行了三个调整:

  • 没有时间戳的注释增加到属性文件中
  • 换行符是依赖于系统的,当然你可以自定义,默认是 \n
  • 所有属性按字母列表存储

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript核心技术

JavaScript核心技术

Shelley Powers / 苏敬凯 / 机械工业出版社 / 2007-6 / 45.00

Ajax是当今Web开发领域最流行的词汇。而JavaScript与CSS、XML和DOM几种老技术,加上XMLHttpRequest就构成了Ajax的四大基石。对于JavaScript,一些更资深的同事告诉我的感觉是失望。面对不同的浏览器和浏览器的不同版本,没有优秀的调试开发工具,JavaScript成了软件开发的泥潭。. 而本书的出版则给我们增加了一丝解决这些问题的信心。 它从最简单......一起来看看 《JavaScript核心技术》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具