使用 Ninja 代替 make

前言

在傳統(tǒng)的 C/C++ 等項(xiàng)目構(gòu)建時(shí),通常會(huì)采用 make 系統(tǒng)使用 Makefile 文件來(lái)進(jìn)行整個(gè)項(xiàng)目的編譯構(gòu)建,通過(guò) Makefile 中指定的編譯所依賴(lài)的規(guī)則使得程序的構(gòu)建非常簡(jiǎn)單,并且在復(fù)雜項(xiàng)目中可以避免由于少部分源碼修改而造成的很多不必要的重編譯。但是它仍然不夠好,因?yàn)槠浯蠖覐?fù)雜,有時(shí)候我們并不需要 make 那么強(qiáng)大的功能,相反我們需要更靈活,速度更快的編譯工具。Ninja 作為一個(gè)新型的編譯工具,小巧而又高效,它就是為此而生。

這篇文章介紹 Ninja 的安裝以及如何使用 Ninja 來(lái)構(gòu)建項(xiàng)目

首先,我們需要安裝 Ninja,只需要去官網(wǎng)下載一個(gè) release 的二進(jìn)制版本,放在系統(tǒng)目錄(比如 /usr/bin)中就可以了,非常的簡(jiǎn)單。另外,現(xiàn)在大多數(shù) Linux 發(fā)行版都有自己的包管理工具,直接使用包管理工具來(lái)下載也很簡(jiǎn)單。

下面簡(jiǎn)單介紹下通過(guò)編譯 Ninja 源碼的方式來(lái)安裝
首先,確保已經(jīng)安裝了這些依賴(lài):g++,graphviz,gtest,git,re2c 和 python2.7+。

獲取源碼
$ git clone git://github.com/ninja-build/ninja.git && cd ninja
$ git checkout release
$ cat README

$ ls
COPYING  HACKING.md  README  RELEASING  bootstrap.py  configure.py  doc/  misc/  src/

我們可以去 HACKING.md 中查看更多信息。

編譯

一切就緒之后,執(zhí)行下列命令來(lái)編譯 ninja

$ ./configure.py --bootstrap

上述命令會(huì)在當(dāng)前目錄下生成一個(gè)叫 ninja (Windows 下是 ninja.exe)的可執(zhí)行文件,然后我們把這個(gè)文件拷到系統(tǒng)目錄(比如 /usr/bin)就完成安裝了。

編譯過(guò)程解析

實(shí)際上 ninja 本身也是通過(guò) ninja 系統(tǒng)來(lái)編譯完成的。
具體過(guò)程就是:執(zhí)行 ./configure.py --bootstrap 之后先編譯源碼(生成一個(gè) a.out),然后在當(dāng)前目錄生成一個(gè) ninja.build(這個(gè)文件類(lèi)似于 make 工具的 Makefile,語(yǔ)法和規(guī)則非常類(lèi)似)。然后再根據(jù)這個(gè) ninja.build 來(lái)重新編譯生成可執(zhí)行文件 ninja,在 ninja 根據(jù) ninja.build 來(lái)編譯時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè) build 目錄用于存放編譯過(guò)程中的臨時(shí)文件,比如 *.o 等。

執(zhí)行./configure.py 時(shí)還可以指定其他選項(xiàng):

--bootstrap    bootstrap a ninja binary from nothing
--verbose    enable verbose build
--platform     choose known platforms
--host       choose host known_platforms
--debug      enable debugging extras
--profile      enable profiling
--with-gtest
--with-python   use EXE as the Python interpreter
--force-pselect   ppoll() is used by default where available

可以通過(guò) ./configure.py -h 可以查看更多幫助。
如果我們想要開(kāi)啟 Ninja 的其他特性(比如:Bash completion, Emacs 和 Vim 編輯模式等),編譯完成之后,我們需要把 /misc 目錄中的文件拷貝到合適的位置。

測(cè)試

現(xiàn)在,我們可以測(cè)試一下 ninja 是否成功安裝并且可以使用。

當(dāng)直接執(zhí)行 ninja 命令是,它會(huì)在當(dāng)前目錄下默認(rèn)尋找 build.ninja 文件來(lái)進(jìn)行編譯。
ninja 的語(yǔ)法格式是:

$ ninja [options] TARGETs

上述 options 如果沒(méi)有則可以省略。比如,直接執(zhí)行 ./ninja ninja_test 將會(huì)生成可執(zhí)行文件 ninja_test,然后再執(zhí)行 ninja_test 就可以看到測(cè)試結(jié)果。

如下:

$ ./ninja ninja_test
$ ./ninja_test
[214/226] SubprocessTest.SetWithLotsRaise [ulimit -n] above 1025 (currently 1024) to make this test go
[226/226] ElideMiddle.ElideInTheMiddle
passed

或者,我們還可以直接執(zhí)行 ./ninja all,這樣,ninja 就會(huì)執(zhí)行 ninja.build 中指定的所有目標(biāo)了。

$ ./ninja all
[10/10] LINK canon_perftest

上述 ninja_test 和 all 都是 ninja.build 中的 build rule,概念類(lèi)似于 Makefile 中的 target recipe。

