Electronの内部構造: Chromiumをライブラリとしてビルドする
Electronは、GoogleのオープンソースであるChromiumをベースにしています。Chromiumは、必ずしも他のプロジェクトで使用されるように設計されたものではありません。この記事では、ChromiumがElectronで使用するためのライブラリとしてどのようにビルドされているか、また、ビルドシステムが長年にわたってどのように進化してきたかを紹介します。
CEFの使用
Chromium Embedded Framework(CEF)は、Chromiumをライブラリに変え、Chromiumのコードベースに基づいて安定したAPIを提供するプロジェクトです。AtomエディターとNW.jsの初期バージョンでは、CEFが使用されていました。
安定したAPIを維持するために、CEFはChromiumの詳細をすべて隠し、ChromiumのAPIを独自のインターフェースでラップします。そのため、Node.jsをWebページに統合するなど、基盤となるChromium APIにアクセスする必要がある場合、CEFの利点がブロッカーになりました。
そのため、最終的にElectronとNW.jsの両方が、ChromiumのAPIを直接使用するように切り替えました。
Chromiumの一部としてビルドする
Chromiumは公式には外部プロジェクトをサポートしていませんが、コードベースはモジュール式であり、Chromiumをベースにした最小限のブラウザーを簡単にビルドできます。ブラウザーインターフェースを提供するコアモジュールは、コンテンツモジュールと呼ばれます。
コンテンツモジュールでプロジェクトを開発する最も簡単な方法は、Chromiumの一部としてプロジェクトをビルドすることです。これは、最初にChromiumのソースコードをチェックアウトし、次にプロジェクトをChromiumのDEPS
ファイルに追加することで実行できます。
NW.jsと初期バージョンのElectronは、この方法でビルドしていました。
欠点は、Chromiumが非常に大きなコードベースであり、ビルドには非常に強力なマシンが必要になることです。通常のラップトップの場合、5時間以上かかることがあります。そのため、プロジェクトに貢献できる開発者の数が大幅に減少し、開発も遅くなります。
Chromiumを単一の共有ライブラリとしてビルドする
コンテンツモジュールのユーザーとして、Electronはほとんどの場合Chromiumのコードを変更する必要がないため、Electronのビルドを改善する明らかな方法は、Chromiumを共有ライブラリとしてビルドし、Electronでリンクすることです。これにより、開発者はElectronに貢献する際にChromium全体をビルドする必要がなくなります。
libchromiumcontentプロジェクトは、この目的のために@arobenによって作成されました。これは、Chromiumのコンテンツモジュールを共有ライブラリとしてビルドし、Chromiumのヘッダーとプリビルドされたバイナリをダウンロード用に提供します。libchromiumcontentの初期バージョンのコードは、このリンクにあります。
brightrayプロジェクトもlibchromiumcontentの一部として生まれ、コンテンツモジュールの周りに薄いレイヤーを提供します。
libchromiumcontentとbrightrayを一緒に使用することで、開発者はChromiumをビルドする詳細に立ち入ることなく、ブラウザーをすばやくビルドできます。また、プロジェクトをビルドするための高速なネットワークと強力なマシンの要件がなくなります。
Electronとは別に、Breachブラウザーのように、この方法でビルドされた他のChromiumベースのプロジェクトもありました。
エクスポートされたシンボルのフィルタリング
Windowsでは、1つの共有ライブラリがエクスポートできるシンボルの数に制限があります。Chromiumのコードベースが大きくなるにつれて、libchromiumcontentでエクスポートされるシンボルの数がすぐに制限を超えました。
解決策は、DLLファイルを生成するときに不要なシンボルをフィルタリングすることでした。これは、リンカーに.def
ファイルを提供し、次にスクリプトを使用して、名前空間の下のシンボルをエクスポートする必要があるかどうかを判断することで機能しました。
このアプローチを取ることで、Chromiumはエクスポートされた新しいシンボルを追加し続けましたが、libchromiumcontentはより多くのシンボルを削除することで共有ライブラリファイルを生成できました。
コンポーネントビルド
libchromiumcontentで実行された次のステップについて説明する前に、最初にChromiumのコンポーネントビルドの概念を紹介することが重要です。
巨大なプロジェクトとして、Chromiumをビルドするとき、リンキングステップには非常に時間がかかります。通常、開発者が小さな変更を加えた場合、最終出力が表示されるまでに10分かかることがあります。これを解決するために、Chromiumはコンポーネントビルドを導入しました。これは、Chromiumの各モジュールを分離された共有ライブラリとしてビルドするため、最終的なリンキングステップに費やされる時間が目立たなくなります。
生のバイナリの配布
Chromiumが成長を続けるにつれて、Chromiumには非常に多くのエクスポートされたシンボルがあり、コンテンツモジュールとWebkitのシンボルでさえ制限を超えていました。シンボルを単に削除するだけでは、使用可能な共有ライブラリを生成することは不可能でした。
最終的に、単一の共有ライブラリを生成する代わりに、Chromiumの生のバイナリを配布する必要がありました。
先述の通り、Chromiumには2つのビルドモードがあります。生のバイナリを配布する必要があるため、libchromiumcontentでは2種類のバイナリ配布物を提供する必要があります。1つはstatic_library
ビルドと呼ばれ、Chromiumの通常のビルドで生成される各モジュールのすべての静的ライブラリを含みます。もう1つはshared_library
と呼ばれ、コンポーネントビルドで生成される各モジュールのすべての共有ライブラリを含みます。
Electronでは、Debugバージョンはlibchromiumcontentのshared_library
バージョンとリンクされます。これは、ダウンロードサイズが小さく、最終的な実行ファイルをリンクする際に時間がかからないためです。一方、ElectronのReleaseバージョンはlibchromiumcontentのstatic_library
バージョンとリンクされます。これにより、コンパイラはデバッグに重要な完全なシンボルを生成でき、リンカはどのオブジェクトファイルが必要でどれが不要かを知っているため、より良い最適化を行うことができます。
したがって、通常の開発では、開発者はDebugバージョンのみをビルドする必要があります。これは、良好なネットワークや強力なマシンを必要としません。一方、Releaseバージョンはビルドに高性能なハードウェアが必要ですが、より最適化されたバイナリを生成できます。
gn
のアップデート
世界最大級のプロジェクトの1つであるChromiumは、ほとんどの通常のシステムではビルドに適しておらず、Chromiumチームは独自のビルドツールを開発しています。
初期バージョンのChromiumでは、ビルドシステムとしてgyp
を使用していましたが、速度が遅く、複雑なプロジェクトでは構成ファイルが理解しにくいという問題がありました。長年の開発を経て、Chromiumはビルドシステムとしてgn
に切り替えました。これははるかに高速で、明確なアーキテクチャを持っています。
gn
の改善点の1つは、オブジェクトファイルのグループを表すsource_set
を導入したことです。gyp
では、各モジュールはstatic_library
またはshared_library
のいずれかで表され、Chromiumの通常のビルドでは、各モジュールは静的ライブラリを生成し、それらは最終的な実行ファイルにリンクされていました。gn
を使用することで、各モジュールはオブジェクトファイルの束のみを生成するようになり、最終的な実行ファイルはすべてのオブジェクトファイルをリンクするだけになりました。そのため、中間的な静的ライブラリファイルは生成されなくなりました。
しかし、この改善はlibchromiumcontentに大きな問題を引き起こしました。中間的な静的ライブラリファイルは、実際にはlibchromiumcontentに必要だったからです。
この問題を解決するための最初の試みは、静的ライブラリファイルを生成するようにgn
にパッチを適用することでした。これにより問題は解決されましたが、適切な解決策とは言えませんでした。
2番目の試みは、@alesperglによって、オブジェクトファイルのリストからカスタムの静的ライブラリを作成することで行われました。これは、まずダミービルドを実行して生成されたオブジェクトファイルのリストを収集し、そのリストをgn
に渡して実際に静的ライブラリをビルドするというトリックを使用しました。Chromiumのソースコードへの変更は最小限に抑えられ、Electronのビルドアーキテクチャは維持されました。
まとめ
ご覧のとおり、Chromiumの一部としてElectronをビルドするのと比較して、Chromiumをライブラリとしてビルドするにはより多くの労力が必要であり、継続的なメンテナンスが必要です。しかし、後者はElectronをビルドするための高性能なハードウェアの要件をなくし、より多くの開発者がElectronをビルドして貢献できるようにします。この労力は十分に価値があるものです。