天天看點

Nebula: Netflix 開源的 Gradle 插件集合

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 項目建構的不同階段,提供不同的功能。代碼清單 1 給出了使用 Nebula 的插件的項目的 Gradle 腳本。Nebula 的插件都釋出到 Gradle 插件倉庫中,是以需要在腳本中添加插件倉庫位址"https://plugins.gradle.org/m2/"。要使用 Nebula 的插件,隻需要在腳本中的 buildscript 中添加對相應插件的依賴,再通過 apply plugin 來應用插件。在應用了插件之後,可以在腳本中進行相應的配置,并通過 Gradle 指令行來運作相關的任務。代碼清單 1 中給出了示例的 Gradle 腳本。

清單 1. 使用 Nebula 的 Gradle 腳本

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

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 所示。

表 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 來指定不同依賴的版本号。代碼清單 2 中給出了作為示例的 Maven BOM 檔案。

清單 2. 示例 Maven BOM 檔案

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<

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 所示。

清單 3. 使用 Maven BOM 檔案指定推薦版本

1

2

3

dependencyRecommendations {

mavenBom module: 'sample:sample-bom:1.0'

}

第二種方式是通過屬性檔案來指定版本。屬性檔案中的鍵是依賴的全名,值是對應的版本号,如代碼清單 4 所示。

清單 4. 使用屬性檔案指定推薦版本

1

2

3

dependencyRecommendations {

propertiesFile file: 'recommendations.properties'

}

第三種方式是通過由 dependency-lock 插件生成的依賴版本鎖定檔案來指定版本号,如代碼清單 5 所示。該鎖定檔案在生成之後,通常會被送出到代碼倉庫中。項目可以直接使用鎖定檔案來推薦版本号。

清單 5. 使用依賴版本鎖定檔案推薦版本

1

2

3

dependencyRecommendations {

dependencyLock module: 'sample:dependencies:1.0'

}

第四種方式是在 Gradle 檔案中直接使用 java.util.Map 接口對象來提供推薦的版本号,如代碼清單 6 所示。

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

1

2

3

4

5

6

dependencyRecommendations {

map recommendations: [

'com.google.guava:guava': '18.0',

'org.slf4j:slf4j-api': '1.7.21'

]

}

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

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

1

2

3

dependencyRecommendations {

add { org, name -> '1.0' }

}

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

清單 8. 使用 OverrideTransitives 政策

1

2

3

4

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 自己維護一個公開的代碼倉庫來包含常見庫的依賴解析規則。

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

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

1

2

resolutionRules files('local-rules.json')

resolutionRules 'com.netflix.nebula:gradle-resolution-rules:latest.release'

代碼清單 10 給出了 local-rules.json 檔案的内容,其中通過 deny 規則聲明了不能使用 io.netty:netty-all 依賴,因為該包中有其所使用的其他依賴,很容易造成類名重複。

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

1

2

3

4

5

6

7

8

9

10

{

"deny": [

{

"module": "io.netty:netty-all",

"reason": "不應該使用包含了其他依賴的庫",

"author" : "[email protected]",

"date" : "2016-08-04T20:21:20.368Z"

}

]

}

除了代碼清單 10 中給出的 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)

  • 參考 Nebula 的官方網站,了解 Nebula 的更多内容。
  • 檢視 Nebula 的快速入門文檔。
  • 了解 Nebula 的版本鎖定插件的更多内容。
  • 了解 Nebula 的版本推薦插件的更多内容。
  • 了解 Nebula 的依賴解析規則插件的更多内容。
  • 了解 Nebula 的版本釋出插件的更多内容。

繼續閱讀