Nebula: Netflix 开源的 Gradle 插件集合
fbuy4059
8年前
<p>Gradle 作为 Apache Maven 的有力竞争者,在 Java 项目的构建领域逐渐流行起来。很多开源项目,如 Spring 框架、Hibernate、Elasticsearch 和 RxJava 等都使用 Gradle 进行构建。Gradle 也是 Android Studio 中 Android 项目的标准构建方式。越来越多的开发人员开始使用 Gradle 构建自己的 Java 项目。在开始使用 Gradle 时经常会面临的一个问题是从何处开始。Maven 中可以使用 Archetype 来作为项目的模板,Gradle 并没有提供类似的机制。本文要介绍的 Nebula 是由 Netflix 开发的 Gradle 项目构建框架,其目的是为 Gradle 项目提供一个良好的起点,把一些常见的任务添加到构建过程中,从而简化 Gradle 项目的构建配置。</p> <h2><strong>基本配置</strong></h2> <p>本文通过一个基于 Spring Boot 的 Java Web 示例应用来介绍 Nebula 的使用。Nebula 的核心是一系列由 Netflix 开发和维护的 Gradle 插件。这些插件覆盖 Gradle 项目构建的不同阶段,提供不同的功能。给出了使用 Nebula 的插件的项目的 Gradle 脚本。Nebula 的插件都发布到 Gradle 插件仓库中,因此需要在脚本中添加插件仓库地址"https://plugins.gradle.org/m2/"。要使用 Nebula 的插件,只需要在脚本中的 buildscript 中添加对相应插件的依赖,再通过 apply plugin 来应用插件。在应用了插件之后,可以在脚本中进行相应的配置,并通过 Gradle 命令行来运行相关的任务。中给出了示例的 Gradle 脚本。</p> <p>清单 1. 使用 Nebula 的 Gradle 脚本</p> <pre> <code class="language-groovy">group 'com.midgetontoes' version '1.0-SNAPSHOT' buildscript { ext { springBootVersion = '1.3.5.RELEASE' } repositories { mavenLocal() mavenCentral() jcenter() maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath "com.netflix.nebula:nebula-project-plugin:3.2.0" classpath "gradle.plugin.com.netflix.nebula:gradle-ospackage-plugin:3.6.1" classpath "com.netflix.nebula:nebula-publishing-plugin:4.8.0" classpath "com.netflix.nebula:nebula-release-plugin:4.0.1" classpath "com.netflix.nebula:gradle-resolution-rules-plugin:1.8.0" } } apply plugin: 'java' apply plugin: 'war' apply plugin: 'application' apply plugin: 'spring-boot' apply plugin: 'nebula.project' apply plugin: 'nebula.resolution-rules' apply plugin: 'nebula.dependency-lock' apply plugin: 'nebula.ospackage-daemon' apply plugin: 'nebula.maven-publish' apply plugin: 'nebula.javadoc-jar' apply plugin: 'nebula.source-jar' apply plugin: 'nebula.nebula-release' sourceCompatibility = 1.8 mainClassName = 'com.midgetontoes.nebulasample.Application' repositories { mavenLocal() mavenCentral() jcenter() } dependencies { resolutionRules files('local-rules.json') resolutionRules 'com.netflix.nebula:gradle-resolution-rules:latest.release' compile('org.springframework.boot:spring-boot-starter-web') compile('com.google.guava:guava:19.0') // compile('io.netty:netty-all:4.1.4.Final') testCompile('org.springframework.boot:spring-boot-starter-test') }</code></pre> <p>下面对 Nebula 提供的常用插件进行具体的介绍。</p> <h2><strong>依赖版本锁定</strong></h2> <p>在进行项目构建时一个很重要的要求是构建的可重复性。也就是说,代码仓库中任意时刻的代码都应该是可重复构建的。只有这样才可以保证代码的稳定性和质量。不论是最近的代码,还是几个星期之前、几个月之前甚至是几年之前的代码,都应该满足这样的条件。</p> <p>可重复构建所面临的挑战之一来自于项目所依赖的第三方库。随着项目的演化,这些第三方库的版本可能升级。之前的项目版本也许只能与特定版本的第三方库协同工作。Gradle 项目直接在 Gradle 文件中声明所依赖的第三方库的版本。除了直接声明的第三方库版本之外,有些依赖是通过传递关系引入的。这些传递依赖的版本是不受应用本身控制的,而由所依赖的库自己来管理。因此第三方库自身的依赖的版本更新,也可能造成应用的构建失败。当项目的传递依赖关系很复杂时,很可能会出现传递依赖冲突的情况。</p> <p>Nebula 提供的 nebula.dependency-lock 插件的作用是生成一个包含了全部依赖的具体版本的锁定文件。这个文件由代码仓库进行管理。当这个文件存在时,该插件会确保 Gradle 只会使用正确版本的依赖。实际上,使用过 Ruby 中的 Gem 管理工具 Bundler 的开发人员会发现,这种版本锁定功能与 Bundler 生成的 Gemfile.lock 是一样的。当每次版本发布时,在构建成功之后,应该通过该插件生成锁定文件,并提交到代码仓库。</p> <p>nebula.dependency-lock 插件支持两类不同的锁定文件,分别是项目锁定文件和全局锁定文件。当 Gradle 项目中包含多个子项目时,每个子项目可以有自己的锁定文件。当全局锁定文件存在时,子项目中的锁定文件不起作用。子项目锁定文件的名称默认为 dependencies.lock,全局锁定文件的名称默认为 global.lock。插件提供的任务如所示。</p> <p>表 1. nebula.dependency-lock 插件提供的任务</p> <table cellspacing="0"> <thead> <tr> <th>任务</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>generateLock / generateGlobalLock</td> <td>生成锁定文件。generateLock 生成子项目的锁定文件,<br> generateGlobalLock 生成全局锁定文件。锁定文件生成在项目的 build 目录中。</td> </tr> <tr> <td>updateLock / updateGlobalLock</td> <td>更新子项目/全局锁定文件。</td> </tr> <tr> <td>saveLock / saveGlobalLock</td> <td>把生成的锁定文件复制到项目目录中。</td> </tr> <tr> <td>deleteLock / deleteGlobalLock</td> <td>删除子项目/全局锁定文件。</td> </tr> <tr> <td>commitLock</td> <td>把锁定文件提交到代码仓库。</td> </tr> </tbody> </table> <p>该插件提供了一些额外的参数来对任务的行为进行配置。比如,在 updateLock 时可以通过 dependencyLock.updateDependencies 来指定需要更新的依赖的名称。</p> <h2><strong>依赖版本推荐</strong></h2> <p>在 Gradle 项目中添加第三方依赖时都需要指定版本号。在 Gradle 脚本文件中直接引用版本号可能造成依赖版本升级时的维护困难。一般的做法是把版本号提取到项目属性中,从而可以在统一的地方管理所有依赖的版本信息。当项目较多时,这样的管理方式也会变得很繁琐。因为有些通用的库会在多个项目中使用,而当需要升级这些通用库的版本时,会需要修改多个项目的 Gradle 文件。另外一个常见的需求是解决多个依赖库的版本兼容问题。有些第三方库,如 Spring 框架,包含很多个子项目,当引用这些依赖时,需要确保这些依赖的版本一致,否则可能出现兼容性问题。</p> <p>这些与依赖的版本号相关的问题,都可以通过 Nebula 提供的依赖推荐插件来解决。在使用了依赖推荐插件之后,没有声明版本的第三方依赖的版本号由插件来决定。</p> <p>依赖推荐插件支持五种方式来声明所推荐的依赖的版本。第一种方式是通过 Maven BOM 文件。Maven 的 BOM 文件中直接定义了依赖的版本信息。比如,Spring Boot 项目就提供了相应的 BOM 文件,可以作为创建 Spring Boot 项目的父 POM 文件。Maven BOM 文件中通过 dependencyManagement 来指定不同依赖的版本号。中给出了作为示例的 Maven BOM 文件。</p> <p>清单 2. 示例 Maven BOM 文件</p> <pre> <code class="language-groovy"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>sample</groupId> <artifactId>sample-bom</artifactId> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> </dependencies> </dependencyManagement> </project></code></pre> <p>当 Maven BOM 文件发布到 Maven 仓库之后,可以作为依赖推荐插件的规则来源,如所示。</p> <p>清单 3. 使用 Maven BOM 文件指定推荐版本</p> <pre> <code class="language-groovy">dependencyRecommendations { mavenBom module: 'sample:sample-bom:1.0' }</code></pre> <p>第二种方式是通过属性文件来指定版本。属性文件中的键是依赖的全名,值是对应的版本号,如所示。</p> <p>清单 4. 使用属性文件指定推荐版本</p> <pre> <code class="language-groovy">dependencyRecommendations { propertiesFile file: 'recommendations.properties' }</code></pre> <p>第三种方式是通过由 dependency-lock 插件生成的依赖版本锁定文件来指定版本号,如所示。该锁定文件在生成之后,通常会被提交到代码仓库中。项目可以直接使用锁定文件来推荐版本号。</p> <p>清单 5. 使用依赖版本锁定文件推荐版本</p> <pre> <code class="language-groovy">dependencyRecommendations { dependencyLock module: 'sample:dependencies:1.0' }</code></pre> <p>第四种方式是在 Gradle 文件中直接使用 java.util.Map 接口对象来提供推荐的版本号,如所示。</p> <p>清单 6. 使用 Map 接口对象推荐版本</p> <pre> <code class="language-groovy">dependencyRecommendations { map recommendations: [ 'com.google.guava:guava': '18.0', 'org.slf4j:slf4j-api': '1.7.21' ] }</code></pre> <p>最后一种方式是通过完全自定义的代码来声明推荐的版本号。在 Gradle 脚本中通过 add 方法来添加规则。add 方法需要根据依赖的组织名和名称,返回其对应的推荐版本号。比如可以把推荐的版本号保存在数据库之中,然后在 add 方法中进行数据库查询并返回版本号。在中,对所有的依赖都返回推荐的版本号 1.0。</p> <p>清单 7. 使用 add 方法推荐版本</p> <pre> <code class="language-groovy">dependencyRecommendations { add { org, name -> '1.0' } }</code></pre> <p>在使用了版本推荐插件之后,Gradle 对依赖版本的选择过程发生了变化。优先级最高的是强制应用的依赖版本号,其次是显式指定了版本号的普通依赖,接着是通过插件推荐的依赖版本号,最后则是由直接依赖引入的传递依赖。当传递依赖的版本号与插件推荐的版本号发生冲突时,可以应用不同的冲突解决策略。默认的策略是 ConflictResolved,即通过 Gradle 自己的机制来选择合适的版本号,即优先考虑传递依赖中的版本号,再考虑插件所推荐的版本号;另外一种策略是 OverrideTransitives,即选择插件推荐的版本号,而完全忽略传递依赖中的版本号。中给出了使用 OverrideTransitives 策略的示例。</p> <p>清单 8. 使用 OverrideTransitives 策略</p> <pre> <code class="language-groovy">dependencyRecommendations { strategy OverrideTransitives map recommendations: ['commons-logging:commons-logging': '1.0'] }</code></pre> <h2><strong>依赖解析规则</strong></h2> <p>Gradle 本身已经提供了强大的依赖解析功能,可以满足各种特殊的依赖解析需求。在使用第三方提供的库时,不可避免的会遇到一些特殊情况,造成正常的依赖解析方式无法满足需求。这一方面是由于第三方库本身的原因,如库可能修改了在 Maven 仓库中的组织名和名称,但是并没有修改其内部的 Java 包名;有的库可能把所依赖的其他库打包在自己的 jar 包中。这两种情况都会造成解析时出现重复名称的 Java 类。另外一方面是额外的依赖限制。比如某些库可能只兼容特定版本的其他库。这样的版本依赖关系需要显式声明。还有一个常见需求是限制库使用的最低版本。这些需求都可以通过 Gradle 脚本来实现。但是当有多个项目时,这些特殊的依赖解析规则会在不同的 Gradle 脚本中重复,并没有很好的方式来复用。</p> <p>Nebula 中的 nebula.resolution-rules 插件提供了一种更好的方式来管理和复用这些依赖解析规则。通过该插件可以把依赖解析规则记录在 JSON 文件中,从而可以更好的复用。可以为这些 JSON 文件创建专门的 Maven 项目并进行版本管理。公司和组织可以管理和维护自己的依赖解析规则。Netflix 自己维护一个公开的代码仓库来包含常见库的依赖解析规则。</p> <p>在中,通过 nebula.resolution-rules 插件的 resolutionRules 声明了两种依赖解析规则,第一种来自项目文件 local-rules.json,第二种来自 Netflix 提供的通用解析规则。</p> <p>清单 9. 通过 resolutionRules 声明依赖解析规则</p> <pre> <code class="language-groovy">resolutionRules files('local-rules.json') resolutionRules 'com.netflix.nebula:gradle-resolution-rules:latest.release'</code></pre> <p>给出了 local-rules.json 文件的内容,其中通过 deny 规则声明了不能使用 io.netty:netty-all 依赖,因为该包中有其所使用的其他依赖,很容易造成类名重复。</p> <p>清单 10. 依赖解析规则示例</p> <pre> <code class="language-groovy">{ "deny": [ { "module": "io.netty:netty-all", "reason": "不应该使用包含了其他依赖的库", "author" : "admin@example.org", "date" : "2016-08-04T20:21:20.368Z" } ] }</code></pre> <p>除了中给出的 deny 规则之外,nebula.resolution-rules 插件还支持其他不同的规则:</p> <ul> <li>replace:当两个依赖同时出现时,用其中一个替换掉另外一个。</li> <li>substitute:类似于 replace,不同的是只要旧的依赖出现,则替换成新的依赖。</li> <li>deny:当出现指定的依赖时,会使得 Gradle 构建失败。</li> <li>reject:指定的依赖不会出现在动态版本的计算过程中。但如果项目显式的包含这个依赖,则该依赖仍然会被加入。</li> <li>align:要求一组依赖的使用相同的版本。</li> </ul> <h2><strong>版本发布</strong></h2> <p>当需要发布一个项目的新版本时,通常需要执行一系列的动作,包括对代码仓库的处理,构建当前版本并发布到 Maven 仓库等。nebula-release-plugin 插件的作用是自动化执行这些操作。该插件使用符合语义版本号规则的版本,即 major.minor.patch-<prerelease>+<metadata>的格式。该插件提供了如下的任务:</p> <ul> <li>snapshot:发布的版本号为<major>.<minor>.<patch>-SNAPSHOT,如 1.0.0-SNAPSHOT。</li> <li>devSnapshot:发布的版本号为<major>.<minor>.<patch>-dev.#+<hash>,如 0.1.0-dev.1+b8dd0f3。该任务与 snapshot 的差别在于生成的版本号中包含当前 Git commit 的 hash。</li> <li>candidate:发布的版本号为<major>.<minor>.<patch>-rc.#,表示版本发布的候选,可能存在多个候选,如 1.0.0-rc.1,1.0.0-rc.2 等。该任务会创建与版本号相同的 Git 标签。</li> <li>final:发布的版本号为<major>.<minor>.<patch>,如 1.0.0。该任务会创建与版本号相同的 Git 标签。</li> </ul> <p>该插件会在当前的构建成功之后才进行相应的 Git 操作,适合于在持续集成服务器中运行。</p> <h2><strong>其他插件</strong></h2> <p>除了上述的插件之后,Nebula 还提供了其他有用的插件。</p> <p>gradle-aggregate-javadocs-plugin 插件用来把多个子项目的 javadoc 合并成单一的文档。这对于包含多个子项目的项目来说是非常实用的。在添加了该插件之后,可以通过 aggregateJavadocs 任务来生成合并之后的 javadoc。</p> <p>gradle-override-plugin 插件允许在命令行直接覆写项目构建中的属性值。有两种方式可以覆写属性值,一种是以"OVERRIDE_."开头的环境变量,另外一种是以"override."作为前缀的系统属性。比如在 Gradle 命令行可以通过"-Doverride.sampleProp=value"来覆写"sampleProp"的值为"value"。</p> <p>gradle-contacts-plugin 插件允许 Gradle 项目添加开发人员的相关信息。这些信息对于开源项目来说尤其重要。通过该插件可以声明开发人员的基本信息、联系方式和角色等。这些信息会被其他插件所使用,如出现在生成的 jar 包的清单文件中,所发布的 Maven 项目的 POM 文件中。</p> <p>gradle-metrics-plugin 插件用来收集构建过程中的各种数据并推送到数据存储中,以方便相关的数据分析。数据可以被推送到 Elasticsearch 或 Splunk 中。</p> <p>gradle-ospackage-plugin 插件用来生成可以在操作系统上直接运行的包,支持 RedHat 和 Debian。</p> <h2><strong>小结</strong></h2> <p>Gradle 作为流行的构建工具,已经被越来越多的项目所采用。在使用 Gradle 的过程中,会发现有些通用的任务需要在 Gradle 脚本中不断的重复。Nebula 的意义在于把这些通用的任务整理成单独的开源插件,使得可以被其他项目所复用。本文对 Nebula 所提供的依赖版本锁定、依赖版本推荐、依赖解析规则、版本发布和其他插件进行了详细的介绍,具体说明了这些插件在实际项目中的用法。在 Gradle 项目中使用这些插件可以极大的减少相关的工作量,并应用来自 Netflix 的最佳实践。</p> <h2><strong>参考资源 (resources)</strong></h2> <ul> <li>参考 Nebula 的 <a href="/misc/goto?guid=4958972604976753934" rel="nofollow,noindex">官方网站</a> ,了解 Nebula 的更多内容。</li> <li>查看 Nebula 的 <a href="/misc/goto?guid=4959725301049005159" rel="nofollow,noindex">快速入门文档</a> 。</li> <li>了解 Nebula 的版本锁定插件的 <a href="/misc/goto?guid=4959725301156668179" rel="nofollow,noindex">更多内容</a> 。</li> <li>了解 Nebula 的版本推荐插件的 <a href="/misc/goto?guid=4959725301240485343" rel="nofollow,noindex">更多内容</a> 。</li> <li>了解 Nebula 的依赖解析规则插件的 <a href="/misc/goto?guid=4959725301318597655" rel="nofollow,noindex">更多内容</a> 。</li> <li>了解 Nebula 的版本发布插件的 <a href="/misc/goto?guid=4959725301400607456" rel="nofollow,noindex">更多内容</a> 。</li> </ul> <p> </p> <p>来自:http://www.ibm.com/developerworks/cn/java/j-nebula-netflix-gradle/index.html?ca=drs-</p> <p> </p>