在 Gradle 中,文件操作是 Gradle 构建的基础,几乎所有的操作都离不开文件。这篇文件就将对 Gradle 的文件 api进行详细的介绍。当我们熟悉 Gradle 的文件api时,再去理解 Gradle 构建,就可以达到事半功倍的效果。

文件的增删改查如何创建文件和目录

Gradle 完全支持 java 语言,因此你可以使用 Java 的相关 api 来创建文件或者目录。比如说使用 Files.createDirectories 来创建目录。除此之外,我们也可以使用 Project 的 mkdir 方法来创建。代码示例如下:

// gradle 推荐使用 file() 方法来获取 File 对象,不是直接 new File 的方式
// 下面的 F:/work/Directories/a/b 是绝对路径,file 也支持相对路径,比如 
// file("Directories/a/b") ,表示相对于在根目录下的位置
val file = file("F:/work/Directories/a/b")
// 使用 Java 的 Files api 创建目录
Files.createDirectories(file.toPath())
// 使用 createNewFile() 创建文件
file("F:/work/ab/a.txt").createNewFile()
// 使用 project 的 mkdir 创建目录
mkdir("F:/work/ab")

如何删除文件和目录

和上面的创建文件和目录的 api 类似。我们可以使用 java 的原生方法删除文件;也可以使用 Project 的 delete 方法删除文件,代码示例如下:

// 使用原生的方法删除目录
val file = file("F:/work/Directories/a/b")
file.delete()
// 使用 project 方法删除文件
val file1 = file("F:/work/ab/a.txt")
delete(file1.toPath())

除此之外,Gradle 还提供了 Delete 删除任务,代码示例如下:

tasks.register("myClean") {
    delete(buildDir)
}
// 除此之外,删除任务还可以批量删除指定的文件和目录,这个涉及到文件集合相关的东西,文章后面再详细介绍
tasks.register("cleanTempFiles") {
    delete(fileTree("src").matching {
        include("**/*.tmp")
    })
}

移动或者重命名文件和目录

在 Gradle 中,移动文件位置或者重命名文件都可以使用 java 的 renameTo 来实现。除此之外我们也可以使用 Copy 复制任务实现相同的效果,由于和 Copy 复制任务比较复杂,文章后面在介绍。

val sourceFile = file("F:/work/ab.txt")
val targetFile = file("F:/work/ab/a.txt")
sourceFile.renameTo(targetFile)

获取对应的文件和目录对象

上面提到过,我们可以使用 file 方法,通过传入绝对路径或者相对路径的方式,来获取对应的文件(File)对象。除此之外,Gradle 还提供了 ProjectLayout 这个类,来提供项目中几个重要位置的访问能力。

// 获取当前项目的重要文件位置路径
// 获取项目下 generated 目录的路径,即使这个目录不存在也会有结果,比如
// F:workappgenerated
project.layout.projectDirectory.dir("generated")
project.layout.projectDirectory.file("test.txt")
// 获取项目中 build 目录下的 generated 目录的路径,比如
// F:workappbuildgenerated
project.layout.buildDirectory.dir("generated")
project.layout.buildDirectory.file("test.txt")

需要注意的是 ProjectLayout 会返回一个 Provider 对象,这个类似于 kotlin 中的 by lazy,提供延时访问的能力。这是因为构建流程中,大部分文件都是后面生成的,因此需要延时访问。

如何读取/写入文件和目录

如果你需要对文件进行读写,直接使用 java/kotlin 原生的 api 方法就可以了,Gradle 暂时没有提供相应的方法或者任务。

文件集合

由于构建涉及到大量的文件和目录,因此 Gradle 对文件集合提供丰富的 api 支持。在 Gradle 中,文件集合分成两种:FileCollection 和 FileTree。它们的区别是,FileCollection 没有文件目录之间的层次结构,而 FileTree 有。如下图所示,使用 Gradle 提供的 Copy 任务,复制 FileCollection 和 FileTree 的区别如下:

// 创建 FileCollection,支持各种文件格式输入,非常方便
val collection: FileCollection = project.layout.files(
    "src",
    "src/file1.txt",
    File("src/file2.txt"),
    listOf("src/file3.csv", "src/file4.csv"),
    Paths.get("src", "file5.txt")
)
// FileCollection 本质是集合,支持各种集合操作
// 遍历
collection.forEach {
    if (it.isDirectory) {
        it.createDirectory()
    } else {
        it.createNewFile()
    }
}
// 过滤
val textFiles: FileCollection = collection.filter { f: File ->
    f.name.endsWith(".txt")
}
// 添加和删除集合
val union = collection + project.layout.files("src/file2.txt")
val difference = collection - project.layout.files("src/file2.txt")

