Skip to main content

Electron 内部机制:将 Chromium 构建为库

· 16 min read

Electron 基于 Google 的开源 Chromium 项目,该项目的设计目的并非供其他项目使用。本文介绍了如何将 Chromium 构建为 Electron 使用的库,以及构建系统多年来的发展历程。

¥Electron is based on Google's open-source Chromium, a project that is not necessarily designed to be used by other projects. This post introduces how Chromium is built as a library for Electron's use, and how the build system has evolved over the years.


使用 CEF

¥Using CEF

Chromium 嵌入式框架 (CEF) 是一个将 Chromium 转变为库的项目,并基于 Chromium 的代码库提供稳定的 API。Atom 编辑器和 NW.js 的早期版本使用了 CEF。

¥The Chromium Embedded Framework (CEF) is a project that turns Chromium into a library, and provides stable APIs based on Chromium's codebase. Very early versions of Atom editor and NW.js used CEF.

为了维护稳定的 API,CEF 隐藏了 Chromium 的所有细节,并用自己的接口封装了 Chromium 的 API。因此,当我们需要访问底层 Chromium API(例如将 Node.js 集成到网页中)时,CEF 的优势就变成了阻碍。

¥To maintain a stable API, CEF hides all the details of Chromium and wraps Chromium's APIs with its own interface. So when we needed to access underlying Chromium APIs, like integrating Node.js into web pages, the advantages of CEF became blockers.

因此,最终 Electron 和 NW.js 都切换到直接使用 Chromium 的 API。

¥So in the end both Electron and NW.js switched to using Chromium's APIs directly.

作为 Chromium 的一部分构建

¥Building as part of Chromium

尽管 Chromium 官方不支持外部项目,但其代码库是模块化的,基于 Chromium 构建一个极简浏览器很容易。提供浏览器界面的核心模块称为内容模块。

¥Even though Chromium does not officially support outside projects, the codebase is modular and it is easy to build a minimal browser based on Chromium. The core module providing the browser interface is called Content Module.

要使用内容模块开发项目,最简单的方法是将项目构建为 Chromium 的一部分。此操作可以通过首先检出 Chromium 的源代码,然后将项目添加到 Chromium 的 DEPS 文件中来完成。

¥To develop a project with Content Module, the easiest way is to build the project as part of Chromium. This can be done by first checking out Chromium's source code, and then adding the project to Chromium's DEPS file.

NW.js 和 Electron 的早期版本都使用这种方式进行构建。

¥NW.js and very early versions of Electron are using this way for building.

缺点是,Chromium 的代码库非常庞大,需要非常强大的机器才能构建。对于普通注意本电脑来说,这可能需要 5 个多小时。因此,这极大地影响了能够为项目做出贡献的开发者数量,也降低了开发速度。

¥The downside is, Chromium is a very large codebase and requires very powerful machines to build. For normal laptops, that can take more than 5 hours. So this greatly impacts the number of developers that can contribute to the project, and it also makes development slower.

将 Chromium 构建为单个共享库

¥Building Chromium as a single shared library

作为内容模块的用户,Electron 在大多数情况下不需要修改 Chromium 的代码,因此改进 Electron 构建的一个显而易见的方法是将 Chromium 构建为共享库,然后在 Electron 中链接它。这样,开发者在为 Electron 做贡献时,就不再需要完全依赖 Chromium 进行构建。

¥As a user of Content Module, Electron does not need to modify Chromium's code under most cases, so an obvious way to improve the building of Electron is to build Chromium as a shared library, and then link with it in Electron. In this way developers no longer need to build all off Chromium when contributing to Electron.

libchromiumcontent 项目是由 @aroben 为此创建的。它将 Chromium 的内容模块构建为共享库,然后提供 Chromium 的头文件和预构建的二进制文件供下载。libchromiumcontent 初始版本的代码可在 在此链接中 找到。

¥The libchromiumcontent project was created by @aroben for this purpose. It builds the Content Module of Chromium as a shared library, and then provides Chromium's headers and prebuilt binaries for download. The code of the initial version of libchromiumcontent can be found in this link.

