Nebula: Netflix 開源的 Gradle 插件集合

fbuy4059 8年前發布 | 33K 次閱讀 Gradle Java 開源 項目構建

Gradle 作為 Apache Maven 的有力競爭者,在 Java 項目的構建領域逐漸流行起來。很多開源項目,如 Spring 框架、Hibernate、Elasticsearch 和 RxJava 等都使用 Gradle 進行構建。Gradle 也是 Android Studio 中 Android 項目的標準構建方式。越來越多的開發人員開始使用 Gradle 構建自己的 Java 項目。在開始使用 Gradle 時經常會面臨的一個問題是從何處開始。Maven 中可以使用 Archetype 來作為項目的模板,Gradle 并沒有提供類似的機制。本文要介紹的 Nebula 是由 Netflix 開發的 Gradle 項目構建框架,其目的是為 Gradle 項目提供一個良好的起點,把一些常見的任務添加到構建過程中,從而簡化 Gradle 項目的構建配置。

基本配置

本文通過一個基于 Spring Boot 的 Java Web 示例應用來介紹 Nebula 的使用。Nebula 的核心是一系列由 Netflix 開發和維護的 Gradle 插件。這些插件覆蓋 Gradle 項目構建的不同階段,提供不同的功能。給出了使用 Nebula 的插件的項目的 Gradle 腳本。Nebula 的插件都發布到 Gradle 插件倉庫中,因此需要在腳本中添加插件倉庫地址"https://plugins.gradle.org/m2/"。要使用 Nebula 的插件,只需要在腳本中的 buildscript 中添加對相應插件的依賴,再通過 apply plugin 來應用插件。在應用了插件之后,可以在腳本中進行相應的配置,并通過 Gradle 命令行來運行相關的任務。中給出了示例的 Gradle 腳本。

清單 1. 使用 Nebula 的 Gradle 腳本

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')
}

下面對 Nebula 提供的常用插件進行具體的介紹。

依賴版本鎖定

在進行項目構建時一個很重要的要求是構建的可重復性。也就是說,代碼倉庫中任意時刻的代碼都應該是可重復構建的。只有這樣才可以保證代碼的穩定性和質量。不論是最近的代碼,還是幾個星期之前、幾個月之前甚至是幾年之前的代碼,都應該滿足這樣的條件。

可重復構建所面臨的挑戰之一來自于項目所依賴的第三方庫。隨著項目的演化,這些第三方庫的版本可能升級。之前的項目版本也許只能與特定版本的第三方庫協同工作。Gradle 項目直接在 Gradle 文件中聲明所依賴的第三方庫的版本。除了直接聲明的第三方庫版本之外,有些依賴是通過傳遞關系引入的。這些傳遞依賴的版本是不受應用本身控制的,而由所依賴的庫自己來管理。因此第三方庫自身的依賴的版本更新,也可能造成應用的構建失敗。當項目的傳遞依賴關系很復雜時,很可能會出現傳遞依賴沖突的情況。

Nebula 提供的 nebula.dependency-lock 插件的作用是生成一個包含了全部依賴的具體版本的鎖定文件。這個文件由代碼倉庫進行管理。當這個文件存在時,該插件會確保 Gradle 只會使用正確版本的依賴。實際上,使用過 Ruby 中的 Gem 管理工具 Bundler 的開發人員會發現,這種版本鎖定功能與 Bundler 生成的 Gemfile.lock 是一樣的。當每次版本發布時,在構建成功之后,應該通過該插件生成鎖定文件,并提交到代碼倉庫。

nebula.dependency-lock 插件支持兩類不同的鎖定文件,分別是項目鎖定文件和全局鎖定文件。當 Gradle 項目中包含多個子項目時,每個子項目可以有自己的鎖定文件。當全局鎖定文件存在時,子項目中的鎖定文件不起作用。子項目鎖定文件的名稱默認為 dependencies.lock,全局鎖定文件的名稱默認為 global.lock。插件提供的任務如所示。

表 1. nebula.dependency-lock 插件提供的任務

任務 描述
generateLock / generateGlobalLock 生成鎖定文件。generateLock 生成子項目的鎖定文件,
generateGlobalLock 生成全局鎖定文件。鎖定文件生成在項目的 build 目錄中。
updateLock / updateGlobalLock 更新子項目/全局鎖定文件。
saveLock / saveGlobalLock 把生成的鎖定文件復制到項目目錄中。
deleteLock / deleteGlobalLock 刪除子項目/全局鎖定文件。
commitLock 把鎖定文件提交到代碼倉庫。

該插件提供了一些額外的參數來對任務的行為進行配置。比如,在 updateLock 時可以通過 dependencyLock.updateDependencies 來指定需要更新的依賴的名稱。

依賴版本推薦

