首页 gradle
文章
取消

gradle

优点:

  1. 构建工具进化出内在逻辑
  2. 不僵化,灵活,构建逻辑可以直接放在构建脚本中。

缺点:

  1. 学习成本高,为了一个构建,学习一门新语言groovy,还要学习gradle的DSL
  2. 因为是一门语言,而不仅仅是一个构建工具,导致易用性骤降,比maven掉几百条街
  3. IDE的插件支持差,语法提示差,甚至闭包内的语法提示全无
  4. 构建慢,构建脚本间的任务过于松散,怎么串联到一起的都不知道
  5. 仅仅是为了打包,可gradle的打包过于费劲
  6. 构建任务是一个不需要经常改动的过程,gradle花了大量的心血让你在改动的时候可以简单通过脚本实现,但是为了这1%可能的变动,需要花掉99%的经历学习融汇贯通gradle以应对这1%的变动,已经是得不偿失,而且在构建脚本中添加逻辑的行为已经是本末倒置了。

对比maven,maven的优点:

  1. Xml严谨,不容易出错
  2. 插件逻辑和配置分离,清晰,不像gradle可以把构建逻辑放在构建脚本中。
  3. IDE插件支持完善, 可以通过插件完成递归性依赖的排查,语法提示完善。

一. 构建JAVA项目

1.1 gradle项目结构

gradle项目结构跟maven的类似,gradle项目需要有一个build.gradle文件。

1.2 使用JAVA插件

1
apply plugin: "java"

使用这个命令便是使用java插件,java插件是gradle官方的自带的插件之一, 这个插件会对java源码进行编译,测试,打包等。

那这个插件如何知道源码的位置的呢?这个是有这个JAVA插件引入的约定之一,插件会到src/main/java目录下进行查找。

1
2
3
4
5
6
7
8
9
10
11
12
> gradle build    //java插件提供的一个任务build,这个build任务会以正确的顺序编译,测试和打包源码
:compileJava    //每一行就代表这个java插件的一个任务的执行
:processResources
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE  //意味着这个任务被跳过了,gradle的增量式构建自动跳过不需要被运行的任务
:processTestResources
:testClasses
:test
:check
:build

执行完这个java插件的build任务以后,将会在项目的根目录中生成一个build的目录,这个目录与maven中的target目录相似。 里面包含了编译出来的class文件, jar文件

1.3 gradle 命令行

上面说了,在build.gradle中使用到了java插件, 执行命令的时候实际是执行这个插件中的一个task。如何知道有什么task可以执行呢?

常用命令

命令解释
gradle -q tasks查看build.gradle脚本中可执行的tasks有哪些
gradle properties查看build.gradle脚本中所有的可以设置的属性有哪些,包括插件中定义的属性
和gradle定义的内置属性
  
  
  

1.4 定制项目

java插件引入以后,源码结构(src/main/java/),编译打包(build/)都是使用的默认的约定。 gradle支持通过在build.gradle中通过DSL语法进行自定义这部分的约定的选项和配置。

build.gradle 实际就是一个groovy脚本,groovy是一门语言,groovy脚本实际就是一个源码文件, 所有的源码文件都是由属性变量和方法组成。 gradle脚本中引入了插件以后, 除了可以使用这个插件的方法意外, 还可以修改相关的属性。

gradle的脚本中属性的来源主要有引入的插件中的属性和来自gradle内置的属性。除了看代码外还有什么方法能知道你当前能定义的属性有哪些呢, gradle提供了一个命令gradle properties来查看当前的脚本中有哪些属性是可以设置的。

Gradle DSL规范可以在 Gradle DSL reference 中进行参考. 这里面可以看到能在gradle脚本中修改的属性有哪些

1.4.1 修改项目属性

1
2
3
4
5
6
7
8
9
apply plugin: "java"

version=0.1
sourceCompatibility=1.6
jar {											//具体可以看上面的reference
	manifest {							//设置名字为jar的task的方法 manifest(Closur clusor) 传参为闭包
		attributes "Main-Class":"com.my.hello.todo"	//这里的attributes只能通过看源码进行查看
	}
}

1.4.2 配置外部依赖

要配置外部依赖主要用到两个配置元素:repositories和dependencies。在java项目中, Gradle要求你至少使用一个集中仓库,这样后面定义的类就会优先到仓库里面查找, 找到的就会复制到自己的本地cache目录下面, 没找到就会到远端下载到自己的cache目录下面。

1
2
3
4
5
6
7
8
9
reposities {
	mavenCentral()
}

dependencies {
  compile group: "org.apache.common", name: "commons-lang3", version: "3.1"
  providerCompile: "javax.servlet:servlet-api:3.1"
  runtime: "javax.servlet:jstl:1.1.2"
}

1.5 Gradle包装器

gradle wrapper的意义在于保持gradle的版本,做到在每台机器上build的结果一致的效果。

  1. build.gradle中创建生成wrapper的任务
  2. 执行wrapper任务,生成gradlew和gradlew.bat脚本
