Saltar al contenido principal

Internos de Electron: Compilar Chromium como una Biblioteca

· 7 lectura mínima

Electron se basa en el código abierto de Chromium en Google, un proyecto que no es necesariamente diseñado para ser utilizado por otros proyectos. Esta publicación presenta como se compila Chromium como una biblioteca para uso de Electron, y como el sistema de compilación ha evolucionado a lo largo de los años.


Usar CEF

El Chromium Embedded Framework (CEF) es un proyecto que convierte a Chromium en una biblioteca, y proporciona las API estables basadas en el código base de Chromium. Las versiones muy iniciales del editor Atom y NW.js usaron CEF.

Para mantener una API estable, CEF oculta todos los detalles de Chromium y envuelve las API de Chromium con su propia interfaz. Por tanto cuando necesitamos acceder bajo las API de Chromium, como integrar Node.js en páginas web, las ventajas de CEF se vuelven bloqueantes.

Así que al final tanto Electron como NW.js cambiaron a usar directamente las API de Chromium.

Compilar como parte de Chromium

Incluso aunque Chromium oficialmente mantiene proyectos externos, el código base es modular y es fácil compilar un explorador mínimo basado en Chromium. El módulo del núcleo que proporciona la interfaz del navegador se llama Content Module.

Para desarrollar un proyecto con el Content Module, la forma más fácil es compilar el proyecto como parte de Chromium. Esto primero se puede hacer revisando el código fuente de Chromium, y luego agregando el proyecto al archivo DEPS de Chromium.

NW.js y versiones previamente de Electron están utilizando esto de esta manera para la compilación.

La desventaja es que Chromium es una base de código muy grande y requiere máquinas muy potentes para compilar. Para portátiles normales, esto puede tomar más de 5 horas. Así que esto impacta enormemente en el número de desarrolladores que puedan contribuir al proyecto, y también hace que el desarrollo sea más lento.

Compilar Chromium como una biblioteca compartida única

Como un usuario de Content Module, Electron no necesita modificar el código de Chromium en muchos casos, por lo que una manera obvia de mejorar la compilación de Electron es compilar Chromium como una biblioteca compartida, y entonces enlace con ésta en Electron. De esta manera los desarrolladores no necesitan más compilar todo desde el principio de Chromium cuando contribuya en Electron.

El proyecto libchromiumcontent fue creado por @aroben para este propósito. Compila el Contenido del Módulo de Chromium como una biblioteca compartida, y luego proporciona los encabezados de Chromium y binarios precompilados para descargar. Construye el Módulo de Contenido de Chromium como una biblioteca compartida, y luego proporciona cabeceras de Chromium y binarios preconstruidos para su descarga.

El proyecto brightray también nació como parte de libchromiumcontent, que proporciona una capa fina alrededor del Módulo de Contenido.

Al usar juntos libchromiumcontent y brightray, los desarrolladores pueden compilar rápidamente un navegador sin entrar en los detalles de compilar Chromium. Y elimina el requisito de una red rápida y una máquina potente para compilar el proyecto.

Aparte de Electron, también hubo otros proyectos basados en Chromium construidos de esta forma , como el navegador Breach.

Filtrando símbolos exportados

En Windows hay una limitación de cuantos símbolos de biblioteca compartida puede exportar. A medida que creció el código base de Chromium, el número de símbolos exportados en libchromiumcontent pronto excedió la limitación.

La solución fue filtrar fuera los símbolos innecesarios al generar el archivo DLL. Funcionó al proporcionar un archivo .def al enlazador, y luego usar un script a juzgar si los símbolos bajo un espacio de nombres serían exportados.

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

Component build

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

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

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.

Al final, tuvimos que enviar los binarios crudos de Chromium en lugar de generar una sola biblioteca compartida.

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.

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.

The gn update

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.

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.

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.

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

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.

El segundo intento fue realizado por @alespergl a produce librerías estáticas personalizadas de la lista de archivos de objetos. 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.

Resumen

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.