bug 1256642 - have the build system automatically detect LIB_IS_C_ONLY. r?glandium draft
authorTed Mielczarek <ted@mielczarek.org>
Wed, 24 Aug 2016 11:29:00 -0400
changeset 405110 d0c9a6d58f2287aedc8c44b5b567d882264d4244
parent 404375 bad612fd3ab0263e3bcb37c06f2659d48a0687d7
child 529348 39e0c015895f86cdcfad9052ec410137cbd63cf7
push id27386
push userbmo:ted@mielczarek.org
push dateWed, 24 Aug 2016 18:47:14 +0000
reviewersglandium
bugs1256642
milestone51.0a1
bug 1256642 - have the build system automatically detect LIB_IS_C_ONLY. r?glandium MozReview-Commit-ID: 3xWSEe0Tmp9
config/external/sqlite/Makefile.in
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/frontend/data.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
python/mozbuild/mozbuild/test/frontend/data/sources-just-c/d.c
python/mozbuild/mozbuild/test/frontend/data/sources-just-c/e.m
python/mozbuild/mozbuild/test/frontend/data/sources-just-c/g.S
python/mozbuild/mozbuild/test/frontend/data/sources-just-c/h.s
python/mozbuild/mozbuild/test/frontend/data/sources-just-c/i.asm
python/mozbuild/mozbuild/test/frontend/data/sources-just-c/moz.build
python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build
python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/foo.cpp
python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build
python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/three/moz.build
python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/foo.c
python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build
python/mozbuild/mozbuild/test/frontend/test_emitter.py
deleted file mode 100644
--- a/config/external/sqlite/Makefile.in
+++ /dev/null
@@ -1,5 +0,0 @@
-# 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/.
-
-LIB_IS_C_ONLY    = 1
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -1155,16 +1155,18 @@ class RecursiveMakeBackend(CommonBackend
         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)
+        if not libdef.cxx_link:
+            backend_file.write('LIB_IS_C_ONLY := 1\n')
 
     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/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -308,35 +308,39 @@ class ExampleWebIDLInterface(ContextDeri
 
 class LinkageWrongKindError(Exception):
     """Error thrown when trying to link objects of the wrong kind"""
 
 
 class Linkable(ContextDerived):
     """Generic context derived container object for programs and libraries"""
     __slots__ = (
+        'cxx_link',
         'lib_defines',
         'linked_libraries',
         'linked_system_libs',
     )
 
     def __init__(self, context):
         ContextDerived.__init__(self, context)
+        self.cxx_link = False
         self.linked_libraries = []
         self.linked_system_libs = []
         self.lib_defines = Defines(context, {})
 
     def link_library(self, obj):
         assert isinstance(obj, BaseLibrary)
         if isinstance(obj, SharedLibrary) and obj.variant == obj.COMPONENT:
             raise LinkageWrongKindError(
                 'Linkable.link_library() does not take components.')
         if obj.KIND != self.KIND:
             raise LinkageWrongKindError('%s != %s' % (obj.KIND, self.KIND))
         self.linked_libraries.append(obj)
+        if obj.cxx_link:
+            self.cxx_link = True
         obj.refs.append(self)
 
     def link_system_library(self, lib):
         # The '$' check is here as a special temporary rule, allowing the
         # inherited use of make variables, most notably in TK_LIBS.
         if not lib.startswith('$') and not lib.startswith('-'):
             if self.config.substs.get('GNU_CC'):
                 lib = '-l%s' % lib
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -490,59 +490,65 @@ class TreeMetadataEmitter(LoggingMixin):
                 'crate-type %s is not permitted for %s' % (crate_type, libname),
                 context)
 
         self._verify_local_paths(context, context.relsrcdir, config)
 
         return RustLibrary(context, libname, cargo_file, crate_type, **static_args)
 
     def _handle_linkables(self, context, passthru, generated_files):
-        has_linkables = False
+        linkables = []
+        host_linkables = []
+        def add_program(prog, var):
+            if var.startswith('HOST_'):
+                host_linkables.append(prog)
+            else:
+                linkables.append(prog)
 
         for kind, cls in [('PROGRAM', Program), ('HOST_PROGRAM', HostProgram)]:
             program = context.get(kind)
             if program:
                 if program in self._binaries:
                     raise SandboxValidationError(
                         'Cannot use "%s" as %s name, '
                         'because it is already used in %s' % (program, kind,
                         self._binaries[program].relativedir), context)
                 self._binaries[program] = cls(context, program)
                 self._linkage.append((context, self._binaries[program],
                     kind.replace('PROGRAM', 'USE_LIBS')))