brightray 项目也是 libchromiumcontent 的一部分,libchromiumcontent 围绕内容模块提供了一个薄层。

¥The brightray project was also born as part of libchromiumcontent, which provides a thin layer around Content Module.

通过结合使用 libchromiumcontent 和 brightray,开发者可以快速构建浏览器,而无需深入了解 Chromium 的构建细节。它消除了构建项目对快速网络和强大机器的要求。

¥By using libchromiumcontent and brightray together, developers can quickly build a browser without getting into the details of building Chromium. And it removes the requirement of a fast network and powerful machine for building the project.

除了 Electron 之外,还有其他基于 Chromium 的项目以这种方式构建,例如 浏览器漏洞

¥Apart from Electron, there were also other Chromium-based projects built in this way, like the Breach browser.

过滤导出的符号

¥Filtering exported symbols

在 Windows 上,一个共享库可以导出的符号数量存在限制。随着 Chromium 代码库的增长,libchromiumcontent 中导出的符号数量很快就超出了限制。

¥On Windows there is a limitation of how many symbols one shared library can export. As the codebase of Chromium grew, the number of symbols exported in libchromiumcontent soon exceeded the limitation.

解决方案是在生成 DLL 文件时过滤掉不需要的符号。它在 向链接器提供 .def 文件 版本中运行,然后使用脚本升级到 判断命名空间下的符号是否应该导出 版本。

¥The solution was to filter out unneeded symbols when generating the DLL file. It worked by providing a .def file to the linker, and then using a script to judge whether symbols under a namespace should be exported.

通过采用这种方法,尽管 Chromium 不断添加新的导出符号,但 libchromiumcontent 仍然可以通过剥离更多符号来生成共享库文件。

¥By taking this approach, though Chromium kept adding new exported symbols, libchromiumcontent could still generate shared library files by stripping more symbols.

组件构建

¥Component build

在讨论 libchromiumcontent 的后续步骤之前,首先介绍一下 Chromium 中的组件构建概念。

¥Before talking about the next steps taken in libchromiumcontent, it is important to introduce the concept of component build in Chromium first.

作为一个庞大的项目,在 Chromium 中构建时,链接步骤会花费很长时间。通常,开发者进行小改动后,可能需要 10 分钟才能看到最终输出。为了解决这个问题,Chromium 引入了组件构建功能,它将 Chromium 中的每个模块构建为独立的共享库,因此最终链接步骤所花费的时间变得微不足道。

¥As a huge project, the linking step takes very long in Chromium when building. Normally when a developer makes a small change, it can take 10 minutes to see the final output. To solve this, Chromium introduced component build, which builds each module in Chromium as separated shared libraries, so the time spent in the final linking step becomes unnoticeable.

发送原始二进制文件

¥Shipping raw binaries

随着 Chromium 的不断发展,Chromium 中导出的符号非常多,甚至内容模块和 Webkit 的符号也超出了限制。仅仅通过剥离符号是不可能生成可用的共享库的。

¥With Chromium continuing to grow, there were so many exported symbols in Chromium that even the symbols of Content Module and Webkit were more than the limitation. It was impossible to generate a usable shared library by simply stripping symbols.

最终,我们不得不使用 发布 Chromium 原始二进制文件,而不是生成单个共享库。

¥In the end, we had to ship the raw binaries of Chromium instead of generating a single shared library.

如前所述,Chromium 有两种构建模式。由于需要发布原始二进制文件,我们必须在 libchromiumcontent 中发布两个不同的二进制发行版。一个版本称为 static_library 构建,它包含 Chromium 常规构建生成的每个模块的所有静态库。另一种方法是 shared_library,它包含组件构建生成的每个模块的所有共享库。

¥As introduced earlier there are two build modes in Chromium. As a result of shipping raw binaries, we have to ship two different distributions of binaries in libchromiumcontent. One is called static_library build, which includes all static libraries of each module generated by the normal build of Chromium. The other is shared_library, which includes all shared libraries of each module generated by the component build.

