• .gradle:Gradle自动生成的项目缓存目录。没用
  • .idea:这个是IDEA的项目配置目录,跟Gradle生成的没关系,无视掉就行。没用
  • app:存放整个项目的源代码、测试等,这里面就是我们写代码的地方了。
    • build.gradle.kts:项目的gradle构建脚本。
    • src:存放源代码和测试代码。
      • main:编写所有项目核心代码。
      • test:编写项目测试代码。
  • gradle:包含JAR文件和Gradle Wrapper的配置。不重要
  • gradlew:适用于macOS和Linux的使用Gradle Wrapper执行构建的脚本(这里的版本就是GradleWrapper指定的版本)
  • gradlew.bat:适用于Windows的使用Gradle Wrapper执行构建的脚本。
  • settings.gradle.kts:定义子项目列表的项目配置文件,也是最关键的设置文件。

项目配置

整个Gradle项目构建的流程:

设置文件settings.gradle是整个Gradle项目的入口点:

设置文件(settings.gradle)用于定义所有的子项目,并让它们参与到构建中,Gradle支持单项目和多项目的构建:

  • 对于单个项目构建,设置文件是可选的。
  • 对于多项目构建,设置文件必须存在,并在其中定义所有子项目。
    设置文件(settings.gradle)可以使用Groovy语言(名称为settings.gradle)或是Kotlin语言(名称为settings.gradle.kts
    一个标准的Gradle设置文件按照以下样式进行编写,这里使用Kotlin语言介绍:
rootProject.name = "root-project"   //rootProject对象代表当前这个项目,其name属性就是当前项目的名称
 
include("sub-project-a")     //所有的子项目使用include()函数进行添加,如果没有子项目可以不写
include("sub-project-b")
include("sub-project-c")

接着我们来看针对于单个项目的构建文件,其中gradle.build文件就是对应的构建配置,这里我们使用的是Kotlin语言,因此项目中会存在一个gradle.build.kts文件:

plugins {
    id("java")
    // 使用id()函数指定需要使用的插件,这里使用的是java插件,
    // java插件将Java编译以及测试和捆绑功能添加到项目中,为建任何类型的 Java 项目提供支持。
    // 除了java插件之外,我们如果需要构建其他类型的项目,也可以使用多种多样的插件:
}
// 这里的group和version分别设置了当前项目所属的组名称和版本:
group = "cn.itbaima"
version = "1.0-SNAPSHOT"
 
// 接着是所有依赖的仓库配置,默认使用的是Maven中心仓库:
repositories {
    mavenCentral()
}
// 所有的依赖列表,这里默认为我们导入了JUnit相关依赖用于测试:
dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}
 
// 最后是任务相关配置,这里对`test`任务进行了相关配置:
tasks.test {
    useJUnitPlatform()
}

看最顶上的plugins函数,后面的Lambda中编写了当前项目需要使用到的插件:

plugins {
	id("cpp-application") //用于构建C++应用程序
	// id("swift-application") //用于在MacOS构建Swift应用程序
	// id("org.jetbrains.kotlin.jvm") version "1.9.0" //使用version中缀函数为id指定的插件添加版本
}

编写设置文件

settings.gradle

我们前面介绍了Gradle构建的大致流程和配置文件各个部分,而设置文件settings.gradle.kts则是整个构建的入口点,在Gradle构建生命周期的早期,初始化阶段会在项目根目录中找到设置文件。

当找到设置文件settings.gradle(.kts)时,Gradle会实例化一个Settings对象,我们可以通过此对象来声明要包含在构建中的所有项目,包括我们项目名称的声明也是通过它来完成:

settings.rootProject.name = "KotlinGradle"  
// rootProject.name = "KotlinGradle"  也可以省略 settings
 
// 全局插件
plugins {  //使用plugins函数配置插件,结合Lambda表达式可以使用简洁语法完成配置
    id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"  
}

其中,Settings对象包含以下常用属性:

