Bug 1411712 - Move LDFLAGS to mozbuild. draft
authorChris Manchester <cmanchester@mozilla.com>
Thu, 02 Nov 2017 14:14:58 -0700
changeset 692246 06765f655f6bbe0fa759a73539443c78e28a49e9
parent 692245 37d2590fd114fd3cc8801a857f02c6693319593f
child 692247 6c8064bab196bf1aad430e1a99188abea90cf02c
push id87463
push userbmo:cmanchester@mozilla.com
push dateThu, 02 Nov 2017 21:50:29 +0000
bugs1411712
milestone58.0a1
Bug 1411712 - Move LDFLAGS to mozbuild. MozReview-Commit-ID: A8cZ2FUveAf
config/config.mk
config/rules.mk
js/src/build/Makefile.in
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
python/mozbuild/mozbuild/test/frontend/data/link-flags/moz.build
python/mozbuild/mozbuild/test/frontend/data/link-flags/test1.c
python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
python/mozbuild/mozbuild/test/frontend/test_emitter.py
--- a/config/config.mk
+++ b/config/config.mk
@@ -129,61 +129,32 @@ CXX := $(CXX_WRAPPER) $(CXX)
 MKDIR ?= mkdir
 SLEEP ?= sleep
 TOUCH ?= touch
 
 PYTHON_PATH = $(PYTHON) $(topsrcdir)/config/pythonpath.py
 
 # determine debug-related options
 _DEBUG_ASFLAGS :=
-_DEBUG_LDFLAGS :=
 
 ifneq (,$(MOZ_DEBUG)$(MOZ_DEBUG_SYMBOLS))
   ifeq ($(AS),$(YASM))
     ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
       _DEBUG_ASFLAGS += -g cv8
     else
       ifneq ($(OS_ARCH),Darwin)
         _DEBUG_ASFLAGS += -g dwarf2
       endif
     endif
   else
     _DEBUG_ASFLAGS += $(MOZ_DEBUG_FLAGS)
   endif
-  _DEBUG_LDFLAGS += $(MOZ_DEBUG_LDFLAGS)
 endif
 
 ASFLAGS += $(_DEBUG_ASFLAGS)
-OS_LDFLAGS += $(_DEBUG_LDFLAGS)
-
-# XXX: What does this? Bug 482434 filed for better explanation.
-ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
-ifndef MOZ_DEBUG
-
-# MOZ_DEBUG_SYMBOLS generates debug symbols in separate PDB files.
-# Used for generating an optimized build with debugging symbols.
-# Used in the Windows nightlies to generate symbols for crash reporting.
-ifdef MOZ_DEBUG_SYMBOLS
-OS_LDFLAGS += -DEBUG
-endif
-
-#
-# Handle DMD in optimized builds.
-#
-ifdef MOZ_DMD
-OS_LDFLAGS = -DEBUG
-endif # MOZ_DMD
-
-ifdef MOZ_OPTIMIZE
-OS_LDFLAGS += -OPT:REF,ICF
-endif # MOZ_OPTIMIZE
-
-endif # MOZ_DEBUG
-
-endif # WINNT && !GNU_CC
 
 #
 # Build using PIC by default
 #
 _ENABLE_PIC=1
 
 # Don't build SIMPLE_PROGRAMS with PGO, since they don't need it anyway,
 # and we don't have the same build logic to re-link them in the second pass.
@@ -210,19 +181,16 @@ ifdef MOZ_PROFILE_USE
 PGO_CFLAGS += $(if $(filter $(notdir $<),$(notdir $(NO_PROFILE_GUIDED_OPTIMIZE))),,$(PROFILE_USE_CFLAGS))
 PGO_LDFLAGS += $(PROFILE_USE_LDFLAGS)
 ifeq (WINNT,$(OS_ARCH))
 AR_FLAGS += -LTCG
 endif
 endif # MOZ_PROFILE_USE
 endif # NO_PROFILE_GUIDED_OPTIMIZE
 
-# linker
-OS_LDFLAGS += $(LINKER_LDFLAGS)
-
 MAKE_JARS_FLAGS = \
 	-t $(topsrcdir) \
 	-f $(MOZ_JAR_MAKER_FILE_FORMAT) \
 	$(NULL)
 
 ifdef USE_EXTENSION_MANIFEST
 MAKE_JARS_FLAGS += -e
 endif
@@ -244,21 +212,17 @@ INCLUDES = \
   -I$(srcdir) \
   -I$(CURDIR) \
   $(LOCAL_INCLUDES) \
   -I$(ABS_DIST)/include \
   $(NULL)
 
 include $(MOZILLA_DIR)/config/static-checking-config.mk
 