// 使用 project.fileTree 来创建 ConfigurableFileTree 对象,它是 FileTree 的实现类,增加了正则表达式过滤的功能
// 直接创建
var tree: ConfigurableFileTree = project.fileTree("src/main").apply {
    // 设置过滤条件
    include("**/*.java")
    exclude("**/Abstract*")
}
// 通过 closure 创建
tree = fileTree("src") {
    include("**/*.java")
}
// 通过 map 创建
tree = project.fileTree("dir" to "src", "include" to "**/*.java", "exclude" to "**/*test*/**")
`kotlin
// 普通的遍历

如何对文件进行解读__了解文件内容

tree.forEach{ file: File -> println(file) } // 结构化的遍历方式,采用深度优先前缀顺序遍历文件和目录 tree.visit { println("${this.relativePath} => ${this.file}") } // 过滤 val filtered: FileTree = tree.matching { include("org/gradle/api/**") } // 添加 fileTree val sum: FileTree = tree + fileTree("src/test")

复制任务复制/重命名/移动文件

// 复制文件,from 设置复制的来源,into 设置复制的目的
tasks.register("copyReport") {  
    from(layout.buildDirectory.file("reports/my-report.pdf"))  
    into(layout.buildDirectory.dir("toArchive"))  
}
// 重命名文件,在 rename 返回重命名后的名字
tasks.register("copyReport") {
    from(layout.buildDirectory.file("reports/my-report.txt"))
    into(layout.buildDirectory.dir("toArchive"))
    rename { oldName ->
        "xxx.txt"
    }
}
// 移动文件位置
tasks.register('moveFile') {
    doLast {
        // 复制文件
        copy {
            from 'src/file.txt'
            into 'destination/dir'
        }
        // 删除源文件
        delete 'src/file.txt'
    }
}

子规范

有的时候,我们希望只对部分目录做一些特殊的操作。比如说只处理指定目录下的 html、png 文件。这时候就可以使用 Copy 任务的子规范。代码示例如下:

tasks.register("nestedSpecs") {
    into(layout.buildDirectory.dir("explodedWar"))
    exclude("**/*staging*")
    from("src/dist") {
        // 子规范
        include("**/*.html", "**/*.png", "**/*.jpg")
    }
    from(sourceSets.main.get().output) {
        // 子规范
        into("WEB-INF/classes")
    }
    into("WEB-INF/lib") {
        // 子规范
        from(configurations.runtimeClasspath)
    }
}

CopySpec

如果我们想要让不同的任务使用相同的规范,我们可以使用 CopySpec 来实现,代码示例如下:

val webAssetsSpec: CopySpec = copySpec {
    from("src/main/webapp")
    include("**/*.html", "**/*.png", "**/*.jpg")
    rename("(.+)-staging(.+)", "$1$2")
}
tasks.register("copyAssets") {
    into(layout.buildDirectory.dir("inPlaceApp"))
    with(webAssetsSpec)
}

Sync 任务

Sync 任务扩展了 Copy 任务,它在复制源文件到目标目录后,会删除目标目录中未被复制的文件。一般用于安装应用、创建归档的展开副本或维护项目依赖的副本等。代码示例如下:

tasks.register("libs") {
    from(configurations["runtime"])
    into(layout.buildDirectory.dir("libs"))
}

文件归档(archives)

在 Gradle 中,打包是最常见的操作,因此 Gradle 提供了 Zip、Tar、Jar等任务来简化流程。在 Gradle 眼中,将文件打包成Zip、Tar或Jar等格式实际上是一次 archives (归档)的过程,只是格式不同而已。因此我们只需要了解 Zip 任务即可。代码示例如下:

tasks.register("packageDistribution") {
    archiveFileName = "my-distribution.zip"
    destinationDirectory = layout.buildDirectory.dir("dist")
    from(layout.buildDirectory.dir("toArchive"))
}

文件的解压缩代码示例如下:

tasks.registerCopy>("unpackFiles") {
    from(zipTree("src/resources/thirdPartyResources.zip"))
    into(layout.buildDirectory.dir("resources"))
}

参考

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。