Gradle之恋(9)-依赖管理1

概述

依赖管理是评价每个构建工具的重要指标,而Gradle提供了一流的依赖管理。它即容易理解,又能兼容Maven、Ivy的依赖管理方式,而且还提供了自定义的灵活特性。

Gradle的依赖管理特性:支持依赖传递;支持本地文件依赖;支持模块、项目依赖;无缝整合Maven的POM、Ivy的配置文件;兼容Maven仓库和Ivy仓库等。

依赖管理的话题分为两个层面的含义:把需要的依赖加入项目的class path以便顺利地构建项目;把项目构建成其他项目的依赖,发布出去。
Gradle

声明依赖的示例

我们先来看个依赖声明的例子,让你有个感性的认识

// build.gradle
apply plugin: 'java'

repositories {
mavenCentral()
}

dependencies {
compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
compile('org.springframework:spring-webmvc:4.3.0.RELEASE') {
exclude(module: 'commons-logging')
}
compile 'org.slf4j:slf4j-api:1.7.21'
testCompile group: 'junit', name: 'junit', version: '4.+'
}

例子中有声明的长格式和简短的格式、排除依赖和动态版本的依赖,这些内容我们一起来看看它们的内涵。

声明依赖的格式

// 基本格式
configurationName dependencyNotation1, dependencyNotation2, ...

// 高级格式
configurationName(dependencyNotation){
configStatement1
configStatement2
}
  • configurationName 如上面例子中的compile、testCompile等,后续会学习到;
  • dependencyNotation 有长(map)短(string)格式两种:”group: group, name: name, version: version, classifier: classifier, ext: extension”和”group:name:version:classifier@extension”,具体可参考上面例子;
  • configStatement的选项就非常多了,如在依赖树中排除依赖的exclude,设置依赖是否传递的transitive等;

name是必须的

如果你使用Maven仓库,你需要声明依赖的group,name和version,而如果你使用文件仓库依赖你只需name或name和version即可。

repositories {
flatDir {
dirs 'lib'
}
flatDir {
dirs 'lib1', 'lib2'
}
}

dependencies {
runtime ":junit:4.12", ":testng"
runtime name: 'testng'
}

集合式声明

你也可以指定声明集合或数组配置依赖。

List groovy = ["org.codehaus.groovy:groovy-all:2.4.7@jar",
"commons-cli:commons-cli:1.0@jar",
"org.apache.ant:ant:1.9.6@jar"]
List hibernate = ['org.hibernate:hibernate:3.0.5@jar',
'somegroup:someorg:1.0@jar']
dependencies {
runtime groovy, hibernate
}

依赖声明的种类

外部模块依赖

最常用的依赖,通常它们存储在远程仓库中.

dependencies {
runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
runtime 'org.springframework:spring-core:2.5',
'org.springframework:spring-aop:2.5'
runtime(
[group: 'org.springframework', name: 'spring-core', version: '2.5'],
[group: 'org.springframework', name: 'spring-aop', version: '2.5']
)
runtime('org.hibernate:hibernate:3.0.5') {
transitive = true
}
runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
transitive = true
}
}

Gralde会先查询模块在远程仓库中的描述文件(如pom.xml或ivy.xml),如果有描述文件,Gradle会下载该模块的Jar及其依赖;如果没有描述文件,Gradle直接下载该模块的Jar包。Maven的模块有其只有一个构件(artifact),而Gradle和Ivy一个模块可以包含多个构件。

然而有时你可能并不想下载模块的依赖,只想下载模块的jar,因此Gradle提供了artifactonly声明,只需要设置声明的extension为jar即可。如

dependencies {
runtime "org.groovy:groovy:2.2.0@jar"
runtime group: 'org.groovy', name: 'groovy', version: '2.2.0', ext: 'jar'
}

如果想查看依赖情况,除了使用gradle dependencies外,还可以自定义任务:

task listJars {
doLast {
configurations.compile.each { File file -> println file.name }
}
}

客户端模块依赖

允许你直接在build script中配置依赖传递,取代了依赖描述文件(pom.xml或ivm.xml)。

dependencies {
runtime module("org.codehaus.groovy:groovy:2.4.7") {
dependency("commons-cli:commons-cli:1.0") {
transitive = false
}
module(group: 'org.apache.ant', name: 'ant', version: '1.9.6') {
dependencies "org.apache.ant:ant-launcher:1.9.6@jar",
"org.apache.ant:ant-junit:1.9.6"
}
}
}

Gradle不再查找groovy的依赖描述文件,直接从build.gradle中获取它的依赖commons-cli。
但这种方式目前有个弊端,如果你的项目作为一个jar供别人依赖,放在远程仓库因为没有依赖描述文件而故障。Gradle整努力解决这个问题。