-                has_linkables = True
+                add_program(self._binaries[program], kind)
 
         for kind, cls in [
                 ('SIMPLE_PROGRAMS', SimpleProgram),
                 ('CPP_UNIT_TESTS', SimpleProgram),
                 ('HOST_SIMPLE_PROGRAMS', HostSimpleProgram)]:
             for program in context[kind]:
                 if program in self._binaries:
                     raise SandboxValidationError(
                         'Cannot use "%s" in %s, '
                         'because it is already used in %s' % (program, kind,
                         self._binaries[program].relativedir), context)
                 self._binaries[program] = cls(context, program,
                     is_unit_test=kind == 'CPP_UNIT_TESTS')
                 self._linkage.append((context, self._binaries[program],
                     'HOST_USE_LIBS' if kind == 'HOST_SIMPLE_PROGRAMS'
                     else 'USE_LIBS'))
-                has_linkables = True
+                add_program(self._binaries[program], kind)
 
         host_libname = context.get('HOST_LIBRARY_NAME')
         libname = context.get('LIBRARY_NAME')
 
         if host_libname:
             if host_libname == libname:
                 raise SandboxValidationError('LIBRARY_NAME and '
                     'HOST_LIBRARY_NAME must have a different value', context)
             lib = HostLibrary(context, host_libname)
             self._libs[host_libname].append(lib)
             self._linkage.append((context, lib, 'HOST_USE_LIBS'))
-            has_linkables = True
+            host_linkables.append(lib)
 
         final_lib = context.get('FINAL_LIBRARY')
         if not libname and final_lib:
             # If no LIBRARY_NAME is given, create one.
             libname = context.relsrcdir.replace('/', '_')
 
         static_lib = context.get('FORCE_STATIC_LIB')
         shared_lib = context.get('FORCE_SHARED_LIB')
@@ -679,17 +685,17 @@ class TreeMetadataEmitter(LoggingMixin):
                         '(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'))
-                has_linkables = True
+                linkables.append(lib)
                 generated_files.add(lib.lib_name)
                 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__)),
@@ -703,40 +709,39 @@ class TreeMetadataEmitter(LoggingMixin):
             if static_lib:
                 is_rust_library = context.get('IS_RUST_LIBRARY')
                 if is_rust_library:
                     lib = self._rust_library(context, libname, static_args)
                 else:
                     lib = StaticLibrary(context, libname, **static_args)
                 self._libs[libname].append(lib)
                 self._linkage.append((context, lib, 'USE_LIBS'))
+                linkables.append(lib)
 
                 # Multiple staticlibs for a library means multiple copies
                 # of the Rust runtime, which will result in linking errors
                 # later on.
                 if is_rust_library:
                     staticlibs = [l for l in self._libs[libname]
                                   if isinstance(l, RustLibrary) and l.crate_type == 'staticlib']
                     if len(staticlibs) > 1:
                         raise SandboxValidationError(
                             'Cannot have multiple Rust staticlibs in %s: %s' % (libname, ', '.join(l.basename for l in staticlibs)),
                             context)
 
-                has_linkables = True
-
             if lib_defines:
                 if not libname:
                     raise SandboxValidationError('LIBRARY_DEFINES needs a '
                         'LIBRARY_NAME to take effect', context)
                 lib.lib_defines.update(lib_defines)
 
         # Only emit sources if we have linkables defined in the same context.
         # Note the linkables are not emitted in this function, but much later,
         # after aggregation (because of e.g. USE_LIBS processing).
-        if not has_linkables:
+        if not (linkables or host_linkables):
             return
 
         sources = defaultdict(list)
         gen_sources = defaultdict(list)
         all_flags = {}
         for symbol in ('SOURCES', 'HOST_SOURCES', 'UNIFIED_SOURCES'):
             srcs = sources[symbol]
             gen_srcs = gen_sources[symbol]
