转自:NO END FOR LEARNINGhttp://benweizhu.github.io/blog/2015/01/31/deep-into-gradle-in-action-1/

什么是构建工具?

一个可编程的工具,能够以可执行和有序的任务来表达满足需要的自动化过程。

以Java为例,要得到一个简单可运行的Jar文件,需要下面几步:

1.编译源代码
2.运行测试(前提是你有测试) 3.拷贝Class文件到目标目录
4.打包Class文件为Jar文件

这是一个完整的可自动化的过程,在没有构建工具之前,是由谁来做?IDE。一个强大的IDE,以上的步骤都只需要按几个按钮,这让开发人员的生活变得很美好,完全集中在写出优秀的代码。

现在,本来整个开发过程只需要你一个人,随着任务的难度和复杂度的加剧,你的团队从一个人变成了3个人或者更多。这时,你肯定会需要代码集成,这个问题好解决,使用版本控制,无论是中心式的SVN还是分布式的Git,总之可以既可以解决版本问题,也解决代码集成的问题。

这种情况下,在没有自动化构建时,你肯定会遇到下面几个问题: 
1.在我的机器上可以跑 
2.从版本控制check out代码,发现编译不过,有人少提交了代码文件
3.有个人提交代码时没跑测试,导致其他人check out代码后,测试跑不过
4.版本发布时,由一个人来check out所有代码,在他自己的机器上编译打包,结果部署到服务器上运行不了

导致上面这些问题出现的原因都有两个特点: 
1.手动介入
2.重复任务

开发人员要关注的应该是编写实现功能的代码,至于编译编译代码,拷贝文件,运行测试等一切重复和可自动化的事情都应该交给机器去做,因为人是容易犯错的。

Java世界的构建工具

在Java的世界里,目前在被使用的常用构建工具有三个:Ant,Maven,Gradle。

Ant的核心是由Java编写,采用XML作为构建脚本,这样就允许你在任何环境下,运行构建。Ant基于任务链思想,任务之间定义依赖,形成先后顺序。缺点是使用XML定义构建脚本,导致脚本臃肿,Ant自身没有为项目构建提供指导,导致每个build脚本都不一样,开发人员对于每个项目都需要去熟悉脚本内容,没有提供在Ant生态环境内的依赖管理工具。

Maven团队意识到Ant的缺陷,采用标准的项目布局,和统一的生命周期,采用约定由于配置的思想,减少构建脚本需要的编写内容,活跃的社区,可以方便找到合适的插件,强大的依赖管理工具。缺点是采用默认的结构和生命周期,太过限制,编写插件扩展麻烦,XML作为构建脚本。

如果有一个构建工具可以折中,同时拥有Ant和Maven的优点,是不是很爽?告诉你有,那就是Gradle。

Gradle

基于Groovy的DSL,提供声明式的构建语言 
采用标准的项目布局,但拥有完全的可配置性,就是可以改 
通过插件,提供默认的构建生命周期,也可以自己定义任务,单独运行任务,定义任务间的依赖 
强大的依赖管理工具,与Maven和Ivy仓库结合
与Ant天生兼容,有效的重用Ant的任务
多种实现插件的方式,强大的官方插件库
从构建级别,支持从Ant或者Maven的逐步迁移
通过包装器,无缝的在各个平台运行

看一个超级简单的例子:

如果你的项目采用标准的Maven布局(Java世界的标准布局)

1
2
3
4
5
6
7
8
9
10
src {//目录结构而已,不是代码
main {
java
resources
}
test {
java
resources
}
}

在项目根目录下,创建一个build.gradle,这个是Gradle的构建脚本文件,就和build.xml,POM.xml道理一样。

那么你要实现Java的编译,测试,拷贝class到目标目录,打包Jar文件等,只需要在构建脚本中,使用下面一句话,使用Java插件。

1
apply plugin: 'java'

然后运行gradle build。

下一篇,我们深入到实战学习Java插件的使用和依赖管理,让你快速开始Java应用开发。

没有介绍Gradle的基础知识,直接开始实战,目的是为了更快的让大家开始使用Gradle做构建,快速上手,当需要实现的自动化需求更复杂时,在深入学习基础知识。