-LDFLAGS		= $(OS_LDFLAGS) $(PGO_LDFLAGS) $(MOZBUILD_LDFLAGS) $(MOZ_FIX_LINK_PATHS) $(MK_LDFLAGS)
-
-ifdef MOZ_OPTIMIZE
-LDFLAGS		+= $(MOZ_OPTIMIZE_LDFLAGS)
-endif # MOZ_OPTIMIZE
+LDFLAGS		= $(COMPUTED_LDFLAGS) $(PGO_LDFLAGS) $(MK_LDFLAGS)
 
 COMPILE_CFLAGS	= $(COMPUTED_CFLAGS) $(PGO_CFLAGS) $(_DEPEND_CFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CXXFLAGS = $(COMPUTED_CXXFLAGS) $(PGO_CFLAGS) $(_DEPEND_CFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS)
 COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS)
 ASFLAGS += $(MOZBUILD_ASFLAGS)
 
 HOST_CFLAGS = $(COMPUTED_HOST_CFLAGS) $(_DEPEND_CFLAGS)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -163,27 +163,17 @@ endif
 COMPILE_CFLAGS += $(COMPILE_PDB_FLAG)
 COMPILE_CXXFLAGS += $(COMPILE_PDB_FLAG)
 
 LINK_PDBFILE ?= $(basename $(@F)).pdb
 ifdef MOZ_DEBUG
 CODFILE=$(basename $(@F)).cod
 endif
 
-ifdef DEFFILE
-OS_LDFLAGS += -DEF:$(call normalizepath,$(DEFFILE))
 EXTRA_DEPS += $(DEFFILE)
-endif
-
-else #!GNU_CC
-
-ifdef DEFFILE
-OS_LDFLAGS += $(call normalizepath,$(DEFFILE))
-EXTRA_DEPS += $(DEFFILE)
-endif
 
 endif # !GNU_CC
 
 endif # WINNT
 
 ifeq (arm-Darwin,$(CPU_ARCH)-$(OS_TARGET))
 ifdef PROGRAM
 MOZ_PROGRAM_LDFLAGS += -Wl,-rpath -Wl,@executable_path/Frameworks
--- a/js/src/build/Makefile.in
+++ b/js/src/build/Makefile.in
@@ -24,17 +24,17 @@ check-vanilla-allocations:
 	$(PYTHON) $(topsrcdir)/config/check_vanilla_allocations.py $(REAL_LIBRARY)
 
 # The "aggressive" variant will likely fail on some compiler/platform
 # combinations, but is worth running by hand every once in a while.
 check-vanilla-allocations-aggressive:
 	$(PYTHON) $(topsrcdir)/config/check_vanilla_allocations.py --aggressive $(REAL_LIBRARY)
 
 ifeq ($(OS_ARCH),Linux)
-ifeq (,$(filter -flto,$(COMPUTED_CFLAGS) $(COMPUTED_CXXFLAGS) $(LDFLAGS)))
+ifeq (,$(filter -flto,$(COMPUTED_CFLAGS) $(COMPUTED_CXXFLAGS) $(COMPUTED_LDFLAGS)))
 check:: check-vanilla-allocations
 endif
 endif
 
 $(LIBRARY_NAME).pc: js.pc
 	cp $^ $@
 
 # Install versioned file, for parallel installability in Linux distributions
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -339,16 +339,66 @@ class HostCompileFlags(BaseCompileFlags)
         optimize_flags = []
         if self._context.config.substs.get('CROSS_COMPILE'):
             optimize_flags += self._context.config.substs.get('HOST_OPTIMIZE_FLAGS')
         elif self._context.config.substs.get('MOZ_OPTIMIZE'):
             optimize_flags += self._context.config.substs.get('MOZ_OPTIMIZE_FLAGS')
         return optimize_flags
 
 