@@ -801,16 +806,20 @@ class TreeMetadataEmitter(LoggingMixin):
         # A map from moz.build variables to the canonical suffixes of file
         # kinds that can be listed therein.
         all_suffixes = list(suffix_map.keys())
         varmap = dict(
             SOURCES=(Sources, GeneratedSources, all_suffixes),
             HOST_SOURCES=(HostSources, None, ['.c', '.mm', '.cpp']),
             UNIFIED_SOURCES=(UnifiedSources, None, ['.c', '.mm', '.cpp']),
         )
+        # Track whether there are any C++ source files.
+        # Technically this won't do the right thing for SIMPLE_PROGRAMS in
+        # a directory with mixed C and C++ source, but it's not that important.
+        cxx_sources = dict((k, False) for k in varmap.keys())
 
         for variable, (klass, gen_klass, suffixes) in varmap.items():
             allowed_suffixes = set().union(*[suffix_map[s] for s in suffixes])
 
             # First ensure that we haven't been given filetypes that we don't
             # recognize.
             for f in itertools.chain(sources[variable], gen_sources[variable]):
                 ext = mozpath.splitext(f)[1]
@@ -819,28 +828,40 @@ class TreeMetadataEmitter(LoggingMixin):
                         '%s has an unknown file type.' % f, context)
 
             for srcs, cls in ((sources[variable], klass),
                               (gen_sources[variable], gen_klass)):
                 # Now sort the files to let groupby work.
                 sorted_files = sorted(srcs, key=canonical_suffix_for_file)
                 for canonical_suffix, files in itertools.groupby(
                         sorted_files, canonical_suffix_for_file):
+                    if canonical_suffix in ('.cpp', '.mm'):
+                        cxx_sources[variable] = True
                     arglist = [context, list(files), canonical_suffix]
                     if (variable.startswith('UNIFIED_') and
                             'FILES_PER_UNIFIED_FILE' in context):
                         arglist.append(context['FILES_PER_UNIFIED_FILE'])
                     obj = cls(*arglist)
                     yield obj
 
         for f, flags in all_flags.iteritems():
             if flags.flags:
                 ext = mozpath.splitext(f)[1]
                 yield PerSourceFlag(context, f, flags.flags)
 
