目录
[IntelliJ IDEA + EduTools] 从零开始编写一套 Java 教材(零)
[IntelliJ IDEA + EduTools] 从零开始编写一套 Java 教材(一)
[IntelliJ IDEA + EduTools] 从零开始编写一套 Java 教材(二)
上期回顾
上一节课,我们介绍了与 EduTools 有关的4种 YAML 文件的格式。在日常的开发过程中,很难遇到需要我们人为编辑这些 YAML 文件的情况,EduTools 中的 Course Creator 工具会辅助我们进行数字教材的编写工作。
这一节课,我们将着重讲解 Gradle 项目的2个核心配置文件:build.gradle 和 settings.gradle 。
第三课 Gradle 构建脚本
Gradle 是一个以 Groovy 语言为项目构建脚本语言的自动化构建工具,支持 Java / Groovy / Scala / Kotlin 等各类 JVM 编程语言。build.gradle 与 settings.gradle 是项目的核心,它们共同管理着 Gradle 项目的一切。IntelliJ IDEA 会首先启动 Gradle ,Gradle 执行 *.gradle 构建脚本并生成各项配置,IntelliJ IDEA 自动分析这些配置,并将其与自身的设置进行同步,这正是我们在 IntelliJ IDEA 中打开 Maven / Gradle / SBT 项目时会先『加载项目结构』再『导入 IntelliJ IDEA 项目』的原因。
我们的教程项目结构本质上是一个 Gradle 多子项目结构。单一项目的 Gradle 构建脚本可能十分简单,但对于拥有四层目录分级的大型数字教程项目而言,一个正确的配置脚本至关重要。EduTools 已经为我们生成了这样的构建脚本,我们要做的只是读懂它、理解它。
第0节 build.gradle
// 用于构建脚本自身的配置
buildscript {
// 远程仓库列表
repositories {
// 华为云 Maven 远程仓库
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
// 阿里云 Maven 远程仓库
maven { url "https://maven.aliyun.com/repository/public" }
// Maven 中央仓库
mavenCentral()
}
}
/**
* 格式化 Gradle 标准输出
*
* @param output 字节数组输出缓冲流
*/
def printOutput(def output) {
// 创建一个新的 Gradle 任务
return tasks.create("printOutput") {
// 遍历缓冲流中的每一行内容
for (line in output.toString().readLines()) {
// 补充前缀
println "#educational_plugin" + line
}
}
}
// 子项目统一配置
subprojects {
// 引入 Java 语言插件
apply plugin: 'java'
// 引入构建插件
apply plugin: 'application'
// 限制 Java 语法为 JDK8 及以下
// JDK9 及更高版本的语法特性将不可使用
sourceCompatibility = 1.8
// 指定子项目所使用的远程仓库地址
repositories {
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
maven { url "https://maven.aliyun.com/repository/public" }
mavenCentral()
}
// 子项目依赖项列表
dependencies {
// 编译时使用的依赖项
compile group: 'org.apache.commons', name: 'commons-math', version: '2.2'
// 测试编译时使用的依赖项
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.6.2'
}
// 项目源文件列表
sourceSets {
// 主源文件目录
main { java.srcDir 'src' }
// 测试源文件目录
test { java.srcDir 'test' }
}
// 指定项目的主类名
mainClassName = project.hasProperty("mainClass") ? // 当项目配置了 mainClass 属性时
project.getProperty("mainClass") : "" // 获取配置值,否则留空
// 定义 Gradle 运行时的标准输出
def runOutput = new ByteArrayOutputStream()
tasks.run.setStandardOutput(runOutput)
// 指定构建过程最后执行的动作
tasks.run.doLast { printOutput(runOutput) }
}
// 通用项目,这个子项目用于编写其他子项目所共用的类或方法
project(':util') {
// 特定项目的依赖项
dependencies {
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.6.2'
}
}
// 配置除 util 之外的其他子项目
configure(subprojects.findAll { it.name != 'util' }) {
// 添加 util 项目编译生成的类文件
dependencies {
compile project(':util').sourceSets.main.output
testCompile project(':util').sourceSets.test.output
}
}
第1节 settings.gradle
/**
* 名称标准化(用于去除目录中的非法字符,转换为 Gradle 可接受的项目名)
*
* @param 任意字符串
* @return 格式化后的字符串
*/
static String sanitizeName(String name) {
// 将空格与各类标点符号统一替换为下划线
return name.replaceAll("[ /\\\\:<>\"?*|()]", "_")
// 去除名称开头和结尾多余的小数点
.replaceAll("(^[.]+)|([.]+\$)", "")
}
// 指定根项目名
rootProject.name = 'Tutorial'
// 递归遍历根项目目录的各级子目录
rootProject.projectDir.eachDirRecurse {
// 如果项目不是任务目录,或者目录存在 .idea/ 子目录
if (!isTaskDir(it) || it.path.contains(".idea")) {
// 忽略此目录
return
}
// 获取当前目录相对根项目目录的路径
def taskRelativePath = rootDir.toPath().relativize(it.toPath())
def parts = []
// 将相对路径中每一部分的目录名添加到数组中
for (name in taskRelativePath) {
parts.add(sanitizeName(name.toString()))
}
// 将相对路径用连字符串起来,作为项目名
def moduleName = parts.join("-")
// 引入项目名
include "$moduleName"
// 指定子项目的项目根目录
project(":$moduleName").projectDir = it
}
/**
* 检查一个目录是不是任务目录
*
* @param 目录对象
* @return 拥有 src/ 目录的项目是任务项目,否则不是
*/
static def isTaskDir(File dir) {
return new File(dir, "src").exists()
}
// 引入全局工具项目
include 'util'