这一篇,我们直接开始Java插件的使用。

应用Java插件

Gradle是一个通用构建工具,也就是说,它不单是为Java而生。比如,还可以做Groovy,Scala的构建。这取决于你使用什么样的插件。

大部分Java项目的基本步骤都非常类似,编译Java源代码,运行单元测试,拷贝生成的class文件到目标目录,打包Jar文件(或者War包,Ear包),而这些重复且约定俗成的任务,如果可以不用写一行构建代码就实现,是多么的棒!Maven就做到这一点,采用约定由于配置的思想,预先定义常用的任务,并定义它们的执行顺序。

Gradle吸收了Maven的这个优点,通过插件,实现预定义任务和任务之间依赖关系的导入,这样就可以在一行代码都不写的情况下(如果应用插件,你觉得也算一行的话,那就写一行吧),直接使用已经定义的任务。

1
apply plugin: 'java'

SourceSet和项目布局

就和Maven一样,在默认的情况下,项目的目录结构是固定的Java世界的标准项目目录布局,只不过Maven的不可以改,但是Gradle可以改。

1
2
3
4
5
6
7
8
9
10
src {//目录结构而已,不是代码
main {
java
resources
}
test {
java
resources
}
}

Java插件引入了一个概念叫做SourceSets,它代表了一组源文件,通过修改SourceSets中的属性,可以指定哪些源文件(或文件夹下的源文件)要被编译,哪些源文件要被排除。Gradle就是通过它实现Java项目的布局定义。

Java插件默认实现了两个SourceSet,main和test。每个SourceSet都提供了一系列的属性,通过这些属性,可以定义该SourceSet所包含的源文件。比如,java.srcDirs,resources.srcDirs。Java插件中定义的其他任务,就根据main和test的这两个SourceSet的定义来寻找产品代码和测试代码等。

在构建脚本中,怎么样定义或者修改SourceSet呢?Gradle提供了一系列的DSL,可以让你方便的定义或者修改配置。比如,sourceSets的DSL。

1
2
3
4
5
6
7
8
9
10
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}

上面的这个例子,在sourceSets中,修改了Java插件中已经定义的SourceSet main,修改了它的java.srcDir和resources.srcDir。于是,项目的目录结构就改变了。

改变Java插件中预定义的项目目录结构,不是我们最终的目的,因为它是目前Java世界,标准的项目布局,或者说大家都遵守的项目布局。

sourceSets最主要的作用是增加新的目录约定,比如,你想要定义一个新的SourceSet来管理集成测试的源文件,这样可以将单元测试和集成测试分开管理。

至于,关于具体如何为集成测试写一个新的SourceSet会在后面介绍依赖管理时举例说明。

Java插件提供的任务

Java插件提供了一系列的任务给你使用,包括编译,运行测试,打包等等。当你在项目中应用Java插件时,就已经将这些任务集成到你的项目中了。

在命令行中,运行gradle tasks命令,可以查看当前项目下主要的task。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Build tasks
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes. Documentation tasks
javadoc - Generates Javadoc API documentation for the main source code. Verification tasks
check - Runs all checks.
test - Runs the unit tests.

你可以对比Java插件应用前和应用后该命令的输出,Java插件提供的任务有很多,至于每个任务是做什么,这里就不赘述了。

Java插件除了为你预定义这些任务,该预定义了这些任务之间的依赖关系。如下图:

你也可以通过命令gradle tasks –all来查看每个task各自有什么依赖。

当然,这里还是重点提下,Java插件中四个重要和常用的任务,assemble,check,build,clean。

assemble 
All archive tasks in the project, including jar. Some plugins add additional archive tasks to the project. 
check 
All verification tasks in the project, including test. Some plugins add additional verification tasks to the project. 
build 
check and assemble 
clean 
Deletes the project build directory.

assemble被用来产生Jar文件,输出目录在build/libs下。

check用来运行所有的验收任务,包括test任务,以及其他验收任务,比如checkstyle。

Tips:在命令行中运行单个测试

JAVA插件中的test任务提供了一个filter属性,可以帮助指定运行test任务时,什么测试源文件要包含,什么要排除。