1
2
3
4
5
6
7
8
//build.gradle
task wrapper(type: Wrapper) {
	gradleVersion: 1.7
}
//执行build.gradle中的wrapper任务
gradle wrapper
//生成gradlew或gradlew.bat命令

二. 构建脚本

2.1 构建块

每个gradle项目的build.gradle脚本都包含3个基本的构建块, project、task和property。

  • 一个gradle至少有一个project, 多个project中必须有依赖关系。
  • 一个project中有多个task, task之间也可以有依赖关系。
  • project和task中的暴露属性都可以影响构建。

2.1.1 project

gradle中的buiild.gradle类似于maven中的 pom.xml。

1
//TODO 画下面的UML图

2.1.2 task

task的重要任务功能包括: 任务动作和任务依赖。

1
//TODO 画下面的UML图

2.1.3 property

每个Task和Project都提供了可以通过getter和setter访问的属性。

属性主要包括以下几种:

  1. gradle属性:项目根目录/gradle.properties或者<USER HOME>/.gradle/gradle.properties下面,在用户目录下的属性文件只能有一个,且级别比项目根目录的要高
  2. 自定义属性:在项目的buiild.gradle文件中定义
  3. 项目属性:通过-P命令行传入
  4. 系统属性:通过-D命令行传入
  5. 环境属性

2.2 使用task

默认情况下, 新创建的task都是org.gradle.api.DefaultTask类型的,标准的org.gradle.apii.Task实现。

2.2.1 给task添加动作

gradle的project接口给了两个地方声明task动作,doFirst(Closure)和doLast(Closure)

doLast动作也可以通过 « 进行声明。

1
2
3
4
5
6
7
8
9
task printVersion {
	doFirst {
		println "do first"
	}
	
	doLast {
		println "do last"
	}
}

2.2.2 访问task属性

直接在task定义的属性中进行访问或者在动作中进行访问task。

2.2.3 定义task依赖

1
2
3
4
5
task first << {println "first"}
task second << {println "second"}
task printVersion(dependsOn: [second, first]) << {logger.quiet "version $version"}
task third << {println "third"}
third.dependsOn("printVersion")

// 最后执行链路为:first -> second -> printVersion -> third

2.2.4 终结task

2.2.5 Task添加任意代码

java中的bean叫做POJO, groovy中的bean叫做POGO, 同时groovy提供了语法糖

  1. 属性不需要添加控制权限符
  2. 属性的getter和setter方法自动由groovy生成。
2.2.5.1 添加配置块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ext.versionFile=file("version.properties")

task loadVersion {
        project.version=readVersion()
}

ProjectVersion readVersion(){
        logger.quiet "reading the version.properties file"
        if(!versionFile.exists()){
                throw new GradleException("version.properties is not exist")
        }
        Properties pros = new Properties()
        versionFile.withInputStream(stream -> pros.load(stream))
        new ProjectVersion(Integer.valueOf(pros.major), Integer.valueOf(pros.minor), Boolean.valueOf(pros.release))
}

执行gradle loadVersion, 可以看到这个readVersion的方法被完全执行, 上面的这个task只定义了属性, 并没有定义动作(doFirst, doLast这些),但是这个task的属性部分依然执行了。

2.2.5.2 gradle构建的生命周期

上面只定义了task的属性部分, 没有定义task的动作,但是task的属性部分依然完全执行了, 原因就是在gradle的生命周期中,有三个阶段, 初始化,配置阶段,执行阶段。

2.2.6 task的input和output

2.2.7 编写自定义task

2.2.8 使用自定义task

2.2.9 内置task类型

2.2.9.1 使用task类型

2.2.10 task依赖推断

2.2.11 task规则

2.2.12 buildSrc-构建代码

前面的在build.gradle中依赖到的自定义类ProjectVersion,这个类仅仅在构建过程中需要依赖到,如果都放在build.gradle中的话就会使得脚本杂乱,于是gradle提供了一个构建标准,在构建过程需要用到的类统一放在buildSrc目录下, java代码放在 buildSrc/src/main/java下,groovy代码放在buildSrc/src/main/groovy下。

在buildSrc中构建自定义插件

2.2.13 构建过程生命周期钩子

三. 依赖管理

3.1 依赖配置

前面知道通过引入了java插件以后,可以通过compile, runtime,testCompile,testRuntime,archives,default等来进行声明源代码所需要的依赖。这些都是java插件引入的”配置“。

java插件声明的这些从依赖在构建中的生命周期进行划分, 这些配置下面对应的有一堆的dependency依赖

  • compile: [dependency1, dependency2…]
  • runtime: [dependency1, dependency2…]
  • testCompile: [dependency1, dependency2…]
  • testRuntime: [dependency1, dependency2…]
  • archives: [dependency1, dependency2…]

