Bug 1235132 - Add support for a more-or-less cross-platform symbols file. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 25 Dec 2015 17:36:16 +0900
changeset 278033 3b61f9f09b04cd421f45b2ee415d551f7417915b
parent 278032 5bea8f924bdd34aebef8f73c9c71a4009272ec6d
child 278034 186450f22aabc54781e63a057a8eeb463644fe81
push id29841
push userryanvm@gmail.com
push dateSat, 02 Jan 2016 00:29:52 +0000
treeherdermozilla-central@f7fbc524f9f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1235132
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1235132 - Add support for a more-or-less cross-platform symbols file. r=gps Currently, one needs to define DEFFILE or LD_VERSION_SCRIPT appropriately, and somehow deal with the fact that their input format is different, which currently relies on manual invocations of the convert_def_file script, with awkward aggregations. This simplifies the problem by using a simple list of symbols, with preprocessing, allowing #includes.
config/rules.mk
python/mozbuild/mozbuild/action/generate_symbols_file.py
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/data.py
python/mozbuild/mozbuild/frontend/emitter.py
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -457,16 +457,29 @@ ifdef IS_COMPONENT
 EXTRA_DSO_LDOPTS += -Wl,-Bsymbolic
 endif
 ifdef LD_VERSION_SCRIPT
 EXTRA_DSO_LDOPTS += -Wl,--version-script,$(LD_VERSION_SCRIPT)
 EXTRA_DEPS += $(LD_VERSION_SCRIPT)
 endif
 endif
 
+ifdef SYMBOLS_FILE
+ifdef GCC_USE_GNU_LD
+EXTRA_DSO_LDOPTS += -Wl,--version-script,$(SYMBOLS_FILE)
+else
+ifeq ($(OS_TARGET),Darwin)
+EXTRA_DSO_LDOPTS += -Wl,-exported_symbols_list,$(SYMBOLS_FILE)
+endif
+ifeq ($(OS_TARGET),WINNT)
+EXTRA_DSO_LDOPTS += -DEF:$(call normalizepath,$(SYMBOLS_FILE))
+endif
+endif
+EXTRA_DEPS += $(SYMBOLS_FILE)
+endif
 #
 # GNU doesn't have path length limitation
 #
 
 ifeq ($(OS_ARCH),GNU)
 OS_CPPFLAGS += -DPATH_MAX=1024 -DMAXPATHLEN=1024
 endif
 
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/action/generate_symbols_file.py
@@ -0,0 +1,79 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import buildconfig
+import os
+from StringIO import StringIO
+from mozbuild.preprocessor import Preprocessor
+
+
+def generate_symbols_file(output, input):
+    ''' '''
+    input = os.path.abspath(input)
+
+    pp = Preprocessor()
+    pp.context.update(buildconfig.defines)
+    # Hack until MOZ_DEBUG_FLAGS are simply part of buildconfig.defines
+    if buildconfig.substs['MOZ_DEBUG']:
+        pp.context['DEBUG'] = '1'
+    # Ensure @DATA@ works as expected (see the Windows section further below)
+    if buildconfig.substs['OS_TARGET'] == 'WINNT':
+        pp.context['DATA'] = 'DATA'
+    else:
+        pp.context['DATA'] = ''
+    pp.out = StringIO()
+    pp.do_filter('substitution')
+    pp.do_include(input)
+
+    symbols = [s.strip() for s in pp.out.getvalue().splitlines() if s.strip()]
+
+    if buildconfig.substs['GCC_USE_GNU_LD']:
+        # A linker version script is generated for GNU LD that looks like the
+        # following:
+        # {
+        # global:
+        #   symbol1;
+        #   symbol2;
+        #   ...
+        # local:
+        #   *;
+        # };
+        output.write('{\nglobal:\n  %s;\nlocal:\n  *;\n};'
+                     % ';\n  '.join(symbols))
+    elif buildconfig.substs['OS_TARGET'] == 'Darwin':
+        # A list of symbols is generated for Apple ld that simply lists all
+        # symbols, with an underscore prefix.
+        output.write(''.join('_%s\n' % s for s in symbols))
+    elif buildconfig.substs['OS_TARGET'] == 'WINNT':
+        # A def file is generated for MSVC link.exe that looks like the
+        # following:
+        # LIBRARY library.dll
+        # EXPORTS
+        #   symbol1
+        #   symbol2
+        #   ...
+        #
+        # link.exe however requires special markers for data symbols, so in
+        # that case the symbols look like:
+        #   data_symbol1 DATA
+        #   data_symbol2 DATA
+        #   ...
+        #
+        # In the input file, this is just annotated with the following syntax:
+        #   data_symbol1 @DATA@
+        #   data_symbol2 @DATA@
+        #   ...
+        # The DATA variable is "simply" expanded by the preprocessor, to
+        # nothing on non-Windows, such that we only get the symbol name on
+        # those platforms, and to DATA on Windows, so that the "DATA" part
+        # is, in fact, part of the symbol name as far as the symbols variable
+        # is concerned.
+        libname, ext = os.path.splitext(os.path.basename(output.name))
+        assert ext == '.symbols'
+        output.write('LIBRARY %s\nEXPORTS\n  %s\n'
+                     % (libname, '\n  '.join(symbols)))
+
+    return set(pp.includes)
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -1166,16 +1166,18 @@ INSTALL_TARGETS += %(prefix)s
         backend_file.write('IMPORT_LIBRARY := %s\n' % libdef.import_name)
         backend_file.write('SHARED_LIBRARY := %s\n' % libdef.lib_name)
         if libdef.variant == libdef.COMPONENT:
             backend_file.write('IS_COMPONENT := 1\n')
         if libdef.soname:
             backend_file.write('DSO_SONAME := %s\n' % libdef.soname)
         if libdef.is_sdk:
             backend_file.write('SDK_LIBRARY := %s\n' % libdef.import_name)