1
2
3
4
5
6
7
8
9
10
11
12
test {
filter {
//include specific method in any of the tests
includeTestsMatching "*UiCheck" //include all tests from package
includeTestsMatching "org.gradle.internal.*" //include all integration tests
includeTestsMatching "*IntegTest"
}
}

当然一般情况下,你不会这么去做。

但重点是,你可以通过命令行传递的参数来指定这个matching规则,这样你就可以通过命令行来指定跑某一类测试,或者单个测试。你一定遇到过,某个测试在命令行中可以运行,在IDE中不能运行,或者反过来。这时,你可以不会想要跑全部的测试来验证某一个测试。于是,你就可以通过命令行来运行某一个测试:

1
2
gradle test --tests org.gradle.SomeTest.someSpecificFeature
gradle test --tests *IntegTest

到目前为止,你已经了解了Java插件提供的一些核心功能和有用小技巧。虽然还未涉及到Jar任务和uploadfile任务(这些任务当需要时,再去看就行了),但是就启动项目而言,对Java插件的使用所需要了解的知识已经足够了。

下一节,讲解依赖管理

大部分的项目都不是自包含的,也就是说,需要使用到其他项目的构建结果,比如一些Jar文件。它们作为输入文件,必须存在于项目的ClassPath下,程序才能编译和运行。这些输入文件有一个很表意的名字,叫做依赖。

Gradle允许你告诉它项目的依赖是什么,然后它就会负责找到这些依赖。这些依赖会从Maven或者Ivy的远程仓库下载下来(大部分情况),并缓存在本地的某个路径,这个过程叫做依赖解析。

Maven和Gradle一样也提供了类似的功能,而Ant没有,你只能告诉Ant依赖文件的相对或者绝对路径,让它去加载。

常常一个依赖自己也存在依赖,我们称为传递依赖,依赖管理工具又具有解析传递依赖的能力。

Gradle的依赖管理

那么如何在Gradle中定义依赖呢?看个最简单的例子。

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

repositories {
mavenCentral()
} dependencies {
testCompile 'junit:junit:4.11'// testCompile group: 'junit', name: 'junit', version: '4.11'
}

项目使用了Java的插件,在repositories块中告诉Gradle使用maven的远程仓库作为依赖下载地址,在dependencies块定义了一个junit的依赖,并说明了分组(Maven中的Scope),后面注释中有一个表意更完整的依赖定义,说明了依赖声明使用的三个坐标group,name,version。

整个看起来是那么的表意,使用过Maven更会觉得是无缝转换,甚至更简洁。

Dependency configurations 依赖分组

在Gradle中,依赖都被会分配到某一个具体的configuration中(这里我不倾向于翻译成配置,我觉得布局,或者分组更适合)。Configuration代表着一个或多个构件及构件所需依赖的一个分组。

Java插件已经预定义了一些configuration,比如,compile,runtime,testCompile,testRuntime等。

compile 放在这个configuration下的依赖是在编译产品代码时所使用的,但它作为一个分组,包含产品代码和编译所需的依赖。 
runtime 产品代码在运行时需要的依赖,默认,也会包含compile中的依赖。 
testCompile 编译测试代码时所需要的依赖,默认,被编译的产品代码和产品代码需要的编译依赖也属于该分组。 
testRuntime 运行测试时需要的依赖。默认,包含compile,runtime和testCompile的分组的构建和依赖。

使用过Maven的都应该知道分组的含义,这里讲解给不明白的同学,依赖之所以要分组,是因为,每个阶段对依赖的需要不一样,最明显的是产品代码和测试代码,比如junit在产品代码中就不需要。

那么,为什么产品代码的编译阶段和运行阶段也分组,一般编译阶段需要的依赖,在运行阶段也需要,但是反过来就不一定了。比如,你通过反射去load一个class,这时该class就不一定需要在编译阶段存在。

一个更常见的例子,做web开发时需要servlet的依赖,但是只是编译阶段,运行时servlet依赖由servlet容器来提供。所以Gradle的War插件也提供了两个configuration,分别是providedCompile和providedRuntime,它们对依赖的使用范围定义和compile以及runtime一致,只不过依赖的Jar包不会被加到War包里面。

定义SourceSet时,添加的Configuration