在 Gradle 項目中添加第三方依賴時都需要指定版本號。在 Gradle 腳本文件中直接引用版本號可能造成依賴版本升級時的維護困難。一般的做法是把版本號提取到項目屬性中,從而可以在統一的地方管理所有依賴的版本信息。當項目較多時,這樣的管理方式也會變得很繁瑣。因為有些通用的庫會在多個項目中使用,而當需要升級這些通用庫的版本時,會需要修改多個項目的 Gradle 文件。另外一個常見的需求是解決多個依賴庫的版本兼容問題。有些第三方庫,如 Spring 框架,包含很多個子項目,當引用這些依賴時,需要確保這些依賴的版本一致,否則可能出現兼容性問題。

這些與依賴的版本號相關的問題,都可以通過 Nebula 提供的依賴推薦插件來解決。在使用了依賴推薦插件之后,沒有聲明版本的第三方依賴的版本號由插件來決定。

依賴推薦插件支持五種方式來聲明所推薦的依賴的版本。第一種方式是通過 Maven BOM 文件。Maven 的 BOM 文件中直接定義了依賴的版本信息。比如,Spring Boot 項目就提供了相應的 BOM 文件,可以作為創建 Spring Boot 項目的父 POM 文件。Maven BOM 文件中通過 dependencyManagement 來指定不同依賴的版本號。中給出了作為示例的 Maven BOM 文件。

清單 2. 示例 Maven BOM 文件

<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>

當 Maven BOM 文件發布到 Maven 倉庫之后,可以作為依賴推薦插件的規則來源,如所示。

清單 3. 使用 Maven BOM 文件指定推薦版本

dependencyRecommendations {
  mavenBom module: 'sample:sample-bom:1.0'
}

第二種方式是通過屬性文件來指定版本。屬性文件中的鍵是依賴的全名,值是對應的版本號,如所示。

清單 4. 使用屬性文件指定推薦版本

dependencyRecommendations {
  propertiesFile file: 'recommendations.properties'
}

第三種方式是通過由 dependency-lock 插件生成的依賴版本鎖定文件來指定版本號,如所示。該鎖定文件在生成之后,通常會被提交到代碼倉庫中。項目可以直接使用鎖定文件來推薦版本號。

清單 5. 使用依賴版本鎖定文件推薦版本

dependencyRecommendations {
  dependencyLock module: 'sample:dependencies:1.0'
}

第四種方式是在 Gradle 文件中直接使用 java.util.Map 接口對象來提供推薦的版本號,如所示。

清單 6. 使用 Map 接口對象推薦版本

dependencyRecommendations {
  map recommendations: [
  'com.google.guava:guava': '18.0',
  'org.slf4j:slf4j-api': '1.7.21'
  ]
}

最后一種方式是通過完全自定義的代碼來聲明推薦的版本號。在 Gradle 腳本中通過 add 方法來添加規則。add 方法需要根據依賴的組織名和名稱,返回其對應的推薦版本號。比如可以把推薦的版本號保存在數據庫之中,然后在 add 方法中進行數據庫查詢并返回版本號。在中,對所有的依賴都返回推薦的版本號 1.0。

清單 7. 使用 add 方法推薦版本

dependencyRecommendations {
  add { org, name -> '1.0' }
}

在使用了版本推薦插件之后,Gradle 對依賴版本的選擇過程發生了變化。優先級最高的是強制應用的依賴版本號,其次是顯式指定了版本號的普通依賴,接著是通過插件推薦的依賴版本號,最后則是由直接依賴引入的傳遞依賴。當傳遞依賴的版本號與插件推薦的版本號發生沖突時,可以應用不同的沖突解決策略。默認的策略是 ConflictResolved,即通過 Gradle 自己的機制來選擇合適的版本號,即優先考慮傳遞依賴中的版本號,再考慮插件所推薦的版本號;另外一種策略是 OverrideTransitives,即選擇插件推薦的版本號,而完全忽略傳遞依賴中的版本號。中給出了使用 OverrideTransitives 策略的示例。

清單 8. 使用 OverrideTransitives 策略

dependencyRecommendations {
 strategy OverrideTransitives
 map recommendations: ['commons-logging:commons-logging': '1.0']
}

依賴解析規則

Gradle 本身已經提供了強大的依賴解析功能,可以滿足各種特殊的依賴解析需求。在使用第三方提供的庫時,不可避免的會遇到一些特殊情況,造成正常的依賴解析方式無法滿足需求。這一方面是由于第三方庫本身的原因,如庫可能修改了在 Maven 倉庫中的組織名和名稱,但是并沒有修改其內部的 Java 包名;有的庫可能把所依賴的其他庫打包在自己的 jar 包中。這兩種情況都會造成解析時出現重復名稱的 Java 類。另外一方面是額外的依賴限制。比如某些庫可能只兼容特定版本的其他庫。這樣的版本依賴關系需要顯式聲明。還有一個常見需求是限制庫使用的最低版本。這些需求都可以通過 Gradle 腳本來實現。但是當有多個項目時,這些特殊的依賴解析規則會在不同的 Gradle 腳本中重復,并沒有很好的方式來復用。

Nebula 中的 nebula.resolution-rules 插件提供了一種更好的方式來管理和復用這些依賴解析規則。通過該插件可以把依賴解析規則記錄在 JSON 文件中,從而可以更好的復用。可以為這些 JSON 文件創建專門的 Maven 項目并進行版本管理。公司和組織可以管理和維護自己的依賴解析規則。Netflix 自己維護一個公開的代碼倉庫來包含常見庫的依賴解析規則。

