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 gyp during the project generation phase. There are a few ways to tell gyp to generate projects for a component build:

1. Add a file named chromium.gyp_env next to your /src tree in the same directory as your .gclient file, eg. /checkout_root/chromium.gyp_env. 
    Add the following contents and then regenerate projects by running "gclient runhooks":

{'GYP_DEFINES': 'component=shared_library'} # use space to delimit additional defines.
   chromium.gyp_env applies only to the current tree. This is useful if you wish to have multiple checkouts.    
  
2. Add the following line to your ~/.gyp/include.gypi (you might need to create that directory and file if they don't already exist) and regenerate projects:

{'variables': {'component': 'shared_library'}}
   Note: this will affect all your chromium projects. It also changes the build mode when you run "gclient sync"

3. Override the "component" variable with "shared_library" when running gyp:

python build\gyp_chromium -D"component=shared_library"

Note: you may need to do a "clean" when switching between non-Component/Component builds.

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 a gyp file. 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 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.

My version of chrome fails to run on another machine

Remember that now you not only have to copy the regular files of a shipping build, but also all the intermediate libraries (base.dll, v8.dll etc). Furthermore, the run-time libraries also have to be present on the target machine. Yes, this is a Windows specific issue for the most part.

We need the same CRT files installed on the target computer. For release builds, Microsoft has downloadable packages that install various CRTs, as they are supposed to coexist on the WinSxS folder. Debug versions of the CRT are not redistributable so either Visual Studio is installed on the target machine, or the files are copied from the Visual Studio installation folder to the same directory that contains chrome.exe on the target. See Deploying Visual C++ library DLLs as private assemblies for the second option.

However, just copying the CRT files doesn't work as advertised due to a version mismatch. There are two ways to fix the problem:

1. Open one of the generated DLL files with Visual Studio (for example base.dll). Inspect the resources of the file, and take note of the CRT version that is declared in the embedded manifest. Edit the manifest file copied from the CRT directory in the target machine, and replace the version number there with the number embedded into the DLLs that we build. This has to be done for every new target machine each time the CRT version is updated, for instance by a new service pack (not very often).

This may go from something like

name="Microsoft.VC90.DebugCRT" version="9.0.30729.4148"

To something like

name="Microsoft.VC90.DebugCRT" version="9.0.21022.8"

Note that the version number will go backwards.

2. Edit common.gypi, and add a new definition for the shared build: _BIND_TO_CURRENT_CRT_VERSION=1
The downside of this approach is that it may backfire if there is a new version of the CRT installed on the target machine, for instance if Visual Studio is installed and the version is not exactly the same one that was used on the development machine. In other words, this approach is the simplest one for a developer to test a build, but it doesn't work that well for the bots. Don't forget to rebuild everything after adding the new definition.

The change to common.gypi looks something like this:

@@ -1025,7 +1025,13 @@
         'defines': ['CHROMIUM_BUILD'],
       }],
       ['component=="shared_library"', {
-        'defines': ['COMPONENT_BUILD'],
+        'defines': [
+          'COMPONENT_BUILD',
+          # This makes it easier to copy files from a development machine
+          # to a test machine. The downside is that the CRT has to be updated
+          # on the test machine if it changes on the development one.
+          '_BIND_TO_CURRENT_CRT_VERSION=1',
+        ],
       }],
       ['component=="shared_library" and incremental_chrome_dll==1', {

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. Use a component=static_library build generated by devenv.com if any of the following issues might cause you discomfort or confusion:
  • Uninstall: the Chrome installation's directories are not removed. Furthermore, the uninstaller will forcefully kill any other Chrome processes running (e.g., your own installation of Chrome that you may be using). This is because in a component=shared_library build, setup.exe dynamically links to many other .dlls that are still in the version dir, so all of those are in use and prevent the dir from being deleted.

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, 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