姓名描述
buildCache项目构建所用缓存配置。
plugins用于设置的插件。
rootDir项目构建的根目录,根目录是整个项目目录最外层。
rootProject构建的根项目。
settings返回设置对象。
Settings对象也包含以下方法可供调用:
姓名描述
include()将指定名称的项目添加到构建列表中。
includeBuild()添加指定路径上的其他Gradle项目到构建列表中。
在Gradle中,和Maven一样也分为插件和依赖,我们可以在settings.gradle.kt中可以为所有的项目进行统一配置(全局配置),比如要修改获取插件的仓库位置:
pluginManagement {   //使用pluginManagement函数配置插件仓库列表
    repositories {   //在repositories函数中配置需要用的仓库
        gradlePluginPortal()   //Gradle插件仓库
        google()    //Google插件仓库
    }
}

修改为国内的阿里云镜像:

pluginManagement {
    repositories {
      	//手动指定maven仓库地址,修改URL地址
        maven {
            setUrl("https://maven.aliyun.com/repository/public/")
        }
    }
}

对于所有的依赖,也可以直接配置为国内的阿里云镜像仓库:

dependencyResolutionManagement {   //依赖解析管理中可以配置全局依赖仓库
    repositories {  //只不过这种方式目前还在孵化阶段,可能会在未来某个版本移除
        maven {
            setUrl("https://maven.aliyun.com/repository/public/")
        }
    }
}

除了在settings.gradle.kts中配置三方仓库之外,我们更推荐在之后学习的build.gradle.kts中对仓库进行配置。

build.gradle

对于设置文件中包含的每个项目,Gradle都会为其创建一个Project实例对象,我们可以直接在build.gradle.kts中使用,跟之前一样,可以省略:

group = "cn.itbaima"
version = "1.0-SNAPSHOT"
project.group = "cn.itbaima"   //本质上就是project的属性
project.version = "1.0-SNAPSHOT"

在此对象中,包含以下常见属性:

姓名类型描述
nameString项目目录的名称。
pathString该项目的完全限定名称。
descriptionString该项目的描述。
dependenciesDependencyHandler配置项目的依赖列表。
repositoriesRepositoryHandler配置项目的依赖仓库。
layoutProjectLayout通过此对象来访问项目中的关键位置。
groupObject项目的组。
versionObject项目的版本。
首先是整个项目所采用的插件,我们可以像下面这样编写:
plugins {
    id("org.jetbrains.kotlin.jvm") version "1.9.0" // 非官方 要加 version
    id("application") // 官方插件不用加 version
    id("java")
}

其中,插件application是由官方内置的插件,可以直接使用id()函数来选择,而上面的org.jetbrains.kotlin.jvm插件没有被官方内置,需要我们手动使用version中缀函数指定其版本,这样Gradle才能在仓库中正确找到它。

我们也可以对这个插件进行一些配置,比如我们要生成目标的Java版本等:

configure<JavaPluginExtension> {
    targetCompatibility = JavaVersion.VERSION_17    //编译目标版本
    sourceCompatibility = JavaVersion.VERSION_17    //源代码版本(会导致只能使用对应版本具有的特性,如果使用更高版本的特性或语法将无法通过编译)
}

如果我们需要将项目打包为一个可执行的文件,也可以使用application插件,它包含java插件的全部功能,同样支持编译和打包Java代码,并且支持生成可执行的应用程序:

// for example
plugins {
    id("application")
}
 
java {   //configure<JavaApplication> 也可以直接写成 java 这个扩展函数,效果一样
    targetCompatibility = JavaVersion.VERSION_17
    sourceCompatibility = JavaVersion.VERSION_17
}
 
application {   //同configure<JavaApplication>
    mainClass = "com.test.Main"  //配置主类
}

接着我们来看如何配置项目中的依赖项,首先是对于依赖仓库的选择,默认情况下生成的代码选择的是Maven中心仓库:

repositories {
    mavenCentral()  //Maven中心仓库
    google()   //谷歌仓库
}

我们可以自己定义,比如只使用本地仓库中的包:

repositories {
    mavenLocal()   //只使用Maven本地仓库中的软件包
}

可以通过maven函数来直接指定第三方仓库:

