為了更好的閱讀本文,請(qǐng)先學(xué)習(xí)有關(guān)maven的生命周期、phase、goal相關(guān)知識(shí)??梢撇轿业倪@篇文章:
【精】講透Maven-- Build Lifecycle, Phases和 Goals
好的,進(jìn)入正題:今天我們來(lái)學(xué)一下自定義maven插件。
在hadoop的源碼中,有一個(gè)module是hadoop-maven-plugins,如下圖:

這個(gè)模塊主要功能是實(shí)現(xiàn)了一個(gè)自定義的maven插件,用來(lái)幫助執(zhí)行cmake,編譯native的代碼并打包成靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)。
因此本文的目標(biāo)有兩點(diǎn):
① 熟悉hadoop打包native庫(kù)的整體過(guò)程
② 從①的過(guò)程中學(xué)習(xí)如果自定義一個(gè)maven plugin
一、Hadoop打包native庫(kù)的過(guò)程
我們?nèi)绻枰幾ghadoop的native本地庫(kù)時(shí),會(huì)執(zhí)行如下命令:
mvn clean install -Pdist,native -DskipTests -Dmaven.javadoc.skip
注意在-P后面指定了native這個(gè)profile。
因此我們找到了hadoop-common這個(gè)module下的pom.xml里的native profile,如下圖所示:

接下來(lái)我們就拆解這個(gè)profile標(biāo)簽里的內(nèi)容。