在 Electron 中,调试版本与 libchromiumcontent 的 shared_library 版本链接,因为它下载量小,并且链接最终可执行文件所需的时间很短。Electron 的 Release 版本与 libchromiumcontent 的 static_library 版本链接,因此编译器可以生成对调试至关重要的完整符号,并且链接器可以进行更好的优化,因为它知道哪些目标文件是必需的,哪些不是。

¥In Electron, the Debug version is linked with the shared_library version of libchromiumcontent, because it is small to download and takes little time when linking the final executable. And the Release version of Electron is linked with the static_library version of libchromiumcontent, so the compiler can generate full symbols which are important for debugging, and the linker can do much better optimization since it knows which object files are needed and which are not.

因此,对于常规开发,开发者只需构建调试版本,这不需要良好的网络或强大的设备。虽然发布版本需要更好的硬件来构建,但它可以生成更优化的二进制文件。

¥So for normal development, developers only need to build the Debug version, which does not require a good network or powerful machine. Though the Release version then requires much better hardware to build, it can generate better optimized binaries.

gn 更新

¥The gn update

作为世界上最大的项目之一,大多数常规系统并不适合构建 Chromium,因此 Chromium 团队开发了自己的构建工具。

¥Being one of the largest projects in the world, most normal systems are not suitable for building Chromium, and the Chromium team develops their own build tools.

早期版本的 Chromium 使用 gyp 作为构建系统,但它速度慢,而且其配置文件对于复杂项目来说难以理解。经过多年的开发,Chromium 已改用 gn 作为构建系统,它速度更快,架构更清晰。

¥Earlier versions of Chromium were using gyp as a build system, but it suffers from being slow, and its configuration file becomes hard to understand for complex projects. After years of development, Chromium switched to gn as a build system, which is much faster and has a clear architecture.

gn 的一项改进是引入了 source_set,它代表一组目标文件。在 gyp 中,每个模块都由 static_libraryshared_library 表示;对于 Chromium 的正常构建,每个模块都会生成一个静态库,并在最终的可执行文件中将它们链接在一起。通过使用 gn,每个模块现在只生成一堆目标文件,最终的可执行文件只需将所有目标文件链接在一起,因此不再生成中间静态库文件。

¥One of the improvements of gn is to introduce source_set, which represents a group of object files. In gyp, each module was represented by either static_library or shared_library, and for the normal build of Chromium, each module generated a static library and they were linked together in the final executable. By using gn, each module now only generates a bunch of object files, and the final executable just links all the object files together, so the intermediate static library files are no longer generated.

然而,这项改进给 libchromiumcontent 带来了很大的麻烦,因为 libchromiumcontent 实际上需要中间静态库文件。

¥This improvement however made great trouble to libchromiumcontent, because the intermediate static library files were actually needed by libchromiumcontent.

解决这个问题的第一次尝试是 补丁 gn 以生成静态库文件,它解决了问题,但远非一个理想的解决方案。

¥The first try to solve this was to patch gn to generate static library files, which solved the problem, but was far from a decent solution.

第二次尝试是由 @alespergl从目标文件列表生成自定义静态库 进行的。它使用了一个技巧,首先运行一个虚拟构建来收集生成的目标文件列表,然后通过将列表提供给 gn 来实际构建静态库。它只对 Chromium 的源代码进行了微小的更改,并保留了 Electron 的构建架构。

¥The second try was made by @alespergl to produce custom static libraries from the list of object files. It used a trick to first run a dummy build to collect a list of generated object files, and then actually build the static libraries by feeding gn with the list. It only made minimal changes to Chromium's source code, and kept Electron's building architecture still.

概括

¥Summary

如你所见,与将 Electron 构建为 Chromium 的一部分相比,将 Chromium 构建为库需要付出更多努力,并且需要持续维护。但是后者消除了构建 Electron 对强大硬件的要求,从而使更多开发者能够构建 Electron 并为其做出贡献。这些努力完全值得。

¥As you can see, compared to building Electron as part of Chromium, building Chromium as a library takes greater efforts and requires continuous maintenance. However the latter removes the requirement of powerful hardware to build Electron, thus enabling a much larger range of developers to build and contribute to Electron. The effort is totally worth it.