项目依赖

在多项目构建中,不可避免地使用项目依赖。

dependencies {
compile project(':shared')
}

文件依赖

Gradle允许你直接依赖文件,而不必把他们上传到远程仓库。这完全避开了你不愿或不能使用远程仓库的烦恼。

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

Gradle API依赖

当你自定义任务或插件时,依赖当前版本的Gradle API就大有用处。

dependencies {
compile gradleApi()
}

Groovy依赖

当你自定义任务或插件时,也可以依赖随着当前Gradle一同分发的Groovy。

dependencies {
compile localGroovy()
}

动态版本和变更模块

有时你想使用最新的版本,或一定范围内的最新版本,你可以声明动态版本的依赖。”+”表示范围内的最新,”latest.integration”获取最新的占位符。

dependencies {
//使用最新的jcl-over-slf4j
compile 'org.slf4j:jcl-over-slf4j:latest.integration'
//使用大版本为4的最新junit
testCompile 'junit:junit:4.+'
}

变更模块是模块名和版本都没变,但模块的内容实际上已经被改变了,Maven的SNAPSHOT版本就是变更模块,它总是指向最新的版本。
默认地,Gralde动态版本和变更模块缓存有效期是24小时,过期后会检测是否有新版本或变更模块,如果有则更新。你可以使用命令行参数–refresh-dependencies立刻检查更新,也可以配置build script

configurations.all {
// 有效期10分钟
resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
// 有效期4小时
//resolutionStrategy.cacheChangingModulesFor 4, 'hours'
}

版本冲突

Gradle提供了两种解决版本冲突的策略:Newest和Fail.默认策略是Newest,配置Fail模式:

configurations.all {
resolutionStrategy.failOnVersionConflict()
}

这两种策略一般能够满足使用,但Gradle提供了颗粒度更细的控制:

  • 强制依赖

    apply plugin: 'java'

    configurations.all {
      //如果有冲突,强制依赖asm-all的3.31版本和commons-io的1.4版本
    resolutionStrategy.force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4'
    }
  • 排除传递中的依赖

    apply plugin: 'java'

    dependencies {
    compile('org.hibernate:hibernate:3.1') {
    //如果有冲突,强制使用3.1版本
    force = true

    //排除传递中的依赖
    exclude module: 'cglib' //通过artifact的名字排除
    exclude group: 'org.jmock' //通过artifact的group名字排除
    exclude group: 'org.unwanted', module: 'iAmBuggy' //通过artifact的名字和grop名字排除

    //禁止该依赖传递
    transitive = false
    }
    }
  • 自定义模块优先
    有时自己fork了别人的模块,做了修改,此时希望Gradle依赖自己的fork而不是官方的,这是就可以

    apply plugin: 'java' //so that there are some configurations

    configurations.all {
    resolutionStrategy.preferProjectModules()
    }

使用命令行”gradle dependencies”可以查看项目的依赖,这方便了排查版本冲突。

依赖配置

配置概述

配置(configuration)是包括依赖的文件集合(FileCollection),而配置是由ConfigurationContainer声明和管理的,build script的属性configurations是ConfigurationContainer的实例,当然你也可以使用Project.getConfigurations()获取。

Java插件的标准配置

依赖是以配置为组的,如前文依赖定义格式。Gradle的Java插件定义了一些标准的配置如compile、runtime、testCompile、testRuntime等。当然你也可以自己定义配置,具体参考dsl的ConfigurationContainer。

  • compile 依赖在编译产品代码时需要;
  • runtime 依赖在运行产品时需要,默认地依赖包括compile时的依赖;
  • testCompile 依赖在编译测试代码时需要,默认地依赖包括产品class,已经compile时的依赖;
  • testRuntime 依赖在运行测试时需要,默认地依赖包括compile、runtime以及testCompile时的依赖;

Java插件还有跟多的配置,如compileOnly、compileClasspath、testCompileOnly、testCompileClasspath、archives等。

创建、访问配置
//使用java插件,可以访问其标准配置
apply plugin: 'java'

//创建配置
configurations.create('myConfiguration')
//访问配置并设置
configurations.myConfiguration.transitive = false
//closure方式访问并配置
configurations.myConfiguration {
transitive = false
}

//拷贝compile的所有依赖到指定目录allLibs
task copyAllDependencies(type: Copy) {
from configurations.compile
into 'allLibs'
}
经典案例

当使用Gradle创建有依赖的可执行jar包时,使用任务jar打包中并不包括依赖,所以你需要如此配置build script:

jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
manifest {
attributes 'Main-Class':'App'
}
}

翟前锋 wechat
欢迎订阅我的微信公众号:zhaiqianfeng!