profile標(biāo)簽里的build表示了要對(duì)此profile進(jìn)行構(gòu)建。
build標(biāo)簽里面內(nèi)部包含了一些plugin,表示執(zhí)行此build過(guò)程需要用到的插件。
每個(gè)plugin標(biāo)簽里面會(huì)有多個(gè)execution,每個(gè)execution里面的phase標(biāo)簽和goals標(biāo)簽定義了這個(gè)插件的此goal需要綁定到build過(guò)程的哪個(gè)phase,以及一些配置項(xiàng)。
Ok,了解完大概的pom文件標(biāo)簽結(jié)構(gòu)后,我們來(lái)看下插件,注意到有個(gè)插件名字叫:hadoop-maven-plugins。這個(gè)插件是hadoop自己為了打包動(dòng)態(tài)鏈接庫(kù)而實(shí)現(xiàn)的一個(gè)自定義maven插件。
我們?cè)诘诙戮蛠?lái)學(xué)一下這個(gè)插件,然后介紹一下如何自定義maven插件。
二、hadoop-maven-plugins && 自定義Maven Plugin
我先把hadoop-common項(xiàng)目的pom.xml種關(guān)于使用hadoop-maven-plugins的代碼貼出來(lái),后續(xù)分析的時(shí)候可以回看做對(duì)比。
<plugin>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-maven-plugins</artifactId>
<executions>
<execution>
<id>cmake-compile</id>
<phase>compile</phase>
<goals><goal>cmake-compile</goal></goals>
<configuration>
<source>${basedir}/src</source>
<vars>
<GENERATED_JAVAH>${project.build.directory}/native/javah</GENERATED_JAVAH>
<JVM_ARCH_DATA_MODEL>${sun.arch.data.model}</JVM_ARCH_DATA_MODEL>
<REQUIRE_BZIP2>${require.bzip2}</REQUIRE_BZIP2>
<REQUIRE_SNAPPY>${require.snappy}</REQUIRE_SNAPPY>
<REQUIRE_ZSTD>${require.zstd}</REQUIRE_ZSTD>
<CUSTOM_SNAPPY_PREFIX>${snappy.prefix}</CUSTOM_SNAPPY_PREFIX>
<CUSTOM_SNAPPY_LIB>${snappy.lib} </CUSTOM_SNAPPY_LIB>
<CUSTOM_SNAPPY_INCLUDE>${snappy.include} </CUSTOM_SNAPPY_INCLUDE>
<CUSTOM_ZSTD_PREFIX>${zstd.prefix}</CUSTOM_ZSTD_PREFIX>
<CUSTOM_ZSTD_LIB>${zstd.lib} </CUSTOM_ZSTD_LIB>
<CUSTOM_ZSTD_INCLUDE>${zstd.include} </CUSTOM_ZSTD_INCLUDE>
<REQUIRE_ISAL>${require.isal} </REQUIRE_ISAL>
<CUSTOM_ISAL_PREFIX>${isal.prefix} </CUSTOM_ISAL_PREFIX>
<CUSTOM_ISAL_LIB>${isal.lib} </CUSTOM_ISAL_LIB>
<REQUIRE_OPENSSL>${require.openssl} </REQUIRE_OPENSSL>
<CUSTOM_OPENSSL_PREFIX>${openssl.prefix} </CUSTOM_OPENSSL_PREFIX>
<CUSTOM_OPENSSL_LIB>${openssl.lib} </CUSTOM_OPENSSL_LIB>
<CUSTOM_OPENSSL_INCLUDE>${openssl.include} </CUSTOM_OPENSSL_INCLUDE>
<EXTRA_LIBHADOOP_RPATH>${extra.libhadoop.rpath}</EXTRA_LIBHADOOP_RPATH>
</vars>
</configuration>
</execution>
<execution>
<id>test_bulk_crc32</id>
<goals><goal>cmake-test</goal></goals>
<phase>test</phase>
<configuration>
<binary>${project.build.directory}/native/test_bulk_crc32</binary>
<timeout>1200</timeout>
<results>${project.build.directory}/native-results</results>
</configuration>
</execution>
<execution>
<id>erasure_code_test</id>
<goals><goal>cmake-test</goal></goals>
<phase>test</phase>
<configuration>
<binary>${project.build.directory}/native/erasure_code_test</binary>
<timeout>300</timeout>
<results>${project.build.directory}/native-results</results>
<skipIfMissing>true</skipIfMissing>
<env>
<LD_LIBRARY_PATH>${LD_LIBRARY_PATH}:${isal.lib}:${isal.prefix}:/usr/lib</LD_LIBRARY_PATH>
</env>
</configuration>
</execution>
</executions>
</plugin>
maven官網(wǎng)中有關(guān)于開(kāi)發(fā)自定義插件的詳細(xì)介紹,真的非常詳細(xì),網(wǎng)址如下:
https://maven.apache.org/guides/plugin/guide-java-plugin-development.html
它會(huì)帶你自己寫(xiě)一個(gè)your first plugin,幾分鐘就搞定,可以嘗試下。
這里我來(lái)介紹一下編寫(xiě)Maven插件的基本步驟,然后再去看hadoop的實(shí)現(xiàn)。
- 創(chuàng)建 Maven 項(xiàng)目。插件的功能肯定需要編寫(xiě) Java 類(lèi)的,所以插件本身就是一個(gè) Maven 項(xiàng)目。當(dāng)然,相對(duì)于以前研究的 Maven 項(xiàng)目,插件項(xiàng)目有它的特殊點(diǎn):packaging 必須是 maven-plugin 類(lèi)型,可以通過(guò) maven-archetype-plugin 快速創(chuàng)建一個(gè) Maven 插件項(xiàng)目。
- 編寫(xiě)插件目標(biāo)。每個(gè)插件都至少包含一個(gè)goal,每個(gè)goal對(duì)應(yīng)一個(gè)獨(dú)立的 Java 類(lèi)。這里把這種類(lèi)叫 Mojo 類(lèi)(對(duì)象)。Mojo 類(lèi)必須繼承 AbstractMojo 父類(lèi)。
- 設(shè)置目標(biāo)的配置點(diǎn)。大部分 Maven 件和它的目標(biāo)都是可以配置的。根據(jù)需要,可以在編寫(xiě) Mojo 的時(shí)候給它設(shè)置好可以配置的參數(shù)。
- 編寫(xiě)邏輯代碼,實(shí)現(xiàn)目標(biāo)功能。用 Java 代碼實(shí)現(xiàn)插件的功能。
- 處理錯(cuò)誤和日志。當(dāng) Mojo 運(yùn)行的時(shí)候發(fā)生異常時(shí),需要根據(jù)情況控制 Maven 的運(yùn)行狀況,并且用代碼實(shí)現(xiàn)必要的日志輸出,為用戶提供必要的提示信息。
- 測(cè)試插件。編寫(xiě)測(cè)試案例,綁定(或命令行)執(zhí)行插件。
好,我們直接來(lái)看Hadoop怎么實(shí)現(xiàn)的。
我們用了cmake-compile這個(gè)goal(可參見(jiàn)上面的pom.xml代碼)。
<goals><goal>cmake-compile</goal></goals>
所以找到CompileMojo類(lèi):

簡(jiǎn)單的幾個(gè)注解就搞定。
Map類(lèi)型的變量也支持,例如:

在pom.xml里就對(duì)應(yīng)這些值:

這是如何傳遞個(gè)性化參數(shù)。那執(zhí)行插件的入口在哪呢? 答案是execute方法。自定義的插件所有的phase都要實(shí)現(xiàn)Mojo接口,Mojo接口里有execute方法,是插件的執(zhí)行入口。如下圖所示:

具體到hadoop的CompileMojo類(lèi)的實(shí)現(xiàn)如下:

runCMake方法如下:

當(dāng)然少不了CMakeList.txt文件(編譯C++工程需要用到的,這個(gè)需要手動(dòng)編寫(xiě))

了解了maven如何自定插件之后,我們以后就能自己開(kāi)發(fā)滿足自己項(xiàng)目需求的maven插件了,或者給開(kāi)源的maven插件提merge request啦!