+        # If there are any C++ sources, set all the linkables defined here
+        # to require the C++ linker.
+        for vars, linkable_items in ((('SOURCES', 'UNIFIED_SOURCES'), linkables),
+                                     (('HOST_SOURCES',), host_linkables)):
+            for var in vars:
+                if cxx_sources[var]:
+                    for l in linkable_items:
+                        l.cxx_link = True
+                    break
+
 
     def emit_from_context(self, context):
         """Convert a Context to tree metadata objects.
 
         This is a generator of mozbuild.frontend.data.ContextDerived instances.
         """
 
         # We only want to emit an InstallationTarget if one of the consulted
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -890,28 +890,30 @@ class TestRecursiveMakeBackend(BackendTe
             + '$(DEPTH)/dist/bin/components/components.manifest '
             + "'binary-component foo')\n",
             'LIBRARY_NAME := foo\n',
             'FORCE_SHARED_LIB := 1\n',
             'IMPORT_LIBRARY := foo\n',
             'SHARED_LIBRARY := foo\n',
             'IS_COMPONENT := 1\n',
             'DSO_SONAME := foo\n',
+            'LIB_IS_C_ONLY := 1\n',
         ])
 
         with open(mozpath.join(env.topobjdir, 'bar', 'backend.mk')) as fh:
             lines = fh.readlines()[2:]
 
         self.assertEqual(lines, [
             'LIBRARY_NAME := bar\n',
             'FORCE_SHARED_LIB := 1\n',
             'IMPORT_LIBRARY := bar\n',
             'SHARED_LIBRARY := bar\n',
             'IS_COMPONENT := 1\n',
             'DSO_SONAME := bar\n',
+            'LIB_IS_C_ONLY := 1\n',
         ])
 
         self.assertTrue(os.path.exists(mozpath.join(env.topobjdir, 'binaries.json')))
         with open(mozpath.join(env.topobjdir, 'binaries.json'), 'rb') as fh:
             binaries = json.load(fh)
 
         self.assertEqual(binaries, {
             'programs': [],
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    '''Template for libraries.'''
+    LIBRARY_NAME = name
+
+Library('dummy')
+
+SOURCES += [
+    'd.c',
+]
+
+SOURCES += [
+    'e.m',
+]
+
+SOURCES += [
+    'g.S',
+]
+
+SOURCES += [
+    'h.s',
+    'i.asm',
+]
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ['one','two','three']
+@template
+def SharedLibrary(name):
+    LIBRARY_NAME = name
+    FORCE_SHARED_LIB = True
+
+SharedLibrary('cxx_shared')
+USE_LIBS += ['cxx_static']
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    LIBRARY_NAME = name
+
+Library('cxx_static')
+SOURCES += ['foo.cpp']
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/three/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SharedLibrary('just_c_shared')
+USE_LIBS += ['just_c_static']
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    LIBRARY_NAME = name
+
+Library('just_c_static')
+SOURCES += ['foo.c']
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -827,18 +827,19 @@ class TestEmitterBasic(unittest.TestCase
             defines[lib.basename] = ' '.join(lib.lib_defines.get_defines())
         self.assertEqual(expected, defines)
 
     def test_sources(self):
         """Test that SOURCES works properly."""
         reader = self.reader('sources')
         objs = self.read_topsrcdir(reader)
 
-        # The last object is a Linkable, ignore it
-        objs = objs[:-1]
+        # The last object is a Linkable.
+        linkable = objs.pop()
+        self.assertTrue(linkable.cxx_link)
         self.assertEqual(len(objs), 6)
         for o in objs:
             self.assertIsInstance(o, Sources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 6)
 
         expected = {
@@ -850,23 +851,47 @@ class TestEmitterBasic(unittest.TestCase
             '.s': ['h.s', 'i.asm'],
         }
         for suffix, files in expected.items():
             sources = suffix_map[suffix]
             self.assertEqual(
                 sources.files,
                 [mozpath.join(reader.config.topsrcdir, f) for f in files])
 
+    def test_sources_just_c(self):
+        """Test that a linkable with no C++ sources doesn't have cxx_link set."""
+        reader = self.reader('sources-just-c')
+        objs = self.read_topsrcdir(reader)
+
+        # The last object is a Linkable.
+        linkable = objs.pop()
+        self.assertFalse(linkable.cxx_link)
+
+    def test_linkables_cxx_link(self):
+        """Test that linkables transitively set cxx_link properly."""
+        reader = self.reader('test-linkables-cxx-link')
+        got_results = 0
+        for obj in self.read_topsrcdir(reader):
+            if isinstance(obj, SharedLibrary):
+                if obj.basename == 'cxx_shared':
+                    self.assertTrue(obj.cxx_link)
+                    got_results += 1
+                elif obj.basename == 'just_c_shared':
+                    self.assertFalse(obj.cxx_link)
+                    got_results += 1
+        self.assertEqual(got_results, 2)
+
     def test_generated_sources(self):
         """Test that GENERATED_SOURCES works properly."""
         reader = self.reader('generated-sources')
         objs = self.read_topsrcdir(reader)
 
-        # The last object is a Linkable, ignore it
-        objs = objs[:-1]
+        # The last object is a Linkable.
+        linkable = objs.pop()
+        self.assertTrue(linkable.cxx_link)
         self.assertEqual(len(objs), 6)
 
         generated_sources = [o for o in objs if isinstance(o, GeneratedSources)]
         self.assertEqual(len(generated_sources), 6)
 
         suffix_map = {obj.canonical_suffix: obj for obj in generated_sources}
         self.assertEqual(len(suffix_map), 6)
 
@@ -884,18 +909,19 @@ class TestEmitterBasic(unittest.TestCase
                 sources.files,
                 [mozpath.join(reader.config.topobjdir, f) for f in files])
 
     def test_host_sources(self):
         """Test that HOST_SOURCES works properly."""
         reader = self.reader('host-sources')
         objs = self.read_topsrcdir(reader)
 
-        # The last object is a Linkable, ignore it
-        objs = objs[:-1]
+        # The last object is a Linkable
+        linkable = objs.pop()
+        self.assertTrue(linkable.cxx_link)
         self.assertEqual(len(objs), 3)
         for o in objs:
             self.assertIsInstance(o, HostSources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 3)
 
         expected = {