For Developers‎ > ‎How-Tos‎ > ‎

Component build / Shared Library / Multi-DLL build

There is an option to build chrome using multiple shared libraries instead of the single library that we have on a regular build. This mode is intended to ease development by making less demands on the compiler and linker as they have to deal with smaller final output files, and allow faster build times when making small localized changes. Debug time can also improve given that the symbol files are substantially smaller.

This mode is also known as "Shared build" or "Multi-DLL build".

Setup

The build mode is selected by a GN build argument:
is_component_build = true
See GN build configuration for more how to configure your build directory.

Common Issues

My change breaks the shared build bot, but the other bots are happy; how do I fix it?

1. The most common reason for this to happen is modifying a project in a way that creates a new dependency but failing to declare that dependency in the build. The rule is actually quite simple: if project B uses code implemented by project A, there should be an explicit statement indicating that dependency.

For example, the following error means that the project libphonenumber is not declaring the dependency on dynamic_annotations:

libphonenumber.lib(phonenumberutil.obj) :error LNK2019: unresolved external symbol _AnnotateCondVarSignal referenced in function "private: static class i18n::phonenumbers::PhoneNumberUtil * __cdecl Singleton<class i18n::phonenumbers::PhoneNumberUtil,struct DefaultSingletonTraits<class i18n::phonenumbers::PhoneNumberUtil>,class i18n::phonenumbers::PhoneNumberUtil>::get(void)" (?get@?$Singleton@VPhoneNumberUtil@phonenumbers@i18n@@U?$DefaultSingletonTraits@VPhoneNumberUtil@phonenumbers@i18n@@@@V123@@@CAPAVPhoneNumberUtil@phonenumbers@i18n@@XZ)

Even if it doesn't looks like it, this error also means that  libphonenumber is not declaring the dependency on base:

base.lib(base.dll) :error LNK2005: "public: class std::basic_ostream<char,struct std::char_traits<char> > & __thiscall logging::LogMessage::stream(void)" (?stream@LogMessage@logging@@QAEAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@XZ) already defined in libphonenumber.lib(regexp_adapter_icuregexp.obj)

2. Another common error that affects Windows is neglecting to export symbols for a component with consumers in other libraries. Most modules define a set of *_EXPORT preprocessor macros used to decorate public API.

For example, see the base module's src/base/base_export.h and the use of BASE_EXPORT for exposed classes and functions in src/base/logging.h

BASE_EXPORT void SetMinLogLevel(int level);
...
class BASE_EXPORT LogMessage {
...

3. Switching from a "regular" build to a shared build basically invalidates any previously compiled code so a clean build is required.


What should I know about when working on Chrome's installer for Windows?

The Windows installer for Chrome gets tripped up by the component=shared_library build in many ways. Mostly everything is supported now, but you should generally use a non-component build build that more precisely reproduces behaviour of real Chrome installs.

Creating New Components

In order to build some existing part of the code into a separate dynamic library, the first thing to do is to make sure that there is a relatively clean set of dependencies and ideally a limited interface exposed to other parts of the code.

A common pattern for a viable target (teleporter) looks something like this:

src/base
src/crypto
src/teleporter/common
src/teleporter/ui
src/teleporter/power_converter
src/teleporter/laser_controller
src/teleporter/modulator
src/teleporter/demodulator
src/teleporter/integration_tests
src/third_party/scanner

Most likely, each of these directories generates a static library or GN source set, and the current final executable (chrome.dll) links against most of these libraries and the rest of the code. The important part about that is that more often that not, there are hidden dependencies that are masked by the current process, and the build works just by the fact that the linker has visibility over all included static libraries.

Let's assume for a second that teleporter/modulator depends on third_party/scanner. If there is some other piece of code linked into chrome.dll that also uses third_party/scanner, things may go wrong if we just have that static library linked with teleporter.dll and chrome.dll: if the library only performs computation, it may be fine to have some small code duplication, but if the library performs IO, we may end up with corruption due to two global objects in the same process that don't know about each other.

In other words, it is likely that before attempting to build teleoprter.dll, we need to have base, crypto and scanner, all building as a shared library.

The next thing to consider is how do we want to break up this project: we have at least 6 static libraries here, and it would probably be inconvenient to end up with 7 DLLs for the teleporter. It may make more sense to have a single resulting DLL, but then we have the problem of merging the current static targets into a single target (at the gyp level), something currently not supported by gyp, or we could simply eliminate most of the targets altogether by hand, from every build configuration, at the cost of ending up with large target descriptions and maybe too little granularity for project settings (warning silencing or something like that).

The final point to consider is how to export the actual interface for the module, and the interaction with tests.

We want to have unit tests for every piece of code that we write, and tests often end up touching internal classes or helpers that are not really intended to be used by consumers. We could also have a very simple "public" interface for the consumer, but a good number of implementation specific classes that we want to test. Once we have teleporter.dll, teleporter_unittests will only be able to see whatever code is explicitly exported from the library, and having a satatic version of the code to be linked against the different tests would end up almost doubling the size of the code that we compile.

The current solution is to have multiple definition of the export macros, so that at least someone reading the code will see that a given class or method is exported for the unit tests and not because it is intended to be used by consumers. See for example src/net/base/net_export.h

The last thing to do is to go through the code, annotating the public and testing API, building all the unit tests from this particular code, and finally moving into the consumers, making sure that all targets work correctly.
Comments