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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lastsweetop/article/details/79038161

转载请注明出处:http://blog.csdn.net/lastsweetop/article/details/79038161

构建的大部分工作是基于文件的,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
  • 所有属性按字母列表存储

猜你喜欢

转载自blog.csdn.net/lastsweetop/article/details/79038161