上一节,在介绍Java插件的时候,提到了SourceSet概念。针对每一个新添加的SourceSet,Java插件都会动态的给它添加两个Configuration,分别是sourceSetCompile和sourceSetRuntime。

比如:新添加一个SourceSet,叫做int,那么对应的Configuration是intCompile和intRuntime。

这一特性也正好印证,Java插件是如何识别自定义SourceSet来进行编译和运行。

依赖的多种定义方式

除了通过远程仓库和依赖坐标来定义依赖,Gradle还提供了另外两种常用的依赖定义方式,对本地文件的依赖,对某个项目的依赖。

对文件的依赖

这种情况看起来是不是很奇葩,都有依赖管理了和Maven仓库了还要什么文件依赖。其实不然,使用这种定义方式,最常见场景是项目构建工具的迁移,从Ant到Gradle。无论任何项目,迁移过程都是小步前进,Gradle提供文件依赖的配置,就是为了解决这些特殊性。

1
2
3
4
dependencies {
runtime files('libs/a.jar', 'libs/b.jar')
runtime fileTree(dir: 'libs', include: '*.jar')
}

对另一个工程的依赖

项目中划分子模块是很平常的事情,前端Controller和数据层Dao分离管理就是一个例子,那么在进行前端Controller模块构建时,就需要将数据层模块作为依赖。定义方式如下:

1
2
3
dependencies {
compile project(':shared')
}

依赖版本冲突

依赖冲突是所以依赖管理中最头痛的问题,这常常出现在传递依赖中。Gradle对解决传递依赖提供了两种策略,使用最新版本或者直接导致构建失败。默认的策略是使用最新版本。虽然这样的策略能够解决一些问题,但是还是不够。常见的一种情况是,NoSuchMethond或者ClassNotFound。这时候,你可能需要一些特殊手段,比如排除不想要的传递依赖。

排除传递依赖

排除传递依赖有多种原因,远程仓库中不存在,运行时不需要,或者版本冲突。排除传递依赖的方式有两种:1.直接在configuration中排除 2.在具体的某个dependency中排除

1
2
3
4
5
6
7
8
9
10
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提供了两个任务来帮助你分析依赖关系

dependencies - Displays all dependencies declared in root project ‘projectReports’. 
dependencyInsight - Displays the insight into a specific dependency in root project ‘projectReports’.

Tips:输出依赖关系图到文件

在命令行中直接使用gradle dependencies可以打印出依赖图,但是在命令行中查看始终不太方便,我们可以将结果输出到一个文件中,如下:

1
gradle dependencies > dependencies.txt

dependencies.txt保存在项目的根目录

Gradle的官方文档中关于Gradle的依赖管理的内容还有很多,比如,如何访问需要用户名密码授权的Maven仓库等等。等多内容,可以参考官方文档:http://gradle.org/docs/current/userguide/dependency_management.html

下一节,利用前三节学到的知识,编写集成测试任务,并单独划分SourceSet。

