Gradle基础篇
项目和任务
Gradle中的所有内容都基于两个基本概念:项目和任务。
每个Gradle构建都由一个或多个项目组成。项目代表什么取决于您使用Gradle做什么。例如,项目可能表示JAR或Web应用程序。
每个项目由一个或多个任务组成。任务代表构建执行的一些原子工作。这可能是编译某些类,创建JAR,生成Javadoc或将一些存档发布到存储库。
Hello,World
您使用gradle
命令运行Gradle构建。gradle
命令查找当前目录中的build.gradle
文件。我们称这个build.gradle
文件为构建脚本,但严格来说它是一个构建配置脚本
task hello {
doLast {
println 'Hello world!'
}
}
命令行shell中,执行gradle -q hello
n$ gradle -q hello
Hello world!
任务依赖
您可以声明依赖于其他任务的任务。
task hello {
doLast {
println 'Hello world!'
}
}
task intro {
dependsOn hello
doLast {
println "I'm Gradle"
}
}
输出
$ gradle -q intro
Hello world!
I'm Gradle
依赖尚不存在的任务
task taskX {
dependsOn 'taskY'
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
$ gradle -q taskX
taskY
taskX
可以在定义之前声明taskX
的依赖关系。这种自由对于多项目构建非常重要。
动态任务
您还可以使用动态创建任务。
4.times { counter ->
task "task$counter" {
doLast {
println "I'm task number $counter"
}
}
}
$ gradle -q task1
I'm task number 1
您可以在运行时动态地向任务添加依赖项
4.times { counter ->
task "task$counter" {
doLast {
println "I'm task number $counter"
}
}
}
task0.dependsOn task2, task3
$ gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0
或者,您可以向现有任务添加行为。
task hello {
doLast {
println 'Hello Earth'
}
}
hello.doFirst {
println 'Hello Venus'
}
hello.configure {
doLast {
println 'Hello Mars'
}
}
hello.configure {
doLast {
println 'Hello Jupiter'
}
}
$ gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter
doFirst
和doLast
可以执行多次。他们将操作添加到任务的操作列表的开头或结尾。执行任务时,将按顺序执行操作列表中的操作。
Groovy DSL快捷方式表示法
有一种方便的表示法来访问现有任务。每个任务都可以作为构建脚本的属性使用:
将任务作为构建脚本的属性进行访问
task hello {
doLast {
println 'Hello world!'
}
}
hello.doLast {
println "Greetings from the $it.name task."
}
$ gradle -q hello
Hello world!
Greetings from the hello task.
默认任务
Gradle允许您定义在未指定其他任务时执行的一个或多个默认任务。
defaultTasks 'clean', 'run'
task clean {
doLast {
println 'Default Cleaning!'
}
}
task run {
doLast {
println 'Default Running!'
}
}
task other {
doLast {
println "I'm not a default task!"
}
}
$ gradle -q
Default Cleaning!
Default Running!
gradle 项目分析
├── app
│ ├── app.iml
│ ├── build.gradle
│ ├── libs
│ ├── proguard-rules.pro
│ └── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ └── binzi
│ │ │ └── gradle
│ │ │ └── MainActivity.java
│ │ └── res
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.iml
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle
settings.gradle
settings.gradle 是负责配置项目的脚本
对应 Settings
类,gradle 构建过程中,会根据 settings.gradle 生成 Settings 的对象
其中几个主要的方法有:
- include(projectPaths)
- includeFlat(projectNames)
- project(projectDir)
一般在项目里见到的引用子模块的方法,就是使用 include
include ':app'
如果想指定子模块的位置,可以使用 project 方法获取 Project 对象,设置其 projectDir 参数
include ':app'
project(':app').projectDir = new File('./app')
rootproject/build.gradle
build.gradle 负责整体项目的一些配置,对应的是 Project
类
gradle 构建的时候,会根据 build.gradle 生成 Project 对象,所以在 build.gradle 里写的 dsl,其实都是 Project 接口的一些方法,Project 其实是一个接口,真正的实现类是 DefaultProject
其中几个主要方法有:
- buildscript // 配置脚本的 classpath
- allprojects // 配置项目及其子项目
- respositories // 配置仓库地址,后面的依赖都会去这里配置的地址查找
- dependencies // 配置项目的依赖
module/build.gradle
build.gradle 是子项目的配置,对应的也是 Project 类
子项目和根项目的配置是差不多的,不过在子项目里可以看到有一个明显的区别,就是引用了一个插件 apply plugin "com.android.application",后面的 android dsl 就是 application 插件的 extension
其中几个主要方法有:
- compileSdkVersion // 指定编译需要的 sdk 版本
- defaultConfig // 指定默认的属性,会运用到所有的 variants 上
- buildTypes // 一些编译属性可以在这里配置,可配置的所有属性在 这里
- productFlavor // 配置项目的 flavor
依赖
在 gradle 3.4 里引入了新的依赖配置,如下:
新配置 | 弃用配置 | 行为 | 作用 |
---|---|---|---|
implementation | compile | 依赖项在编译时对模块可用,并且仅在运行时对模块的消费者可用。 对于大型多项目构建,使用 implementation 而不是 api/compile 可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的项目量。 大多数应用和测试模块都应使用此配置。 | api 只会暴露给直接依赖的模块,使用此配置,在模块修改以后,只会重新编译直接依赖的模块,间接依赖的模块不需要改动 |
api | compile | 依赖项在编译时对模块可用,并且在编译时和运行时还对模块的消费者可用。 此配置的行为类似于 compile(现在已弃用),一般情况下,您应当仅在库模块中使用它。 应用模块应使用 implementation,除非您想要将其 API 公开给单独的测试模块。 | api 会暴露给间接依赖的模块,使用此配置,在模块修改以后,模块的直接依赖和间接依赖的模块都需要重新编译 |
compileOnly | provided | 依赖项仅在编译时对模块可用,并且在编译或运行时对其消费者不可用。 此配置的行为类似于 provided(现在已弃用)。 | 只在编译期间依赖模块,打包以后运行时不会依赖,可以用来解决一些库冲突的问题 |
runtimeOnly | apk | 依赖项仅在运行时对模块及其消费者可用。 此配置的行为类似于 apk(现在已弃用)。 | 只在运行时依赖模块,编译时不依赖 |
gradle wrapper
gradlew / gradlew.bat 这个文件用来下载特定版本的 gradle 然后执行的,就不需要开发者在本地再安装 gradle 了。这样做有什么好处呢?开发者在本地安装 gradle,会碰到的问题是不同项目使用不同版本的 gradle 怎么处理,用 wrapper 就很好的解决了这个问题,可以在不同项目里使用不同的 gradle 版本。gradle wrapper 一般下载在 GRADLE_CACHE/wrapper/dists 目录下
gradle/wrapper/gradle-wrapper.properties 是一些 gradlewrapper 的配置,其中用的比较多的就是 distributionUrl,可以执行 gradle 的下载地址和版本
gradle/wrapper/gradle-wrapper.jar 是 gradlewrapper 运行需要的依赖包
gradle 生命周期
gradle 构建分为三个阶段
初始化阶段
在初始化阶段,Gradle判断需要参与编译的工程,为每个工程创建一个Project对象。在这个阶段,Gradle会创建Settings对象,并在其上执行settings.gradle
脚本,建立工程之间的层次关系。
配置阶段
在这个阶段,Gradle会分别在每个Project对象上执行对应的build.gradle
脚本,对Project进行配置。
执行阶段
在执行阶段,Gradle会判断配置阶段创建的哪些Task需要被执行,然后执行选中的每个Task。
gradle 在构建过程中,会提供一些列回调接口,方便在不同的阶段做一些事情,主要的接口有下面几个:
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println('构建开始')
// 这个回调一般不会调用,因为我们注册的时机太晚,注册的时候构建已经开始了,是 gradle 内部使用的
}
@Override
void settingsEvaluated(Settings settings) {
println('settings 文件解析完成')
}
@Override
void projectsLoaded(Gradle gradle) {
println('项目加载完成')
gradle.rootProject.subprojects.each { pro ->
pro.beforeEvaluate {
println("${pro.name} 项目配置之前调用")
}
pro.afterEvaluate{
println("${pro.name} 项目配置之后调用")
}
}
}
@Override
void projectsEvaluated(Gradle gradle) {
println('项目解析完成')
}
@Override
void buildFinished(BuildResult result) {
println('构建完成')
}
})
gradle.taskGraph.whenReady {
println("task 图构建完成")
}
gradle.taskGraph.beforeTask {
println("每个 task 执行前会调这个接口")
}
gradle.taskGraph.afterTask {
println("每个 task 执行完成会调这个接口")
}
常用API
org.gradle.api.Project
Project
对象是Gradle中最核心的API,通过Project
对象可以访问所有Gradle特性。
Project与build.gradle
Project对象和build.gradle
文件一一对应。在Gradle构建时,会先创建Settings实例并在其上执行settings.gradle
;再通过Settings对象定义的Project层级,创建若干个Project实例,并分别在其上执行对应的build.gradle
Extra属性
Project有一个Extra属性,可通过ext前缀在其中定义属性,定义好后可以不加ext前缀直接访问。
project.ext.prop1 = "foo"
task doStuff {
ext.prop2 = "bar"
}
ext.isSnapshot = version.endsWith("-SNAPSHOT")
if (isSnapshot) {
// do snapshot stuff
}
Project的属性/方法调用
在build.gradle
中调用属性,或调用Project.property(java.lang.String)
方法时,会按顺序从以下范围查找:
- Project自身定义的属性
- Project的Extra属性
- 插件添加的Extension属性
- 插件添加的Convension属性
- Project中Task的名字
- 从父Project继承的属性,一直递归到RootProject
在build.gradle
中调用方法时,会按顺序从以下范围查找:
- Project自身定义的方法
- build.gradle脚本定义的方法
- 插件添加类型为Action或Closure的Extension
- 插件添加的Convension方法
- Project中Task的名字都会创建一个对应方法
- 从父Project继承的方法,一直递归到RootProject
- Project中为Closure类型的属性可以作为方法调用
Project
继承了PluginAware
、ExtensionAware
,分别用于支持Plugin和Extension方法。部分常用API如下。
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
Project getRootProject();
File getRootDir();
File getBuildDir();
void allprojects(Closure configureClosure);
ScriptHandler getBuildscript();
void buildscript(Closure configureClosure);
RepositoryHandler getRepositories();
void repositories(Closure configureClosure);
ConfigurationContainer getConfigurations();
void configurations(Closure configureClosure);
DependencyHandler getDependencies();
void dependencies(Closure configureClosure);
ConfigurableFileCollection files(Object... paths);
ConfigurableFileTree fileTree(Object baseDir);
Convention getConvention();
ExtensionContainer getExtensions();
Task task(String name) throws InvalidUserDataException;
Task task(String name, Closure configureClosure);
void afterEvaluate(Closure closure);
// ...
}
常用API示例(以下脚本均写在build.gradle
中):
// 配置Gradle插件,闭包参数会在ScriptHandler上执行
buildscript {
// ...
}
// 配置所有工程,闭包参数会分别在每个Project上执行
allprojects {
// ...
}
// 配置使用的仓库,闭包参数会在RepositoryHandler上执行
repositories {
// ...
}
// 配置依赖项,闭包参数会在DependencyHandler上执行。
// files和fileTree也是Project提供的API,
// 而project则是DependencyHandler提供的API。
dependencies {
compile files('hibernate.jar', 'libs/spring.jar')
compile fileTree('libs')
compile project(path: ':library')
// ...
}
// 在当前Project配置完成后,闭包会被执行
afterEvaluate {
println "Project '$name' has been evaluated!"
}
// 在RootProject配置完成后,闭包会被执行
rootProject.afterEvaluate {
println "RootProject '$name' has been evaluated!"
}
org.gradle.api.invocation.Gradle
Gradle
对象表示一次Gradle调用,通过Project.getGradle()
可以获取这个对象。在一次构建过程中只有一个Gradle
对象。
public interface Gradle extends PluginAware {
String getGradleVersion();
File getGradleUserHomeDir();
File getGradleHomeDir();
Gradle getParent();
Project getRootProject() throws IllegalStateException;
void rootProject(Action< ? super Project> action);
void allprojects(Action< ? super Project> action);
TaskExecutionGraph getTaskGraph();
StartParameter getStartParameter();
ProjectEvaluationListener addProjectEvaluationListener(ProjectEvaluationListener listener);
void removeProjectEvaluationListener(ProjectEvaluationListener listener);
void beforeProject(Closure closure);
void afterProject(Closure closure);
void buildStarted(Closure closure);
void settingsEvaluated(Closure closure);
void projectsLoaded(Closure closure);
void projectsEvaluated(Closure closure);
void buildFinished(Closure closure);
void addBuildListener(BuildListener buildListener);
public void addListener(Object listener);
public void removeListener(Object listener);
public void useLogger(Object logger);
Gradle getGradle();
}
org.gradle.api.initialization.Settings
Settings
对象主要用于配置Project的层级结构。
Settings
对象和settings.gradle
文件一一对应。Gradle构建的第一步,就是创建Settings
对象并其上执行settings.gradle
脚本。
public interface Settings extends PluginAware {
String DEFAULT_SETTINGS_FILE = "settings.gradle";
void include(String[] projectPaths);
void includeFlat(String[] projectNames);
Settings getSettings();
File getSettingsDir();
File getRootDir();
ProjectDescriptor getRootProject();
ProjectDescriptor project(String path) throws UnknownProjectException;
ProjectDescriptor findProject(String path);
ProjectDescriptor project(File projectDir) throws UnknownProjectException;
ProjectDescriptor findProject(File projectDir);
StartParameter getStartParameter();
Gradle getGradle();
}
常用API示例:
-
include()
可以配置包含Project,例如include ':app', ':library'
-
project()
可获取ProjectDescriptor从而做一些配置,例如经常会配置Gradle依赖本地Library工程的路径:include ':img:library'project(':img:library').projectDir = new File('../../img/library')
org.gradle.api.Task
Task
Task
也是Gradle中很重要的API。Task代表构建过程中的一个原子操作,例如编译classes文件或生成JavaDoc。
每个Task属于一个Project。每个Task都有一个名字。所属Project名+Task名可组成唯一的完整名(fully qualified path),例如:app:assemble
。
Action
每个Task包含一个Action序列,并在Task执行时按先后顺序执行。通过Task的doFirst/doLast方法可以往Action序列的头部/末尾添加Action,支持Action或闭包(闭包会被转换成Action对象)。
Task依赖和排序
每个Task可以依赖其他Task,执行Task时会先执行其依赖的Task,通过dependsOn可设置依赖。每个Task还可以设置在其他Task之前、之后执行,一般可通过mustRunAfter设置。
例如下面的配置,执行A时一定会先执行B;执行A不一定会执行C;当A、C都要执行时一定先执行C。
taskA.dependsOn(taskB)
taskA.mustRunAfter(taskC)
Task的部分常用API如下:
public interface Task extends Comparable<Task>, ExtensionAware {
String getName();
Project getProject();
TaskDependency getTaskDependencies();
Task dependsOn(Object... paths);
String getPath();
Task doFirst(Action< ? super Task> action);
Task doFirst(Closure action);
Task doLast(Action< ? super Task> action);
Task doLast(Closure action);
Task configure(Closure configureClosure);
Task mustRunAfter(Object... paths);
TaskDependency shouldRunAfter(Object... paths);
// ...
}
Task 的一些重要方法分类如下:
- Task 行为
Task.doFirst
Task.doLast - Task 依赖顺序
Task.dependsOn
Task.mustRunAfter
Task.shouldRunAfter
Task.finalizedBy - Task 的分组描述
Task.group
Task.description - Task 是否可用
Task.enabled - Task 输入输出
gradle 会比较 task 的 inputs 和 outputs 来决定 task 是否是最新的,如果 inputs 和 outputs 没有变化,则认为 task 是最新的,task 就会跳过不执行
Task.inputs
Task.outputs - Task 是否执行
可以通过指定 Task.upToDateWhen = false 来强制 task 执行
Task.upToDateWhen
Task创建
注:Gradle不推荐使用
task hello << { ... }
的方式定义Task,并会在后续版本删除,因此这里不做介绍。
在build.gradle
中创建Task,最常见写法如下。task(xxx)
是Project提供的API,最终调用了TaskContainer的create方法。可接收参数包括:
- Task名称(必选)
-
Map<String, ?>
类型配置(可选) - 闭包配置(可选)
task hello(dependsOn: clean) {
doLast {
println 'hello'
}
}
也可以直接调用TaskContainer创建Task,Project中的tasks属性即为TaskContainer对象。
tasks.create('hello')
Task创建后会在Project上添加一个同名方法,调用这个方法可以配置Task。
task hello
hello {
doLast {
println 'hello'
}
}
Task的type属性,带参数的Task
还可以用类实现Task,创建Task时指定type为这个class即可,定义Task的类通常继承自DefaultTask。下列示例代码中给Task定义了一个名为name
的参数。
import org.gradle.api.internal.tasks.options.Option
class HelloTask extends DefaultTask {
String personName = '';
HelloTask() {
doLast {
println "Hello " + personName
}
}
@Option(description = "set person name", option = "name")
def setMessage(String name) {
this.personName = name;
}
}
task hello(type: HelloTask)
命令行中执行效果:
$ ./gradlew hello --name Tom
:hello
Hello Tom
BUILD SUCCESSFUL
Total time: 0.889 secs