GNを使ってV8を組み込む

Google V8は、depot_toolsの中のGNというツールを使ってビルドされています。GNはgenerate ninjaの略です。ニンジャナンデ！！！！

ninjaは並列ビルドシステムで、Make代替のものです。

GNはninja向けのファイルを生成するメタビルドシステムで、特徴としては、クロスプラットフォームやカスタマイズなどを行えるという点です。ninjaがMake代替だとすれば、GNはGNU autotools代替と言えるでしょう。

V8をビルドして（静的もしくは動的な）ライブラリを生成して、それをリンクして組み込むだけならGNを意識する必要はありませんが、せっかくのクロスプラットフォームな仕組みなので、そこに乗っかってみるという記事です。

## 前提

この記事では [direnv](https://direnv.net/) を前提としてしています。特定ディレクトリ以下でのPATH設定に使っているだけなので、PATH設定さえ別の手段で行うなら、direnv は無くてもかまいません。

Macで動作確認をしています。LinuxやWindowsで行う場合、他に手順が必要なケースがあるかもしれません。

## depot_toolsをインストールする

まずはtoybox-v8というディレクトリを作成し、その中にdepot_toolsをインストールします。ディストリビューションなどによってはdepot_toolsはパッケージ化されているようです。

```shell
mkdir toybox-v8
cd toybox-v8
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo "export PATH=`pwd`/depot_tools:$PATH" > .envrc
direnv allow .
```

最後の二行はパス設定のためのものです。depot_toolsはあまり汎用的とは言いがたいエコシステムなため、筆者はこのようにしています。

## 必要なファイルを用意する

```shell
fetch v8
```

もしもGNを使わずに手動でV8をリンクしたいということであれば、あとは、[Getting started with embedding V8](https://v8.dev/docs/embed)の手順に従うといいでしょう。

今回はhello_world.ccを使います。まず今回のディレクトリを整備します。

```shell
mkdir hello-world
cd hello-world
```

GNでV8を組み込む場合、とても多くの下準備が必要になります。

```shell
ln -s ../depot_tools .
ln -s ../v8 .
ln -s v8/build .
ln -s v8/build_overrides .
ln -s v8/buildtools .
ln -s v8/testing .

mkdir third_party
(cd third_party ; ln -s ../v8/third_party/googletest .)
(cd third_party ; ln -s ../v8/third_party/jinja2 .)
(cd third_party ; ln -s ../v8/third_party/llvm-build .)
(cd third_party ; ln -s ../v8/third_party/markupsafe .)

mkdir tools
(cd tools ; ln -s ../v8/tools/clang .)

cp v8/samples/hello-world.cc .
```

ここまでで toybox-v8の中身はこのようになっているはずです。

```
├── depot_tools
├── hello-world
│   ├── build -> v8/build
│   ├── build_overrides -> v8/build_overrides
│   ├── buildtools -> v8/buildtools
│   ├── depot_tools -> ../depot_tools
│   ├── hello-world.cc
│   ├── testing -> v8/testing
│   ├── third_party
│   │   ├── googletest -> ../v8/third_party/googletest
│   │   ├── jinja2 -> ../v8/third_party/jinja2
│   │   ├── llvm-build -> ../v8/third_party/llvm-build
│   │   └── markupsafe -> ../v8/third_party/markupsafe
│   ├── tools
│   │   └── clang -> ../v8/tools/clang
│   └── v8 -> ../v8
└── v8
```

## GNファイルを作成する

GNファイルを2つ用意する必要があります。`.gn` というファイルと `BUILD.gn` です。

### `.gn`

このファイルはV8のビルド情報を組み込んでビルド設定（引数）を定義するのが主目的です。

```gn
buildconfig = "//build/config/BUILDCONFIG.gn"

check_targets = []

default_args = {
  cc_wrapper = "ccache"
  v8_enable_i18n_support = false
  clang_use_chrome_plugins = false
  v8_monolithic = true
  is_component_build = false
  v8_use_external_startup_data = false
  v8_experimental_extra_library_files = []
  v8_extra_library_files = []
}
```

ここでは`cc_wrapper`として [ccache](https://ccache.samba.org/) というC/C++コンパイル結果をキャッシュするラッパーを使っています。V8のビルドでは、数百〜千のファイルをコンパイルする必要があるためキャッシュがあれば圧倒的に楽になります。

無い場合や使いたくない場合は、`cc_wrapper = "ccache"`の行を削りましょう。

### `BUILD.gn`

BUILD.gnはhello-worldをmakeするためのファイルです。

```gn
import("//v8/gni/v8.gni")

group("default") {
  deps = [
    ":hello_world",
  ]
}

v8_executable("hello_world") {
  include_dirs =["v8"]
  sources = [
    "hello-world.cc",
  ]

  configs = [ "v8:external_config" ]

  deps = [
    "//v8:v8_monolith",
  ]
}
```

## 実際にビルドする

```shell
gn gen out
ninja -C out
```

最初の`gn gen out`で、outというディレクトリの下にninjaコマンドで必要なファイルをすべて生成します。

`ninja -C out`で、実際にビルドを行います。

成功すれば、out/hello_worldというバイナリが生成されているはずです。

```shell
$ ./out/hello_world
Hello, World!
3 + 4 = 7
```

ちなみに1行目はJavaScriptのコードで、2行目の3+4という計算はWeb Assemblyで行われています。