Gradle深入与实战(转)的更多相关文章

  1. gradle教程 [原创](eclipse/ADT下 非插件 非Android Studio/AS)纯手打 第三篇:gradle完整的实战

    上两篇的地址 安装配置 http://www.cnblogs.com/uncle2000/p/4276833.html 简单实战 http://www.cnblogs.com/uncle2000/p/ ...

  2. Gradle入门到实战(一) — 全面了解Gradle

    声明:本文来自汪磊的博客,转载请注明出处 可关注个人公众号,那里更新更及时,阅读体验更好:  友情提示由于文章是从个人公众号拷贝过来整理的,发现图片没有正常显示,没关注公众号的同学可通过如下链接查看: ...

  3. Gradle入门到实战(二) — ImageOptimization安卓图片转换压缩插件

    上一篇我们了解了Gradle的各个方面,本篇介绍一款安卓图片优化转换插件,目前已在项目中使用,可一键批量转换压缩图片,webp转换与png/jpg压缩就是那么简单 GitHub项目地址:ImageOp ...

  4. 【Gradle教程】Gradle 入门

    本文为我在学习群内分享时在B站直播分享时的文档,直播间地址 http://live.bilibili.com/22263819 PS:问一下,Linux下有什么好用的会议软件么? 知道的朋友烦请评论告 ...

  5. Android Gradle插件

    目录 什么是Gradle 编写方法 buildSrc 基础概念 Extension 自定义Task Plugin Transformer Gradle用处 好文章 常见问题 Gradle插件练习地址: ...

  6. “入职一年,那个被高薪挖来的Android开发被劝退了。”

    其实,在很多小伙伴的想法中,是希望通过跳槽实现薪酬涨幅,可是跳槽不是冲动后决定,应该谨慎啊~ 01 我的学弟,最近向我吐槽,2020 年上半年入职一家公司,当时是高薪挖走的他,所谓钱到位,工作也是充满 ...

  7. 心酸!30岁深漂失业3个月,从巅峰跌落谷底,大龄Android开发必须要懂的事!

    2021年3月,我的前同事,在我们群里说他准备回老家了,问我们有没有人可以暂时收养他的猫. --他说,这周末就要离开深圳了. 他失业了.3个多月没收入,还要交着房租,过年来之后找了快一个月的工作也没有 ...

  8. 身边好几个技术一般的程序员都面上了,阿里P7门槛降低?

    经常在网上的论坛里看到讨论程序员的级别,尤其在跳槽类的信息里可以看到对标阿里P7,百度T6,腾讯3.1等字眼,似乎大厂的级别俨然可以成为业内的通用货币,类似于高考分数一样,哪一档就对应着什么样的待遇. ...

  9. 工资8000以下的Android程序员注意了!接下来要准备面对残酷现实了……

    最近在知乎看到一个测试,特扎心: 以下三种情况,哪个最让你绝望? ❶ 每月工资去掉开销还存不到3千: ❷ 家人突然急病住院,医药费10万: ❸ 同班的家长都在争先恐后给孩子报名各种辅导班.兴趣班,但一 ...

随机推荐

  1. ASP.NET MVC中错误日志信息记录

    MVC中有一个处理异常的过滤器 HandleErrorAttribute 1.新建一个类继承自 HandleErrorAttribute,然后重写OnException这个方法 public clas ...

  2. Python入门笔记(26):Python执行环境

    一.python特定的执行环境 在当前脚本继续进行 创建和管理子进程 执行外部命令或程序 执行需要输入的命令 通过网络来调用命令 执行命令来创建需要处理的输出 动态生成Python语句 导入Pytho ...

  3. Access数据库的常用数据类型和alter的用法

    一.Access比较常用的数据类型:文本.备注.数字.日期/时间.货币 意思          Sql                    Access 1)文本      nvarchar(30) ...

  4. EventUtil 根据IE自动适配事件

    var EventUtil = { addHandler: function (element, type, handler) { if (element.addEventListener) { el ...

  5. Python函数:一个简单的迭代

    #!/usr/bin/env python # -*- coding: utf-8 -*- def fact(n): if n == 1 : return 1 return n * fact(n-1) ...

  6. xscript脚本

    最近看<游戏脚本高级编程>,然后顺便把里面实现的虚拟机,汇编器以及编译器手动用C++重写了一遍,原版书中提供的代码,风格不是很好,而且有几处BUG.我现在开源的代码中已经修复了BUG,而且 ...

  7. Js中的this指向问题

    函数中的this指向和当前函数在哪定义的或者在哪执行的都没有任何的关系分析this指向的规律如下: [非严格模式]1.自执行函数中的this永远是window [案例1] var obj={ fn:( ...

  8. JavaScript Array(数组)对象

    一,定义数组 数组对象用来在单独的变量名中存储一系列的值. 创建 Array 对象的语法: new Array(); new Array(size); new Array(element0, elem ...

  9. win10应用部署到手机出现问题Exception from HRESULT: 0x80073CFD

    今天把应用部署到手机上时,出现了这样的问题 Exception from HRESULT: 0x80073CFD 具体错误是: Error Error : DEP0001 : Unexpected E ...

  10. windows 8 设置hyper-v网络设置

    1 windwos 8 设置hyperv 比较简单,和装操作系统都不多做解释.我只多说说网络的设置问题,因为可能装提windows 2008虚拟机,根据网上设置网络的方式都是要不然只能虚拟 机上网 , ...