+class LinkFlags(BaseCompileFlags):
+    def __init__(self, context):
+        self._context = context
+
+        self.flag_variables = (
+            ('OS', self._os_ldflags(), ('LDFLAGS',)),
+            ('LINKER', context.config.substs.get('LINKER_LDFLAGS'),
+             ('LDFLAGS',)),
+            ('DEFFILE', None, ('LDFLAGS',)),
+            ('MOZBUILD', None, ('LDFLAGS',)),
+            ('FIX_LINK_PATHS', context.config.substs.get('MOZ_FIX_LINK_PATHS'),
+             ('LDFLAGS',)),
+            ('OPTIMIZE', (context.config.substs.get('MOZ_OPTIMIZE_LDFLAGS', []) if
+                          context.config.substs.get('MOZ_OPTIMIZE') else []),
+             ('LDFLAGS',)),
+        )
+        BaseCompileFlags.__init__(self, context)
+
+    def _os_ldflags(self):
+        flags = self._context.config.substs.get('OS_LDFLAGS', [])[:]
+
+        if (self._context.config.substs.get('MOZ_DEBUG') or
+            self._context.config.substs.get('MOZ_DEBUG_SYMBOLS')):
+            flags += self._context.config.substs.get('MOZ_DEBUG_LDFLAGS', [])
+
+        # TODO: This is pretty convoluted, and isn't really a per-context thing,
+        # configure would be a better place to aggregate these.
+        if all([self._context.config.substs.get('OS_ARCH') == 'WINNT',
+                not self._context.config.substs.get('GNU_CC'),
+                not self._context.config.substs.get('MOZ_DEBUG')]):
+
+            # MOZ_DEBUG_SYMBOLS generates debug symbols in separate PDB files.
+            # Used for generating an optimized build with debugging symbols.
+            # Used in the Windows nightlies to generate symbols for crash reporting.
+            if self._context.config.substs.get('MOZ_DEBUG_SYMBOLS'):
+                flags.append('-DEBUG')
+
+
+            if self._context.config.substs.get('MOZ_DMD'):
+                # On Windows Opt DMD builds we actually override everything
+                # from OS_LDFLAGS. Bug 1413728 is on file to figure out whether
+                # this is necessary.
+                flags = ['-DEBUG']
+
+            if self._context.config.substs.get('MOZ_OPTIMIZE'):
+                flags.append('-OPT:REF,ICF')
+
+        return flags
+
+
 class CompileFlags(BaseCompileFlags):
     def __init__(self, context):
         main_src_dir = mozpath.dirname(context.main_path)
         self._context = context
 
         self.flag_variables = (
             ('STL', context.config.substs.get('STL_FLAGS'), ('CXXFLAGS',)),
             ('VISIBILITY', context.config.substs.get('VISIBILITY_FLAGS'),
@@ -1901,16 +1951,21 @@ VARIABLES = {
         """Directories containing Python packages that Sphinx documents.
         """),
 
     'COMPILE_FLAGS': (CompileFlags, dict,
         """Recipe for compile flags for this context. Not to be manipulated
         directly.
         """),
 
+    'LINK_FLAGS': (LinkFlags, dict,
+        """Recipe for linker flags for this context. Not to be manipulated
+        directly.
+        """),
+
     'CFLAGS': (List, list,
         """Flags passed to the C compiler for all of the C source files
            declared in this directory.
 
            Note that the ordering of flags matters here, these flags will be
            added to the compiler's command line in the same order as they
            appear in the moz.build file.
         """),
@@ -2351,16 +2406,17 @@ SPECIAL_VARIABLES = {
                                   else TestDirsPlaceHolder, list,
         """Like DIRS but only for directories that contain test-only code.
 
         If tests are not enabled, this variable will be ignored.
 
         This variable may go away once the transition away from Makefiles is
         complete.
         """),
+
 }
 
 # Deprecation hints.
 DEPRECATION_HINTS = {
     'CPP_UNIT_TESTS': '''
         Please use'
 
             CppUnitTests(['foo', 'bar'])
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -920,16 +920,17 @@ class TreeMetadataEmitter(LoggingMixin):
         # We always emit a directory traversal descriptor. This is needed by
         # the recursive make backend.
         for o in self._emit_directory_traversal_from_context(context): yield o
 
         for obj in self._process_xpidl(context):
             yield obj
 
         computed_flags = ComputedFlags(context, context['COMPILE_FLAGS'])
+        computed_link_flags = ComputedFlags(context, context['LINK_FLAGS'])
         computed_host_flags = ComputedFlags(context, context['HOST_COMPILE_FLAGS'])
 
         # Proxy some variables as-is until we have richer classes to represent
         # them. We should aim to keep this set small because it violates the
         # desired abstraction of the build definition away from makefiles.
         passthru = VariablePassthru(context)
         varlist = [
             'ANDROID_APK_NAME',
@@ -951,28 +952,39 @@ class TreeMetadataEmitter(LoggingMixin):
                 passthru.variables[v] = context[v]
 
         if context.config.substs.get('OS_TARGET') == 'WINNT' and \
                 context['DELAYLOAD_DLLS']:
             context['LDFLAGS'].extend([('-DELAYLOAD:%s' % dll)
                 for dll in context['DELAYLOAD_DLLS']])
             context['OS_LIBS'].append('delayimp')
 
-        for v in ['CMFLAGS', 'CMMFLAGS', 'ASFLAGS', 'LDFLAGS']:
+        for v in ['CMFLAGS', 'CMMFLAGS', 'ASFLAGS']:
             if v in context and context[v]:
                 passthru.variables['MOZBUILD_' + v] = context[v]
 
         for v in ['CXXFLAGS', 'CFLAGS']:
             if v in context and context[v]:
                 computed_flags.resolve_flags('MOZBUILD_%s' % v, context[v])
 
         for v in ['HOST_CXXFLAGS', 'HOST_CFLAGS']:
             if v in context and context[v]:
                 computed_host_flags.resolve_flags('MOZBUILD_%s' % v, context[v])
 
+        if 'LDFLAGS' in context and context['LDFLAGS']:
+            computed_link_flags.resolve_flags('MOZBUILD', context['LDFLAGS'])
+
+        deffile = context['DEFFILE']
+        if deffile and context.config.substs.get('OS_ARCH') == 'WINNT':
+            if context.config.substs.get('GNU_CC'):
+                computed_link_flags.resolve_flags('DEFFILE', [deffile])
+            else:
+                computed_link_flags.resolve_flags('DEFFILE',
+                                                  ['-DEF:' + deffile])
+
         dist_install = context['DIST_INSTALL']
         if dist_install is True:
             passthru.variables['DIST_INSTALL'] = True
         elif dist_install is False:
             passthru.variables['NO_DIST_INSTALL'] = True
 
         # Ideally, this should be done in templates, but this is difficult at
         # the moment because USE_STATIC_LIBS can be set after a template
@@ -1184,16 +1196,17 @@ class TreeMetadataEmitter(LoggingMixin):
         if android_extra_packages:
             yield AndroidExtraPackages(context, android_extra_packages)
 
         if passthru.variables:
             yield passthru
 
         if context.objdir in self._compile_dirs:
             self._compile_flags[context.objdir] = computed_flags
+            yield computed_link_flags
         if context.objdir in self._host_compile_dirs:
             yield computed_host_flags
 
 
     def _create_substitution(self, cls, context, path):
         sub = cls(context)
         sub.input_path = '%s.in' % path.full_path
         sub.output_path = path.translated
--- a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
@@ -4,10 +4,9 @@
 
 DELAYLOAD_DLLS = ['foo.dll', 'bar.dll']
 
 RCFILE = 'foo.rc'
 RESFILE = 'bar.res'
 RCINCLUDE = 'bar.rc'
 DEFFILE = 'baz.def'
 
-LDFLAGS += ['-ld flag with spaces', '-x']
 WIN32_EXE_LDFLAGS += ['-subsystem:console']
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -323,22 +323,16 @@ class TestRecursiveMakeBackend(BackendTe
                 'RESFILE := bar.res',
             ],
             'RCINCLUDE': [
                 'RCINCLUDE := bar.rc',
             ],
             'DEFFILE': [
                 'DEFFILE := baz.def',
             ],
-            'MOZBUILD_LDFLAGS': [
-                "MOZBUILD_LDFLAGS += '-ld flag with spaces'",
-                'MOZBUILD_LDFLAGS += -x',
-                'MOZBUILD_LDFLAGS += -DELAYLOAD:foo.dll',
-                'MOZBUILD_LDFLAGS += -DELAYLOAD:bar.dll',
-            ],
             'WIN32_EXE_LDFLAGS': [
                 'WIN32_EXE_LDFLAGS += -subsystem:console',
             ],
         }
 
         for var, val in expected.items():
             # print("test_variable_passthru[%s]" % (var))
             found = [str for str in lines if str.startswith(var)]
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/link-flags/moz.build
@@ -0,0 +1,14 @@
+# 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')
+
+UNIFIED_SOURCES += ['test1.c']
+
+LDFLAGS += ['-Wl,-U_foo']
+LDFLAGS += ['-framework Foo', '-x']
\ No newline at end of file
new file mode 100644
--- a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
@@ -6,10 +6,9 @@ DIST_INSTALL = False
 
 DELAYLOAD_DLLS = ['foo.dll', 'bar.dll']
 
 RCFILE = 'foo.rc'
 RESFILE = 'bar.res'
 RCINCLUDE = 'bar.rc'
 DEFFILE = 'baz.def'
 
-LDFLAGS += ['-framework Foo', '-x']
 WIN32_EXE_LDFLAGS += ['-subsystem:console']
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -183,106 +183,158 @@ class TestEmitterBasic(unittest.TestCase
         self.assertIsInstance(objs[0], VariablePassthru)
 
         wanted = {
             'NO_DIST_INSTALL': True,
             'RCFILE': 'foo.rc',
             'RESFILE': 'bar.res',
             'RCINCLUDE': 'bar.rc',
             'DEFFILE': 'baz.def',
-            'MOZBUILD_LDFLAGS': ['-framework Foo', '-x', '-DELAYLOAD:foo.dll',
-                                 '-DELAYLOAD:bar.dll'],
             'WIN32_EXE_LDFLAGS': ['-subsystem:console'],
         }
 
         variables = objs[0].variables
         maxDiff = self.maxDiff
         self.maxDiff = None
         self.assertEqual(wanted, variables)
         self.maxDiff = maxDiff
 
     def test_compile_flags(self):
         reader = self.reader('compile-flags', extra_substs={
             'WARNINGS_AS_ERRORS': '-Werror',
         })
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['STL'], reader.config.substs['STL_FLAGS'])
         self.assertEqual(flags.flags['VISIBILITY'], reader.config.substs['VISIBILITY_FLAGS'])
         self.assertEqual(flags.flags['WARNINGS_AS_ERRORS'], ['-Werror'])
         self.assertEqual(flags.flags['MOZBUILD_CFLAGS'], ['-Wall', '-funroll-loops'])
         self.assertEqual(flags.flags['MOZBUILD_CXXFLAGS'], ['-funroll-loops', '-Wall'])
 
     def test_debug_flags(self):
         reader = self.reader('compile-flags', extra_substs={
             'MOZ_DEBUG_FLAGS': '-g',
             'MOZ_DEBUG_SYMBOLS': '1',
         })
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['DEBUG'], ['-g'])
 
     def test_disable_debug_flags(self):
         reader = self.reader('compile-flags', extra_substs={
             'MOZ_DEBUG_FLAGS': '-g',
             'MOZ_DEBUG_SYMBOLS': '',
         })
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['DEBUG'], [])
 
+    def test_link_flags(self):
+        reader = self.reader('link-flags', extra_substs={
+            'OS_LDFLAGS': ['-Wl,rpath-link=/usr/lib'],
+            'LINKER_LDFLAGS': ['-fuse-ld=gold'],
+            'MOZ_OPTIMIZE': '',
+            'MOZ_OPTIMIZE_LDFLAGS': ['-Wl,-dead_strip'],
+            'MOZ_DEBUG_LDFLAGS': ['-framework ExceptionHandling'],
+        })
+        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+        self.assertIsInstance(ldflags, ComputedFlags)
+        self.assertEqual(ldflags.flags['OS'], reader.config.substs['OS_LDFLAGS'])
+        self.assertEqual(ldflags.flags['LINKER'], reader.config.substs['LINKER_LDFLAGS'])
+        self.assertEqual(ldflags.flags['MOZBUILD'], ['-Wl,-U_foo', '-framework Foo', '-x'])
+        self.assertEqual(ldflags.flags['OPTIMIZE'], [])
+
+    def test_debug_ldflags(self):
+        reader = self.reader('link-flags', extra_substs={
+            'MOZ_DEBUG_SYMBOLS': '1',
+            'MOZ_DEBUG_LDFLAGS': ['-framework ExceptionHandling'],
+        })
+        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+        self.assertIsInstance(ldflags, ComputedFlags)
+        self.assertEqual(ldflags.flags['OS'],
+                         reader.config.substs['MOZ_DEBUG_LDFLAGS'])
+
+    def test_windows_opt_link_flags(self):
+        reader = self.reader('link-flags', extra_substs={
+            'OS_ARCH': 'WINNT',
+            'GNU_CC': '',
+            'MOZ_OPTIMIZE': '1',
+            'MOZ_DEBUG_SYMBOLS': '1',
+            'MOZ_OPTIMIZE_FLAGS': [],
+            'MOZ_OPTIMIZE_LDFLAGS': [],
+        })
+        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+        self.assertIsInstance(ldflags, ComputedFlags)
+        self.assertIn('-DEBUG', ldflags.flags['OS'])
+        self.assertIn('-OPT:REF,ICF', ldflags.flags['OS'])
+
+    def test_windows_dmd_link_flags(self):
+        reader = self.reader('link-flags', extra_substs={
+            'OS_ARCH': 'WINNT',
+            'GNU_CC': '',
+            'MOZ_DMD': '1',
+            'MOZ_DEBUG_SYMBOLS': '1',
+            'MOZ_OPTIMIZE': '1',
+            'MOZ_OPTIMIZE_FLAGS': [],
+            'OS_LDFLAGS': ['-Wl,-U_foo'],
+        })
+        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+        self.assertIsInstance(ldflags, ComputedFlags)
+        self.assertEqual(ldflags.flags['OS'],
+                         ['-DEBUG', '-OPT:REF,ICF'])
+
     def test_host_compile_flags(self):
         reader = self.reader('host-compile-flags', extra_substs={
             'HOST_CXXFLAGS': ['-Wall', '-Werror'],
             'HOST_CFLAGS': ['-Werror', '-Wall'],
         })
-        sources, flags, lib, target_flags = self.read_topsrcdir(reader)
+        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['HOST_CXXFLAGS'], reader.config.substs['HOST_CXXFLAGS'])
         self.assertEqual(flags.flags['HOST_CFLAGS'], reader.config.substs['HOST_CFLAGS'])
         self.assertEqual(set(flags.flags['HOST_DEFINES']),
                          set(['-DFOO', '-DBAZ="abcd"', '-UQUX', '-DBAR=7', '-DVALUE=xyz']))
         self.assertEqual(flags.flags['MOZBUILD_HOST_CFLAGS'], ['-funroll-loops', '-host-arg'])
         self.assertEqual(flags.flags['MOZBUILD_HOST_CXXFLAGS'], [])
 
     def test_host_no_optimize_flags(self):
         reader = self.reader('host-compile-flags', extra_substs={
             'MOZ_OPTIMIZE': '',
             'MOZ_OPTIMIZE_FLAGS': ['-O2'],
         })
-        sources, flags, lib, target_flags = self.read_topsrcdir(reader)
+        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['HOST_OPTIMIZE'], [])
 
     def test_host_optimize_flags(self):
         reader = self.reader('host-compile-flags', extra_substs={
             'MOZ_OPTIMIZE': '1',
             'MOZ_OPTIMIZE_FLAGS': ['-O2'],
         })
-        sources, flags, lib, target_flags = self.read_topsrcdir(reader)
+        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['HOST_OPTIMIZE'], ['-O2'])
 
     def test_cross_optimize_flags(self):
         reader = self.reader('host-compile-flags', extra_substs={
             'MOZ_OPTIMIZE': '1',
             'MOZ_OPTIMIZE_FLAGS': ['-O2'],
             'HOST_OPTIMIZE_FLAGS': ['-O3'],
             'CROSS_COMPILE': '1',
         })
-        sources, flags, lib, target_flags = self.read_topsrcdir(reader)
+        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['HOST_OPTIMIZE'], ['-O3'])
 
     def test_host_rtl_flag(self):
         reader = self.reader('host-compile-flags', extra_substs={
             'OS_ARCH': 'WINNT',
             'MOZ_DEBUG': '1',
         })
-        sources, flags, lib, target_flags = self.read_topsrcdir(reader)
+        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['RTL'], ['-MDd'])
 
     def test_compile_flags_validation(self):
         reader = self.reader('compile-flags-field-validation')
 
         with self.assertRaisesRegexp(BuildReaderError, 'Invalid value.'):
             self.read_topsrcdir(reader)
@@ -296,73 +348,73 @@ class TestEmitterBasic(unittest.TestCase
         reader = self.reader('compile-flags-templates', extra_substs={
             'NSPR_CFLAGS': ['-I/nspr/path'],
             'NSS_CFLAGS': ['-I/nss/path'],
             'MOZ_JPEG_CFLAGS': ['-I/jpeg/path'],
             'MOZ_PNG_CFLAGS': ['-I/png/path'],
             'MOZ_ZLIB_CFLAGS': ['-I/zlib/path'],
             'MOZ_PIXMAN_CFLAGS': ['-I/pixman/path'],
         })
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['STL'], [])
         self.assertEqual(flags.flags['VISIBILITY'], [])
         self.assertEqual(flags.flags['OS_INCLUDES'], [
             '-I/nspr/path',
             '-I/nss/path',
             '-I/jpeg/path',
             '-I/png/path',
             '-I/zlib/path',
             '-I/pixman/path',
         ])
 
     def test_disable_stl_wrapping(self):
         reader = self.reader('disable-stl-wrapping')
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['STL'], [])
 
     def test_visibility_flags(self):
         reader = self.reader('visibility-flags')
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['VISIBILITY'], [])
 
     def test_defines_in_flags(self):
         reader = self.reader('compile-defines')
-        defines, sources, lib, flags = self.read_topsrcdir(reader)
+        defines, sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['LIBRARY_DEFINES'],
                          ['-DMOZ_LIBRARY_DEFINE=MOZ_TEST'])
         self.assertEqual(flags.flags['DEFINES'],
                          ['-DMOZ_TEST_DEFINE'])
 
     def test_resolved_flags_error(self):
         reader = self.reader('resolved-flags-error')
         with self.assertRaisesRegexp(BuildReaderError,
             "`DEFINES` may not be set in COMPILE_FLAGS from moz.build"):
             self.read_topsrcdir(reader)
 
     def test_includes_in_flags(self):
         reader = self.reader('compile-includes')
-        defines, sources, lib, flags = self.read_topsrcdir(reader)
+        defines, sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(flags, ComputedFlags)
         self.assertEqual(flags.flags['BASE_INCLUDES'],
                          ['-I%s' % reader.config.topsrcdir,
                           '-I%s' % reader.config.topobjdir])
         self.assertEqual(flags.flags['EXTRA_INCLUDES'],
                          ['-I%s/dist/include' % reader.config.topobjdir])
         self.assertEqual(flags.flags['LOCAL_INCLUDES'],
                          ['-I%s/subdir' % reader.config.topsrcdir])
 
     def test_allow_compiler_warnings(self):
         reader = self.reader('allow-compiler-warnings', extra_substs={
             'WARNINGS_AS_ERRORS': '-Werror',
         })
-        sources, lib, flags = self.read_topsrcdir(reader)
+        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
         self.assertEqual(flags.flags['WARNINGS_AS_ERRORS'], [])
 
     def test_use_yasm(self):
         # When yasm is not available, this should raise.
         reader = self.reader('use-yasm')
         with self.assertRaisesRegexp(SandboxValidationError,
             'yasm is not available'):
             self.read_topsrcdir(reader)
@@ -538,25 +590,26 @@ class TestEmitterBasic(unittest.TestCase
         icons = files._children['icons']
 
         self.assertEqual(icons._strings, ['quux.icns'])
 
     def test_program(self):
         reader = self.reader('program')
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 4)
+        self.assertEqual(len(objs), 5)
         self.assertIsInstance(objs[0], ComputedFlags)
-        self.assertIsInstance(objs[1], Program)
-        self.assertIsInstance(objs[2], SimpleProgram)
+        self.assertIsInstance(objs[1], ComputedFlags)
+        self.assertIsInstance(objs[2], Program)
         self.assertIsInstance(objs[3], SimpleProgram)
+        self.assertIsInstance(objs[4], SimpleProgram)
 
-        self.assertEqual(objs[1].program, 'test_program.prog')
-        self.assertEqual(objs[2].program, 'test_program1.prog')
-        self.assertEqual(objs[3].program, 'test_program2.prog')
+        self.assertEqual(objs[2].program, 'test_program.prog')
+        self.assertEqual(objs[3].program, 'test_program1.prog')
+        self.assertEqual(objs[4].program, 'test_program2.prog')
 
     def test_test_manifest_missing_manifest(self):
         """A missing manifest file should result in an error."""
         reader = self.reader('test-manifest-missing-manifest')
 
         with self.assertRaisesRegexp(BuildReaderError, 'IOError: Missing files'):
             self.read_topsrcdir(reader)
 
@@ -931,17 +984,18 @@ class TestEmitterBasic(unittest.TestCase
             self.read_topsrcdir(reader)
 
     def test_library_defines(self):
         """Test that LIBRARY_DEFINES is propagated properly."""
         reader = self.reader('library-defines')
         objs = self.read_topsrcdir(reader)
 
         libraries = [o for o in objs if isinstance(o,StaticLibrary)]
-        library_flags = [o for o in objs if isinstance(o, ComputedFlags)]
+        library_flags = [o for o in objs if isinstance(o, ComputedFlags)
+                         and 'LIBRARY_DEFINES' in o.flags]
         expected = {
             'liba': '-DIN_LIBA',
             'libb': '-DIN_LIBA -DIN_LIBB',
             'libc': '-DIN_LIBA -DIN_LIBB',
             'libd': ''
         }
         defines = {}
         for lib in libraries:
@@ -957,16 +1011,18 @@ class TestEmitterBasic(unittest.TestCase
         reader = self.reader('sources')
         objs = self.read_topsrcdir(reader)
 
         computed_flags = objs.pop()
         self.assertIsInstance(computed_flags, ComputedFlags)
         # The second to last object is a Linkable.
         linkable = objs.pop()
         self.assertTrue(linkable.cxx_link)
+        ld_flags = objs.pop()
+        self.assertIsInstance(ld_flags, ComputedFlags)
         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 = {
@@ -1013,17 +1069,17 @@ class TestEmitterBasic(unittest.TestCase
         reader = self.reader('generated-sources')
         objs = self.read_topsrcdir(reader)
 
         flags = objs.pop()
         self.assertIsInstance(flags, ComputedFlags)
         # The second to last object is a Linkable.
         linkable = objs.pop()
         self.assertTrue(linkable.cxx_link)
-        self.assertEqual(len(objs), 6)
+        self.assertEqual(len(objs), 7)
 
         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)
 
         expected = {
@@ -1047,18 +1103,21 @@ class TestEmitterBasic(unittest.TestCase
 
         # This objdir will generate target flags.
         flags = objs.pop()
         self.assertIsInstance(flags, ComputedFlags)
         # The second to last object is a Linkable
         linkable = objs.pop()
         self.assertTrue(linkable.cxx_link)
         # This objdir will also generate host flags.
-        flags = objs.pop()
-        self.assertIsInstance(flags, ComputedFlags)
+        host_flags = objs.pop()
+        self.assertIsInstance(host_flags, ComputedFlags)
+        # ...and ldflags.
+        ldflags = objs.pop()
+        self.assertIsInstance(ldflags, ComputedFlags)
         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 = {
@@ -1073,18 +1132,18 @@ class TestEmitterBasic(unittest.TestCase
                 [mozpath.join(reader.config.topsrcdir, f) for f in files])
 
     def test_unified_sources(self):
         """Test that UNIFIED_SOURCES works properly."""
         reader = self.reader('unified-sources')
         objs = self.read_topsrcdir(reader)
 
         # The last object is a Linkable, the second to last ComputedFlags,
-        # ignore them.
-        objs = objs[:-2]
+        # followed by ldflags, ignore them.
+        objs = objs[:-3]
         self.assertEqual(len(objs), 3)
         for o in objs:
             self.assertIsInstance(o, UnifiedSources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 3)
 
         expected = {
@@ -1100,18 +1159,18 @@ class TestEmitterBasic(unittest.TestCase
             self.assertTrue(sources.have_unified_mapping)
 
     def test_unified_sources_non_unified(self):
         """Test that UNIFIED_SOURCES with FILES_PER_UNIFIED_FILE=1 works properly."""
         reader = self.reader('unified-sources-non-unified')
         objs = self.read_topsrcdir(reader)
 
         # The last object is a Linkable, the second to last ComputedFlags,
-        # ignore them.
-        objs = objs[:-2]
+        # followed by ldflags, ignore them.
+        objs = objs[:-3]
         self.assertEqual(len(objs), 3)
         for o in objs:
             self.assertIsInstance(o, UnifiedSources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 3)
 
         expected = {
@@ -1327,42 +1386,45 @@ class TestEmitterBasic(unittest.TestCase
         self.assertEquals([p.full_path for p in objs[0].paths], expected)
 
     def test_install_shared_lib(self):
         """Test that we can install a shared library with TEST_HARNESS_FILES"""
         reader = self.reader('test-install-shared-lib')
         objs = self.read_topsrcdir(reader)
         self.assertIsInstance(objs[0], TestHarnessFiles)
         self.assertIsInstance(objs[1], VariablePassthru)
-        self.assertIsInstance(objs[2], SharedLibrary)
-        self.assertIsInstance(objs[3], ComputedFlags)
+        self.assertIsInstance(objs[2], ComputedFlags)
+        self.assertIsInstance(objs[3], SharedLibrary)
+        self.assertIsInstance(objs[4], ComputedFlags)
         for path, files in objs[0].files.walk():
             for f in files:
                 self.assertEqual(str(f), '!libfoo.so')
                 self.assertEqual(path, 'foo/bar')
 
     def test_symbols_file(self):
         """Test that SYMBOLS_FILE works"""
         reader = self.reader('test-symbols-file')
-        genfile, shlib, flags = self.read_topsrcdir(reader)
+        genfile, ldflags, shlib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(genfile, GeneratedFile)
         self.assertIsInstance(flags, ComputedFlags)
+        self.assertIsInstance(ldflags, ComputedFlags)
         self.assertIsInstance(shlib, SharedLibrary)
         # This looks weird but MockConfig sets DLL_{PREFIX,SUFFIX} and
         # the reader method in this class sets OS_TARGET=WINNT.
         self.assertEqual(shlib.symbols_file, 'libfoo.so.def')
 
     def test_symbols_file_objdir(self):
         """Test that a SYMBOLS_FILE in the objdir works"""
         reader = self.reader('test-symbols-file-objdir')
-        genfile, shlib, flags = self.read_topsrcdir(reader)
+        genfile, ldflags, shlib, flags = self.read_topsrcdir(reader)
         self.assertIsInstance(genfile, GeneratedFile)
         self.assertEqual(genfile.script,
                          mozpath.join(reader.config.topsrcdir, 'foo.py'))
         self.assertIsInstance(flags, ComputedFlags)
+        self.assertIsInstance(ldflags, ComputedFlags)
         self.assertIsInstance(shlib, SharedLibrary)
         self.assertEqual(shlib.symbols_file, 'foo.symbols')
 
     def test_symbols_file_objdir_missing_generated(self):
         """Test that a SYMBOLS_FILE in the objdir that's missing
         from GENERATED_FILES is an error.
         """
         reader = self.reader('test-symbols-file-objdir-missing-generated')