測(cè)試完成之后,我們就把 ninja 拷貝到一個(gè)系統(tǒng)目錄中 /usr/bin 來(lái)完成整個(gè)的安裝。

提示: build.ninja 文件類(lèi)似于 Makefile,熟悉它的語(yǔ)法規(guī)則之后我們也可以手動(dòng)編寫(xiě)。另外,可以通過(guò) ninja -f NINJA_FILE 的方式來(lái)指定 .ninja 文件

更多選項(xiàng)

實(shí)際上, ninja 還提供了一個(gè) Python based generator ,它實(shí)際上是一個(gè) Python 模塊 misc/ninja_syntax.py,通過(guò)它我們可以較方便的生成 build.ninja 文件。比如,在我們的 Python 文件中引入該模塊之后,就可以直接通過(guò)調(diào)用 ninja.rule(name='foo', command='bar', depfile='$out.d') 來(lái)生成符合 ninja 語(yǔ)法的內(nèi)容。下面是一個(gè)簡(jiǎn)單例子:

from ninja_syntax import Writer

with open("build.ninja", "w") as buildfile:
    n = Writer(buildfile)

    if platform.is_msvc():
        n.rule("link",
                command="$cxx $in $libs /nologo /link $ldflags /out:$out",
                description="LINK $out")
    else:
        n.rule("link",
                command="$cxx $ldflags -o $out $in $libs",
                description="LINK $out")

另外,我們還可以在執(zhí)行 cmake 時(shí)通過(guò) -G 選項(xiàng)指定生成器為 ninja 來(lái)生成 build.ninja。
比如:

$ cd build 
$ cmake -GNinja ../proj_src_dir

Ninja 工具集

Ninja 還集成了 graphviz 等一些對(duì)開(kāi)發(fā)非常有用的工具,通過(guò)執(zhí)行 ./ninja -t list 可以查看 ninja 中集成了哪些工具。

下面是一個(gè)常見(jiàn)的工具集列表:

ninja subtools:

browse        # 在瀏覽器中瀏覽依賴(lài)關(guān)系圖。(默認(rèn)會(huì)在 8080 端口啟動(dòng)一個(gè)基于python的http服務(wù))
clean         # 清除構(gòu)建生成的文件
commands      # 羅列重新構(gòu)建制定目標(biāo)所需的所有命令
deps          # 顯示存儲(chǔ)在deps日志中的依賴(lài)關(guān)系
graph         # 為指定目標(biāo)生成 graphviz dot 文件。
                如 ninja -t graph all |dot -Tpng -ograph.png
query         # 顯示一個(gè)路徑的inputs/outputs
targets       # 通過(guò)DAG中rule或depth羅列target
compdb        # dump JSON兼容的數(shù)據(jù)庫(kù)到標(biāo)準(zhǔn)輸出
recompact     # 重新緊湊化ninja內(nèi)部數(shù)據(jù)結(jié)構(gòu)

手動(dòng)編寫(xiě) .ninja 文件

.ninja 的語(yǔ)法規(guī)則跟 Makefile 類(lèi)似,雖然有許多 generator 工具 可以用來(lái)自動(dòng)生成 .ninja 文件,但是在某些場(chǎng)合可能需要手動(dòng)編寫(xiě)或修改 .ninja 文件,下面做個(gè)簡(jiǎn)單介紹:

# VARIABLE: (referenced like $name or alternate ${name})
cflag = -g -Wall -Werror

# RULE:
rule RULE_NAME
    command = gcc $cflags -c $in -o $out
    description = ${out} will be treat as "$out"

# BUILD statement:
build TARGET_NAME: RULE_NAME INPUTS

# PHONE rule:(creating alias)
build ALIAS: phony INPUTS ...

# DEFAULT target statement(cumulative):
default TARGET1 TARGET2
default TARGET3

    $ ninja
    build TARGET1 TARGET2 TARGET3

例子: build.ninja

ninja_required_version = 1.3

#variable
cc = g++
cflags = -Wall

# rule
rule cc
    command = gcc $cflags -c $in -o $out
    description = compile .cc

# build
build foo.o: cc foo.c

.ninja_log 可以用來(lái)指定保存 build 時(shí)產(chǎn)生的 log。

子模塊 和 include 指令

subninja 指令可以用來(lái)引入其他 .ninja 文件,從而引入一個(gè)新的 scope。這意味著,子模塊中可以引用父模塊中的變量。
比如:

subninja obj/content/content_resources.ninja
subninja obj/extensions/extensions_strings.ninja
subninja obj/third_party/expat/expat_nacl.ninja

include 指令也是用來(lái)引入其他 .ninja 文件,但是不同的是,引入的其他 .ninja 文件會(huì)被引入當(dāng)前 scope,子模塊中不可以訪(fǎng)問(wèn)父模塊中的變量。

include obj/content/content_resource.ninja
include obj/extensions/extensions_strings.ninja
include obj/third_party/expat/expat_nacl.ninja

參考
https://github.com/ninja-build/ninja
https://ninja-build.org/manual.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容