先看下Gradle的定义:
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。
抽住定义中的核心就是DSL版本的编译工具,解决XML版本编译工具的缺点。
虽然gradle是基于groovy的,但是由于其提供了一整套DSL,所以在开发gradle脚本时几乎脱离了groovy的感知。我们在这里把gradle当成一个独立的部署工具,而不去深究原理,从这个角度来看,groovy不是学习gradle的前提条件。
在开始学习gradle脚本之前,我们先弄清楚gradle中的3个组成部分:
Project:项目
1 每个项目都会有自己的gradle领域,配置脚本文件名默认是不变的build.gradle(后面会介绍如何指定自定义文件名)
2 Project之间如果出现父子关系,只有根Project才会有setting.gradle配置文件,该配置文件的作用是声明其包含的子项目
Task:任务
每个Project是由N个Task组织成的一个“有向无环图”(关于有向无环图的说明可以参考Spark中的解释),task之间有依赖关系从而决定了它们的执行顺序。
依赖方式如下:
task taskA(dependsOn: 'taskB') << {
println 'taskA'
}
Task的来源有三种
第一种是Gradle默认自带了几种task:
Dependencies:显示Project的依赖信息
Projects:显示所有Project,包括根Project和子Project
Properties
:
显示一个Project所包含的所有Property
第二种是直接在project中显式创建
hello << {
println 'Hello Task'
}
第三种是成品task以plugin的方式引入
通过apply方式引入:apply plugin: 'groovy'
Property:属性
Project除开包含Task外还包含Property,Property默认自带了一些属性,也可以通过ext.XXX的方法来扩展。
Gradle的工作流:
一次Gradle的工作流分为3大部:
第一:解析setting.gradle并遍历根目录,检查子项目是否满足
第二:解析每个子project的gradle,根据task之间的关系创建有向无环图
第三:执行,涉及到download依赖、build发布包等
Gradle脚本详解:
有了以上的基础知识,我们开始真正的脚本学习之旅:
单project的项目:
buildscript {
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.netflix.nebula:gradle-netflixoss-project-plugin:5.1.1-rc.1'
}
}
apply plugin: 'nebula.netflixoss'
apply plugin: 'groovy'
apply plugin: 'idea'
group = 'com.netflix.spinnaker.gradle'
sourceCompatibility = 1.8
targetCompatibility = 1.8
idea {
project {
jdkName = sourceCompatibility
languageLevel = targetCompatibility
vcs = 'Git'
}
}
bintray {
pkg {
userOrg = 'spinnaker'
repo = 'gradle'
labels = ['Spinnaker', 'Netflix']
}
}
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
compile gradleApi()
compile localGroovy()
compile 'com.netflix.nebula:gradle-netflixoss-project-plugin:5.1.1-rc.1'
compile 'com.netflix.nebula:gradle-ospackage-plugin:4.9.3'
compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.1'
compile 'org.yaml:snakeyaml:1.17'
}
buildscript:因为gradle本身也是groovy语言,所以脚本一开始要为gradle准备好执行环境。
repositories:仓库地址,这里配置了两个仓库jcenter和maven,我们先解释下两者的区别和联系。jcenter和maven分别是JFrog和Sonatype公司提供的仓库服务,jcenter的优点是因为采取了CDN技术所以获取非常迅速;maven的优点是仓库内容比jcenter要全面,在jcenter中找不到的内容可以在maven中找到。也正是因为这两个仓库各自的优缺点才有了你所看到的脚本中配置的先后顺序(gradle寻找依赖包是按照脚本书写的先后顺序来执行的)。Gradle默认不提前定义任何仓库,我们必须手动在使用外部依赖之前定义自己的仓库
一个项目可以有好几个库,Gradle会根据依赖定义的顺序在各个库里寻找它们,在第一个库里找到了就不会再在第二个库里找。
apply plugin:task的第三类引入-插件式
dependencies:具体的依赖包,可以看到在buildscript中出现一次,在脚本结尾出现一次。buildscript中出现是因为它是gradle执行的依赖(classpath指定);第二次出现时项目本身的依赖(compile指定)。
repositories:同dependencies一样在buildscript内外各出现一次,分别是gradle脚本的依赖和项目本身的依赖。
bintra:这个task坑较多,目的是将最终的项目发布到jcenter中,里面配置了组织名、仓库名等信息。
compile:用来编译项目源代码的依赖,先看最后4行,他们的格式是group:name:version,很好理解。gradleApi()和localGroovy()这两行并没有按照我们所说的group:name:version的格式,这是干什么用的么?我们需要再学习下“自定义plugin”,详见下一章节。
与build.gradle平级目录还需要一个settings.gradle,该项目只有一行:
rootProject.name = 'spinnaker-gradle-project'
指明编译的根项目。
自定义plugin
上面在介绍task的时候曾经强调过task的3种来源,其中最简单的引入方式就是plugin了。假设我们有个规模较大的工程,设计到很多个project,我们可以将一些公共的task抽象出来开发一套自己的plugin,那么gradle脚本就会变得干净高度可读了。
下面看下如何开发自定义plugin,案例代码:
package com.netflix.spinnaker.gradle.dependency
import org.gradle.api.Plugin
import org.gradle.api.Project
class SpinnakerDependencyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create('spinnaker', SpinnakerDependency, project)
}
}
实现org.gradle.api.Plugin接口并实现apply(Project project)方法,就是自定义plugin代码部分的所有内容。如果要掌握apply中的内容需要详细的学习gradle的原理,例如你了解了project的扩展属性是如何定义的才能明白为什么要用extensions.create()这个方法。
这里我们import了org.gradle.api进来,所以前面单项目脚本时我们compile gradleApi()、compile localGroovy()就是为了自定义plugin而引入的依赖。
我们自定义的plugin有类名、包名,使用起来非常费劲,所以我们在resources的META_INF下的gradle-plugins下定义出插件名与插件具体类的映射关系。
这样其它项目只要dependence了该项目后就可以用apply plugin spinnaker.project的方式来引入自定义的plug了。
多project的项目:
对于多项目的场景,我们要先看settings.gradle文件搞明白各项目之间的父子关系。
例如我的settings.gradle如下:
rootProject.name = 'rosco'
include 'rosco-core', 'rosco-web', 'rosco-manifests'
def setBuildFile(project) {
project.buildFileName = "${project.name}.gradle"
project.children.each {
setBuildFile(it)
}
}
rootProject.children.each {
setBuildFile(it)
}
跟项目是rosco,它有3个子项目,而且自定义了setBuildFile(project)这个task,我们前面介绍了默认的脚本文件是build.gradle,该task的作用是强制指定了每个子项目的gradle的脚本文件。
再来看根项目的build.gradle脚本:
buildscript {
ext {
springBootVersion = "1.5.4.RELEASE"
}
repositories {
jcenter()
maven { url "https://spinnaker.bintray.com/gradle" }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.netflix.spinnaker.gradle:spinnaker-gradle-project:4.0.0'
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
allprojects {
apply plugin: "spinnaker.project"
apply plugin: "groovy"
group = "com.netflix.spinnaker.rosco"
ext {
spinnakerDependenciesVersion = project.hasProperty('spinnakerDependenciesVersion') ? project.property('spinnakerDependenciesVersion') : '0.149.1'
}
def checkLocalVersions = [spinnakerDependenciesVersion: spinnakerDependenciesVersion]
if (ext.has('versions')) {
def extVers = ext.get('versions')
if (extVers instanceof Map) {
checkLocalVersions.putAll(extVers)
}
}
def localVersions = checkLocalVersions.findAll { it.value.endsWith('-SNAPSHOT') }
if (localVersions) {
logger.info("Enabling mavenLocal repo for $localVersions")
repositories {
mavenLocal()
}
}
spinnaker {
dependenciesVersion = spinnakerDependenciesVersion
}
configurations.all {
resolutionStrategy {
force 'org.apache.httpcomponents:httpclient:4.3.5'
force 'org.apache.httpcomponents:httpcore:4.3.2'
}
exclude group: 'javax.servlet', module: 'servlet-api'
exclude group: "org.slf4j", module: "slf4j-log4j12"
exclude group: "org.mortbay.jetty", module: "servlet-api"
}
dependencies {
spinnaker.group("test")
compile spinnaker.dependency("bootActuator")
compile spinnaker.dependency("groovy")
compile spinnaker.dependency("korkSwagger")
compile spinnaker.dependency("spectatorReg")
compile spinnaker.dependency("korkArtifacts")
}
test {
testLogging {
exceptionFormat = 'full'
}
}
tasks.withType(JavaExec) {
if (System.getProperty('DEBUG', 'false') == 'true') {
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8187'
}
}
}
defaultTasks ':rosco-web:bootRun'
buildscript:该部分与单项目中的区别不大,不再做详细解释。
classpath 'com.netflix.spinnaker.gradle:spinnaker-gradle-project:4.0.0'这个就是我们前面自定义的那个plugin了,com.netflix.spinnaker.gradle是那个项目的group,spinnaker-gradle-project是映射的name,4.0.0是版本号。
allprojects:指所有的project都要进行设置,如果只设置子项目,替换成subprojects
ext. spinnakerDependenciesVersion:project的扩展属性,这里的意义是要给所有项目添加一个统一的版本号
configurations.all这里是要修改依赖的版本,gradle默认会用最新版本的依赖,如果想要强制指定版本号就通过这种方式。
defaultTasks:指定项目的主程序入口
再看一个子项目的的脚本,子项目是没有setting.gradle的,只有根项目才有setting.gradle:
dependencies {
compileOnly spinnaker.dependency("lombok")
compile group: 'commons-io', name: 'commons-io', version: '2.4'
compile project(":rosco-core")
}
看得出子项目的gradle脚本相对简单多了,主要负责两部分功能。第一指定子项目之间的依赖关系,这将影响到最终的有向无环图;第二子项目自身的依赖控制。当然你也可以定制子项目私有的task,这里不再做介绍。
该脚本就指定依赖了平级的rosco-core子项目
最后回到我们前面讲过的gradle的执行过程:
第一:解析setting.gradle并遍历根目录,检查子项目是否满足
第二:解析每个子project的gradle,根据task之间的关系创建有向无环图
第三:执行,涉及到download依赖、build发布包等
最后一步就是下载所有依赖,按有向无环图的一步步执行了,最终build成发布包。
对于产出,按照你的gralde里内容来设置,你可以产出jar包,或者只是编译成class,甚至利用插件产出.deb格式用于ubuntu系统apt-get安装,都可以。
Gradlew和Gradle的区别和联系:
我们通过IDE引入一个gradle项目的时候会有类似下面的选择,例如我的IDE:
前两个选择是Gradle Wrapper的,也就是Gradlew;第三个选择是Gradle的,那么这两种有啥区别呢?
Local Gradle,是我们本地安装的gradle,就跟安装JDK一样,下载安装gradle,配置path环境变量,然后本地就可以gradle build命令来执行gradle了。
Gradlew方式对项目的使用者更友好,因为你可以自己不准备上面的gradle环境,而是交给gradlew来帮你下载安装搞定环境问题,所以本质上Gradlew和Gradle是同一个东西的自动封装。