目录
- 【4】Gradle-快速入门使用【Gradle多模块项目详解】
- 创建多项目构建
- 添加子项目
- 命名建议
- 项目依赖项
- 项目路径
- 不同模块的`build.gradle`配置
- 子项目之间共享构建逻辑
- 公约插件
- 跨项目配置
- buildSrc开发公约插件
- 调整多模块项目配置
- 修改项目树的元素
- 了解Gralde配置时间和执行时间
- 并行项目执行
- 解耦项目
个人主页: 【⭐️个人主页】
需要您的【💖 点赞+关注】支持 💯
【4】Gradle-快速入门使用【Gradle多模块项目详解】
📖 本文核心知识点:
- 多模块项目
- 多模块添加子项目
- 多模块全局配置方式
- 多模块间依赖
- 多模块项目调整
- 多模块buildSrc目录
- 多模块配置时间和执行时间
- 并行执行和解耦合
创建多项目构建
Gradle中的多项目
构建由一个根项目
和一个或多个子项目
组成。
基本的多项目构建包含一个根项目和一个子项目。这是一个多项目构建的结构,它包含一个名为app的子项目:
├── app
│ ...
│ └── build.gradle
└── settings.gradle
这是启动任何Gradle项目的推荐项目结构。build init插件还会生成遵循这种结构的框架项目——一个根项目和一个子项目。
🅰️ 注意:根项目没有Gradle构建文件build.gradle
,只有一个定义要包含的子项目的设置文件settings.gradle
。
settings.gradle
rootProject.name = 'basic-multiproject'
include 'app'
在这种情况下,Gradle将在app目录中查找构建文件。
我们可以通过运行gradlew projects
命令来查看多项目构建的结构
使用 gradle -q run
命令,启动app项目
添加子项目
假设我们想要在前面创建的项目中添加另一个名为infrastructure
的子项目。我们所需要做的就是在根设置文件中添加另一个include
语句:
settings.gradle
rootProject.name = 'study-spring3'
include 'app'
// 基础设施模块
include 'infrastructure'
结构:
.
├── app
│ ...
│ └── build.gradle
├── infrastructure
│ ...
│ └── build.gradle
└── settings.gradle
命名建议
随着项目的发展,命名
和一致性
变得越来越重要。为了保持构建的可维护性
,我们建议采用以下方法:
-
为
子项目
保留默认项目名称
:可以在设置文件中配置自定义项目名称。但是,对于开发人员来说,跟踪哪个项目属于哪个文件夹是不必要的额外工作。 -
对所有项目名称使用
kebab大小写格式
:kebab大小写格式是指
所有字母都是小写
,单词之间用破折号(' - ')
字符分隔(例如kebab-case-formatting
)。这已经是许多大型项目的实际模式。此外,Gradle支持kebab case
名称缩写。 -
在设置文件
settings.gradle
中定义根项目名称``:' rootProject.name
'有效地为整个构建分配了一个名称,这在构建扫描等报告中使用。如果没有设置根项目名称,则名称将是容器目录名称
,这可能是不稳定的(即您可以将项目签出到任何目录)。如果没有设置根项目名,并且它被检出到文件系统的根目录(例如/或C:),则该名称将随机生成。
项目依赖项
❓ 如果一个项目需要另一个项目在其编译类路径上生成的jar,该怎么办?
❓ 如果它还需要其他项目的传递依赖项怎么办?
显然,这是Java多项目构建中非常常见的用例。正如在项目依赖项中提到的,Gradle为此提供了项目依赖项
。
├── buildSrc
│ ...
├── api
│ ├── src
│ │ └──...
│ └── build.gradle
├── domain-core
│ ├── src
│ │ └──...
│ └── build.gradle
├── infrastructure
│ ├── src
│ │ └──...
│ └── build.gradle
└── settings.gradle
有三个项目api
, domain-core
, infrastructure
,它们之间的依赖关系是
我们使用:
分隔符来定义项目路径
。
项目路径
将
给定的项目
添加到构建
中。所提供列表中的每个路径
都被视为要添加到构建中的项目的路径
。注意,这些路径不是文件路径
,而是指定新项目在项目层次结构
中的位置
。因此,所提供的路径必须使用':'
字符作为分隔符
(而不是’/')。
所提供路径的最后一个元素
用作项目名称。提供的路径被转换为相对于根项目目录的项目目录。项目目录
可以在项目被包含之后通过改变’projectDir'属性
来改变(参见ProjectDescriptor.setProjectDir(java.io.File)
)
使用项目路径的一些常见示例如下:
// include two projects, 'foo' and 'foo:bar'
// directories are inferred by replacing ':' with '/'
include 'foo:bar'
// include one project whose project dir does not match the logical project path
include 'baz'
project(':baz').projectDir = file('foo/baz')
// include many projects whose project dirs do not match the logical project paths
file('subprojects').eachDir { dir ->
include dir.name
project(":${dir.name}").projectDir = dir
}
不同模块的build.gradle
配置
- app
dependencies { implementation 'org.apache.commons:commons-text' implementation project(':infrastructure') }
- infrastructure
dependencies { implementation project(':domain-core') }
- domain-core 【无】
执行gradle app:dependencies
查看app的依赖项
子项目之间共享构建逻辑
公约插件
通常,多项目构建
中的子项目
具有一些共同的特征
。
例如,几个子项目可能包含特定编程语言的代码,而另一个子项目可能专门用于文档。
代码质量规则
适用于所有代码子项目,但不适用于文档
子项目。与此同时,具有共同特征的子项目
可能服务于不同的目的——它们可能会产生不同的工件类型
来进一步区分它们,
例如:
1.公共库
: 发布到某些Mavne存储库的库
2.内部库
:其他子项目在项目内部依赖的库
3.命令行应用程序
: 具有特定打包要求的应用程序
4.Web服务
: 具有与上述不同的特定包装要求
的应用程序
等等
其他一些代码子项目可能专门用于测试
目的等。
以上特征
标识了子项目的类型
。或者换句话说,子项目的类型
告诉我们该项目具有哪些特征
。
所以: 多个特征组成了一类的子项目。一类子项目可以作为公共构建进行子项目间共享。
Gradle推荐
的组织构建逻辑的方法
是使用其插件系统
。
-
插件
应该定义子项目的类型
。事实上,Gradle核心插件以相同的方式建模
例如,Java插件
配置了一个通用的java项目,而Java库插件
在内部应用Java插件,并配置特定于Java库的方面。同样,应用程序插件
应用和配置Java插件和分发插件。 -
您可以通过应用和配置核心和外部插件来编写
自定义构建逻辑
,并创建自定义插件
,定义新的项目类型
并配置特定
于您的项目或组织的约定(特征)。
跨项目配置
另一种令人沮丧的子项目之间共享构建逻辑的方法
是通过
subprojects {}
allprojects {}
DSL结构进行跨项目配置。
通过交叉配置,构建逻辑可以注入子项目,在查看子项目的构建脚本时,这并不明显,这使得理解特定子项目的逻辑更加困难。从长远来看,交叉配置通常会随着越来越多的条件逻辑和更高的维护负担而变得复杂。
交叉配置还可以引入项目之间的配置时间耦合
,这可能会阻止按需配置
等优化正常工作。
配置时间耦合: 指下载插件和依赖的时间。指多项目构建时,子项目间交叉依赖,耦合在一起。
交叉配置有两种最常见的用途,都可以使用约定插件的方式
更好地建替代:
- 将插件或其他配置应用于特定类型的子项目。
通常,如果子项目是X类型,那么交叉配置部分就可以完成,然后配置y。这相当于将X-conventions插件直接应用于子项目。
- 从某种类型的子项目中提取信息。此用例可以使用传出的配置变体进行建模。
buildSrc开发公约插件
建议将约定插件
的源代码和测试
放在项目根目录
的 buildSrc
目录中。
使用约定插件编写构建逻辑的多项目构建的另一个更复杂和现实的例子是Gradle构建工具本身的构建。
目录buildSrc
被视为包含的构建
。发现目录后,Gradle会自动编译和测试此代码,并将其放入构建脚本的类路径中。对于多项目构建,只能有一个buildSrc目录,该目录必须位于根项目目录
中。buildSrc应优于脚本插件,因为它更容易维护、重构和测试代码
。
buildSrc使用适用于Java和Groovy项目的相同源代码约定。它还提供了对Gradle API的直接访问。其他依赖项可以在buildSrc下的专用build.gradle中声明。
可以使用gradle init 生成buildSrc目录。并进行约定插件的gradle编写
调整多模块项目配置
多项目构建
总是由具有单个根的树表示
。树中的每个元素代表一个项目。项目有一个路径,表示项目在多项目构建树中的位置。在大多数情况下,项目路径
与项目
在文件系统中的物理位置一致
。然而,这种行为是可配置的。项目树在settings.gradle文件中创建。设置文件的位置也是根项目的位置。
在设置文件中,您可以使用include
方法构建项目树。
通过修改settings.gradle
文件中的include
子项目应用。可以调整子项目的相关信息
include 'project1', 'project2:child', 'project3:child1'
include方法将项目路径作为参数。项目路径假定等于相对的物理文件系统路径。
例如,默认情况下,路径“services:api”映射到文件夹“services/api”(相对于项目根目录)。你只需要指定树的叶子。这意味着包含“services:hotels:api”路径将导致创建3个项目:“services”、“services:hotels”和“services:hotels:api”。
修改项目树的元素
rootProject.name = 'main'
include('project-a')
project(':project-a').projectDir = file('../my-project-a')
project(':project-a').buildFileName = 'project-a.gradle'
了解Gralde配置时间和执行时间
构建阶段描述了每个Gradle构建的阶段。让我们放大多项目构建的配置和执行阶段
。
这里的
配置
意味着评估项目的构建脚本文件,其中包括下载所有插件和构建脚本依赖项
。
默认情况下,所有项目的配置发生在执行任何任务之前。这意味着,当请求来自单个项目的单个任务时,首先配置多项目构建的所有项目
。每个项目都需要配置的原因是Gradle项目模型的任何部分 访问和更改 的灵活性
。
并行项目执行
并行执行
试图:
减少
执行受IO绑定或以其他方式不会消耗所有可用CPU资源的多项目构建的总构建时间
。- 为小型项目的执行提供更快的反馈,而无需等待其他项目的完成。
并行项目执行允许并行执行解耦多项目构建中的独立项目(另见解耦项目)。虽然并行执行在配置时并不严格要求解耦,但长期目标是提供一套强大的功能,这些功能将可用于完全解耦的项目。这些功能包括:
- 按需配置。
- 并行配置项目。
- 为不变的项目重复使用配置。
- 项目级最新检查。
- 在构建依赖项目的地方使用预先建造的工件。
并行执行是如何工作的?
首先,您需要告诉Gradle使用并行模式。您可以使用
--parallel
命令行参数或配置构建环境(Gradle属性
)。除非您提供特定数量的并行线程,否则Gradle会尝试根据可用的CPU内核选择正确的数量
。每个并行工作线程在执行任务时都只拥有给定的项目。任务依赖性得到完全支持,并行工作线程将首先开始执行上游任务。请记住,在并行模式下,不能保证解耦任务的字母顺序,如在顺序执行过程中所示。换句话说,在并行模式下,任务一旦其依赖项完成,任务工作线程可以运行它们,任务将立即运行,这可能比它们在顺序构建期间开始时更早。您应该确保正确声明任务依赖项和任务输入/输出,以避免订购问题。
解耦项目
Gradle允许任何项目在配置和执行阶段访问任何其他项目。
如果两个项目不直接访问彼此的项目模型,它们就会脱钩。解耦的项目只能在声明的依赖项方面进行交互:项目依赖项和/或任务依赖项。任何其他形式的项目交互(即通过修改另一个项目对象或从另一个项目对象读取值)都会使项目耦合。在配置阶段耦合的后果是,如果使用“按需配置”选项调用gradle,构建的结果可能会在几个方面存在缺陷。执行阶段耦合的后果是,如果使用并行选项调用gradle,一个项目任务运行得太晚,无法影响并行项目构建的任务。Gradle不会试图检测耦合并警告用户,因为引入耦合的可能性太多。
项目耦合
的一种非常常见的方法是使用配置注入
。它可能不会立即显现出来,但使用allprojects
和subprojects
关键字等关键Gradle功能会自动使您的项目耦合。这是因为这些关键字用于定义项目的 build.gradle
文件中。通常,这是一个“根项目”,只不过定义了通用配置,但就Gradle而言,这个根项目仍然是一个成熟的项目,通过使用allprojects
,该项目有效地与所有其他项目耦合。将根项目耦合到子项目不会影响按需配置,但在任何子项目的build.gradle文件中使用allprojects和subprojects都会产生影响。
为了充分利用跨项目配置
,而不会出现并行和“按需配置
”选项的问题,请遵循以下建议:
- 避免子项目的构建脚本引用其他子项目;更喜欢从根项目进行交叉配置。
- 避免在执行时更改其他项目的配置。