扒一扒鴻蒙中的Gn與Ninja

鴻蒙系統的編譯構建是基於 Gn 和 Ninja 完成的,那麼 Gn 和 Ninjia 有什麼關係呢?具體又是如何工作的呢?

扒一扒鴻蒙中的Gn與Ninja

想必大多數熱衷於應用開發的同學都還沒有深究過,那麼今天就藉此機會帶著大家扒一扒 Gn 和 Ninja。

我們先來說說 Ninja 吧!

Ninja 是藉由 Google Chrome 專案而誕生的一個構建工具,它的誕生目標是為了速度。

換句話說,在 Google Chrome 專案的開發過程中,開發者們認為同類型的其它構建工具不給力,所以才會考慮重新開發更高效的工具。

要說同類型,那麼不得不提構建界的老大哥 make !make 即 GNU Make,一個用於決定如何使用命令完成最終目標構建的程式。

在這裡強調 make 的 3 個特性:

make 只是一個通用程式,它不知道如何具體的完成目標的構建工作。

make 需要 makefile 中的描述來決定目標構建的具體方案。

make 需要藉助其它工具(如:gcc)才能執行方案,最終完成工作。

扒一扒鴻蒙中的Gn與Ninja

這是不是跑題了!不是說好的討論 Ninja 嗎?怎麼扯到 make 上去了?!

因為 Ninja 可以看作是一個更好的 make !而大多數同學都熟悉 make ,所以透過對比 make 學習 Ninja 是一個非常好的選擇!

上述關於 make 的 3 個特性對於 Ninjia 同樣適用(理論上,make 有的 Ninjia 都有,並且更好)。

那麼,是不是得先學習 make 再學習 Ninja 呢?我覺得倒也不是!畢竟我們最終還是在鴻蒙上做應用開發,編譯構建系統只需要大體瞭解即可。

接下來透過一個簡單的例子向大家展示 Ninja 的用法!

test。c 是一個簡單的 Hello World 程式,用於列印一個字串和標頭檔案 test。h 中常量 CONST 的值。

扒一扒鴻蒙中的Gn與Ninja

根據 C 程式的編譯方式可知:

在預處理階段 test。h 中的程式碼直接嵌入test。c 中(標頭檔案 。h 最終成為原始檔 。c 的一部分)。

test。c 編譯後得到目標檔案 test。o。

test。o 連結後得到最終的可執行程式 test。out。

各個檔案在編譯過程中有明顯的上下游關係,即:上游檔案影響或者產生下游檔案。

扒一扒鴻蒙中的Gn與Ninja

上圖即描述了編譯過程,同時也反映了這樣一個事實:任何一個檔案被改動時只可能影響下游檔案,而不會影響上游檔案。

如:test。c 被修改了,那麼可能導致編譯得到 test。o 發生改變,進而導致最終的可執行程式 test。out 改變。因此,當 test。c 被修改時,那麼應該重新觸發編譯和連結這兩個動作。

看到這裡,有同學可能存在這樣的疑問:

怎麼知道檔案已經被修改了並觸發相應動作呢?

其實很簡單,可以根據檔案修改時間判斷呀!目前幾乎主流的檔案系統都會記錄檔案被修改的時間,所以結合檔案的上下游關係可知:上游檔案被修改的時間應該總是 小於等於 下游檔案被修改的時間。

這樣,只需要遍歷一次上面的構建圖就可以知道執行哪些動作產生最終可執行程式了。

扒一扒鴻蒙中的Gn與Ninja

接下來思考這樣一個問題:如何向構建工具 Ninja 描述構建圖?

Ninja 的本質是一種通用程式。既然是程式,那麼擅長的必然是處理結構化文字!因此,可以用結構化文字(Ninja 指令碼)來描述構建圖。

下面直接上程式碼:

扒一扒鴻蒙中的Gn與Ninja

解讀:

Ninja 指令碼中的 build 語句描述構建圖中的一個檔案上下游關係。

如:build test。o cc test。c 指明 test。o 由 test。c 透過規則 cc 而構建,test。c 在構建圖中位於 test。o 的上游,從 test。c 到 test。o 需要執行的動作透過規則 cc 定義。

Ninja 透過判斷上下游檔案的修改時間決定是否執行規則中定義的動作。多個 build 語句共同描述一個編譯構建圖。

Ninja 指令碼中透過 rule 定義規則描述構建圖中需要執行的動作。

如:規則 cc 所定義的具體動作是 gcc -c $in -o $out ,其中 $in 指代上游檔案, $out 指代下游檔案。

對於 build test。o cc test。c 而言,最終執行的動作為:gcc -c test。c -o test。o 。

由 C 語言及其編譯方式可知:當原始檔包含的標頭檔案改動時,原始檔需要重新編譯。

因此,在構建圖中標頭檔案順理成章的成為了原始檔的上游檔案,需要考慮的僅僅是如何定義 rule 最終觸發編譯動作。

這裡使用的技巧是透過命令 touh 更新原始檔的修改時間,於是可定義 rule dp 的執行動作為 touch $out。

這樣 build test。c : dp test。h 的意思就很清楚了:當 test。h 被修改時,執行 touch test。c 更新修改時間,進而觸發重新編譯。

default test。out 指明預設構建的目標是 test。out,即:ninja 執行當前指令碼時預設編譯構建的是 test。out。

理解了 Ninja 指令碼的基本構成後就可以透過實驗進一步體會了!

1.

將上面的指令碼另存為檔案,並重命名為 build。ninja,且與 test。c 和 test。h 位於同一目錄下。

扒一扒鴻蒙中的Gn與Ninja

2.

開啟命令列定位到原始碼目錄,執行 ninja > log。txt。

扒一扒鴻蒙中的Gn與Ninja

透過編譯輸出(log。txt)以及 test。out 的執行結果可知目標構建成功。

後記:

這只是一個 Ninja 的入門級介紹,更多的細節大家可以參考附件中的手冊。

同時,文中的示例程式碼也可以在附件中下載。大家可以自己動手修改原始碼(比如:修改 test。h 中 CONST 的值)然後自行編譯體會 Ninja 的用法。

作者:

唐佐林

原文連結:

https://mp。weixin。qq。com/s/dyXDGBEH18sL5cW7bA-orQ