除了从上面的这些维度进行划分依赖集合外,还可以通过自定义依赖配置,然后给这个依赖配置指派依赖。

简而言之:配置是gradle依赖的分组,java插件通过compile,runtime等做分组,还可以自己定义分组,然后指派依赖。

这一章为了更好地理解配置信息是如何存储,配合自己和访问的,看看gradle中的api接口是如何组织的。

3.1.1 配置api

配置是可以直接在项目的根级别添加和访问的,可以使用插件所提供的配置或者声明自己的配置。每个项目中都有一个configurationContainer类的容器来管理相应的配置。

3.1.2 自定义配置

1
2
3
4
5
6
configuration {
	cargo { //声明一个自定义的依赖配置,然后下面就可以向这个依赖配置指派依赖
		description = 'classpath for cargo ant tasks'
		visible = false
	}
}

3.2 依赖API

每个gradle项目都有一个依赖处理器实例, 每个依赖都是Dependency类型的一个实例。

1
2
3
dependencies {
	configurationName dependencyNotation1, dependencyNotation2...
}

3.2.1 声明依赖

依赖属性:group, name, version, classifier

在gradle中依赖的类型分下面5种:

类型描述
外部模块依赖依赖仓库中的外部类库,包括其提供的元数据
项目依赖依赖其他gradle项目
文件依赖依赖文件系统的中一系列文件
客户端模块依赖依赖仓库中的外部类库, 具有声明元数据的能力
gradle运行时依赖依赖gradle api或者gradle运行时的类库
1
2
3
4
5
//为自定义配置指定依赖,两种指派方式
dependencies {
	cargo group: "org.codehaus.cargo", name: "cargo-ant", version: "1.3.1"
  cargo "org.codehaus.cargo:cargo-ant:1.3.1"
}

额外知识,maven中的冲突依赖可以通过插件进行动态查看其中的传递依赖,但是gradle因为可以手动设置配置,不仅仅有java插件引入的冲突依赖,还可能是用户自己设置的配置里面的依赖冲突

3.2.2 传递性依赖

操作mavengradle
剔除所有传递性依赖不可以,强制传入可以设置transitive=false
剔除某个传递性依赖可以可以
如何剔除某个传递性依赖单个项目通过插件图搜,多项目通过mvn dependency:tree搜索只能通过gradle dependency进行搜索
1
2
3
4
5
6
dependencies {
  cargo('org.codehaus.cargo:cargo-ant:1.3.1') {
    transitive = false //完全剔除某个依赖的所有传递性依赖
  	exclude group: "xml-apis", module: 'xml-apis' //剔除某个传递性依赖
  }
}

3.2.3 动态版本

当某个项目的构建需要使用最新的依赖包的时候,可以通过两种方式指定使用最新的包进行构建:

  1. version使用占位符 latest-integration, 例: org.codehaus.cargo:cargo-ant:latest-integration
  2. version使用占位符+号,例:org.codehaus.cargo:cargo-ant:1.+,表示使用最新的1.x版本

3.2.4 文件依赖

1
2
3
4
5
6
7
8
task copyDependencyToLocalDir(type: Copy) { //拷贝依赖到本地
	from configurations.cargo.asFileTree
	into "${System.properties['user.home']}/libs/cargo"
}

dependencies { //声明本地依赖
  cargo fileTree(dir:"${System.properties['user.home']}/lib/cargo", include: '*.jar')
}

3.3 配置仓库

gradle中支持配置maven仓库, ivy仓库和扁平的本地系统文件仓库。

1
2
3
4
5
6
7
8
9
10
repositories {
	mavenLocal()
	mavenCentral()
	maven {
		name "自定义maven库"
		url "http://repository-gradle.xxx/xxxxx/release/"
	}
  //本地仓库
  flatDir(dir: "${System.properties['user.home']}/libs/cargo", name: 'local libs directory')
}

3.4 本地依赖缓存

3.5 版本冲突

gradle应对版本冲突的时候并不会主动通知, 默认处理冲突的逻辑是使用最新的版本。可以通过修改这个默认的逻辑,当遇到版本冲突的时候让构建失败。

1
2
3
configuration.cargo.resolutionStrategy {
	failOnVersionConflict()
}

四. 多项目构建

4.1 setting.gradle

通过路径来声明子项目。如果子项目有层级关系,可以通过冒号:进行分隔子项目的目录层级关系。

1
2
3
//setting.gradle
include 'module'  //子项目与build.gradle同级
include 'module:second:third' //子项目在module/second/third目录下

4.2 settting api

构建之前会创建一个Setting类型的实例,是settings文件的映射表示

4.3 setting 执行

setting的执行是在初始化阶段。

4.3.1 获取setting文件

只要项目的根目录或者任何的子目录中存在构建脚本build.gradle,gradle便会从相应的位置进行构建。

本文由作者按照 CC BY 4.0 进行授权

gradle概念扫盲

gradle dsl