在中,通過 nebula.resolution-rules 插件的 resolutionRules 聲明了兩種依賴解析規則,第一種來自項目文件 local-rules.json,第二種來自 Netflix 提供的通用解析規則。

清單 9. 通過 resolutionRules 聲明依賴解析規則

resolutionRules files('local-rules.json')
resolutionRules 'com.netflix.nebula:gradle-resolution-rules:latest.release'

給出了 local-rules.json 文件的內容,其中通過 deny 規則聲明了不能使用 io.netty:netty-all 依賴,因為該包中有其所使用的其他依賴,很容易造成類名重復。

清單 10. 依賴解析規則示例

{
 "deny": [
    {
       "module": "io.netty:netty-all",
       "reason": "不應該使用包含了其他依賴的庫",
       "author" : "admin@example.org",
       "date" : "2016-08-04T20:21:20.368Z"
    }
 ]
}

除了中給出的 deny 規則之外,nebula.resolution-rules 插件還支持其他不同的規則:

  • replace:當兩個依賴同時出現時,用其中一個替換掉另外一個。
  • substitute:類似于 replace,不同的是只要舊的依賴出現,則替換成新的依賴。
  • deny:當出現指定的依賴時,會使得 Gradle 構建失敗。
  • reject:指定的依賴不會出現在動態版本的計算過程中。但如果項目顯式的包含這個依賴,則該依賴仍然會被加入。
  • align:要求一組依賴的使用相同的版本。

版本發布

當需要發布一個項目的新版本時,通常需要執行一系列的動作,包括對代碼倉庫的處理,構建當前版本并發布到 Maven 倉庫等。nebula-release-plugin 插件的作用是自動化執行這些操作。該插件使用符合語義版本號規則的版本,即 major.minor.patch-<prerelease>+<metadata>的格式。該插件提供了如下的任務:

  • snapshot:發布的版本號為<major>.<minor>.<patch>-SNAPSHOT,如 1.0.0-SNAPSHOT。
  • devSnapshot:發布的版本號為<major>.<minor>.<patch>-dev.#+<hash>,如 0.1.0-dev.1+b8dd0f3。該任務與 snapshot 的差別在于生成的版本號中包含當前 Git commit 的 hash。
  • candidate:發布的版本號為<major>.<minor>.<patch>-rc.#,表示版本發布的候選,可能存在多個候選,如 1.0.0-rc.1,1.0.0-rc.2 等。該任務會創建與版本號相同的 Git 標簽。
  • final:發布的版本號為<major>.<minor>.<patch>,如 1.0.0。該任務會創建與版本號相同的 Git 標簽。

該插件會在當前的構建成功之后才進行相應的 Git 操作,適合于在持續集成服務器中運行。

其他插件

除了上述的插件之后,Nebula 還提供了其他有用的插件。

gradle-aggregate-javadocs-plugin 插件用來把多個子項目的 javadoc 合并成單一的文檔。這對于包含多個子項目的項目來說是非常實用的。在添加了該插件之后,可以通過 aggregateJavadocs 任務來生成合并之后的 javadoc。

gradle-override-plugin 插件允許在命令行直接覆寫項目構建中的屬性值。有兩種方式可以覆寫屬性值,一種是以"OVERRIDE_."開頭的環境變量,另外一種是以"override."作為前綴的系統屬性。比如在 Gradle 命令行可以通過"-Doverride.sampleProp=value"來覆寫"sampleProp"的值為"value"。

gradle-contacts-plugin 插件允許 Gradle 項目添加開發人員的相關信息。這些信息對于開源項目來說尤其重要。通過該插件可以聲明開發人員的基本信息、聯系方式和角色等。這些信息會被其他插件所使用,如出現在生成的 jar 包的清單文件中,所發布的 Maven 項目的 POM 文件中。

gradle-metrics-plugin 插件用來收集構建過程中的各種數據并推送到數據存儲中,以方便相關的數據分析。數據可以被推送到 Elasticsearch 或 Splunk 中。

gradle-ospackage-plugin 插件用來生成可以在操作系統上直接運行的包,支持 RedHat 和 Debian。

小結

Gradle 作為流行的構建工具,已經被越來越多的項目所采用。在使用 Gradle 的過程中,會發現有些通用的任務需要在 Gradle 腳本中不斷的重復。Nebula 的意義在于把這些通用的任務整理成單獨的開源插件,使得可以被其他項目所復用。本文對 Nebula 所提供的依賴版本鎖定、依賴版本推薦、依賴解析規則、版本發布和其他插件進行了詳細的介紹,具體說明了這些插件在實際項目中的用法。在 Gradle 項目中使用這些插件可以極大的減少相關的工作量,并應用來自 Netflix 的最佳實踐。

參考資源 (resources)

 

來自:http://www.ibm.com/developerworks/cn/java/j-nebula-netflix-gradle/index.html?ca=drs-

 

 本文由用戶 fbuy4059 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!