repositories {
    maven {
      	//修改其url属性来指定一个第三方仓库地址
        setUrl("https://maven.aliyun.com/repository/public/")
    }
}

配置好仓库地址后,我们就可以开始添加需要的依赖了,在dependencies中进行编写,默认情况下添加了一些测试相关的依赖:

dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter") // 导入依赖,用于编译和运行_测试_代码。
	 //使用implementation来添加依赖 
	 implementation("org.springframework:spring-context:6.1.3") // 导入依赖,用于编译和运行_生产_代码。
}

除了通过直接编写Maven坐标的形式来引入仓库中的依赖之外,我们也可以直接将一个本地的Jar包引入到项目中,这里我们在项目根目录下新建一个lib目录,用于存放我们需要引用的Jar包:

dependencies {
  	//使用files方法来指定一个文件进行导入
    implementation(files("lib/spring-context-6.1.3.jar", 
        "lib/spring-core-6.1.3.jar", "lib/spring-beans-6.1.3.jar", "lib/spring-jcl-6.1.3.jar"))
    //...
}

或者直接导入整个lilb文件夹

implementation(fileTree("lib"))  //直接引入lib下全部jar包

最后我们来列举一下DependencyHandlerScope(dependencies{}这个括号)里面包含的方法:

  1. implementation: 用于添加项目的依赖项,这是最常用的方法。
  2. api: 与 implementation 类似,但它会暴露依赖项给项目的所有模块(多项目配置中讲解)
  3. compileOnly: 用于指定编译时依赖,但不会在运行时包含在最终构建结果中。
  4. testImplementation: 用于添加测试时需要的依赖项。
  5. androidTestImplementation: 用于添加Android测试时需要的依赖项。
  6. kapt: 用于添加 Kotlin 注解处理器依赖项。
  7. annotationProcessor: 用于添加 Java 注解处理器依赖项。
  8. runtimeOnly:仅在运行时使用,不用于编译

自定义任务

Gradle可以在项目上完成的工作由一个或多个任务定义,任务代表构建执行的某些独立工作单元,比如编译一些类,创建jar包,生成Javadoc文档,或将一些内容发布到代码仓库,这些任务通常由插件提供,我们在项目中引入插件后就可以直接执行对应的任务了,在不引入任何插件的情况下,只会包含一些内置的默认任务,在引入java插件后,就出现了各种Java相关的任务,比如编译、构建、打包jar包等(build,clean,jar…)

当然,如果各位小伙伴觉得插件提供的任务不太够,需要自定义添加,我们也可以在build.gradle.kts中编写自定义任务:
注册任务需要使用register或create函数来完成,一个最简单的任务可以像下面这样编写:

// 这里使用 register
tasks.register("hello") {   //第一个参数为任务名称,第二个参数使用Lambda编写任务具体操作
  	//任务包含一个完整的操作列表,我们需要传入对应的Action到队列中,这样就可以依次执行了
    doFirst {   //使用doFirst向任务队列首部插入新的Action,也就是要执行的内容
        println("我是自定义的任务开始")
    }
    doLast {   //向队列尾部插入Action
        println("我是自定义的任务结束")
    }
}

还可以配置此任务所属的组别,以及其它描述信息:

task.create("TestTask"){
	group = "build"
	description = "This is a grand Task"
}

我们还可以将一个任务作为gradle执行的默认任务,也就是说直接执行 gradle命令就可以运行我们的任务了:

defaultTasks("hello")

一个任务还可以依赖于其他任务,比如我们的自定义任务在执行之前需要先完成源代码的编译操作:

tasks.register("hello") {
    group = "build"
    description = "这是一个非常伟大的任务!"
    
    dependsOn(tasks.compileJava)   
    //使用dependsOn函数来指定前置任务,可以是其他插件提供的,也可以是我们自己定义的,这个参数也可以传字符串
    
    //...
}

我们也可以让已经存在的任务来依赖我们的任务或是直接为其添加额外的操作:

tasks.named("build") {   //根据名称进行查找
    dependsOn("hello")   //直接配置依赖
  	doLast { ... }   //添加新的Action到列表中
}
 
