Gradle学习笔记
Gradle是近年来流行起来的自动化构建工具,它具有以下特性:
- 灵活、通用的构建功能,类似Ant
- 基于约定的构建框架,类似Maven
- 强大的多工程构建支持
- 支持基于Apache Ivy的依赖管理,现已使用Gradle自己的依赖处理引擎(dependency resolution engine)
- 完整的支持既有的Maven或者Ivy构件仓库,且不需要pom.xml或者ivy.xml就可以进行依赖管理
- 整合型构建工具:
- 支持Ant任务,Gradle可以直接引入Ant工程,并在运行时将Ant targets转换为Gradle tasks
- 可以自动的把Maven的pom.xml转换为Gradle脚本
- 基于Groovy的构建脚本。构建脚本可以很容易的维护和重用
- 用于描述构建的丰富的领域模型
- Gradle Wrapper:运行在没有安装Gradle的机器上运行Gradle构建
Gradle的核心是一个基于Groovy的、扩展的DSL(Domain Specific Language),而不是向Maven、Ant那样,通过XML去描述构建步骤,具有非常好的灵活性。
名词 | 说明 |
工程(Project) | 每个构建都是由一个或多个projects 构成,project是一个抽象概念,既可以和通常的IDE中的工程对应,也可以仅仅代表某项需要完成的工作 |
任务(Task) |
每个project可以包含一个或者多个task,task代表细化的构建步骤 各Task之间可以形成依赖关系,被依赖的Task总是先执行。Gradle能够依据Task形成的无回路有向图(Directed Acyclic Graph),依次执行Task,保证每个任务至多被执行一次 |
- Gradle Daemon性能增强,让构建速度最快有75%的提升,默认启用
- 更好的IDE支持
- 改进并行任务执行
- 初步的Java 9支持
- 新的plugins DSL,用于启用插件,代替原先的
apply plugin:
123plugins {id 'my.special.plugin' version '1.0' apply false} - 改进增量构建
- 更快的依赖解析
- 改进eclipse-wtp插件
- 插件库升级
- 支持依赖的并行下载
- 改进构建缓存
- 添加Google的Maven仓库的快捷方式:
123repositories {google()} - 支持使用Gradle属性来配置日志级别,属性名org.gradle.logging.level,支持的值:quiet, warn, lifecycle, info, debug,默认lifecycle
-
对删除文件的Task进行更好的建模。一个Task可以使用 @Destroys 注解,明确声明自己会删除文件:
123456789class RemoveTempDirs extends DefaultTask {@DestroysFileCollection tempDirs@TaskActionvoid removeDirs() {project.delete(tempDirs)}}
- 更快的构建速度
- 更细粒度的传递性依赖管理
- 内存使用更高效
- 新的Gradle调用选项
- 新的Task/Plugin API
即Gradle Wrapper,是一段脚本,此脚本调用你声明的版本的Gradle,如果目标版本不存在,则从网络上下载。
使用gradlew可以保证构建过程的一致性,但是网络差的话你就得静静的等待下载完成。
要生成gradlew相关的文件,需要本机已经安装任意版本的Gradle。执行命令 gradle wrapper即可。
生成的gradlew文件包括:
- gradle/wrapper/gradle-wrapper.properties 配置文件
- gradle/wrapper/gradle-wrapper.jar 包含用于下载Gradle发行版的代码
- gradlew, gradlew.bat Shell脚本和Windows批处理脚本
1 2 3 4 5 6 7 8 |
#Tue Jun 25 10:21:26 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists # 从何处下载 # 后缀 -all 表示包含源码,-bin 表示不包含源码 distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip |
如果下载缓慢,可以手工拷贝,存放到 ~/.gradle/wrapper/dists对应的子目录下。
执行下面的命令升级到指定版本:
1 |
./gradlew wrapper --gradle-version 5.4.1 |
gradlew的命令行调用格式和gradle完全一致。
- 安装Gradle需要JDK/JRE,版本在6.0+。Gradle内置了Groovy库,因此后者不需要
- 到官网下载Gradle,并解压到某个目录,该目录设置为环境变量 GRADLE_HOME
- 将 $GRADLE_HOME/bin 添加到环境变量PATH中
- 打开终端,运行 gradle -v 验证安装是否成功
可以从Nodeclipse提供的Update Site:http://www.nodeclipse.org/updates/gradle-ide-pack/ 安装Gradle插件。
此站点中杂项内容非常多,可以根据需要,选择性的安装:
插件 | 说明 |
Gradle IDE | Pivotal提供 的Gradle与Eclipse的集成 |
Minimalist Gradle Editor | .gradle文件的编辑器,提供语法高亮等简单功能 |
打开Window ⇨ Preferences ⇨ Gradle:
- Gradle Distribution 点选 Folder,填写 %GRADLE_HOME% 对应的目录
- Gradle User Home 点选 Directory,填写 %USERPROFILE%\.gradle
打开Window ⇨ Preferences ⇨ Gradle ⇨ Arguments:
- Java Home 点选Workspace JRE,选择一个已安装的JRE
- 默认JVM参数可以根据需要调整
打开Window ⇨ Preferences ⇨ Gradle EnIDE:
- Gradle Home to use:填写 %GRADLE_HOME% 对应的目录
打开Window ⇨ Preferences ⇨ Desktop Environment:
- 点选 Custom Desktop Environment
- Start Shell设置为Console2,例如 D:\Programs\Console2\Console.exe -d ${resource_path}
打开File ⇨ New ⇨ Other,选择Gradle/Gradle Project,即可创建Gradle工程,Sample project选择Java Quickstart,点击完成。
初次创建Gradle IDE会去下载一些东西,完毕后,形成类似下面的工程结构:
可以看到,默认配置下Gradle沿用了Maven的工程目录习惯。除了把pom.xml换成build.gradle,Maven工程和Gradle工程的结构完全相同。
Eclipse工程结构和元数据完全根据build.gradle导出,这个和Maven也是类似的。工程根目录右键 ⇨ Gradle ⇨ Refresh All,可以根据当前的build.gradle更新Eclipse工程结构。
要构建Gradle工程,可以工程根目录右键 ⇨ Run As ⇨ Gradle Build。
命令格式:
1 2 |
//可以同时指定多个Task gradle [option...] [task...] |
用法示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# 依次调用 compile 和 test 任务 # 它们所依赖的任务会根据依赖图的顺序被依次调用,每个任务只会调用一次 # 只需要声明任务之间的依赖,不需要人工控制任务执行顺序,是相对于Maven的优势 gradle compile test # 使用-x 排除某个任务 gradle dist -x test # 默认情况下,一旦有一个任务失败,Gradle就会终止执行,下面的选项禁止此行为 gradle run --continue # 简化任务名:不需要输入完整,只需要足够避免歧义的前缀 gradle ru # 简化任务名:驼峰式首字母,下面的命令调用gradle compTest gradle cT # Gradle默认依据当前目录下的build.gradle,使用-b可以选择构建其它脚本 gradle -q -b subdir/myproject.gradle hello # 使用-p,可以选择构建的目录 gradle -q -p subdir hello # 获取子工程列表 gradle -q projects # 列出工程中所有任务 gradle tasks gradle -q tasks --all #同时显示任务依赖关系 # 显示任务的详细信息 gradle help --task taskName # 显示工程中所有依赖库的列表 gradle dependencies #输出依据依赖配置(Dependencies Configuration)分组 # 显示api子工程的所有依赖列表 gradle -q dependencies api:dependencie # 显示api子工程的、testCompile配置的所有依赖列表 gradle -q api:dependencies --configuration testCompile # 显示某个特定依赖库的情况,显示groovy依赖库的情况 gradle -q api:dependencyInsight --dependency groovy --configuration compile # 显示工程属性列表 gradle properties gradle -q api:propertie #子工程属性列表 # 记录构建日志 gradle run --profile # 日志存放到build/reports/profile # 以特定的工程属性来构建 gradle hello -PskipHello # 设置JVM属性 gradle hello -Djava.io.tempdir=/tmp # 丢弃已经编译的脚本缓存 gradle --recompile--scripts # 显示图形界面 gradle --gui |
除了通过gradle命令行参数 -P 直接为Gradle工程添加属性之外,还可以使用 gradle.properties 文件。如果有多个子工程,则每个子工程的目录都可以提供此文件:
1 2 3 |
gradlePropertiesProp=gradlePropertiesValue # 对于根工程,可以设置JVM系统属性,这些属性以systemProp.开头 systemProp.system=systemValue |
build.gradle 本质上是Groovy脚本,任何合法的Groovy语句都可以添加到其中。
在当前目录下创建一个UTF-8编码的build.gradle文件,或者通过Eclipse创建一个Gradle工程。打开build.gradle文件,输入下面的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//可以声明工程属性 description = 'Hello project' //声明一个名为hello的task //新任务默认是org.gradle.api.DefaultTask的实例 task hello { //闭包指定了hello的action doLast { println 'Hello world!' //任何Groovy脚本都支持 } } //上述任务也可以用下面的快捷声明方式 task hello << { println 'Hello world!' } |
然后,打开终端,pushd到当前目录,运行命令: gradle -q hello ,可以看到输出: Hello world! 。该命令就是调用hello这个Task,而这个Task仅仅是打印一条语句到控制台。
使用下面的脚本可以指定默认任务,这样,如果 gradle 命令不指定task参数,则自动运行这些任务:
1 |
defaultTasks 'hello', 'run' |
对于构建中的每个工程,Gradle都创建了一个 org.gradle.api.Project 类型的对象,工程脚本 build.gradle 的顶级作用域就是该对象(this指向当前工程):
1 2 3 4 5 6 |
apply plugin: 'java' println this.name //当前对象——当前工程——的name属性 println name //自动委托给当前对象 println project.name //this.project == this,预定义属性 //上面三个语句打印一样的结果 |
除了上面的name以外,你还可以访问很多Project的属性,例如:
属性 | 类型 | 说明 |
project | Project | 当前Project 实例对象 |
path | String | 工程的绝对路径 |
description | String | 工程的描述 |
projectDir | File | 包含了构建脚本的目录 |
build | File | projectDir/build |
group | Object | 组标识 |
name | String | 工程名称 |
version | Object | 版本标识 |
ant | org.gradle.api.AntBuilder | Ant实例对象 |
Project包含很多常用的方法,可以在构建脚本中使用:
方法 | 说明 |
buildscript | 配置驱动构建过程本身的依赖,常常在此脚本块中声明依赖和仓库 |
每个构建脚本都自动实现 org.gradle.api.Script 接口,因此该接口提供的属性、方法你可以自由的使用。
在构建脚本中,你可以声明两种变量:
- 局部变量 ( local ) :只在局部作用域可见,使用关键字
def 声明,例如:
12345def dest = "dest"task copy(type: Copy) {form "source"into dest} - 扩展属性 ( extra ):Gradle领域模型中所有增强对象(包括但不限于:projects, tasks, source sets)都可以添加扩展属性,扩展属性在任何地方可见。使用ext脚本块可以同时声明多个属性。下面是一个示例:
123456789101112131415161718192021222324252627282930313233ext {//为Project添加属性springVersion = "3.1.0.RELEASE"emailNotification = "build@master.org"}//也可以这样写ext.springVersion = "3.1.0.RELEASE"springVersion = "3.1.2.RELEASE" //声明以后再修改,不需要ext关键字//这样写会报错,自定义的外部属性,必须首先使用ext声明hibernateVersion = "3.6.10.Final"//设置所有源码集的purpose属性sourceSets.all { ext.purpose = null }sourceSets {//分别设置每个源码集的属性main {purpose = "production"}test {purpose = "test"}plugin {purpose = "production"}}task printProperties << {println springVersionprintln emailNotificationsourceSets.matching { it.purpose == "production" }.each { println it.name }}
Gradle大量使用了Groovy提供的灵活语法特性和DSL支持。整个构建脚本都是合法的Groovy代码,虽然有时看起来更像是XML那样的配置文件:
1 2 3 4 5 |
//下面的脚本,更像是配置文件 dependencies { compile group: 'commons-collections', name: 'commons-collections', version: '3.2' testCompile project(':shared') } |
实际上dependencies是Project对象的一个方法,它的签名如下:
1 2 3 |
public interface Project{ void dependencies(Closure configureClosure); } |
可以看到,该方法的入参是一个闭包,我们可以把上面的脚本改写的更像C-Style的代码:
1 2 3 4 5 6 7 8 9 |
dependencies({ //这里是闭包体,其包含了两个方法调用: compile( [ group: 'commons-collections', name: 'commons-collections', version: '3.2' ] ) testCompile( project(':shared') ) //那么,闭包里面调用的方法,是哪个对象的呢?是this也就是闭包本身吗?不是…… //根据Groovy的规则,闭包里面的变量,其Scope由闭包的delegate属性指定,因此上面的调用实际上是: delegate.testCompile( project(':shared') ) //delegate已经由Gradle自动设置为dependencies }) |
我们还可以把上述脚本改写为等价的Java语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Closure<Object> c = new Closure<Object>(){ public Object call( Object... args ){ assert delegate == project.dependencies Map<String,String> map = new HashMap<String,String>(); map.put( 'group', 'commons-collections' ); map.put( 'name', 'commons-collections' ); map.put( 'version', '3.2' ); delegate.compile( dep ); delegate.testCompile( project(':shared') ) } } c.delegate = project.dependencies project.dependencies( c ); |
可以把其它脚本的内容包含到当前构建脚本中:
1 2 3 4 |
println "configuring $project" task hello << { println' 'hello form other srcipt' } |
1 |
apply from: 'other.gradle' //包含其它脚本 |
其它脚本中定义的任务,可以直接使用: gradle -q hello
依赖管理是任何一种构建工具的关键特性,Gradle提供了易于理解的、和Maven/Ivy兼容的依赖管理机制。Gradle的依赖管理具有以下特点:
- 传递性依赖管理
- 支持非受管的(non-managed)依赖:依赖可以仅仅是位于版本控制工具或者共享磁盘上的文件
- 支持定制化的依赖定义:可以在构建脚本中描述依赖的层次
- 完全可定制的依赖解析机制(Dependency Resolution)
- 与Maven和Ivy完全兼容
- 与既有的依赖管理工具,例如Maven和Ivy集成
依赖管理分为两部分的内容:
- 依赖项(dependencies):当前工程在构建或者运行时需要的东西
- 发布项(publications):当前工程的构建的产出,这些产出可能需要上传到仓库
对于依赖项,Gradle采用了类似Maven的方式进行声明,而不像Ant那样指定绝对路径。依赖项的所在位置(远程仓库或者本地目录)另外声明。对于发布项,行为依赖于配置,可能是发布到本地目录,或者远程仓库。
Gradle把依赖按照依赖配置(dependency configuration)分组。每个分组具有一个名称和若干其它属性,分组可以继承其它分组。某些插件会自动引入一些依赖配置,例如Java插件添加了:
依赖配置 | 说明 |
compile | 用来编译项目源代码的依赖 |
runtime | 在运行时被生成的类使用的依赖,默认也作为编译时依赖 |
testCompile | 编译测试代码的依赖 |
testRuntime | 运行测试所需要的依赖 |
除了插件自动引入的依赖配置以外,你还可以在构建脚本中自行添加。自定义依赖配置在很多情况下有用,例如添加一个既不在编译,也不在测试时需要的“运行时”依赖(典型的例子是JDBC驱动程序)到归档文件中。
下面示例了如何声明依赖配置:
1 2 3 4 5 6 7 8 9 |
configurations { compile { description = 'compile classpath' transitive = true //是否启用传递性依赖 } runtime { extendsFrom compile } } |
每一个依赖项声明的格式为:
1 2 3 |
dependencies{ // 依赖配置名称 依赖项规格说明 } |
依赖项规格是什么形式取决于依赖的类型:
依赖类型 | 依赖项规格说明 | ||
外部模块依赖 |
即对当前构建以外的模块(Module)的依赖,模块可以存放在Maven、Ivy远程仓库或者本地仓库中,甚至位于本地目录中。一个外部依赖使用group、name、version三个属性限定。下面是一些例子:
|
||
工程依赖 |
在多工程构建时,声明各子工程之间的依赖,例如:
|
||
文件依赖 |
即对位于本地文件系统上一系列文件的依赖,例如:
|
||
客户端模块依赖 |
类似于外部模块依赖,目标构件位于某个仓库中,但是模块的元数据文件则由当前构建指定。如果需要覆盖外部模块的元数据,可以使用这种依赖。示例:
|
||
Gradle API依赖 |
针对当前Gradle版本的API的依赖,开发Gradle插件和Task类型时使用:
|
||
本地Groovy依赖 |
针对当前Gradle使用的Groovy版本的依赖,开发Gradle插件和Task类型时使用:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
configurations { //为指定的依赖配置指定传递性依赖的排除规则 compile.exclude module: 'commons' //为所有的依赖配置指定传递性依赖的排除规则 all*.exclude group: 'org.gradle.test.excludes', module: 'reports' } dependencies { compile("org.gradle.test.excludes:api:1.0") { exclude module: 'shared' //为单个依赖项指定传递性依赖的排除规则 } } |
Gradle必须知道从哪里下载外部依赖,这是由仓库配置来指定的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
repositories { //Maven本地仓库,寻找本地仓库的逻辑与Maven相同 mavenLocal() //Maven中心仓库 mavenCentral() //JCenter仓库 jcenter() //其它Maven远程仓库 maven { //可以指定身份验证信息 credentials { username 'user' password 'password' } url "http://repo.mycompany.com/maven2" //如果上面的URL找不到构件,则在下面找 artifactUrls "http://repo.mycompany.com/jars" } //Ivy远程仓库 ivy { url "http://repo.mycompany.com/repo" } //Ivy本地仓库 ivy { url "../local-repo" } //扁平布局的文件系统仓库 flatDir { dirs 'lib' } flatDir { dirs 'lib1', 'lib2' } } |
除了可以发布到本地目录外,Gradle还支持发布构建的产出到Ivy、Maven仓库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
uploadArchives { repositories { //发布到扁平结构的目录 flatDir { dirs 'repos' } //发布到Ivy仓库 ivy { credentials { username "username" password "pw" } url "http://repo.mycompany.com" } //发布到Maven仓库 mavenDeployer { repository(url: "file://localhost/tmp/myRepo/") } } } |
task关键字本质上是Project对象的一组方法:
1 2 3 4 |
Task task(String name) throws InvalidUserDataException Task task(String name, Closure configureClosure) Task task(Map<String,?> args, String name) throws InvalidUserDataException Task task(Map<String,?> args, String name, Closure configureClosure) |
下面列举了不同风格的Task定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// 操作符“<<”被Task重载过,它接受一个闭包,该闭包可以对当前Task进行任意操作 // 直接使用不带引号的hello0,会被解释为this.hello0,Gradle可能进行了Groovy元编程,让不存在的变量指定作为当前project的属性看待 // 不带括号 task hello0 << { description 'Hello task' // 可以给Task添加描述 println "hello" } // 带括号 task(hello1) << { println "hello1" } // 任务名带引号 task('hello2') << { println "hello2" } //指定任务类型 task('copy', type: Copy) { //闭包的delegate是task对象,注意Gradle的这种风格,与上面章节dependencies中的闭包delegate指向是一个风格 from(file('srcDir')) into(buildDir) } //这样也可以 tasks.create(name: 'hello') << { println "hello" } |
首先,任何Task都作为Project的属性看待:
1 2 3 4 |
task hello //作为当前工程的属性来获得Task的引用 println hello.name println project.hello.name |
也可以通过 org.gradle.api.tasks.TaskContainer 来访问Task:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
task hello //作为TaskContainer的属性访问 println tasks.hello.name println tasks['hello'].name project(':projectA') { task hello } //也可以调用getByPath()方法 //路径根部的:可以省略 println tasks.getByPath('hello').path //:hello println tasks.getByPath(':hello').path //:hello println tasks.getByPath('projectA:hello').path //:projectA:hello println tasks.getByPath(':projectA:hello').path //:projectA:hello |
下面实例了指定Task的类型为 org.gradle.api.tasks.Copy 并且设置其属性的几种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//Copy任务用来拷贝文件到目标目录中,支持过滤和重命名 //指定任务类型 task myCopy(type: Copy) //同时设置属性 Copy myCopy = task(myCopy, type: Copy) myCopy.from 'resources' myCopy.into 'target' myCopy.include('**/*.txt', '**/*.xml', '**/*.properties') //闭包方式,再次注意闭包的delegate指向是当前task,delegate在Minimalist Gradle Editor中常常显示为关键字 task copy(type: Copy) { from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//声明任务intro依赖于hello task intro(dependsOn: 'hello') << { //如果被依赖任务在后面定义,则这里引用的任务名必须加引号 println "I'm Gradle" } //被依赖的任务可以在后面定义 task hello << { println 'Hello world!' } //访问Task的方法也可以添加依赖 intro.dependsOn hello //也可以通过闭包添加依赖 intro.dependsOn { tasks.findAll { task -> task.name.startsWith('lib') } } project('projectA') { //要依赖其它项目中的任务,必须指定任务的路径 task taskX(dependsOn: ':projectB:taskY') << { println 'taskX' } } project('projectB') { task taskY << { println 'taskY' } } |
1 2 3 4 |
task copy(type: Copy) task copy(overwrite: true) << { //必须设置overwrite为true println('I am the new one.') } |
Gradle提供几种方式,来绕过某个任务的执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
task hello << { println 'hello world' //只有当工程没有属性“skipHello”的时候,才会执行hello hello.onlyIf { !project.hasProperty('skipHello') } //运行gradle hello -q -PskipHello 不会打印任何信息 //也可以使用StopExecutionException异常跳过任务 hello.doFirst{ //添加如下逻辑到Task最前面 if (true) { throw new StopExecutionException() } } //还可以设置enabled属性,为false则不会执行 hello.enabled = false |
Gradle判断任务是否已经是最新状态的依据是:如果任务的输入没有变化,那么当前任务不需要执行(也就是up-to-date)。更进一步说,Gradle的判断规则如下:
- Task执行前,获得输入文件的散列值
- Task执行完毕后,存储输出文件的散列值
- Task执行前,如果输入、输出的散列值和上一次执行相比,没有变化,那么任务是up-to-date
可以看到,如果任务没有输出,那么它永远不会被看做是up-to-date的。如何声明任务的输入输出呢?
1 2 3 4 5 6 7 8 9 10 11 12 |
task transform { ext.srcFile = file('mountains.xml') ext.destDir = new File(buildDir, 'generated') //下面两行定义了当前任务的输入和输出 inputs.file srcFile outputs.dir destDir doLast { //Task的逻辑 } } |
上述脚本设置了Task的inputs、outputs属性,如果没必要执行,那么Gradle会自动跳过此任务。
对于复杂的场景,可以调用 TaskOutputs.upToDateWhen() 来判断任务是否up-to-date。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//动态创建4个任务task1 ... task3 4.times { counter -> task "task$counter" << { println "I'm task number $counter" } } //基于规则的动态任务 //对于任何pingXxx,都作为任务看待 tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) << { println "Pinging: " + (taskName - 'ping') } } } //执行命令 gradle -q pingServer1 //输出 Pinging: Server1 //这些动态任务甚至还可以用做任务依赖 task groupPing { dependsOn pingServer1, pingServer2 } |
类似于Java的 finally 代码块,这种任务用于在目标任务结束后执行,不论目标任务是否执行成功:
1 2 3 4 5 6 7 8 9 10 |
task taskX << { throw new RuntimeException() } task taskY << { println 'taskY' } taskX.finalizedBy taskY //运行 gradle -q taskX //输出 taskY |
由于大多数构建都需要操作文件,因此Gradle定义了一些额外的文件操作API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
/************************ 文件定位 ***************************/ // 使用一个相对路径 File configFile = project.file('src/config.xml') // 使用一个绝对路径 configFile = file('file:/some/path.xml') configFile = file('F:/config.xml') /************************ 文件集合 ***************************/ FileCollection collection = files( 'src/file1.txt', new File('src/file2.txt'), ['src/file3.txt', 'src/file4.txt'] //会被自动展开 ) //迭代集合 collection.each {File file -> println file.name } //类型转换 List list = collection as List String path = collection.asPath File file = collection.singleFile //添加、去除文件元素 def union = collection + files('src/file3.txt') def different = collection - files('src/file3.txt') /************************ 文件树 ***************************/ //从相对目录创建 FileTree tree = fileTree(dir: 'src/main') tree.include '**/*.java' tree.exclude '**/Abstract*' //从归档文件创建 FileTree zip = zipTree('someFile.zip') FileTree tar = tarTree('someFile.tar') //遍历 tree.each {File file -> println file } //过滤 FileTree filtered = tree.matching { include 'org/gradle/api/**' } //合并 FileTree sum = tree + fileTree(dir: 'src/test') /************************ 复制文件 ***************************/ //各种文件来源 task copyTask(type: Copy) { //复制目录下所有文件 from 'src/main/webapp' //复制单个单独文件 from 'src/staging/index.html' //从一个任务的输出复制 from copyTask //从一个任务的outputs属性复制 from copyTaskWithPatterns.outputs //复制归档文件中的内容 from zipTree('src/main/assets.zip') //最终指定目标目录 into { getDestDir() } } //文件过滤 task copyTaskWithPatterns(type: Copy) { from 'src/main/webapp' into 'build/explodedWar' include '**/*.html' include '**/*.jsp' exclude { details -> details.file.name.endsWith('.html') } } //复制的过程中重命名 task rename(type: Copy) { //闭包指定重命名规则 rename { String fileName -> fileName.replace('-staging-', '') } //使用正则表达式指定映射规则 rename(/(.+)-staging-(.+)/, '$1$2') } //处理文件内容 task filter(type: Copy) { //替换文件中的标记,标记必须是@tokenName@的形式 expand(copyright: '2009', version: '2.3.1') expand(project.properties) //使用闭包处理每一行 filter { String line -> "[$line]" } //使用闭包删除行 filter { String line -> line.startsWith('-') ? null : line } } /************************ 同步两个目录 ***************************/ task libs(type: Sync) { from configurations.runtime into "$buildDir/libs" } /************************ 创建归档文件 ***************************/ task zip(type: Zip) { from 'src/dist' into('libs') { from configurations.runtime //指定依赖配置类型 } baseName = 'customName' //指定归档文件名 } |
Gradle支持以下日志记录级别:
日志级别 | 命令选项 | 说明 |
ERROR | 打印错误消息 | |
QUIET | -q --quiet | 打印重要的、你应当知道的消息 |
WARNING | 打印警告信息 | |
LIFECYCLE | 默认 | 这是Gradle的默认级别,打印处理进度信息 |
INFO | -i --info | 打印一般性的消息 |
DEBUG | -d --debug | 打印调试信息 |
在打印日志的同时,还可以指定命令行参数:
- -s --stacktrace:打印调用栈
- -S --full-stacktrace:打印完整调用栈
可以向标准输出打印日志:
1 |
println 'A message which is logged at QUIET level' |
或者通过 logger 属性打印日志:
1 2 3 4 5 6 7 |
logger.quiet('An info log message which is always logged.') logger.error('An error log message.') logger.warn('A warning log message.') logger.lifecycle('A lifecycle info log message.') logger.info('An info log message.') logger.debug('A debug log message.') logger.trace('A trace log message.') |
还可以使用外部日志工具:
1 2 3 4 5 |
import org.slf4j.Logger import org.slf4j.LoggerFactory Logger slf4jLogger = LoggerFactory.getLogger('some-logger') slf4jLogger.info('An info log message logged using SLF4j') |
就像Maven一样,Gradle核心本身也不能做多少有意义的事情,实际的构建工作都是由插件完成的。Gradle允许插件:
- 扩展Gradle的DSL模型,例如可以配置新的DSL元素
- 应用“约定由于配置”原则,为工程提供缺省配置
- 引入新的默认任务集
Gradle插件分为两类:
- 脚本插件:本质上是额外的构建脚本,可以进一步的配置构建逻辑
- 二进制插件:是接口 org.gradle.api.Plugin 的实现,以编程的的方式操控构建
使用方法 Project.apply() 可以应用插件到工程,应用同一插件多次是安全的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** 应用一个脚本插件 **/ // 实际上就是include一段脚本 apply from: 'other.gradle' /** 应用二进制插件 **/ apply plugin: 'java' //基于短名 apply plugin: JavaPlugin //基于插件类,org.gradle.api.plugins包内的插件不需要全名 //通过构建脚本块可以引入插件依赖,并使用插件 buildscript { repositories { jcenter() } dependencies { classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:0.4.1" } } apply plugin: "com.jfrog.bintray" |
分类 | 插件标识 | 说明 |
语言插件 | java | 为工程添加Java编译、测试功能,是需要其它插件的基础 |
groovy | 支持Groovy代码的编译 | |
scala | 支持Scala代码的编译 | |
antlr | 增加对ANTLR的生成解析器的支持 | |
此外还有尚在孵化中的:assembler、c、cpp、objective-c、windows-resources | ||
集成插件 | application | 支持把Java工程通过命令行运行 |
ear | 增加对构建J2EE应用程序的支持 | |
war |
支持构建War包 |
|
maven |
支持发布构件到Maven仓库 |
|
sogi | 支持构建OSGI工程 | |
jetty | 支持在嵌入式的Jetty容器中部署War包或者工程构建结果(解包) | |
开发插件 | checkstyle | 使用Checksytle对工程的Java代码质量进行检查 |
findbugs | 使用Findbugs对工程的Java代码质量进行检查 | |
codenarc | 使用Codenarc对工程的Groovy代码质量进行检查 | |
eclipse | 生成Eclipse IDE的元数据,以便导入到Eclipse工作区 | |
eclipse-wtp | 与eclipse类似,支持生成Eclipse WTP工程结构 | |
idea | 生成Intellij IDEA IDE的元数据,以便可以导入 | |
signing | 对生成的归档文件进行签名 |
Gradle构建分为三个独立的阶段:
- Initialization:Gradle支持多工程构建。在初始化阶段,Gradle会确定有哪些工程参与构建,并为它们创建Project实例
- Configuration:在此阶段,所有的Project对象被合理配置,每个Project的构建脚本(build script,即build.gradle)被执行
- Execution:Gradle依据命令行参数、当前所在目录决定需要执行的Task的子集。Task的全集在Configuration阶段已经生成
下面示意构建脚本的不同部分在哪个阶段执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
println '该代码在配置阶段执行' task configured { println '该代码在配置阶段执行' } task test << { println '该代码在执行阶段执行' } task testBoth { doFirst { println '该代码在执行阶段执行,最先执行' } doLast { println '该代码在执行阶段执行,最后执行' } println '该代码在配置阶段执行' } |
在初始化阶段,Gradle必须确定进行的单工程还是多工程构建(参见下一章)。多工程的设置总是存放在settings.gradle,Gradle自动的寻找该文件的顺序如下:
- 在当前目录下寻找
- 在同级的、名为master的目录中寻找
- 到父目录中寻找
如果找到settings.gradle,Gradle寻找被构建工程是否存在于settings.gradle所声明的工程树中,如果是,则执行多工程构建。否则,执行单工程构建。找不到settings.gradle则一定执行单工程构建。
注意,上述自动寻找settings.gradle的逻辑,仅在多工程满足下面条件之一时发生:
- 工程物理位置与工程树结构对应
- 或者,使用扁平目录结构,并且用master目录表示主工程
否则,你必须在settings.gradle所在目录执行Gradle命令以触发多工程构建。
在初始化结束之后,如果时单工程构建,其过程很简单。Gradle会寻找命令行传入的任务名,并把每个任务作为单独的构建执行。
在生命周期各关键点前后,Gradle会发布多种事件(通知),你可以在构建脚本中对这些事件进行处理:
事件 | 说明 | ||
Project evaluation | 在工程被evaluate前后,可以得到一个通知:
|
||
Task creation | 在Task被加入到工程前后,可以得到通知:
|
||
Task execution | 在任务执行前后,可以得到通知:
|
所谓多工程构建,是指在执行单次Gradle命令时,同时完成多个工程(一个root工程,外加若干个子工程,每个子工程也可以具有自己的子工程)的构建。必须通过 settings.gradle 文件来指定参与到构建中的工程,该脚本中的this指向 org.gradle.api.initialization.Settings 。Gradle依据该脚本构建工程的树状结构,此树具有唯一的根目录,每个子工程都具有一个“路径”用于表示其在树中的位置,通常情况下路径与子工程在文件系统中的位置一致。默认的,Gradle认为settings脚本所在目录就是根工程,该行为也是可配置的。
假设有一个包含四个子工程的工程,结构如下:
1 2 3 4 5 |
multiproject/ api/ #生成一个JAR包,提供WebService客户端的功能 services/webservice/ #WebService服务端 shared/ #其它三个工程依赖该工程 services/shared/ |
要启用多工程的支持,必须在工程集的根目录添加 settings.gradle 文件,指定哪些工程需要被构建:
1 |
include "shared", "api", "services:webservice", "services:shared" |
根目录通常作为工程的容器,其中可以通过 subprojects 来设置所有子工程共享的配置信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//所有子目录下的工程都获得下列配置信息: subprojects { apply plugin: 'java' apply plugin: 'eclipse-wtp' repositories { mavenCentral() } dependencies { testCompile 'junit:junit:4.12' } version = '1.0' jar { manifest.attributes provider: 'gradle' } } //注意,由于是在subprojects段设置,因此根工程不会获得这些配置 |
可以声明子工程之间的依赖:
1 2 3 |
dependencies { compile project(':shared') } |
上面的设置可以确保shared总是在api前面构建,因为后者依赖于前者。
默认的,Gradle使用全量配置方式:在任何Task执行之前,所有工程都被配置完毕。如果工程数量达到上百个,这会导致配置阶段耗费的时间过于漫长。对不参与本次构建的工程进行配置是不必要的,因此,Gradle 1.4引入了按需配置(Configuration on demand)模式,按需配置仅仅去配置那些与被请求执行任务相关的工程。在未来,按需配置可能变为默认或者唯一的配置模式。
在按需配置模式下,工程按照下面的规则配置:
- 根工程总是被配置,这保证典型的公共配置脚本被支持(allprojects和subprojects脚本块)
- 如果Gradle命令没有指定目标,则当前目录对应的工程被配置。这保证工程默认任务(Default tasks)可以正常工作
- 依赖工程被配置,如果projectA在编译时依赖于projectB,那么构建projectA会导致projectB也被配置
- 依赖任务所在工程被配置,例如 taskA.dependsOn(":projectB:taskB1") 导致构建taskA时projectB被配置
- 通过命令行指定Task的路径,那么路径中牵涉到的工程都会被配置,例如 gradle projectA:projectB:someTask 导致projectB被配置
在配置文件 gradle.properties 中指定 org.gradle.configureondemand ,可以启用按需配置模式。
以一个简单的多工程为例,根工程root包含一个子工程sub0:
1 2 3 4 |
root/ build.gradle settings.gradle sub0/ |
settings.gradle的内容如下: include 'sub0' 。sub0下面的构建脚本文件不是必须的,其构建脚本可以直接放在根工程的build.gradle里面:
1 2 3 4 5 6 7 8 9 |
Closure cl = { task -> println "I'm $task.project.name" } task hello << cl project(':sub0') { task hello << cl } # 在根工程目录运行gradle -q hello输出如下: # I'm root # I'm sub0 # 注意,所有子工程同名Task会自动执行 |
其实,Gradle允许在任何一个工程的构建脚本中访问所有工程,只要调用 project(projectName) 方法即可。
下面的脚本块可以为所有工程添加公共行为:
1 2 3 |
allprojects { task hello << { task -> println "I'm $task.project.name" } } |
下面的脚本则为所有子工程添加公共行为:
1 2 3 |
subprojects { hello << { println "I'm $it.project.name" } } |
还可以依据各种条件进行过滤,针对符合条件的工程添加行为:
1 2 3 4 |
//根据子工程的名称进行过滤 configure(subprojects.findAll {it.name != 'sub1'}) { hello << { println '$it.project.name' } } |
当在根工程目录执行 gradle taskA 时,根工程的taskA以及所有子工程的taskA任务都会被执行。
Gradle支持指定Task的绝对路径:
1 2 3 |
gradle -q :hello //根工程的hello任务 :sub0:hello //sub0工程的hello任务 hello //当前工程的hello任务 |
如果路径以分号开始,则表示是绝对路径(相当于根)。根工程是唯一不需要以名称指代的工程。
要确定Task的执行顺序,可以使用 dependsOn ,例如:
1 2 3 4 |
//跨工程的任务依赖 task consume(dependsOn: ':producer:produce') << { println("Consuming message: ${rootProject.producerMessage}") } |
被依赖的Task总是先被执行。
库依赖(lib dependency)是一种特殊的执行依赖。以Java构建为例,工程构建时,要求作为库的Jar已经生成好(此Jar可能传递性的依赖于其它Jar)并且添加到当前工程的Classpath中。如果这些库作为多工程构建的子工程存在,这就意味着工程之间存在依赖,这种依赖使用 dependencies 块声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
subprojects { // 子工程公共属性 apply plugin: 'java' group = 'cc.gmem.study.gradle' version = '1.0' } project(':api') { configurations { spi //声明一个依赖配置(dependency configurations) } dependencies { compile project(':shared') //声明库依赖:编译时依赖于shared工程 } task spiJar(type: Jar) { baseName = 'api-spi' dependsOn classes from sourceSets.main.output include('org/gradle/sample/api/**') } //声明产出构件 artifacts { spi spiJar } } project(':services:personService') { dependencies { compile project(':shared') //声明库依赖 compile project(path: ':api', configuration: 'spi') //声明库依赖,并指定配置 testCompile "junit:junit:4.12", project(':api') } } |
如果要禁止依赖工程的构建,可以使用gradle命令的 -a 选项。
Gradle自带了很多插件,这些插件可以完成不同领域的构建任务。Gradle从一开始就很好的支持Java的构建,支持编译、打包、单元测试都构建动作。
类似与Maven,Gradle的Java插件也是遵从“约定优于配置”的理念,因而Java插件生成了类似Maven的工程结构。
要添加Java插件的支持,在build.gradle中需要声明: apply plugin: 'java' 。添加该插件后:
- 自动设置src/main/java为Java源码目录,src的其它子目录布局和Maven一致
- 自动添加若干可用的Task,运行 gradle tasks 可以看到这些Task的详细说明
下表列出Java插件提供的常用任务:
分类 | 任务 | 说明 |
生命周期任务 | build | 对工程进行完整构建 |
buildNeeded | 对工程进行完整构建,同时构建该工程依赖的所有其它工程 | |
buildDependents | 对工程进行完整构建,同时构建所有依赖于该工程的其它工程 | |
assemble | 打印工程中的所有归档文件 | |
check | 执行所有验证任务。其他的插件会加入更多的检查步骤。其它插件会扩展这个任务的行为,例如Checkstyle插件会引发静态代码检查 | |
源码集任务 | compileSourceSetJava | 使用java编译指定源码集中的Java代码 |
processSourceSetResources | 拷贝指定源码集中的资源文件到资源目录 | |
sourceSetClasses | Assembles给定源码集中的类和资源文件 | |
其它 | clean | 清空构建结果 |
compileJava | 编译产品(不是测试)Java代码 | |
processResources | 处理产品(不是测试)资源文件 | |
classes | Assembles产品类和资源文件 | |
jar | 打Jar包 | |
javadoc | 生成Javadoc | |
test | 通过TestNG或者JUnit运行测试 | |
uploadArchives | 上传构件 |
与Maven类似,默认有src/main|test/java|resourcs目录,其它源码集存放在: src/sourceSetName/java 和 src/sourceSetName/resources 中。要改变此默认布局,可以:
1 2 3 4 5 6 7 8 9 10 |
sourceSets { main { java { srcDir 'src/java' } resources { srcDir 'src/resources' } } } |
Gradle维护一个和Maven Repository类似的依赖库,默认位于 %USERPROFILE%/.gradle/caches/ 。我们可以指定外部仓库的位置,以便从中下载依赖库:
1 2 3 |
repositories { mavenCentral() } |
使用下面的方式声明当前工程的依赖库:
1 2 3 4 |
dependencies { compile group: 'commons-collections', name: 'commons-collections', version: '3.2' testCompile group: 'junit', name: 'junit', version: '4.+' } |
Java插件默认给工程添加了很多属性,通过命令 gradle properties 可以查看这些属性的值。你可以在build.gradle中修改属性:
1 2 |
sourceCompatibility = 1.5 version = '1.0' |
也可以在jar的manifest中添加属性:
1 2 3 4 5 6 |
jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version } } |
或者在测试阶段添加一个系统属性:
1 2 3 |
test { systemProperties 'property': 'value' } |
可以声明把构建结果发布到仓库中,下面示例了如何发布到本地目录:
1 2 3 4 5 6 7 |
uploadArchives { repositories { flatDir { dirs 'repos' } } } |
添加插件: apply plugin: 'eclipse' ,然后运行命令 gradle eclipse ,即可自动依据build.gradle生成对应的Eclipse工程元数据,可以导入到Eclipse工作区中。
Annotation processing是Java 1.5引入的功能,允许在编译期进行代码检查、代码生成等操作。
要让Gradle能自动调用注解处理器,可以使用net.ltgt.apt插件:
1 2 3 4 5 6 7 8 9 10 11 12 |
buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "net.ltgt.gradle:gradle-apt-plugin:0.21" } } apply plugin: "net.ltgt.apt" |
然后,执行gradle build会调用注解处理器。
如果使用Intellij IDEA作为开发工具,可以将插件更换为:
1 |
apply plugin: "net.ltgt.apt-idea" |
这样,如果注解处理器生成了源代码,这些源代码的目录自动变为source root。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
task fatJar( type: Jar ) { manifest { attributes 'Implementation-Title': project.name, 'Implementation-Version': version, 'Main-Class': 'cc.gmem.yun.alcm.repository.source.GrpcServer' } baseName = project.name + '-all' from { configurations.compile.collect { it.isDirectory() ? it : zipTree( it ).matching { it.exclude( 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/DEPENDENCIES', 'META-INF/INDEX.LIST', 'META-INF/LICENSE*', 'META-INF/NOTICE*', 'META-INF/README*' ) } } } with jar } |
如果你希望把构件安装到Maven本地仓库,则需要此插件:
1 |
apply plugin: 'maven' |
安装此插件后,可以使用如下任务:
任务 | 说明 | ||
install |
安装构建的产出为Maven构件,并保存到本地仓库 可以使用系统属性指定本地仓库的位置:
|
需要使用到插件:
1 |
apply plugin: 'maven-publish' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
publishing { publications { maven( MavenPublication ) { // 源码在哪 from components.java // 发布JavaDocs和源码 artifact javadocJar artifact sourcesJar // 编辑POM内容 pom { name = project.group + ":" + project.name url = 'https://github.com/grpc/grpc-java' afterEvaluate { description = project.description } scm { connection = 'scm:git:https://github.com/grpc/grpc-java.git' developerConnection = 'scm:git:git@github.com:grpc/grpc-java.git' url = 'https://github.com/grpc/grpc-java' } licenses { license { name = 'Apache 2.0' url = 'https://opensource.org/licenses/Apache-2.0' } } developers { developer { id = "grpc.io" name = "gRPC Contributors" email = "grpc-io@googlegroups.com" url = "https://grpc.io/" organization = "gRPC Authors" organizationUrl = "https://www.google.com" } } withXml { if ( !( project.name in [ "grpc-stub", "grpc-protobuf", "grpc-protobuf-lite", ] ) ) { asNode().dependencies.'*'.findAll() { dep -> dep.artifactId.text() in [ 'grpc-api', 'grpc-core' ] }.each() { core -> core.version*.value = "[" + core.version.text() + "]" } } } } } } // 远程仓库配置 repositories { maven { credentials { if ( rootProject.hasProperty( 'pacloudUsername' ) && rootProject.hasProperty( 'pacloudPassword' ) ) { username = rootProject.pacloudUsername password = rootProject.pacloudPassword } } def releaseUrl = 'https://nexus.pacloud.io/repository/maven-releases/' def snapshotUrl = 'https://nexus.pacloud.io/repository/maven-snapshots/' url = version.endsWith( 'SNAPSHOT' ) ? snapshotUrl : releaseUrl } } } } |
执行下面的命令,完成打包和发布:
1 2 |
gradle sourceJar gradle publishToMavenLocal |
Gradle提供了Groovy插件:
1 2 3 4 5 6 7 8 9 10 11 |
apply plugin: 'eclipse' apply plugin: 'groovy' repositories { mavenCentral() } dependencies { compile 'org.codehaus.groovy:groovy-all:2.3.7' testCompile 'junit:junit:4.11' } |
该插件引入了以下任务:
任务 | 依赖于 | 被依赖 | 说明 |
compileGroovy | compileJava | classes | 编译产品Groovy代码 |
compileTestGroovy | compileTestJava | testClasses | 编译测试Groovy代码 |
compileSourceSetGroovy | compileSourceSetJava | sourceSetClasses | 编译指定的Groovy源码集 |
groovydoc | 生成Groovy文档 |
Groovy工程的目录布局与Java工程类似,新引入src/main/groovy、src/test/groovy目录存放Groovy源码。
如果要改变默认工程目录布局,可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
sourceSets { main { groovy { srcDirs = ['src/groovy'] } } test { groovy { srcDirs = ['test/groovy'] } } } |
Gradle提供了两个和Web应用相关的插件:
- War插件:在Java插件的基础上进行扩展,支持构建war包
- Jetty插件:允许发布War到Jetty容器
要添加War插件,可以:
1 2 |
apply plugin: 'war' //注意,该插件会自动引入Java插件 |
运行 gradle build 可以编译、测试并打war包。
War插件引入以下任务:
任务 | 依赖于 | 被依赖 | 说明 |
war | compile | assemble | 打包Java的Web归档(war) |
Web资源,例如JavaScript、CSS、HTML、JSP等,默认存放到src/main/webapp目录中。
War插件引入了两个新的依赖配置:
依赖配置 | 说明 |
providedCompile | 与compile、runtime类似,但是它们不会被打包到war中 |
providedRuntime |
下面的脚本示例了如何定制War插件的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
configurations { moreLibs } repositories { flatDir { dirs "lib" } //把lib目录作为依赖仓库 mavenCentral() } dependencies { providedCompile "javax.servlet:servlet-api:2.5" } war { from 'src/rootContent' // 添加文件集到war中 webInf { from 'src/additionalWebInf' } // 添加文件夹到WEB-INF目录下 classpath fileTree('additionalLibs') // 添加文件夹 classpath configurations.moreLibs // 添加一个配置到WEB-INF/lib目录下 webXml = file('src/someWeb.xml') // 拷贝文件为WEB-INF/web.xml } |
Jetty插件继承自War插件,可以通过下面的脚本添加该插件:
1 |
apply plugin: 'jetty' |
运行 gradle jettyRunWar 可以打包并部署到Jetty中运行。
Jetty插件引入以下任务:
任务 | 依赖于 | 被依赖 | 说明 |
jettyRun | compile | 运行Jetty实例,并把解开的构建目录部署上去 | |
jettyRunWar | war | 运行Jetty实例,并把打包后的war部署上去 | |
jettyStop | 停止Jetty实例 |
下面的脚本示例了如何定制Jetty插件的属性:
1 2 3 4 5 6 |
jetty { contextPath '上下文路径,默认war的名称' httpPort '监听端口,默认8080' stopPort '管理端口' stopKey '要求Jetty停止时,传递给他的key' } |
Gradle是当前Android官方IDE—— Android Studio内置的构建工具,你可以脱离AS,直接通过命令行调用Gradle,完成Android工程的构建。
通过编写构建脚本, 你可以配置Android构建的如下几个方面:
- 构建变体(Build variants):可以生成多个不同配置的APK,例如Debug/Release配置,又例如针对不同的体系结构(x86、arm)
- Android库依赖
- Manifest条目:可以在构建变体配置中覆盖AndroidManifest.xml中某些元素的取值。如果需要创建多个具有不同应用名称、最小SDK版本要求、目标SDK版本的APK,可以设置Manifest条目。如果有多个Manifest文件,会根据buildType、productFlavor、/main Manifest、library Manifest的优先顺序自动合并
- 签名:可以指定数字签名参数,在构建过程中对APK进行签名
- ProGuard:允许配置ProGuard,对APK进行混淆、裁剪和优化
Android Studio区分工程(Project)、模块(两个概念):
- 工程:表示一个顶级的Android开发结构(相当于Gradle的根工程),一个工程中可以包含多个模块(相当于Gradle子工程)
- 模块:表示一个可以独立构建、测试、调试的组件
AS支持以下类型的模块:
- Android Application Module:包含手机、电视 或手表的程序代码和资源,依赖于Library Module。构建系统为此类模块生成APK归档
- Android Library Module:可重用的Android程序代码和资源。构建系统为此类模块生成AAR(Android ARchive)归档
- App Engine Module:包含用于与Google App Engine集成的代码和资源
- Java Library Module:包含可重用的Java代码。构建系统为此类模块生成JAR归档
AS为工程和每个模块都生成Gradle构建脚本(build.gradle),这些脚本完全遵守Gradle的规范。
Android Studio工程包含了一个Gradle Wrapper,此Wrapper包含一个Gradle jar、一个属性文件、Windows/Linux的Shell脚本。使用Gradle Wrapper可以确保总是运行 local.properties 中声明的Gradle版本。你可以修改此文件,以使用不同的Gradle版本。
可以指定Android工程的依赖,以及所有模块的公共依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//构建本身的依赖配置 buildscript { repositories { jcenter() } dependencies { //使用特定版本的Gradle classpath 'com.android.tools.build:gradle:1.0.1' } } //针对Android工程和所有模块的依赖配置 allprojects { repositories { jcenter() } } |
每个模块都可以有自己的构建脚本,要对模块启用Android构建的支持,必须应用android插件:
1 2 3 4 |
//注意,不得同时应用java插件,会导致构建错误 apply plugin: 'android' //或者: apply plugin: 'com.android.application' |
可以在构建脚本中覆盖src/main/AndroidManifest.xml中的设置,或者对打包选项进行定制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
android { //Android 基本设置 compileSdkVersion 16 buildToolsVersion "23.0.2" //覆盖AndroidManifest.xml中的manifest properties设置 defaultConfig { applicationId "cc.gmem.androidstudy" minSdkVersion 15 targetSdkVersion 16 versionCode 1 versionName "1.0" } //构建类型设置 //默认情况下,Android插件同时创建两个构建类型:Debug、Release。两者的主要区别是: //Debug使用自动创建的密钥签名,而Release版本在构建时不签名 buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { debuggable true //设置ApplicationId,这样设备上可以同时安装Debug版本和Release版本的APK applicationIdSuffix ".debug" } //从debug衍生一个构建类型 jnidebug { initWith(buildTypes.debug) packageNameSuffix ".jnidebug" jniDebuggable true } } } //声明一些依赖项 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:16.+' } |
对于每个新创建的构建类型,Android插件自动创建Task: assemble<BuildTypeName>
Android插件默认使用如下的工程结构:
目录 | 说明 |
src/main/java |
主源码集 Android的Manifest文件位于:src/main/AndroidManifest.xml 其它子目录包括res、assets、aidl、rs、jni、jniLibs等 |
src/main/resources | |
src/androidTest/java | Android测试源码集 |
src/androidTest/resources |
如果默认工程结构不能满足需要,可以使用如下脚本定制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
android { sourceSets { //此闭包修改源码集的默认设置 main { manifest.srcFile 'AndroidManifest.xml' //指定Manif文件名称 //指定子目录位置 java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } androidTest.setRoot('tests') //把整个androidTest源码集移动到tests目录 } } |
Android插件自动引入以下Task:
任务 | 任务描述 | 备注 |
assemble | 收集工程的输出文件并打包。对于Android工程,至少输出两个APK包:Debug/Release,它们分别由任务assembleDebug、assembleRelease完成。assemble依赖于这两个任务 |
和Java插件一样,Android引入这三个一般性Task。这些Task本身不做实质性的工作,它们只是作为anchor,把工作委托给其它Task执行 不同插件使用风格一致的一般任务命名约定,可以简化构建 |
check | 运行所有检查。Android插件支持基于lint的静态检查 | |
build | 同时执行assemble和check | |
clean | 清除工程输出 | |
connectedCheck | 检查是否有连接上的设备或者模拟器 | |
deviceCheck | 检查设备是否支持API | |
installDebug | 安装Debug版本的APK到设备 | |
installRelease | 安装Release版本的APK到设备 | |
uninstallAll | 从设备上卸载所有版本的APK,依赖于:uninstallDebug、uninstallRelease、uninstallDebugAndroidTest |
默认的,Debug构建的keystore存放在 $HOME/.android/debug.keystore ,如果不存在会自动创建。下面的脚本块示例了如何创建新的签名配置(signingConfigs )并且将其应用到某个构建类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
android { signingConfigs { debug { storeFile file("debug.keystore") } //创建新的签名配置 myConfig { storeFile file("other.keystore") storePassword "android" keyAlias "androiddebugkey" keyPassword "android" } } buildTypes { foo { //应用签名配置 signingConfig signingConfigs.myConfig } } } |
[…] 深入学习 Gradle学习笔记 […]
[…] Gradle学习笔记 […]