在 Kotlin 上进行开发时,每个初学者都面临着不了解使用该编程语言的适当工具的问题。这就是本文的目的 - 解释 Gradle for Kotlin 以及 Kotlin 上的工作。我们走吧!
?定义
Gradle是一个使用一组工具自动组装(构建、编译)程序的系统。
但让我们从简单的事情开始 - 如何使用 Gradle 创建我们的第一个项目?
项目
让我们从应用程序构建系统中存在的基本概念——项目开始。
?定义
项目是应用程序组织的一个独立单元,作为一组依赖模块和规则。模块是一个独立的代码组织单元,具有一定的规则(如何构建等)。其存在的目的与 Kotlin 中的包相同——将代码划分为逻辑块,以提高源代码的质量(在一个项目和其他项目中重用代码)。
我在说什么规则?事实上,一切都非常简单 - 我们描述我们的项目将如何构建(技术特性的描述)、针对哪个平台(例如 Android 或 iOS)、使用哪种语言以及使用哪种方式(项目依赖项)。
结构
让我们创建一个示例项目结构:
PS:示例中的名称没有特殊意义,它们只是Foobar的术语。
所有模块都需要具备build.gradle.kts才能工作。重要的是要注意,模块不能单独存在,只有当我们通过以下方式将它们显式包含到我们的项目中时,它们才起作用settings.gradle.kts
正如你所看到的,我们有一种“首席主管”,他决定哪些模块将出现在我们的项目中以及它们将如何工作,以及“本地主管”,他们只为他们下属的代码(模块)设置规则,但是,值得注意的是,在规则方面,项目比模块具有更高的优先级(但这不是我们将在这篇特定文章中介绍的内容)。
有什么规则?事实上,它们有很多 - 这完全取决于你做什么,但基本的是,例如:
- 项目名称、版本、组(类似于 Kotlin 的包的标识符)
- 编程语言(Java / Scala / Groovy / Kotlin / 等)
- 平台(仅与 Kotlin 相关,更清楚地说,与 Kotlin 多平台插件相关)
- 依赖关系(代码中使用的库或框架)
模块
让我们考虑一下这些模块:它们的食用方式和食用内容。让我提醒您什么是模块:
?定义
模块是代码组织的独立单元,具有一组定义模块行为的正式规则。
以模块为例foo: 要创建一个模块,首先我们创建该模块的目录。之后,我们创建一个名为 的文件(或者如果您使用 Groovy 作为脚本语言,没有其他方法),我们已经在其中规定了我们的模块可以执行的操作。
要创建模块,首先,我们创建该模块的目录。 之后,我们创建一个名为 build.gradle.kts 的文件(如果您使用 Groovy 作为脚本语言,则为 build.gradle),我们已经在其中规定了我们的模块可以执行的操作。
build.gradle.kts
我们的设置文件具有以下结构:
别害怕! 即使看起来相当复杂。
任何 build.gradle.kts 中的主要组件都是 Plugins 块。 依赖项和存储库块独立于插件块,但如果没有它,它们就像在空荡荡的剧院里讲笑话的喜剧演员一样 - 他们可能有一些很棒的材料,但没有舞台,没有观众,也听不到笑声。
应用于模块的插件通常会使用您在“依赖项”块中指定的内容。 因此,没有使用它的插件的依赖项是没有用的,这就是为什么依赖项与我们方案中的插件有联系。
存储库本身并不依赖于插件,但您始终需要它们将任何依赖项或插件应用到您的项目。 因此,如果没有插件或依赖项,它的存在就没有意义。 因此,用我们之前的类比来说,这就像一个挤满了人的剧院,但舞台上没有喜剧演员。
任务也是 Gradle 配置文件中的基本内容。 它们始终由您应用于模块的插件提供。 在没有任何插件的空模块中,您不会有任何任务。 但是,在项目级别上可以执行一些基本任务:
任务(返回整个项目中可用任务的列表:名称、任务所依赖的任务等)。
依赖项(打印项目依赖项的报告,显示使用了哪些依赖项及其版本)
帮助(返回整个项目中可用任务的列表以及简要说明)。
模型(提供项目结构、任务等的详细报告;帮助您理解和调试 Gradle 构建)
BonusTasks 可以依赖于其他任务,当您需要其他任务执行的结果时,它特别有用。
例子
现在,让我们考虑一个例子。 例如,让我们创建一个 Kotlin/JVM 项目,并使用 kotlinx.coroutines 库作为依赖项。
首先,我们需要在项目的根目录中创建项目配置文件 – settings.gradle.kts:
rootProject.name = "our-first-project"
为了使其正常工作,您应该在 IDE 中运行 gradlesync:
您可以为新模块创建一个新文件夹,也可以以相同的方式将根文件夹用作模块,只需在根目录中添加 build.gradle.kts 文件 (our-first-project/build.gradle.kts) 。
?? 重要当我们使用根文件夹作为模块时,我们不需要显式地将其添加到项目配置文件中,但是对于任何新模块,我们应该使用 include 函数来声明它 - 例如, include(":foo") ( 对于嵌套文件夹,请使用 include(":foo:bar"))
让我们从插件开始:
plugins {
id("org.jetbrains.kotlin.jvm") version "1.9.0"
}
Bonus我们可以使用 kotlin 函数来简化 Kotlin 插件(例如 jvm、android、js、multiplatform)的声明: id("org.jetbrains.kotlin.jvm") -> kotlin("jvm")。它会自动附加 org. jetbrains.kotlin 开头。
现在,让我们来看看依赖关系:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
我们的 Kotlin/JVM 插件提供了一个对我们有用的功能——实现。 如果没有它,我们将不得不显式地编写一个配置名称(插件的标识符)来消耗我们的依赖项。 您还记得,依赖项并不是独立存在的。 因此,更清楚地说,依赖项块仅提供添加和使用添加的依赖项的基本功能。 我们可以通过以下方式添加依赖项(但我们仍然需要一个插件来使用它):
dependencies {
add(configurationName = "implementation", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
ConfigurationName 代表将依赖项与不同的目标插件(正在消耗我们的依赖项的插件)分开。
但是,如果我们尝试构建我们的模块,我们将遇到下一个问题:
Could not resolve all dependencies for configuration ':compileClasspath'. > Could not find org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3.
要解决此问题,我们需要指定要从中实现依赖项的存储库。 让我们看一下例子:
repositories {
// builtins:
mavenCentral()
mavenLocal()
google()
// or specify exact link to repository:
maven("https://maven.y9vad9.com")
}
? DefinitionMaven 存储库 – 就像在线商店或代码库。 它们是预构建的软件库和依赖项的集合,您可以在项目中轻松访问和使用它们。 这些存储库提供了一种集中且有组织的方式来共享和分发代码。
另外,值得注意的是,Maven 是另一个具有 Gradle 内置支持的构建工具。
但是,对于我们的例子,我们只需要在 mavenCentral() 中。 因此,我们生成的 build.gradle.kts 如下:
plugins {
kotlin("jvm") version "1.9.0"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
对于这样的例子,我们不需要接触任何任务。 但值得一提的是,我们的 Kotlin 插件提供了以下任务:
编译Kotlin
编译Java
ETC
但是,通常,您不需要直接调用这些任务,除非您正在开发自己的依赖于这些任务的结果/输出的插件。
Bonus您可以使用命令行或通过 IDE 运行 gradle 任务:
作为一个例子,我以 gradle 构建任务为例。
我们现在将跳过任务及其使用方法的完整说明。 我将在接下来的文章中介绍它。
现在,我们终于来看看如何以及在哪里编写代码。
源集
我们弄清楚了如何创建项目和模块,但是我们应该在哪里编写代码呢? 在Gradle项目中,有一个不同源代码集(Source Sets)的概念——一种针对不同需求的代码分离。 例如,对于我们前面的示例,默认存在以下集合:
main – 顾名思义,它是放置代码的主要位置。
test – 用于与测试相关的代码。 它依赖于主源集,并具有您在 main.c 中编写的所有依赖项/代码。
Bonus 但情况并非总是如此,例如,Kotlin/多平台项目为您正在编写的每个平台都有专用的源集(基本上,该插件为我们需要的所有平台创建了一组源集)。 因此,值得一提的是,它始终取决于您应用于模块的插件以及您的配置。 这不是一个常数。
主要的
要开始编码,我们需要为我们的编程语言创建一个文件夹,对于 Kotlin,它是 src/main/kotlin 文件夹。 因此,从现在开始,我们可以简单地创建“Hello, World!” 项目。 让我们在最近创建的文件夹中创建 Main.kt:
fun main() = println("Hello, Kotlin!")
您可以使用 IDE 运行它,它会自动处理 Gradle 构建过程。
测试
正如我之前告诉您的,该源集用于测试目的。 但值得一提的是,它有自己的依赖项(但它也从主源集派生)。 因此,您可以实现此源集中可用的依赖项。 例如,让我们实现 kotlin.test 库:
dependencies {
// ...
testImplementation(kotlin("test"))
}
您可以参阅 Kotlin 文档中的完整教程,了解如何使用 kotlin.test 测试代码。
多模块项目
不同模块的创建需要它们之间的交互。 另外,让我们分析一下交互的类型,你不能或不应该做什么,以及有什么意义。 开始吧!
如果您还记得我们最初的项目结构,它有几个模块:
- foo
- bar
- qux
让我们将 foo 视为主模块,我们在其中拥有应用程序的入口点(Main.kt 文件)。 让我们开始为所有三个模块创建配置(这很简单,没有任何依赖项):
plugins {
kotlin("jvm") version ("1.9.0")
}
repositories {
mavenCentral()
}
为了使它工作,让我们将我们的模块添加到settings.gradle.kts:
rootProject.name = "example"
include(":foo", ":bar", "qux")
然后,让 foo 依赖于 bar 模块:
// File: /foo/build.gradle.kts
dependencies {
implementation(project(":bar"))
}
?? 重要要从项目中实现模块,您应该使用项目函数指定它。 当实现模块或在其他地方指定它时,我们使用特殊的符号,其中 / 被替换为 : 符号。
还有额外的好处:要实现根模块,只需使用implementation(project(":"))。
从现在开始,我们可以使用 foo 模块内 bar 中的任何函数或类(当然,如果这些声明的可见性允许的话)。 例如,让我们在 foo 模块中创建一个文件:
package com.my.project
fun printMeow() = println("Meow!")
我们可以在 foo 模块中使用它:
import com.my.project.printMeow
fun main() = printMeow()
但是,无法从 qux 模块使用它。 此外,如果我们尝试实现 foo 模块,bar 仍然无法访问。 默认情况下,模块的依赖关系不会暴露给其他模块。
Bonus我们可以使用 api 函数而不是实现来共享实现我们特定模块的模块的依赖关系。 这样,例如 qux 模块可以通过实现 foo 来访问 bar 模块函数/类/等,而无需显式依赖 bar 模块。
局限性
想象一下,在前面的示例之后,您需要获取任何类/函数/等。 foo 模块中的 bar 内部。 如果您尝试这样做,您将遇到下一个问题:
Circular dependency between the following tasks:
:bar:classes
\--- :bar:compileJava
+--- :bar:compileKotlin
| \--- :foo:jar
| +--- :foo:classes
| | \--- :foo:compileJava
| | +--- :bar:jar
| | | +--- :bar:classes (*)
| | | +--- :bar:compileJava (*)
| | | \--- :bar:compileKotlin (*)
| | \--- :foo:compileKotlin
| | \--- :bar:jar (*)
| +--- :foo:compileJava (*)
| \--- :foo:compileKotlin (*)
\--- :foo:jar (*)
这是什么一回事呢? 一切都很简单——你不能创建循环依赖。
Gradle 中的循环依赖就像一个永无休止的循环,它会使您的构建过程陷入困境,因为任务一直在等待彼此完成,而这种情况永远不会发生。 避免它们对于确保您的构建顺利运行至关重要。 此外,违反依赖倒置原则总是不好的做法。
您可以参考此讨论以了解更多信息。
? 有经验的人的奖励通常,如果我们谈论移动应用程序,我们会使用三层架构。 因此,最好将其划分为不同的模块以强制执行架构规则:
例如,它使得我们的领域层不依赖于数据层——这实际上是不可能的,因为会存在循环依赖问题。
结论
这不仅仅是另一种咖啡冲泡方法;它是一种咖啡冲泡方法。 它是一个强大的构建自动化工具,可以简化您的软件开发过程。 使用 Gradle,您可以管理依赖项、自动执行任务并保持项目井井有条。 这就像有一个值得信赖的助手来处理细节,这样您就可以专注于编写出色的代码!