tasks.build {   //直接从tasks中获取,这仅限于插件提供的任务
    dependsOn("hello")
}

生命周期钩子

有些时候我们希望在Gradle的整个生命周期中的不同时段执行一些操作,我们可以使用官方提供的生命周期钩子函数。

  1. 构建初始阶段
    • gradle.settingsEvaluated() 完成项目的配置阶段之后调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
    • gradle.projectsLoaded() 所有项目加载之后调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
  2. 配置阶段
    • gradle.beforeProject() 每个项目完成配置之前调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
    • gradle.afterProject() 每个项目完成配置之后调用
    • gradle.projectEvaluated() 所有项目全部完成配置之后调用
    • gradle.afterEvaluate() 整个配置阶段完成后调用
    • gradle.taskGraph.whenReady 全部任务图已经构建完成可以就绪后调用
  3. 执行阶段
    • gradle.taskGraph.beforeTask 执行每一个任务之前调用
    • gradle.taskGraph.afterTask 每一个任务执行完成之后调用
    • gradle.buildFinished 整个构建全部结束后调用

这里我们可以尝试编写一个钩子函数试试看:

gradle.settingsEvaluated {
    println("开始构建")
}
 
gradle.buildFinished {
    println("构建结束")
}

这样,我们就可以在不同的阶段执行自定义的内容了。比如,我们可以利用这种特性来统计某一个阶段或是任务耗费的时间:

var time: Long = 0
gradle.taskGraph.beforeTask {
    time = System.currentTimeMillis()
}
 
gradle.taskGraph.afterTask {
    val takeTime = System.currentTimeMillis() - time
    println("任务: $name 执行耗时: ${takeTime}ms")
}

多项目配置

通常对于一些大型的分布式项目来说,我们会在一个项目中包含多个模块,不同的模块负责不同的功能,并且不同模块的代码独立进行编写,这时整个Gradle项目就会存在多个子项目:

large-project // 总项目(根项目)
|
├── auth-service // 子项目
│   ...
│   └── build.gradle.kts
├── chat-service // 子项目
│   ...
│   └── build.gradle.kts
└── settings.gradle.kts 

创建完成后,我们可以打开最外层(large-project )的settings.gradle.kts文件,IDEA已经自动为我们添加了模块引入:

rootProject.name = "large-project" 
include("auth-service")
include("chat-service")

如果在chat-service有一个类Test,我们要在auth-service中使用这个类,就要在auth-service的 build.gradle.kts 中配置依赖
比如

dependencies{
		implementation(project(":chat-service")) // 注意要加冒号:
}

有些时候,我们可能需要将一些部分统一进行配置,比如配置代码仓库地址,以及项目用到的插件,我们可以直接在根项目创建一个build.gradle.kts来统一编写内容:

subprojects {   //subprojects表示对所有的子项目生效
    apply(plugin = "java")   //定义插件需要使用apply来完成,plugin{}在这里不行
 
    group = "cn.itbaima"   //定义组
    version = "unspecified"  //定义版本
 
    repositories {   //定义自定义仓库地址
        maven {
            setUrl("https://maven.aliyun.com/repository/public/")
        }
    }
}

这样,我们即使不在子项目中编写这些,就可以直接得到根项目的配置:

依赖的传递

我们在使用多模块时可能会遇到这样的一个问题,现在有三个模块,并且具有以下依赖关联:

  • service-a
  • service-b implementation(service-a)
  • service-c implementation(service-b)

此时,从链式的依赖关系上来看,它们长这样:service-a -> service-b -> service-c

按照正常的传递关系来说,在B中应该是可以直接使用A中定义的内容的,因为依赖了A模块,同时,由于C依赖了B模块,那么理所应当,C也应该可以直接使用A中定义的内容,但是gradle默认不支持依赖传递。

plugins {
    `java-library`   //java-library提供了传递依赖api函数
     // 等价于id("java-library")
}
 
dependencies {
    api(project(":common"))   //与implementation效果一样,但是支持传递依赖
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

buildSrc模块

略参考原文