本篇参考Gradle官方文档,主要是从Android开发者的视角,介绍在使用Gradle进行构建的过程中涉及到的一些基础概念!不对之处,敬请指正。下面直接切入正题!
脚本语言
- Groovy: 动态语言;脚本文件后缀为.gradle,eg:build.gradle
- Kotlin: 静态语言;脚本文件后缀为.gradle.kts, eg:build.gradle.kts;使用kotlin书写gradle构建脚本,可以获得更好的IDE(IDEA、AndroidStudio)支持,自动补全、编译检查、重命名等。
脚本类型
- init.gradle
- settings.gradle
- build.gradle
- 其他脚本
gradle在构建时,会对脚本文件进行编译,生成对应的脚本对象。如将gradle脚本编译成一个实现Script
接口的对象;将build.gradle.kts编译成KotlinBuildScript
类型对象,将settings.gradle.kotlin编译成KotlinSettingsScript
类型对象。因此,在脚本文件中,可以访问对应脚本对象中声明的属性与方法。
基本概念
- Settings: 一个Settings对象对应settings.gradle脚本,由Gradle在配置阶段负责创建该对象,并执行settings.gradle脚本来对Settings对象进行配置。settings.gradle脚本使用
include
声明此次构建包含哪些工程(即包含哪些Project),并指定这些工程的路径、名称等。 - Project:Gradle中抽象的概念,一般而言,一个Project与一个需要参与构建的软件工程或模块相对应,如Android工程中的App Module或者Library Module等。每一个Project对象与一个build.gradle脚本文件关联,Gradle在初始化阶段会为参与构建的每一个模块创建一个Project对象,并且在配置阶段,会执行对应的build.gradle脚本来对与之关联的Project对象进行配置。在build.gradle脚本中通过
project
顶层属性可以引用到关联的Project对象。 - Task:很明显,代表构建过程中的一个子任务,负责完成构建过程中的某些操作
关于settings.gradle
文件,有以下几点需要注意:
- 单项目构建该文件可选,但多项目构建这个文件是必须的,因为需要在这个文件中声明哪些子项目需要参与构建,也包括子项目路径、名称等
- Gradle允许在任意子项目中进行多项目的构建,那Gradle如何决定此次构建是多项目还是单项目构建呢?如果该构建目录中存在settings.gradle文件,那么就依据该文件来进行构建;如果不存在该文件,那么会向上一级目录查询文件是否存在(注意:只会向父目录查询,而不是一直向上级目录递归查询),如果存在就依据该文件进行构建,否则此次构建就是一个单项目的构建。因此,如果需要在多项目的一个工程目录结构中进行单项目的构建,我们可以在目标子项目的根目录下创建一个settings.gradle文件
构建流程
- Initialize Phase:初始化阶段
在该阶段主要是创建Settings对象,并执行settings.gralde脚本配置该对象,决定哪些Project参与构建,同时创建这些Project对象。settings.gradle脚本中使用include(projectpath)
或者includeFlat(projectname)
来声明包含的Project工程,并允许配置这些project的名称、工程路径等。下面对一个settings.gradle文件进行说明:
// include two projects, 'foo' and 'foo:bar'
// directories are inferred by replacing ':' with '/'
include 'foo:bar' //(1)
// include one project whose project dir does not match the logical project path
include 'baz' //(2)
project(':baz').name = ‘myBaz’
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 //(3)
project(":${dir.name}").projectDir = dir
}
//add a project with path :flatProject, name flatProject and project directory $rootDir/../flatProject
includeFlat('flatProject') //(4)
(1)include方法参数为projectpath,非文件路径,因此不能包含传统的目录分隔符号'/',取而代之使用需要使用冒号:分隔(在project path中冒号代表的是root project);projectpath的最后一个节点作为project名称,且默认情况下gradle会将projectpath转为相对于rootProject的路径。如foo:bar
projectpath,会对应两个project,分别为foo
与foo:bar
,且工程根路径分别为$rootProjectDir/foo
与$rootProjectDir/foo/bar
。
(2) 添加了一个baz工程,名称自定义为myBaz
,默认此工程的根目录为$rootProjectDir/baz
,但此目录不存在,gradle允许我们修改此路径,这里我们修改为$rootProjectDir/foo/baz
。
(3) 遍历subproject目录下的所有子目录,添加为一个project,并设置projectdir;
(4) includeFlat方法也能添加一个project,但查找工程projectDir的策略与include不一致,它会查找相对于rootProject父目录下的文件路径。因此,这里flatProject的工程路径为$rootProject/../flatProject
。
Configure Phase:配置阶段
在该阶段,gradle会执行build.gradle脚本文件对Project进行配置Execute Phase:执行阶段
配置阶段完成之后,Gradle会构建出一张基于task的有向无环图,执行阶段即负责执行这些task
软件模块:Module
这里的Module并不是指Android工程中的Module,而是指向一个依赖,是一个可以随着时间不断更新的软件模块,如Google Guava库。每一个Module都有一个名称module name,随着时间的推移,模块会不断改善并重新发布,而模块的每一次发布都会有一个版本号,使用module version来描述。Module一般会被组织到Reposity中,通过module name以及module version可以精确定位到该模块。
产物:Artifact
产物特指由一次build生成的一个文件或一个目录,如一个Jar、Zip、AAR等。产物的生成就是为了给其他用户或Project使用的。
伴随着Module的每一次发布,都会有相应的产物,称之为artifact
,一个模块可以产出多个产物,如.jar包、.aar包等,而每一个artifact都可以有自己独立的Transitive Dependencies。这些产物都会有对应的描述信息,保存在module的metadata文件中(如maven repository中该文件为pom.xml)。在进行Module依赖解析时,Gradle会根据需要从repository中选择合适的产物下载使用,且默认情况下还会自动解析Transitive Dependencies。
此外,Configuration也可以有相应的产物,这样在声明依赖时,就可以指定具体使用哪个configuration artifact了。如:implementation(project(path = ":api", configuration = "spi"))
- 定义产物
val myJar by tasks.registering(Jar::class)
val someFile = file("$buildDir/somefile.txt")
artifacts {
add("taskArtifact", myJar) //taskArtifact指向myJar
add("fileArtifact", someFile)
}
何以为家,Module的容器:Repository
通常情况下,依赖是以modules
的方式存在和引用的,在声明依赖时,我们需要告知Gradle上哪去获取这些依赖。依赖保存的位置或路径称之为repository
。 类型主要有:
- Flat directory repository:
flatDir()
- Maven Central repository:
mavenCentral()
- JCenter Maven repository:
jcenter()
- Google Maven repository:
google()
- Local Maven repository:
mavenLocal()
- Custom Maven repositories:
maven {url = uri("http://repo.mycompany.com/maven2")}
在构建脚本中,我们需要使用repositories{}
DSL声明这些repository的位置,它可以指向本地或远程的仓库。如下所示:
buildscript {
repositories {
google() -- (1)
jcenter() -- (2)
flatDir("name" to "libs", "dirs" to "libs") -- (3)
}
dependencies {
//dependency declaration
}
}
脚本中我们声明了依赖查找的3个目标仓库,(1)是google的仓库,(2)是jcenter仓库、(3)则是通过
flatDir
方法传入本地文件系统的路径来指定本地仓库,这里创建了一个本地reposity,命名为libs,路径为$rootProject/libs。(Android工程中经常使用flatDir来包含本地的aar依赖包)
在构建过程中,Gradle会在声明的reposity仓库中查找定位我们声明的依赖,从远程下载或从本地目录、仓库获取这些依赖的产物用于构建,并保存在本地缓存中。这个过程称为依赖解析。Gradle根据声明的依赖,按Repository声明的顺序,从上到下依次查找。只要找到了一项就返回,不会继续往下查找!因此,需要注意Repository声明的顺序。
物以类聚,依赖的组织形式: Configuration
Gradle允许针对不同的构建场景声明不同的依赖集合;如在编译打包发布时,我们不希望把测试用例的代码,以及跑测试用例需要依赖到的其他三方库与代码一起编译打包。因此,我们需要把这两种使用场景进行划分,不同的场景各自声明自己感兴趣的依赖,这样在不同场景下构建时,可以只解析和使用自己声明的依赖,避免了代码的混乱,也有利于代码的组织管理以及维护。在Gradle中,Configuration
是一组有名称的依赖的集合,代表了该依赖组的一个使用场景(或作用域),如implementation configuration
是编译project所需依赖的集合,testImplementation configuration
是编译测试用例所需依赖的集合。Gradle要求每一个依赖都必须显示的指定其所属的Configuration。
Gradle框架本身以及我们日常引用的各类插件都会预先根据不同的使用场景定义出不同的configuration供使用,如Android插件中包含的implementation
、api
、compileOnly
、runtimeOnly
等,当然我们也可以进行自定义。
- 自定义Configuration:为某些特殊依赖场景自定义Configuration。
- 继承Configuration:所有加入到父亲中的依赖,儿子都能直接继承和使用。
- 一个Configuration可以包含一系列的Artifact供其他Project依赖使用!
val bar by configurations.registering //委托属性创建
configurations {
create("baz") //直接创建
bar.get().extendsFrom(getByName("implementation")) //继承
providedCompile
compile.extendsFrom providedCompile
}
dependencies {
implementation "group:module:version"
testImplementation "group:module1:version"
bar "group:module2:version"
}
我们在日常开发中使用较多的
mtl-publish
maven发布插件,其实也自定义了一个名为providedCompile
的Configuration,并将compile
从其继承,如上代码片段所示。
由于存在以上的继承关系,因此在构建过程中providedCompile与compile的作用其实是一样的,都能引用到依赖库aar包中的代码和资源文件
但在生成pom描述文件时(该文件中收集了该模块的所有三方依赖),会将所有providedCompile的依赖修改为provided依赖!
(注:如果一开始就使用provided,那么在开发阶段将无法引用到三方依赖库aar包中的资源,因此在引用依赖库资源时编译会失败)
你依赖我,我依赖它:Transitive Dependency
Module一般会提供额外的元数据,来详细描述该module的详细信息(如.module、.pom、ivy.xml文件)。如该module在repository中的坐标信息(group、name、version),作者author信息等。Module之间也可以相互依赖,因此在这些元数据中,包含一类特殊的数据,用于声明该module依赖的其他module,如JUnit 5 platform module需要依赖platform commons module。这些依赖称之为传递依赖(transitive dependencies)
,默认情况下,Gradle会自动解析和使用该module声明的其他module依赖,当然我们也可以针对性的配置这些依赖的解析规则。
依赖类型
- Module Dependency:最常用的依赖类型,指向一个reposity中的目标模块,通过group、name、version来定位。依赖声明方式如下:
configurationname(dependencyNotion, dependencyConfiguration)
。configurationname指定依赖所属的Configuration(Gradle要求每个依赖都必须显示指定其宿主Configuration)、dependencyNotion是依赖的声明,如字符串形式或map形式,格式一般为{group}:{name}:{version}[{:classifier}@{extension}]
。classifier代表同一个module的不同Artifact变种,extension指定Artifact的扩展名。最后还可以传入一个dependencyConfiguration配置Action,用于配置该依赖的一些属性,如change属性或transitive属性、force属性及exclude规则等等,详情可查阅该Action的运行上下文ExternalModuleDependency
提供的接口。栗子:
dependencies {
runtimeOnly(group: 'org.springframework', name: 'spring- core', version:'2.5') {
because("demenstrate the reason we pick this version")
isTransitive = true //是否解析传递依赖,默认为true
isChanging = true //是否为可变版本,过期后会重新获取
isForce = false //当有依赖冲突时,是否强制使用该版本依赖
exclude(group="name", name="name") //解析该依赖的transitive dependency时,不解析被exclude的部分
}
}
- File Dependency:本地文件依赖。可以直接依赖本地的jar、aar包等,而不需要上传至repository中。文件依赖使用
FileCollection
来指定。如下:
dependencies {
//files构建一个FileCollection对象,包含了相对于当前脚本目录的 libs/a.jar和libs/b.jar两个依赖文件
runtimeOnly(files("libs/a.jar", "libs/b.jar"))
//通过libs路径构建一个目录树对象FileTree,该类继承于FileCollection,通过include匹配该目录下的所有.jar文件作为依赖
runtimeOnly(fileTree("libs") { include("*.jar") })
implementation files("$buildDir/classes") {
//通过builtBy指定文件依赖的产物是由compile这个task生成的
//因此,会优先执行该task生成文件依赖
builtBy 'compile'
}
}
- Project Dependency:本地工程依赖。通过
project(projectPath)
方法来引用一个本地工程,projectPath参数为工程路径。如project(":A:A1:A2")
。
依赖声明
- 直接指定具体的版本号进行依赖
dependencies {
implementation 'group:name:5.0.2.RELEASE'
}
- 不指定版本,而是通过Dependency Constraint来统一限定版本,可以指定在脚本中声明过的依赖版本,也可以指定Transitive Dependency依赖的版本。这种方式允许我们在一个地方集中进行声明
dependencies {
implementation 'group:name'
}
dependencies {
constraints {
implementation 'group:name:5.0.2.RELEASE'
}
}
- 动态版本依赖:解析至符合要求的最新的版本进行依赖
dependencies {
implementation 'group:name:5.+'
}
- Rich version declaration: 使用strictly、require、prefer、reject规则进行声明。很少使用
dependencies {
implementation("org.slf4j:slf4j-api") {
version {
strictly("[1.7, 1.8[")
prefer("1.7.25")
}
}
}
- 使用
classifier
与@
符号指定Artifact的种类与扩展名:一般而言,在进行依赖解析时,Gradle默认查找的依赖产物是JAR包,如果无法获取将解析失败。当JAR包类型的Artifact不存在(如为ZIP包、AAR包等),或者存在多个类型不同的Artifact、亦或是我们不想解析Transitive Dependency时,可以使用@符号指定依赖产物的扩展名。同一个模块也可能会输出不同变种的Artifact,比如经过混淆或者未经过混淆的,可以使用classifier进行声明。
dependencies {
//min指定变种为已混淆;@js指定扩展名
js 'jquery:jquery:3.2.1:min@js'
}
- 在声明依赖时,可以额外的传递一个依赖配置lambda表达式Action,该Action在
ExternalModuleDependency
的上下文中执行,可以通过调用该类中的接口对依赖进行更细粒度的配置;具体查阅API文档。
依赖解析
Gradle允许我们自定义依赖解析的规则,可以帮助我们解决依赖冲突。说明如下:
- Dependency Resolve Rules:依赖解析规则
dependencies {
//version scheme
implementation("group:name:default")
}
configurations.all { //this: Configuration
resolutionStrategy.eachDependency { //this: DependencyResolveDetails
//1. change dependency version
if (requested.group == "group") {
useVersion("1.2")
because("why")
}
//2.deal with version scheme
if (requested.group == "group" && requested.version == "default") {
useVersion("1.1")
because("why")
}
//3.change group/name
if (requested.group == "group") {
useTarget("group2:name2:1.1")
because("why")
}
}
}
- Dependency Substitution Rules: 依赖替换规则
configurations.all { //this: Configuration
resolutionStrategy.dependencySubstitution { //this: DependencySubstitutions
//1. substitute module dependency with project dependency
substitute(module("org.utils:api")).apply {
with(project(":projectA"))
because("tell me why")
}
//2. substitute project dependency with module dependency
substitute(project(":projectC")).apply {
with(module("org.utils:api:1.3"))
because("tell me why")
}
}
}
- Changing configuration dependencies prior to resolution:依赖配置
configurations {
implementation {
withDependencies {//this:DependencySet
val dep = find { it.name == "to-modify" } as ExternalModuleDependency
dep.version {
strictly("1.2")
}
dep.isChanging = false
dep.isTransitive = true
dep.exclude("group", "module")
}
}
}
依赖检查与构建检查
通过不同方式,可以输出Project的依赖列表、依赖关系以及依赖冲突如何解决,以及最终所解析至的依赖版本。
- Build时使用
--scan
生成详细的报告,可以查看详细的各个阶段的耗时、task运行情况、依赖解析情况等。 - 运行插件提供的task输出依赖关系,如Android插件提供的
androidDependencies
Task。 - 运行Gradle提供的
dependencies
Task,通过--configuration选项指定要输出哪个Configuration的依赖关系
./gradlew dependencies --configuration implementation
- 运行Gradle提供的
dependencyInsight
Task,可以跟踪某一个自定义Configuration下某个具体的依赖。追踪依赖的引用链,以及依赖最终解析的结果和原因(为什么解析到这个版本);
configurations {
create("scm")
}
dependencies {
"scm"("junit:junit:4.12")
}
./gradlew dependencyInsight --configuration scm --dependency junit:junit
如何解决依赖冲突
- 检查依赖冲突。当出现依赖冲突时,Gradle可能会帮我们解决,但结果可能不是我们想要的。这里可以使用failOnVersionConflict(),当出现冲突时,构建直接失败,需要手动解决
- 在一些情况下,我们可能会同时依赖仓库中的版本以及依赖本地的Project工程,我们想使用本地工程,方便修改和调试!这种情况下可以通过配置preferProjectModules(),在出现上述冲突时,选择编译本地工程版本(前提是Project的name需要跟modulename一致)
configurations.all {
resolutionStrategy {
failOnVersionConflict()
preferProjectModules()
}
}
考虑如下场景:A 模块依赖 B1 和 C, D 模块依赖了 B2,其中 B1 和 B1 是同一个Module B的两个不同版本;同时,工程中我们同时依赖了 A 和 D。这种情况下,会存在针对Module B的依赖冲突!默认情况下Gradle会尝试帮我们解决依赖冲突,解决的方式是使用最新的版本;这里的最新不是判断版本号大小,应该是根据发布时间来决定!现在我们自己手动进行依赖冲突的解决:
- 这里是由于 A 或者 D 的Transitive Dependency引发了冲突,那么可以配置 transitive 属性为false,即不进行传递依赖的解析,这种情况可能需要自己手动添加部分依赖。
(1) 方案一:针对 A 或 D 配置 transitive。这里针对A配置,不解析A模块的传递依赖,因此依赖中不再包含 B1 和 C,这里需要手动添加依赖 C
dependencies {
implementation A {
transitive = false
}
implementation C
implementation D {
//transitive = false
}
}
(2) 方案二:针对 A 或 D 配置 exclude规则
dependencies {
implementation A {
exclude B1
}
implementation D {
//exclude B2
}
}
- 使用force强制依赖某个版本,如强制依赖 B1 或者 B2
configurations.all {
resolutionStrategy {
force B1
// force B2
}
}
- 根据上一节,配置依赖解析规则。进行版本声明、依赖替换等。不再赘述
启用插件
- Script Plugin:指包含额外构建逻辑的一个脚本文件,可以是一个本地文件系统的脚本文件或一个远程http地址指向的脚本文件,通过apply方法指定from(脚本文件的路径或远程地址)来启用,引入后Gradle自动解析并执行该脚本文件;
//groovy dsl
apply from: 'other.gradle'
apply from: 'http://www.example.com/other.gradle'
//kotlin dsl
apply(from="other.gradle.kts")
apply(from="http://www.example.com/other.gradle.kts")
- Binary Plugin: 通常为一个类或一个Jar包,插件类需要实现gradle提供的
Plugin
接口,当插件启用时,其实是调用了Plugin.apply(org.gradle.Project)
方法。每一个插件对应一个唯一ID,通过plugin id来启用插件,如java, com.android.application, com.android.libary;插件实现可以直接定义在脚本文件中的一个实现类,或从远程仓库repositories中拉取。如以下代码,我们从google仓库中拉取了android插件并启用:
//rootProject: build.gradle.kts
buildscript {
repositories {
google() // (1)
}
dependencies {
classpath ‘com.android.tools.build:gradle:3.4.0’ //(2)
}
}
//appProject: app/build.gradle.kts
plugins {
id("com.android.application") //(3)
}
//apply(plugin="com.android.application")
在主工程目录下的build.gradle.kts文件中,一般都会包含如上的片段代码:使用buildscript{}块来声明脚本运行时的依赖。repositories{}块声明工程中依赖查找的仓库,这里声明了google()仓库,脚本中使用的依赖都将首先尝试从该reposity中进行解析获取;在(2)中又声明了classpath依赖,指向了com.android.tools.build:gradle:3.4.0。意思就是将这个依赖包(从google仓库解析并下载jar包)添加到脚本文件编译执行的classpath中去。这样一来,在脚本文件中我们就可以import以及使用这个依赖包(jar包)中包含的相关类和方法、属性了。比如在(3)的位置,我们启用了com.android.application这个插件,这个插件的代码其实就是包含在com.android.tools.build:gradle:3.4.0这个依赖声明中的。
project and task path
- project path: 以一个可选的冒号开头(代表RootProject,而不能以projectname来指定),其余的部分是以冒号分隔的project names,后一个project是前一个project的子project;如:
:projectA:projectB
包含rootProject,projectA和projectB,A和B都是root的子工程,A是第一级儿子工程,B是第二级;同时B也是A的儿子工程。 - task path: ${projectPath:taskName}
apply方法解析
apply方法的作用主要有:
- 启动某个插件
- 应用某个脚本文件到某个目标对象(委托对象)。脚本文件可以为本地路径或远程http协议的gradle脚本,由Gradle负责下载解析
如下面的示例:
task configure {
doLast {
def pos = new java.text.FieldPosition(10)
// Apply the script
apply from: 'other.gradle', to: pos
println pos.beginIndex
println pos.endIndex
}
}
// Set properties.
beginIndex = 1
endIndex = 5
在apply from: 'other.gradle', to: pos
语句中,apply允许我们传递一个map对象。在这个map中,我们使用from这个key声明被应用的原始脚本文件为other.gradle,使用to这个key声明委托对象为pos。这样一来在other.gradle中的方法或属性都将以pos这个类型为FieldPosition的对象作为委托对象,脚本中访问的未声明方法和属性会在delegate对象中寻找,如beginIndex和endIndex其实是访问了委托对象的属性。该Feature在KotlinDSL中尚未得到支持。
SourceSet
Gradle Java Support引入了SourceSet
的概念来构建基于源代码的Project,因为我们一般都是以类型来对源代码以及资源文件进行分类和组织,如应用代码、单元测试代码、集成测试代码,它们通常被分开并放在不同的目录下。每一个逻辑分组可以有自己的一组源代码文件依赖、classpath。SourceSet通常涉及以下几个和编译相关的方面:
- 源代码文件所在的路径
- 编译所需的其他classpath或者一系列依赖(在Dependency块中通过
{sourceSet}Configuration
引入依赖,如testCompileOnly、testImplementation
)。其中sourceSet为名称(如test、androidTest
),而Configuration为依赖类型(如compileOnly、implementation
)。 - 编译结果输出的目录
Gradle会自动为定义的每一个SourceSet生成一些编译Task以及Dependency Configuration。如compilemain
这个SourceSet外。main是一个特殊的SourceSet,用于工程的主生产代码(used for the project’s production code)。
The
main
source set
Most language plugins, Java included, automatically create a source set called main,
which is used for the project’s production code. This source set is special in that its
name is not included in the names of the configurations and tasks, hence why you
have just a compileJava
task and compileOnly
and implementation
configurations
rather than compileMainJava
, mainCompileOnly
and mainImplementation
respectively。
除了main
这个SourceSet外,一般插件还会生成其他的SourceSet,如Android的插件还提供了test
及androidTest
,分别用于跑测试用例和android的测试用例。我们可以配置sourceSet配置额外的代码、资源路径、exclude规则等
sourceSets {
main {
java {
srcDir 'thirdParty/src/main/java'
}
}
}
SourceSet Properties
-
name — (RO) String
The name of the source set, used to identify it. -
output — (RO) SourceSetOutput
The output files of the source set, containing its compiled classes and resources. -
output.classesDirs — FileCollection
Default value: name, e.g. build/classes/java/main
The directories to generate the classes of this source set into. May contain directories for other
JVM languages, e.g. build/classes/kotlin/main. -
output.resourcesDir — File
Default value: name, e.g. build/resources/main
The directory to generate the resources of this source set into. -
compileClasspath — FileCollection
Default value: ${name}CompileClasspath configuration.The classpath to use whencompiling the source files of this source set. -
java — (read-only) SourceDirectorySet
The Java source files of this source set. Contains only .java files found in the Java source
directories, and excludes all other files -
java.srcDirs — Set
Default value: src/$name/java, e.g. src/main/java
The source directories containing the Java source files of this source set -
java.outputDir — File
Default value: name, e.g. build/classes/java/main
The directory to generate compiled Java sources into -
resources — SourceDirectorySet
The resources of this source set. Contains only resources, and excludes any .java files found in the resource directories. -
resources.srcDirs — Set
Default value: [src/$name/resources]
The directories containing the resources of this source set. -
allJava — SourceDirectorySet
Default value: Same as java property
All Java files of this source set. Some plugins, such as the Groovy Plugin, add additional Java
source files to this collection -
allSource — SourceDirectorySet
Default value: Sum of everything in the resources and java properties
All source files of this source set of any language. This includes all resource files and all Java source files. Some plugins, such as the Groovy Plugin, add additional source files to this
collection
configuration与artifacts
subprojects {
apply(plugin = "java")
group = "org.gradle.sample"
version = "1.0"
}
project(":api") {
configurations {
create("spi") -----------(1)
}
dependencies {
"implementation"(project(":shared"))
}
tasks.register("spiJar") {
archiveBaseName.set("api-spi")
from(project.the()["main"].output)
include("org/gradle/sample/api/**")
}
artifacts {
add("spi", tasks["spiJar"]) -------(2)
}
}
project(":services:personService") {
dependencies {
"implementation"(project(":shared"))
"implementation"(project(path = ":api", configuration = "spi")) ---------(3)
"testImplementation"("junit:junit:4.12")
"testImplementation"(project(":api"))
}
}
关于FlatDir
flatDir可以指定一个本地的目录作为一个备选的仓库,Gradle在搜索该仓库,查找对应的Module;在下面的示例中,我们指定aars目录作为一个本地仓库,其中包含名为'moduleA.aar'文件以及'moduleB.aar'文件。在(1)的依赖声明中,我们指定依赖的name为moduleA、扩展名为'aar', Gradle最终会解析到aars目录下的'moduleA.aar'文件。在(2)的依赖声明中,我们使用常规的'group:name:version@ext'声明,最终Gradle也会解析到aars目录下的'moduleB.aar'文件。因为,使用flatDir指定本地仓库时,Gradle会忽略group或者version部分,只要aars目录中存在'moduleB.aar'或者'moduleB-1.0.0.aar'这样的文件,Gradle都会成功解析到该依赖。
repositories {
flatDir {
dirs 'aars'
}
}
dependencies {
implementation(name: 'moduleA', ext: 'aar') // (1)
implementation('groupname.fake:moduleB:1.0.0') //(2)
}