+        if libdef.symbols_file:
+            backend_file.write('SYMBOLS_FILE := %s\n' % libdef.symbols_file)
 
     def _process_static_library(self, libdef, backend_file):
         backend_file.write_once('LIBRARY_NAME := %s\n' % libdef.basename)
         backend_file.write('FORCE_STATIC_LIB := 1\n')
         backend_file.write('REAL_LIBRARY := %s\n' % libdef.lib_name)
         if libdef.is_sdk:
             backend_file.write('SDK_LIBRARY := %s\n' % libdef.import_name)
         if libdef.no_expand_lib:
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -1208,16 +1208,26 @@ VARIABLES = {
         """, None),
 
     'LD_VERSION_SCRIPT': (unicode, unicode,
         """The linker version script for shared libraries.
 
         This variable can only be used on Linux.
         """, None),
 
+    'SYMBOLS_FILE': (SourcePath, unicode,
+        """A file containing a list of symbols to export from a shared library.
+
+        The given file contains a list of symbols to be exported, and is
+        preprocessed.
+        A special marker "@DATA@" must be added after a symbol name if it
+        points to data instead of code, so that the Windows linker can treat
+        them correctly.
+        """, None),
+
     'BRANDING_FILES': (ContextDerivedTypedHierarchicalStringList(Path), list,
         """List of files to be installed into the branding directory.
 
         ``BRANDING_FILES`` will copy (or symlink, if the platform supports it)
         the contents of its files to the ``dist/branding`` directory. Files that
         are destined for a subdirectory can be specified by accessing a field.
         For example, to export ``foo.png`` to the top-level directory and
         ``bar.png`` to the directory ``images/subdir``, append to
--- a/python/mozbuild/mozbuild/frontend/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -456,24 +456,25 @@ class StaticLibrary(Library):
         self.no_expand_lib = no_expand_lib
 
 
 class SharedLibrary(Library):
     """Context derived container object for a shared library"""
     __slots__ = (
         'soname',
         'variant',
+        'symbols_file',
     )
 
     FRAMEWORK = 1
     COMPONENT = 2
     MAX_VARIANT = 3
 
     def __init__(self, context, basename, real_name=None, is_sdk=False,
-            soname=None, variant=None):
+            soname=None, variant=None, symbols_file=False):
         assert(variant in range(1, self.MAX_VARIANT) or variant is None)
         Library.__init__(self, context, basename, real_name, is_sdk)
         self.variant = variant
         self.lib_name = real_name or basename
         assert self.lib_name
 
         if variant == self.FRAMEWORK:
             self.import_name = self.lib_name
@@ -492,16 +493,21 @@ class SharedLibrary(Library):
             self.soname = '%s%s%s' % (
                 context.config.dll_prefix,
                 soname,
                 context.config.dll_suffix,
             )
         else:
             self.soname = self.lib_name
 
+        if symbols_file:
+            self.symbols_file = '%s.symbols' % self.lib_name
+        else:
+            self.symbols_file = None
+
 
 class ExternalLibrary(object):
     """Empty mixin for libraries built by an external build system."""
 
 
 class ExternalStaticLibrary(StaticLibrary, ExternalLibrary):
     """Context derived container for static libraries built by an external
     build system."""
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -502,24 +502,48 @@ class TreeMetadataEmitter(LoggingMixin):
                         'STATIC_LIBRARY_NAME.', context)
                 if shared_name and static_name and shared_name == static_name:
                     raise SandboxValidationError(
                         'Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, '
                         'but SHARED_LIBRARY_NAME is the same as '
                         'STATIC_LIBRARY_NAME. Please change one of them.',
                         context)
 
+            symbols_file = context.get('SYMBOLS_FILE')
+            if symbols_file:
+                if not shared_lib:
+                    raise SandboxValidationError(
+                        'SYMBOLS_FILE can only be used with a SHARED_LIBRARY.',
+                        context)
+                if context.get('DEFFILE') or context.get('LD_VERSION_SCRIPT'):
+                    raise SandboxValidationError(
+                        'SYMBOLS_FILE cannot be used along DEFFILE or '
+                        'LD_VERSION_SCRIPT.', context)
+                if not os.path.exists(symbols_file.full_path):
+                    raise SandboxValidationError(
+                        'Path specified in SYMBOLS_FILE does not exist: %s '
+                        '(resolved to %s)' % (symbols_file,
+                        symbols_file.full_path), context)
+                shared_args['symbols_file'] = True
+
             if shared_lib:
                 lib = SharedLibrary(context, libname, **shared_args)
                 self._libs[libname].append(lib)
                 self._linkage.append((context, lib, 'USE_LIBS'))
                 if is_component and not context['NO_COMPONENTS_MANIFEST']:
                     yield ChromeManifestEntry(context,
                         'components/components.manifest',
                         ManifestBinaryComponent('components', lib.lib_name))
+                if symbols_file:
+                    script = mozpath.join(
+                        mozpath.dirname(mozpath.dirname(__file__)),
+                        'action', 'generate_symbols_file.py')
+                    yield GeneratedFile(context, script,
+                        'generate_symbols_file', lib.symbols_file,
+                        [symbols_file.full_path])
             if static_lib:
                 lib = StaticLibrary(context, libname, **static_args)
                 self._libs[libname].append(lib)
                 self._linkage.append((context, lib, 'USE_LIBS'))
 
             if lib_defines:
                 if not libname:
                     raise SandboxValidationError('LIBRARY_DEFINES needs a '