Linux kernelをninjaでビルドする

ninjaってなに

Ninja, a small build system with a focus on speed

make代替みたいなビルドシステムで, 小さくてスピードが速いのが特徴となっている. Chromiumとかで使われている様子.

Makefileを読めるわけではなく, ninja用のビルドファイルが必要.

kninja

GitHub - rabinv/kninja: Ninja build file generator for the Linux kernel

kninjaはLinux kernel用にkninjaのビルドファイルを生成してくれるスクリプト.

はやいの?

kninjaによるルール生成にはkninja.pyを使う. ルール生成のためにmakeしてから, “make -p"の情報からビルドファイルを生成する. したがって, 下のようにルール生成まで入れると1分ほどkninjaの方が遅くなる.

フルビルドの場合

$ make clean
$ time make -j$(nproc)
real    4m21.090s
user    41m19.146s
sys     2m48.689s
$ make clean
$ time python3.4 ~naota/src/kninja/kninja.py
[INFO] Ensuring full build: make -j 12
…
[INFO] Generating make database: make -p
[INFO] Caching make database to .makedb
[INFO] Parsing make database (2563191 lines)
[INFO] Wrote build.ninja (3282 rules, 3255 build statements)
[INFO] Wrote .ninja_deps (2597 targets, 1144879 deps)
[INFO] Wrote .ninja_log (3282 commands)
[INFO] Checking ninja status: ninja -d explain -n
[INFO] All OK

real    5m15.829s
user    40m58.248s
sys     2m59.674s

ビルド済

$ time make -j$(nproc)
real    0m5.883s
user    0m24.015s
sys     0m9.905s
$ time python3.4 ~naota/src/kninja/kninja.py
real    1m4.807s
user    1m12.236s
sys     0m23.291s

ただ, これはルール生成をしているから遅いのであって, 一度ルールが作られてしまえばninjaの方が速い. たとえば, なんの変更もない時は以下のようにmakeだと6秒に対して, ninjaは1秒かからない. (ただこれはCHKとかCALLとかでビルドで関係ないチェックスクリプトが走っているのもあるか)

$ time make -j$(nproc)
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
  CHK     include/generated/timeconst.h
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  CHK     kernel/config_data.h
…
real    0m6.005s
user    0m24.038s
sys     0m9.827s
$ time ninja
ninja: no work to do.

real    0m0.204s
user    0m0.152s
sys     0m0.052s

1つのファイルを変更した場合は, makeで7秒, ninjaで3秒といったところ.

$ touch fs/btrfs/ctree.c; time make -j$(nproc)
…
real    0m7.129s
user    0m27.334s
sys     0m10.295s
$ touch fs/btrfs/ctree.c; time ninja
[3/3] ld -r -m elf_x86_64 -T ./scripts/module-common.lds --build-id -o fs/btrfs/btrfs.ko fs/btrfs/btrfs.o fs/btrfs/btrfs.mod.o ; true

real    0m2.922s
user    0m2.755s
sys     0m0.222s

速くはなるので, inotifywaitでいじっているファイルを監視して, ninjaを走らせるループなどしておくとべんり. (while inotifywait fs/btrfs/*.c; do ninja ;done とか)

ただ.configやKconfigやらがupdateされるたびにルールを作り直す必要があるため, 全くLinux kernelを開発しませーん, ビルドしてインストールするだけです〜という人にはいらないツールになる.

compile DBを生成する

Emacsだとirony-mode, Atomだとlinter-clangやautocomplete-clangのように, clangを使ってCのsyntax checkや補完をしてくれるパッケージがある.

これらを動かすにあたって, include pathやらdefineなどなどコンパイルオプションを適切に渡す必要がある. このへんのツールは"compile_commands.json"に書いてある, コンパイルオプションを読んでくれる.

ninjaには, このjsonファイルをdumpする機能がある. ninja -t compdbで以降の引数で指定されたルールのjsonをはきだすので下のようなコマンドを実行すると全ルールとれる.

$ ninja -t compdb $(awk '/^rule /{print $2}' build.ninja) > compile_commands.json

ironyはなんやらうまくやってくれてる感じあるけど, gccだけが対応していてclangが対応していないoptionがあるとAtomの子たちはだいたいうまく動かない. よって, こんな感じで適当にargを削ってあげるといい.

DELETE_ARGS=(
    '-falign-jumps=1'
    '-falign-loops=1'
    '-fconserve-stack'
    '-fno-delete-null-pointer-checks'
    '-fno-var-tracking-assignments'
    '-mfentry'
    '-mno-fp-ret-in-387'
    '-mpreferred-stack-boundary=3'
    '-mskip-rax-setup'
    '--param=allow-store-data-races=0'
    '-DCC_HAVE_ASM_GOTO'
    '-Wno-frame-address'
    '-Wno-unused-but-set-variable'
    '-Werror=designated-init'
)
script=""
for x in ${DELETE_ARGS[@]};do
    script="s/$x//;${script}"
done
sed -i -e "${script}" compile_commands.json

compdbを使えばheader情報とかも追加できる

GitHub - Sarcasm/compdb: The compilation database Swiss army knife

いろいろ入れてこんな感じか

gist.github.com

所感

なんだかんだEmacsのパッケージの方がCみたいな言語にはつよいね