近日,广电总局网络视听司发布《管理提示(AI魔改)》指出,近期,AI“魔改”视频以假乱真、“魔改”经典现象频发。如《甄嬛传》变身“枪战片”、《红楼梦》改成“武打戏”、孙悟空骑着摩托车扬长而去等。《管理提示》认为,这些视频为博流量,毫无边界亵渎经典IP,冲击传统文化认知,与原著精神内核相悖,且涉嫌构成侵权行为。本篇文章转自小墙程序员的博客,文章主要分享了如何使用 Gradle 的文件 api,相信会对大家有所帮助!https://juejin.cn/post/7445743658295443466
在 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<Delete>("myClean") {
delete(buildDir)
}
// 除此之外,删除任务还可以批量删除指定的文件和目录,这个涉及到文件集合相关的东西,文章后面再详细介绍
tasks.register<Delete>("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:\work\app\generated
project.layout.projectDirectory.dir("generated")
project.layout.projectDirectory.file("test.txt")
// 获取项目中 build 目录下的 generated 目录的路径,比如
// F:\work\app\build\generated
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<Copy>("copyReport") {
from(layout.buildDirectory.file("reports/my-report.pdf"))
into(layout.buildDirectory.dir("toArchive"))
}
// 重命名文件,在 rename 返回重命名后的名字
tasks.register<Copy>("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<Copy>("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 来实现,代码示例如下:val webAssetsSpec: CopySpec = copySpec {
from("src/main/webapp")
include("**/*.html", "**/*.png", "**/*.jpg")
rename("(.+)-staging(.+)", "$1$2")
}
tasks.register<Copy>("copyAssets") {
into(layout.buildDirectory.dir("inPlaceApp"))
with(webAssetsSpec)
}
Sync 任务扩展了 Copy 任务,它在复制源文件到目标目录后,会删除目标目录中未被复制的文件。一般用于安装应用、创建归档的展开副本或维护项目依赖的副本等。代码示例如下:tasks.register("libs") {
from(configurations["runtime"])
into(layout.buildDirectory.dir("libs"))
}
在 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.register<Copy>("unpackFiles") {
from(zipTree("src/resources/thirdPartyResources.zip"))
into(layout.buildDirectory.dir("resources"))
}