Bug 1429875 - Remove expandlibs and instead generate list files in the mozbuild backend. draft
authorChris Manchester <cmanchester@mozilla.com>
Tue, 20 Mar 2018 16:31:05 -0700
changeset 770317 d47237e7438188c9a4f85ef877c509f07424bfb8
parent 770316 cc03637d59c26ea88240dbfa83a642c53926be2f
child 770318 88ec2b29db54fd370462ab041d0b62bc6ca0c5d9
child 770685 4b7b86a569d7f87f8f6cd7caf77a2741ada04da5
push id103374
push userbmo:cmanchester@mozilla.com
push dateTue, 20 Mar 2018 23:37:52 +0000
bugs1429875
milestone61.0a1
Bug 1429875 - Remove expandlibs and instead generate list files in the mozbuild backend. MozReview-Commit-ID: 5eLwnh1HHGj
build/clang-plugin/Makefile.in
config/config.mk
config/expandlibs.py
config/expandlibs_config.py
config/expandlibs_exec.py
config/expandlibs_gen.py
config/rules.mk
config/tests/python.ini
config/tests/unit-expandlibs.py
python/mozbuild/mozbuild/backend/common.py
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/test/backend/common.py
python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar2.cc
python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/bar_helper1.cpp
python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/moz.build
python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
--- a/build/clang-plugin/Makefile.in
+++ b/build/clang-plugin/Makefile.in
@@ -8,20 +8,16 @@ include $(topsrcdir)/config/config.mk
 # variable to limit ourselves to what we need to build the clang plugin.
 ifneq ($(HOST_OS_ARCH),WINNT)
 DSO_LDOPTS := -shared
 endif
 
 ifeq ($(HOST_OS_ARCH)_$(OS_ARCH),Linux_Darwin)
 # Use the host compiler instead of the target compiler.
 CXX := $(HOST_CXX)
-# expandlibs doesn't know the distinction between host and target toolchains,
-# and on cross linux/darwin builds, the options to give to the linker for file
-# lists differ between both, so don't use file lists.
-EXPAND_MKSHLIB_ARGS :=
 endif
 
 # Use the default OS X deployment target to enable using the libc++ headers
 # correctly.  Note that the binary produced here is a host tool and doesn't need
 # to be distributed.
 MACOSX_DEPLOYMENT_TARGET :=
 
 # Temporarily relax the requirements for libstdc++ symbol versions on static
--- a/config/config.mk
+++ b/config/config.mk
@@ -413,28 +413,16 @@ ifdef MOZ_DEBUG
 JAVAC_FLAGS += -g
 endif
 
 CREATE_PRECOMPLETE_CMD = $(PYTHON) $(abspath $(MOZILLA_DIR)/config/createprecomplete.py)
 
 # MDDEPDIR is the subdirectory where dependency files are stored
 MDDEPDIR := .deps
 
-EXPAND_LIBS_EXEC = $(PYTHON) $(MOZILLA_DIR)/config/expandlibs_exec.py
-EXPAND_LIBS_GEN = $(PYTHON) $(MOZILLA_DIR)/config/expandlibs_gen.py
-EXPAND_AR = $(EXPAND_LIBS_EXEC) --extract -- $(AR)
-EXPAND_CC = $(EXPAND_LIBS_EXEC) --uselist -- $(CC)
-EXPAND_CCC = $(EXPAND_LIBS_EXEC) --uselist -- $(CCC)
-EXPAND_LINK = $(EXPAND_LIBS_EXEC) --uselist -- $(LINKER)
-EXPAND_MKSHLIB_ARGS = --uselist
-ifdef SYMBOL_ORDER
-EXPAND_MKSHLIB_ARGS += --symbol-order $(SYMBOL_ORDER)
-endif
-EXPAND_MKSHLIB = $(EXPAND_LIBS_EXEC) $(EXPAND_MKSHLIB_ARGS) -- $(MKSHLIB)
-
 # $(call CHECK_SYMBOLS,lib,PREFIX,dep_name,test)
 # Checks that the given `lib` doesn't contain dependency on symbols with a
 # version starting with `PREFIX`_ and matching the `test`. `dep_name` is only
 # used for the error message.
 # `test` is an awk expression using the information in the variable `v` which
 # contains a list of version items ([major, minor, ...]).
 define CHECK_SYMBOLS
 @$(TOOLCHAIN_PREFIX)readelf -sW $(1) | \
deleted file mode 100644
--- a/config/expandlibs.py
+++ /dev/null
@@ -1,143 +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/.
-
-'''Expandlibs is a system that allows to replace some libraries with a
-descriptor file containing some linking information about them.
-
-The descriptor file format is as follows:
----8<-----
-OBJS = a.o b.o ...
-LIBS = libfoo.a libbar.a ...
---->8-----
-
-(In the example above, OBJ_SUFFIX is o and LIB_SUFFIX is a).
-
-Expandlibs also canonicalizes how to pass libraries to the linker, such
-that only the ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} form needs to be used:
-given a list of files, expandlibs will replace items with the form
-${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} following these rules:
-
-- If a ${DLL_PREFIX}${ROOT}.${DLL_SUFFIX} or
-  ${DLL_PREFIX}${ROOT}.${IMPORT_LIB_SUFFIX} file exists, use that instead
-- If the ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} file exists, use it
-- If a ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX}.${LIB_DESC_SUFFIX} file exists,
-  replace ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} with the OBJS and LIBS the
-  descriptor contains. And for each of these LIBS, also apply the same
-  rules.
-'''
-from __future__ import with_statement
-import sys, os, errno
-import expandlibs_config as conf
-
-def ensureParentDir(file):
-    '''Ensures the directory parent to the given file exists'''
-    dir = os.path.dirname(file)
-    if dir and not os.path.exists(dir):
-        try:
-            os.makedirs(dir)
-        except OSError, error:
-            if error.errno != errno.EEXIST:
-                raise
-
-def relativize(path):
-    '''Returns a path relative to the current working directory, if it is
-    shorter than the given path'''
-    def splitpath(path):
-        dir, file = os.path.split(path)
-        if os.path.splitdrive(dir)[1] == os.sep:
-            return [file]
-        return splitpath(dir) + [file]
-
-    if not os.path.exists(path):
-        return path
-    curdir = splitpath(os.path.abspath(os.curdir))
-    abspath = splitpath(os.path.abspath(path))
-    while curdir and abspath and curdir[0] == abspath[0]:
-        del curdir[0]
-        del abspath[0]
-    if not curdir and not abspath:
-        return '.'
-    relpath = os.path.join(*[os.pardir for i in curdir] + abspath)
-    if len(path) > len(relpath):
-        return relpath
-    return path
-
-def isObject(path):
-    '''Returns whether the given path points to an object file, that is,
-    ends with OBJ_SUFFIX or .i_o'''
-    return os.path.splitext(path)[1] in [conf.OBJ_SUFFIX, '.i_o']
-
-def isDynamicLib(path):
-    '''Returns whether the given path points to a dynamic library, that is,
-    ends with DLL_SUFFIX.'''
-    # On mac, the xul library is named XUL, instead of libxul.dylib. Assume any
-    # file by that name is a dynamic library.
-    return os.path.splitext(path)[1] == conf.DLL_SUFFIX or os.path.basename(path) == 'XUL'
-
-class LibDescriptor(dict):
-    KEYS = ['OBJS', 'LIBS']
-
-    def __init__(self, content=None):
-        '''Creates an instance of a lib descriptor, initialized with contents
-        from a list of strings when given. This is intended for use with
-        file.readlines()'''
-        if isinstance(content, list) and all([isinstance(item, str) for item in content]):
-            pass
-        elif content is not None:
-            raise TypeError("LibDescriptor() arg 1 must be None or a list of strings")
-        super(LibDescriptor, self).__init__()
-        for key in self.KEYS:
-            self[key] = []
-        if not content:
-            return
-        for key, value in [(s.strip() for s in item.split('=', 2)) for item in content if item.find('=') >= 0]:
-            if key in self.KEYS:
-                self[key] = value.split()
-
-    def __str__(self):
-        '''Serializes the lib descriptor'''
-        return '\n'.join('%s = %s' % (k, ' '.join(self[k])) for k in self.KEYS if len(self[k]))
-
-class ExpandArgs(list):
-    def __init__(self, args):
-        '''Creates a clone of the |args| list and performs file expansion on
-        each item it contains'''
-        super(ExpandArgs, self).__init__()
-        self._descs = set()
-        for arg in args:
-            self += self._expand(arg)
-
-    def _expand(self, arg):
-        '''Internal function doing the actual work'''
-        (root, ext) = os.path.splitext(arg)
-        if ext != conf.LIB_SUFFIX or not os.path.basename(root).startswith(conf.LIB_PREFIX):
-            return [relativize(arg)]
-        if conf.LIB_PREFIX:
-            dll = root.replace(conf.LIB_PREFIX, conf.DLL_PREFIX, 1) + conf.DLL_SUFFIX
-        else:
-            dll = root + conf.DLL_SUFFIX
-        if os.path.exists(dll):
-            if conf.IMPORT_LIB_SUFFIX:
-                return [relativize(root + conf.IMPORT_LIB_SUFFIX)]
-            else:
-                return [relativize(dll)]
-        return self._expand_desc(arg)
-
-    def _expand_desc(self, arg):
-        '''Internal function taking care of lib descriptor expansion only'''
-        desc = os.path.abspath(arg + conf.LIBS_DESC_SUFFIX)
-        if os.path.exists(desc):
-            if desc in self._descs:
-                return []
-            self._descs.add(desc)
-            with open(desc, 'r') as f:
-                desc = LibDescriptor(f.readlines())
-            objs = [relativize(o) for o in desc['OBJS']]
-            for lib in desc['LIBS']:
-                objs += self._expand(lib)
-            return objs
-        return [relativize(arg)]
-
-if __name__ == '__main__':
-    print " ".join(ExpandArgs(sys.argv[1:]))
deleted file mode 100644
--- a/config/expandlibs_config.py
+++ /dev/null
@@ -1,29 +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/.
-
-from buildconfig import substs
-
-def normalize_suffix(suffix):
-    '''Returns a normalized suffix, i.e. ensures it starts with a dot and
-    doesn't starts or ends with whitespace characters'''
-    value = suffix.strip()
-    if len(value) and not value.startswith('.'):
-        value = '.' + value
-    return value
-
-# Variables from the build system
-AR = substs['AR']
-AR_EXTRACT = substs['AR_EXTRACT'].replace('$(AR)', AR)
-DLL_PREFIX = substs['DLL_PREFIX']
-LIB_PREFIX = substs['LIB_PREFIX']
-RUST_LIB_PREFIX = substs['RUST_LIB_PREFIX']
-OBJ_SUFFIX = normalize_suffix(substs['OBJ_SUFFIX'])
-LIB_SUFFIX = normalize_suffix(substs['LIB_SUFFIX'])
-RUST_LIB_SUFFIX = normalize_suffix(substs['RUST_LIB_SUFFIX'])
-DLL_SUFFIX = normalize_suffix(substs['DLL_SUFFIX'])
-IMPORT_LIB_SUFFIX = normalize_suffix(substs['IMPORT_LIB_SUFFIX'])
-LIBS_DESC_SUFFIX = normalize_suffix(substs['LIBS_DESC_SUFFIX'])
-EXPAND_LIBS_LIST_STYLE = substs['EXPAND_LIBS_LIST_STYLE']
-EXPAND_LIBS_ORDER_STYLE = substs['EXPAND_LIBS_ORDER_STYLE']
-LD_PRINT_ICF_SECTIONS = substs['LD_PRINT_ICF_SECTIONS']
deleted file mode 100644
--- a/config/expandlibs_exec.py
+++ /dev/null
@@ -1,354 +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/.
-
-'''expandlibs-exec.py applies expandlibs rules, and some more (see below) to
-a given command line, and executes that command line with the expanded
-arguments.
-
-With the --extract argument (useful for e.g. $(AR)), it extracts object files
-from static libraries (or use those listed in library descriptors directly).
-
-With the --uselist argument (useful for e.g. $(CC)), it replaces all object
-files with a list file. This can be used to avoid limitations in the length
-of a command line. The kind of list file format used depends on the
-EXPAND_LIBS_LIST_STYLE variable: 'list' for MSVC style lists (@file.list)
-or 'linkerscript' for GNU ld linker scripts.
-See https://bugzilla.mozilla.org/show_bug.cgi?id=584474#c59 for more details.
-
-With the --symbol-order argument, followed by a file name, it will add the
-relevant linker options to change the order in which the linker puts the
-symbols appear in the resulting binary. Only works for ELF targets.
-'''
-from __future__ import with_statement
-import sys
-import os
-from expandlibs import (
-    ExpandArgs,
-    relativize,
-    isDynamicLib,
-    isObject,
-)
-import expandlibs_config as conf
-from optparse import OptionParser
-import subprocess
-import tempfile
-import shutil
-import subprocess
-import re
-from mozbuild.makeutil import Makefile
-
-# The are the insert points for a GNU ld linker script, assuming a more
-# or less "standard" default linker script. This is not a dict because
-# order is important.
-SECTION_INSERT_BEFORE = [
-  ('.text', '.fini'),
-  ('.rodata', '.rodata1'),
-  ('.data.rel.ro', '.dynamic'),
-  ('.data', '.data1'),
-]
-
-class ExpandArgsMore(ExpandArgs):
-    ''' Meant to be used as 'with ExpandArgsMore(args) as ...: '''
-    def __enter__(self):
-        self.tmp = []
-        return self
-        
-    def __exit__(self, type, value, tb):
-        '''Automatically remove temporary files'''
-        for tmp in self.tmp:
-            if os.path.isdir(tmp):
-                shutil.rmtree(tmp, True)
-            else:
-                os.remove(tmp)
-
-    def extract(self):
-        self[0:] = self._extract(self)
-
-    def _extract(self, args):
-        '''When a static library name is found, either extract its contents
-        in a temporary directory or use the information found in the
-        corresponding lib descriptor.
-        '''
-        ar_extract = conf.AR_EXTRACT.split()
-        newlist = []
-
-        def lookup(base, f):
-            for root, dirs, files in os.walk(base):
-                if f in files:
-                    return os.path.join(root, f)
-
-        for arg in args:
-            if os.path.splitext(arg)[1] == conf.LIB_SUFFIX:
-                if os.path.exists(arg + conf.LIBS_DESC_SUFFIX):
-                    newlist += self._extract(self._expand_desc(arg))
-                    continue
-                elif os.path.exists(arg) and (len(ar_extract) or conf.AR == 'lib'):
-                    tmp = tempfile.mkdtemp(dir=os.curdir)
-                    self.tmp.append(tmp)
-                    if conf.AR == 'lib':
-                        out = subprocess.check_output([conf.AR, '-NOLOGO', '-LIST', arg])
-                        files = out.splitlines()
-                        # If lib -list returns a list full of dlls, it's an
-                        # import lib.
-                        if all(isDynamicLib(f) for f in files):
-                            newlist += [arg]
-                            continue
-                        for f in files:
-                            subprocess.call([conf.AR, '-NOLOGO', '-EXTRACT:%s' % f, os.path.abspath(arg)], cwd=tmp)
-                    else:
-                        subprocess.call(ar_extract + [os.path.abspath(arg)], cwd=tmp)
-                    objs = []
-                    basedir = os.path.dirname(arg)
-                    for root, dirs, files in os.walk(tmp):
-                        for f in files:
-                            if isObject(f):
-                                # If the file extracted from the library also
-                                # exists in the directory containing the
-                                # library, or one of its subdirectories, use
-                                # that instead.
-                                maybe_obj = lookup(os.path.join(basedir, os.path.relpath(root, tmp)), f)
-                                if maybe_obj:
-                                    objs.append(relativize(maybe_obj))
-                                else:
-                                    objs.append(relativize(os.path.join(root, f)))
-                    newlist += sorted(objs)
-                    continue
-            newlist += [arg]
-        return newlist
-
-    def makelist(self):
-        '''Replaces object file names with a temporary list file, using a
-        list format depending on the EXPAND_LIBS_LIST_STYLE variable
-        '''
-        objs = [o for o in self if isObject(o)]
-        if not len(objs): return
-        fd, tmp = tempfile.mkstemp(suffix=".list",dir=os.curdir)
-        if conf.EXPAND_LIBS_LIST_STYLE == "linkerscript":
-            content = ['INPUT("%s")\n' % obj for obj in objs]
-            ref = tmp
-        elif conf.EXPAND_LIBS_LIST_STYLE == "filelist":
-            content = ["%s\n" % obj for obj in objs]
-            ref = "-Wl,-filelist," + tmp
-        elif conf.EXPAND_LIBS_LIST_STYLE == "list":
-            content = ["%s\n" % obj for obj in objs]
-            ref = "@" + tmp
-        else:
-            os.close(fd)
-            os.remove(tmp)
-            return
-        self.tmp.append(tmp)
-        f = os.fdopen(fd, "w")
-        f.writelines(content)
-        f.close()
-        idx = self.index(objs[0])
-        newlist = self[0:idx] + [ref] + [os.path.normpath(item) for item in self[idx:] if item not in objs]
-        self[0:] = newlist
-
-    def _getFoldedSections(self):
-        '''Returns a dict about folded sections.
-        When section A and B are folded into section C, the dict contains:
-        { 'A': 'C',
-          'B': 'C',
-          'C': ['A', 'B'] }'''
-        if not conf.LD_PRINT_ICF_SECTIONS:
-            return {}
-
-        proc = subprocess.Popen(self + [conf.LD_PRINT_ICF_SECTIONS], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
-        (stdout, stderr) = proc.communicate()
-        result = {}
-        # gold's --print-icf-sections output looks like the following:
-        # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o'
-        # In terms of words, chances are this will change in the future,
-        # especially considering "into" is misplaced. Splitting on quotes
-        # seems safer.
-        for l in stderr.split('\n'):
-            quoted = l.split("'")
-            if len(quoted) > 5 and quoted[1] != quoted[5]:
-                result[quoted[1]] = [quoted[5]]
-                if quoted[5] in result:
-                    result[quoted[5]].append(quoted[1])
-                else:
-                    result[quoted[5]] = [quoted[1]]
-        return result
-
-    def _getOrderedSections(self, ordered_symbols):
-        '''Given an ordered list of symbols, returns the corresponding list
-        of sections following the order.'''
-        if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
-            raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
-        finder = SectionFinder([arg for arg in self if isObject(arg) or os.path.splitext(arg)[1] == conf.LIB_SUFFIX])
-        folded = self._getFoldedSections()
-        sections = set()
-        ordered_sections = []
-        for symbol in ordered_symbols:
-            symbol_sections = finder.getSections(symbol)
-            all_symbol_sections = []
-            for section in symbol_sections:
-                if section in folded:
-                    if isinstance(folded[section], str):
-                        section = folded[section]
-                    all_symbol_sections.append(section)
-                    all_symbol_sections.extend(folded[section])
-                else:
-                    all_symbol_sections.append(section)
-            for section in all_symbol_sections:
-                if not section in sections:
-                    ordered_sections.append(section)
-                    sections.add(section)
-        return ordered_sections
-
-    def orderSymbols(self, order):
-        '''Given a file containing a list of symbols, adds the appropriate
-        argument to make the linker put the symbols in that order.'''
-        with open(order) as file:
-            sections = self._getOrderedSections([l.strip() for l in file.readlines() if l.strip()])
-        split_sections = {}
-        linked_sections = [s[0] for s in SECTION_INSERT_BEFORE]
-        for s in sections:
-            for linked_section in linked_sections:
-                if s.startswith(linked_section):
-                    if linked_section in split_sections:
-                        split_sections[linked_section].append(s)
-                    else:
-                        split_sections[linked_section] = [s]
-                    break
-        content = []
-        # Order is important
-        linked_sections = [s for s in linked_sections if s in split_sections]
-
-        if conf.EXPAND_LIBS_ORDER_STYLE == 'section-ordering-file':
-            option = '-Wl,--section-ordering-file,%s'
-            content = sections
-            for linked_section in linked_sections:
-                content.extend(split_sections[linked_section])
-                content.append('%s.*' % linked_section)
-                content.append(linked_section)
-
-        elif conf.EXPAND_LIBS_ORDER_STYLE == 'linkerscript':
-            option = '-Wl,-T,%s'
-            section_insert_before = dict(SECTION_INSERT_BEFORE)
-            for linked_section in linked_sections:
-                content.append('SECTIONS {')
-                content.append('  %s : {' % linked_section)
-                content.extend('    *(%s)' % s for s in split_sections[linked_section])
-                content.append('  }')
-                content.append('}')
-                content.append('INSERT BEFORE %s' % section_insert_before[linked_section])
-        else:
-            raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
-
-        fd, tmp = tempfile.mkstemp(dir=os.curdir)
-        f = os.fdopen(fd, "w")
-        f.write('\n'.join(content)+'\n')
-        f.close()
-        self.tmp.append(tmp)
-        self.append(option % tmp)
-
-class SectionFinder(object):
-    '''Instances of this class allow to map symbol names to sections in
-    object files.'''
-
-    def __init__(self, objs):
-        '''Creates an instance, given a list of object files.'''
-        if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
-            raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
-        self.mapping = {}
-        for obj in objs:
-            if not isObject(obj) and os.path.splitext(obj)[1] != conf.LIB_SUFFIX:
-                raise Exception('%s is not an object nor a static library' % obj)
-            for symbol, section in SectionFinder._getSymbols(obj):
-                sym = SectionFinder._normalize(symbol)
-                if sym in self.mapping:
-                    if not section in self.mapping[sym]:
-                        self.mapping[sym].append(section)
-                else:
-                    self.mapping[sym] = [section]
-
-    def getSections(self, symbol):
-        '''Given a symbol, returns a list of sections containing it or the
-        corresponding thunks. When the given symbol is a thunk, returns the
-        list of sections containing its corresponding normal symbol and the
-        other thunks for that symbol.'''
-        sym = SectionFinder._normalize(symbol)
-        if sym in self.mapping:
-            return self.mapping[sym]
-        return []
-
-    @staticmethod
-    def _normalize(symbol):
-        '''For normal symbols, return the given symbol. For thunks, return
-        the corresponding normal symbol.'''
-        if re.match('^_ZThn[0-9]+_', symbol):
-            return re.sub('^_ZThn[0-9]+_', '_Z', symbol)
-        return symbol
-
-    @staticmethod
-    def _getSymbols(obj):
-        '''Returns a list of (symbol, section) contained in the given object
-        file.'''
-        proc = subprocess.Popen(['objdump', '-t', obj], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
-        (stdout, stderr) = proc.communicate()
-        syms = []
-        for line in stdout.splitlines():
-            # Each line has the following format:
-            # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>
-            tmp = line.split(' ',1)
-            # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"]
-            # We only need to consider cases where "<section>\t<length> <symbol>" is present,
-            # and where the [FfO] flag is either F (function) or O (object).
-            if len(tmp) > 1 and len(tmp[1]) > 6 and tmp[1][6] in ['O', 'F']:
-                tmp = tmp[1][8:].split()
-                # That gives us ["<section>","<length>", "<symbol>"]
-                syms.append((tmp[-1], tmp[0]))
-        return syms
-
-def print_command(out, args):
-    print >>out, "Executing: " + " ".join(args)
-    for tmp in [f for f in args.tmp if os.path.isfile(f)]:
-        print >>out, tmp + ":"
-        with open(tmp) as file:
-            print >>out, "".join(["    " + l for l in file.readlines()])
-    out.flush()
-
-def main(args, proc_callback=None):
-    parser = OptionParser()
-    parser.add_option("--extract", action="store_true", dest="extract",
-        help="when a library has no descriptor file, extract it first, when possible")
-    parser.add_option("--uselist", action="store_true", dest="uselist",
-        help="use a list file for objects when executing a command")
-    parser.add_option("--verbose", action="store_true", dest="verbose",
-        help="display executed command and temporary files content")
-    parser.add_option("--symbol-order", dest="symbol_order", metavar="FILE",
-        help="use the given list of symbols to order symbols in the resulting binary when using with a linker")
-
-    (options, args) = parser.parse_args(args)
-
-    with ExpandArgsMore(args) as args:
-        if options.extract:
-            args.extract()
-        if options.symbol_order:
-            args.orderSymbols(options.symbol_order)
-        if options.uselist:
-            args.makelist()
-
-        if options.verbose:
-            print_command(sys.stderr, args)
-        try:
-            proc = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
-            if proc_callback:
-                proc_callback(proc)
-        except Exception, e:
-            print >>sys.stderr, 'error: Launching', args, ':', e
-            raise e
-        (stdout, stderr) = proc.communicate()
-        if proc.returncode and not options.verbose:
-            print_command(sys.stderr, args)
-        sys.stderr.write(stdout)
-        sys.stderr.flush()
-        if proc.returncode:
-            return proc.returncode
-        return 0
-
-if __name__ == '__main__':
-    exit(main(sys.argv[1:]))
deleted file mode 100644
--- a/config/expandlibs_gen.py
+++ /dev/null
@@ -1,41 +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/.
-
-'''Given a list of object files and library names, prints a library
-descriptor to standard output'''
-
-from __future__ import with_statement
-import sys
-import os
-import expandlibs_config as conf
-from expandlibs import LibDescriptor, isObject, ensureParentDir
-from optparse import OptionParser
-
-def generate(args):
-    desc = LibDescriptor()
-    for arg in args:
-        if isObject(arg):
-            if os.path.exists(arg):
-                desc['OBJS'].append(os.path.abspath(arg))
-            else:
-                raise Exception("File not found: %s" % arg)
-        elif os.path.splitext(arg)[1] == conf.LIB_SUFFIX:
-            if os.path.exists(arg) or os.path.exists(arg + conf.LIBS_DESC_SUFFIX):
-                desc['LIBS'].append(os.path.abspath(arg))
-            else:
-                raise Exception("File not found: %s" % arg)
-    return desc
-
-if __name__ == '__main__':
-    parser = OptionParser()
-    parser.add_option("-o", dest="output", metavar="FILE",
-        help="send output to the given file")
-
-    (options, args) = parser.parse_args()
-    if not options.output:
-        raise Exception("Missing option: -o")
-
-    ensureParentDir(options.output)
-    with open(options.output, 'w') as outfile:
-        print >>outfile, generate(args)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -96,19 +96,18 @@ endif # ENABLE_TESTS
 #
 # If FORCE_STATIC_LIB is set, build a static library.
 # Otherwise, build a shared library.
 #
 
 ifndef LIBRARY
 ifdef REAL_LIBRARY
 ifdef NO_EXPAND_LIBS
+# Only build actual library if it is requested.
 LIBRARY			:= $(REAL_LIBRARY)
-else
-LIBRARY			:= $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
 endif
 endif
 endif
 
 ifndef HOST_LIBRARY
 ifdef HOST_LIBRARY_NAME
 HOST_LIBRARY		:= $(LIB_PREFIX)$(HOST_LIBRARY_NAME).$(LIB_SUFFIX)
 endif
@@ -426,32 +425,35 @@ ECHO := true
 QUIET := -q
 endif
 
 # Do everything from scratch
 everything::
 	$(MAKE) clean
 	$(MAKE) all
 
-STATIC_LIB_DEP = $(if $(wildcard $(1).$(LIBS_DESC_SUFFIX)),$(1).$(LIBS_DESC_SUFFIX),$(1))
-STATIC_LIBS_DEPS := $(foreach l,$(STATIC_LIBS),$(call STATIC_LIB_DEP,$(l)))
-
 # Dependencies which, if modified, should cause everything to rebuild
 GLOBAL_DEPS += Makefile $(addprefix $(DEPTH)/config/,$(INCLUDED_AUTOCONF_MK)) $(MOZILLA_DIR)/config/config.mk
 
 ##############################################
 ifdef COMPILE_ENVIRONMENT
 OBJ_TARGETS = $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS)
 
 compile:: host target
 
 host:: $(HOST_LIBRARY) $(HOST_PROGRAM) $(HOST_SIMPLE_PROGRAMS) $(HOST_RUST_PROGRAMS) $(HOST_RUST_LIBRARY_FILE)
 
 target:: $(LIBRARY) $(SHARED_LIBRARY) $(PROGRAM) $(SIMPLE_PROGRAMS) $(RUST_LIBRARY_FILE) $(RUST_PROGRAMS)
 
+ifndef LIBRARY
+ifdef OBJS
+target:: $(OBJS)
+endif
+endif
+
 syms::
 
 include $(MOZILLA_DIR)/config/makefiles/target_binaries.mk
 endif
 
 ##############################################
 ifneq (1,$(NO_PROFILE_GUIDED_OPTIMIZE))
 ifdef MOZ_PROFILE_USE
@@ -525,28 +527,28 @@ distclean::
 	$(wildcard *.$(LIB_SUFFIX)) $(wildcard *$(DLL_SUFFIX)) \
 	$(wildcard *.$(IMPORT_LIB_SUFFIX))
 
 alltags:
 	$(RM) TAGS
 	find $(topsrcdir) -name dist -prune -o \( -name '*.[hc]' -o -name '*.cp' -o -name '*.cpp' -o -name '*.idl' \) -print | $(TAG_PROGRAM)
 
 define EXPAND_CC_OR_CXX
-$(if $(PROG_IS_C_ONLY_$(1)),$(EXPAND_CC),$(EXPAND_CCC))
+$(if $(PROG_IS_C_ONLY_$(1)),$(CC),$(CCC))
 endef
 
 #
 # PROGRAM = Foo
 # creates OBJS, links with LIBS to create Foo
 #
-$(PROGRAM): $(PROGOBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(RESFILE) $(GLOBAL_DEPS) $(call mkdir_deps,$(FINAL_TARGET))
+$(PROGRAM): $(PROGOBJS) $(STATIC_LIBS) $(EXTRA_DEPS) $(RESFILE) $(GLOBAL_DEPS) $(call mkdir_deps,$(FINAL_TARGET))
 	$(REPORT_BUILD)
 	@$(RM) $@.manifest
 ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
-	$(EXPAND_LINK) -NOLOGO -OUT:$@ -PDB:$(LINK_PDBFILE) -IMPLIB:$(basename $(@F)).lib $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(PROGOBJS) $(RESFILE) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
+	$(LINKER) -NOLOGO -OUT:$@ -PDB:$(LINK_PDBFILE) -IMPLIB:$(basename $(@F)).lib $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $($(notdir $@)_OBJS) $(RESFILE) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$(notdir $@).manifest'; then \
 			echo 'Embedding manifest from $(srcdir)/$(notdir $@).manifest and $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST '$(win_srcdir)/$(notdir $@).manifest' $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		else \
 			echo 'Embedding manifest from $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
@@ -557,140 +559,133 @@ ifdef MSMANIFEST_TOOL
 	fi
 endif	# MSVC with manifest tool
 ifdef MOZ_PROFILE_GENERATE
 # touch it a few seconds into the future to work around FAT's
 # 2-second granularity
 	touch -t `date +%Y%m%d%H%M.%S -d 'now+5seconds'` pgo.relink
 endif
 else # !WINNT || GNU_CC
-	$(call EXPAND_CC_OR_CXX,$@) -o $@ $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) $(PROGOBJS) $(RESFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
+	$(call EXPAND_CC_OR_CXX,$@) -o $@ $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) $($(notdir $@)_OBJS) $(RESFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
 	$(call CHECK_BINARY,$@)
 endif # WINNT && !GNU_CC
 
 ifdef ENABLE_STRIP
 	$(STRIP) $(STRIP_FLAGS) $@
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
 endif
 
 $(HOST_PROGRAM): $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (_WINNT,$(GNU_CC)_$(HOST_OS_ARCH))
-	$(EXPAND_LIBS_EXEC) -- $(LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $(HOST_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $(HOST_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$@.manifest'; then \
 			echo 'Embedding manifest from $(srcdir)/$@.manifest and $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST '$(win_srcdir)/$@.manifest' $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		else \
 			echo 'Embedding manifest from $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		fi; \
 	elif test -f '$(srcdir)/$@.manifest'; then \
 		echo 'Embedding manifest from $(srcdir)/$@.manifest'; \
 		$(MT) -NOLOGO -MANIFEST '$(win_srcdir)/$@.manifest' -OUTPUTRESOURCE:$@\;1; \
 	fi
 endif	# MSVC with manifest tool
 else
 ifeq ($(HOST_CPP_PROG_LINK),1)
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CXX) -o $@ $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_CXX) -o $@ $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) -o $@ $(HOST_C_LDFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_CC) -o $@ $(HOST_C_LDFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif # HOST_CPP_PROG_LINK
 endif
 ifndef CROSS_COMPILE
 	$(call CHECK_STDCXX,$@)
 endif
 
 #
 # This is an attempt to support generation of multiple binaries
 # in one directory, it assumes everything to compile Foo is in
 # Foo.o (from either Foo.c or Foo.cpp).
 #
 # SIMPLE_PROGRAMS = Foo Bar
 # creates Foo.o Bar.o, links with LIBS to create Foo, Bar.
 #
-$(SIMPLE_PROGRAMS): %$(BIN_SUFFIX): %.$(OBJ_SUFFIX) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
+$(SIMPLE_PROGRAMS): %$(BIN_SUFFIX): %.$(OBJ_SUFFIX) $(STATIC_LIBS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
-	$(EXPAND_LINK) -nologo -out:$@ -pdb:$(LINK_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
+	$(LINKER) -nologo -out:$@ -pdb:$(LINK_PDBFILE) $($@_OBJS) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		rm -f $@.manifest; \
 	fi
 endif	# MSVC with manifest tool
 else
-	$(call EXPAND_CC_OR_CXX,$@) $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) -o $@ $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
+	$(call EXPAND_CC_OR_CXX,$@) $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) -o $@ $($@_OBJS) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
 	$(call CHECK_BINARY,$@)
 endif # WINNT && !GNU_CC
 
 ifdef ENABLE_STRIP
 	$(STRIP) $(STRIP_FLAGS) $@
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
 endif
 
 $(HOST_SIMPLE_PROGRAMS): host_%$(HOST_BIN_SUFFIX): host_%.$(OBJ_SUFFIX) $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (WINNT_,$(HOST_OS_ARCH)_$(GNU_CC))
-	$(EXPAND_LIBS_EXEC) -- $(LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 ifneq (,$(HOST_CPPSRCS)$(USE_HOST_CXX))
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_CXX_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_CXX_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_C_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_C_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 endif
 ifndef CROSS_COMPILE
 	$(call CHECK_STDCXX,$@)
 endif
 
-$(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
+$(LIBRARY): $(OBJS) $(STATIC_LIBS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
-# Always remove both library and library descriptor
-	$(RM) $(REAL_LIBRARY) $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
-	$(EXPAND_AR) $(AR_FLAGS) $(OBJS) $(STATIC_LIBS)
-
-$(filter-out %.$(LIB_SUFFIX),$(LIBRARY)): $(filter %.$(LIB_SUFFIX),$(LIBRARY)) $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
-# When we only build a library descriptor, blow out any existing library
-	$(REPORT_BUILD)
-	$(if $(filter %.$(LIB_SUFFIX),$(LIBRARY)),,$(RM) $(REAL_LIBRARY))
-	$(EXPAND_LIBS_GEN) -o $@ $(OBJS) $(STATIC_LIBS)
+	$(RM) $(REAL_LIBRARY)
+	$(AR) $(AR_FLAGS) $(OBJS) $($@_OBJS)
 
 ifeq ($(OS_ARCH),WINNT)
 # Import libraries are created by the rules creating shared libraries.
 # The rules to copy them to $(DIST)/lib depend on $(IMPORT_LIBRARY),
 # but make will happily consider the import library before it is refreshed
 # when rebuilding the corresponding shared library. Defining an empty recipe
 # for import libraries forces make to wait for the shared library recipe to
 # have run before considering other targets that depend on the import library.
 # See bug 795204.
 $(IMPORT_LIBRARY): $(SHARED_LIBRARY) ;
 endif
 
 $(HOST_LIBRARY): $(HOST_OBJS) Makefile
 	$(REPORT_BUILD)
 	$(RM) $@
-	$(EXPAND_LIBS_EXEC) --extract -- $(HOST_AR) $(HOST_AR_FLAGS) $(HOST_OBJS)
+	$(HOST_AR) $(HOST_AR_FLAGS) $(HOST_OBJS)
 
 # On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files,
 # so instead of deleting .o files after repacking them into a dylib, we make
 # symlinks back to the originals. The symlinks are a no-op for stabs debugging,
 # so no need to conditionalize on OS version or debugging format.
 
-$(SHARED_LIBRARY): $(OBJS) $(RESFILE) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
+$(SHARED_LIBRARY): $(OBJS) $(RESFILE) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(STATIC_LIBS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifndef INCREMENTAL_LINKER
 	$(RM) $@
 endif
-	$(EXPAND_MKSHLIB) $(OBJS) $(RESFILE) $(LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(OS_LIBS)
+	$(MKSHLIB) $($@_OBJS) $(RESFILE) $(LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(OS_LIBS)
 	$(call CHECK_BINARY,$@)
 
 ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
 ifdef MSMANIFEST_TOOL
 ifdef EMBED_MANIFEST_AT
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$@.manifest'; then \
 			echo 'Embedding manifest from $(srcdir)/$@.manifest and $@.manifest'; \
--- a/config/tests/python.ini
+++ b/config/tests/python.ini
@@ -1,5 +1,4 @@
 [test_mozbuild_reading.py]
-[unit-expandlibs.py]
 [unit-mozunit.py]
 [unit-nsinstall.py]
 [unit-printprereleasesuffix.py]
deleted file mode 100644
--- a/config/tests/unit-expandlibs.py
+++ /dev/null
@@ -1,431 +0,0 @@
-import subprocess
-import unittest
-import sys
-import os
-import imp
-from tempfile import mkdtemp
-from shutil import rmtree
-import mozunit
-
-from UserString import UserString
-# Create a controlled configuration for use by expandlibs
-config_win = {
-    'AR': 'lib',
-    'AR_EXTRACT': '',
-    'DLL_PREFIX': '',
-    'LIB_PREFIX': '',
-    'OBJ_SUFFIX': '.obj',
-    'LIB_SUFFIX': '.lib',
-    'DLL_SUFFIX': '.dll',
-    'IMPORT_LIB_SUFFIX': '.lib',
-    'LIBS_DESC_SUFFIX': '.desc',
-    'EXPAND_LIBS_LIST_STYLE': 'list',
-}
-config_unix = {
-    'AR': 'ar',
-    'AR_EXTRACT': 'ar -x',
-    'DLL_PREFIX': 'lib',
-    'LIB_PREFIX': 'lib',
-    'OBJ_SUFFIX': '.o',
-    'LIB_SUFFIX': '.a',
-    'DLL_SUFFIX': '.so',
-    'IMPORT_LIB_SUFFIX': '',
-    'LIBS_DESC_SUFFIX': '.desc',
-    'EXPAND_LIBS_LIST_STYLE': 'linkerscript',
-}
-
-config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config')
-
-from expandlibs import LibDescriptor, ExpandArgs, relativize
-from expandlibs_gen import generate
-from expandlibs_exec import ExpandArgsMore, SectionFinder
-
-def Lib(name):
-    return config.LIB_PREFIX + name + config.LIB_SUFFIX
-
-def Obj(name):
-    return name + config.OBJ_SUFFIX
-
-def Dll(name):
-    return config.DLL_PREFIX + name + config.DLL_SUFFIX
-
-def ImportLib(name):
-    if not len(config.IMPORT_LIB_SUFFIX): return Dll(name)
-    return config.LIB_PREFIX + name + config.IMPORT_LIB_SUFFIX
-
-class TestRelativize(unittest.TestCase):
-    def test_relativize(self):
-        '''Test relativize()'''
-        os_path_exists = os.path.exists
-        def exists(path):
-            return True
-        os.path.exists = exists
-        self.assertEqual(relativize(os.path.abspath(os.curdir)), os.curdir)
-        self.assertEqual(relativize(os.path.abspath(os.pardir)), os.pardir)
-        self.assertEqual(relativize(os.path.join(os.curdir, 'a')), 'a')
-        self.assertEqual(relativize(os.path.join(os.path.abspath(os.curdir), 'a')), 'a')
-        # relativize is expected to return the absolute path if it is shorter
-        self.assertEqual(relativize(os.sep), os.sep)
-        os.path.exists = os.path.exists
-
-class TestLibDescriptor(unittest.TestCase):
-    def test_serialize(self):
-        '''Test LibDescriptor's serialization'''
-        desc = LibDescriptor()
-        desc[LibDescriptor.KEYS[0]] = ['a', 'b']
-        self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0]))
-        desc['unsupported-key'] = ['a']
-        self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0]))
-        desc[LibDescriptor.KEYS[1]] = ['c', 'd', 'e']
-        self.assertEqual(str(desc),
-                         "{0} = a b\n{1} = c d e"
-                         .format(LibDescriptor.KEYS[0], LibDescriptor.KEYS[1]))
-        desc[LibDescriptor.KEYS[0]] = []
-        self.assertEqual(str(desc), "{0} = c d e".format(LibDescriptor.KEYS[1]))
-
-    def test_read(self):
-        '''Test LibDescriptor's initialization'''
-        desc_list = ["# Comment",
-                     "{0} = a b".format(LibDescriptor.KEYS[1]),
-                     "", # Empty line
-                     "foo = bar", # Should be discarded
-                     "{0} = c d e".format(LibDescriptor.KEYS[0])]
-        desc = LibDescriptor(desc_list)
-        self.assertEqual(desc[LibDescriptor.KEYS[1]], ['a', 'b'])
-        self.assertEqual(desc[LibDescriptor.KEYS[0]], ['c', 'd', 'e'])
-        self.assertEqual(False, 'foo' in desc)
-
-def wrap_method(conf, wrapped_method):
-    '''Wrapper used to call a test with a specific configuration'''
-    def _method(self):
-        for key in conf:
-            setattr(config, key, conf[key])
-        self.init()
-        try:
-            wrapped_method(self)
-        except:
-            raise
-        finally:
-            self.cleanup()
-    return _method
-
-class ReplicateTests(type):
-    '''Replicates tests for unix and windows variants'''
-    def __new__(cls, clsName, bases, dict):
-        for name in [key for key in dict if key.startswith('test_')]:
-            dict[name + '_unix'] = wrap_method(config_unix, dict[name])
-            dict[name + '_unix'].__doc__ = dict[name].__doc__ + ' (unix)'
-            dict[name + '_win'] = wrap_method(config_win, dict[name])
-            dict[name + '_win'].__doc__ = dict[name].__doc__ + ' (win)'
-            del dict[name]
-        return type.__new__(cls, clsName, bases, dict)
-
-class TestCaseWithTmpDir(unittest.TestCase):
-    __metaclass__ = ReplicateTests
-    def init(self):
-        self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir))
-
-    def cleanup(self):
-        rmtree(self.tmpdir)
-
-    def touch(self, files):
-        for f in files:
-            open(f, 'w').close()
-
-    def tmpfile(self, *args):
-        return os.path.join(self.tmpdir, *args)
-
-class TestExpandLibsGen(TestCaseWithTmpDir):
-    def test_generate(self):
-        '''Test library descriptor generation'''
-        files = [self.tmpfile(f) for f in
-                 [Lib('a'), Obj('b'), Lib('c'), Obj('d'), Obj('e'), Lib('f')]]
-        self.touch(files[:-1])
-        self.touch([files[-1] + config.LIBS_DESC_SUFFIX])
-
-        desc = generate(files)
-        self.assertEqual(desc['OBJS'], [self.tmpfile(Obj(s)) for s in ['b', 'd', 'e']])
-        self.assertEqual(desc['LIBS'], [self.tmpfile(Lib(s)) for s in ['a', 'c', 'f']])
-
-        self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))])
-        self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))])
-
-class TestExpandInit(TestCaseWithTmpDir):
-    def init(self):
-        ''' Initializes test environment for library expansion tests'''
-        super(TestExpandInit, self).init()
-        # Create 2 fake libraries, each containing 3 objects, and the second
-        # including the first one and another library.
-        os.mkdir(self.tmpfile('libx'))
-        os.mkdir(self.tmpfile('liby'))
-        self.libx_files = [self.tmpfile('libx', Obj(f)) for f in ['g', 'h', 'i']]
-        self.liby_files = [self.tmpfile('liby', Obj(f)) for f in ['j', 'k', 'l']] + [self.tmpfile('liby', Lib('z'))]
-        self.touch(self.libx_files + self.liby_files)
-        with open(self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), 'w') as f:
-            f.write(str(generate(self.libx_files)))
-        with open(self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX), 'w') as f:
-            f.write(str(generate(self.liby_files + [self.tmpfile('libx', Lib('x'))])))
-
-        # Create various objects and libraries 
-        self.arg_files = [self.tmpfile(f) for f in [Lib('a'), Obj('b'), Obj('c'), Lib('d'), Obj('e')]]
-        # We always give library names (LIB_PREFIX/SUFFIX), even for
-        # dynamic/import libraries
-        self.files = self.arg_files + [self.tmpfile(ImportLib('f'))]
-        self.arg_files += [self.tmpfile(Lib('f'))]
-        self.touch(self.files)
-
-    def assertRelEqual(self, args1, args2):
-        self.assertEqual(args1, [relativize(a) for a in args2])
-
-class TestExpandArgs(TestExpandInit):
-    def test_expand(self):
-        '''Test library expansion'''
-        # Expanding arguments means libraries with a descriptor are expanded
-        # with the descriptor content, and import libraries are used when
-        # a library doesn't exist
-        args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
-        self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) 
-
-        # When a library exists at the same time as a descriptor, we still use
-        # the descriptor.
-        self.touch([self.tmpfile('libx', Lib('x'))])
-        args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
-        self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
-
-        self.touch([self.tmpfile('liby', Lib('y'))])
-        args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
-        self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
-
-class TestExpandArgsMore(TestExpandInit):
-    def test_makelist(self):
-        '''Test grouping object files in lists'''
-        # ExpandArgsMore does the same as ExpandArgs
-        with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
-            self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) 
-
-            # But also has an extra method replacing object files with a list
-            args.makelist()
-            # self.files has objects at #1, #2, #4
-            self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1])
-            self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))])
-
-            # Check the list file content
-            objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)]
-            if config.EXPAND_LIBS_LIST_STYLE == "linkerscript":
-                self.assertNotEqual(args[3][0], '@')
-                filename = args[3]
-                content = ['INPUT("{0}")'.format(relativize(f)) for f in objs]
-                with open(filename, 'r') as f:
-                    self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
-            elif config.EXPAND_LIBS_LIST_STYLE == "list":
-                self.assertEqual(args[3][0], '@')
-                filename = args[3][1:]
-                content = objs
-                with open(filename, 'r') as f:
-                    self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
-
-            tmp = args.tmp
-        # Check that all temporary files are properly removed
-        self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
-
-    def test_extract(self):
-        '''Test library extraction'''
-        # Divert subprocess.call
-        subprocess_call = subprocess.call
-        subprocess_check_output = subprocess.check_output
-        def call(args, **kargs):
-            if config.AR == 'lib':
-                self.assertEqual(args[:2], [config.AR, '-NOLOGO'])
-                self.assertTrue(args[2].startswith('-EXTRACT:'))
-                extract = [args[2][len('-EXTRACT:'):]]
-                self.assertTrue(extract)
-                args = args[3:]
-            else:
-                # The command called is always AR_EXTRACT
-                ar_extract = config.AR_EXTRACT.split()
-                self.assertEqual(args[:len(ar_extract)], ar_extract)
-                args = args[len(ar_extract):]
-            # Remaining argument is always one library
-            self.assertEqual(len(args), 1)
-            arg = args[0]
-            self.assertEqual(os.path.splitext(arg)[1], config.LIB_SUFFIX)
-            # Simulate file extraction
-            lib = os.path.splitext(os.path.basename(arg))[0]
-            if config.AR != 'lib':
-                extract = [lib, lib + '2']
-            extract = [os.path.join(kargs['cwd'], f) for f in extract]
-            if config.AR != 'lib':
-                extract = [Obj(f) for f in extract]
-            if not lib in extracted:
-                extracted[lib] = []
-            extracted[lib].extend(extract)
-            self.touch(extract)
-        subprocess.call = call
-
-        def check_output(args, **kargs):
-            # The command called is always AR
-            ar = config.AR
-            self.assertEqual(args[0:3], [ar, '-NOLOGO', '-LIST'])
-            # Remaining argument is always one library
-            self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[3:]],
-[config.LIB_SUFFIX])
-            # Simulate LIB -NOLOGO -LIST
-            lib = os.path.splitext(os.path.basename(args[3]))[0]
-            return '%s\n%s\n' % (Obj(lib), Obj(lib + '2'))
-        subprocess.check_output = check_output
-
-        # ExpandArgsMore does the same as ExpandArgs
-        self.touch([self.tmpfile('liby', Lib('y'))])
-        for iteration in (1, 2):
-            with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
-                files = self.files + self.liby_files + self.libx_files
-
-                self.assertRelEqual(args, ['foo', '-bar'] + files)
-
-                extracted = {}
-                # ExpandArgsMore also has an extra method extracting static libraries
-                # when possible
-                args.extract()
-
-                # With AR_EXTRACT, it uses the descriptors when there are, and
-                # actually
-                # extracts the remaining libraries
-                extracted_args = []
-                for f in files:
-                    if f.endswith(config.LIB_SUFFIX):
-                        base = os.path.splitext(os.path.basename(f))[0]
-                        # On the first iteration, we test the behavior of
-                        # extracting archives that don't have a copy of their
-                        # contents next to them, which is to use the file
-                        # extracted from the archive in a temporary directory.
-                        # On the second iteration, we test extracting archives
-                        # that do have a copy of their contents next to them,
-                        # in which case those contents are used instead of the
-                        # temporarily extracted files.
-                        if iteration == 1:
-                            extracted_args.extend(sorted(extracted[base]))
-                        else:
-                            dirname = os.path.dirname(f[len(self.tmpdir)+1:])
-                            if base.endswith('f'):
-                                dirname = os.path.join(dirname, 'foo', 'bar')
-                            extracted_args.extend([self.tmpfile(dirname, Obj(base)), self.tmpfile(dirname, Obj(base + '2'))])
-                    else:
-                        extracted_args.append(f)
-                self.assertRelEqual(args, ['foo', '-bar'] + extracted_args)
-
-                tmp = args.tmp
-            # Check that all temporary files are properly removed
-            self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
-
-            # Create archives contents next to them for the second iteration.
-            base = os.path.splitext(Lib('_'))[0]
-            self.touch(self.tmpfile(Obj(base.replace('_', suffix))) for suffix in ('a', 'a2', 'd', 'd2'))
-            try:
-                os.makedirs(self.tmpfile('foo', 'bar'))
-            except:
-                pass
-            self.touch(self.tmpfile('foo', 'bar', Obj(base.replace('_', suffix))) for suffix in ('f', 'f2'))
-            self.touch(self.tmpfile('liby', Obj(base.replace('_', suffix))) for suffix in ('z', 'z2'))
-
-        # Restore subprocess.call and subprocess.check_output
-        subprocess.call = subprocess_call
-        subprocess.check_output = subprocess_check_output
-
-class FakeProcess(object):
-    def __init__(self, out, err = ''):
-        self.out = out
-        self.err = err
-
-    def communicate(self):
-        return (self.out, self.err)
-
-OBJDUMPS = {
-'foo.o': '''
-00000000 g     F .text\t00000001 foo
-00000000 g     F .text._Z6foobarv\t00000001 _Z6foobarv
-00000000 g     F .text.hello\t00000001 hello
-00000000 g     F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv
-''',
-'bar.o': '''
-00000000 g     F .text.hi\t00000001 hi
-00000000 g     F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv
-''',
-}
-
-PRINT_ICF = '''
-ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o'
-ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o'
-'''
-
-class SubprocessPopen(object):
-    def __init__(self, test):
-        self.test = test
-
-    def __call__(self, args, stdout = None, stderr = None):
-        self.test.assertEqual(stdout, subprocess.PIPE)
-        self.test.assertEqual(stderr, subprocess.PIPE)
-        if args[0] == 'objdump':
-            self.test.assertEqual(args[1], '-t')
-            self.test.assertTrue(args[2] in OBJDUMPS)
-            return FakeProcess(OBJDUMPS[args[2]])
-        else:
-            return FakeProcess('', PRINT_ICF)
-
-class TestSectionFinder(unittest.TestCase):
-    def test_getSections(self):
-        '''Test SectionFinder'''
-        # Divert subprocess.Popen
-        subprocess_popen = subprocess.Popen
-        subprocess.Popen = SubprocessPopen(self)
-        config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
-        config.OBJ_SUFFIX = '.o'
-        config.LIB_SUFFIX = '.a'
-        finder = SectionFinder(['foo.o', 'bar.o'])
-        self.assertEqual(finder.getSections('foobar'), [])
-        self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv'])
-        self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
-        self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
-        subprocess.Popen = subprocess_popen
-
-class TestSymbolOrder(unittest.TestCase):
-    def test_getOrderedSections(self):
-        '''Test ExpandMoreArgs' _getOrderedSections'''
-        # Divert subprocess.Popen
-        subprocess_popen = subprocess.Popen
-        subprocess.Popen = SubprocessPopen(self)
-        config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
-        config.OBJ_SUFFIX = '.o'
-        config.LIB_SUFFIX = '.a'
-        config.LD_PRINT_ICF_SECTIONS = ''
-        args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
-        self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
-        self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
-        subprocess.Popen = subprocess_popen
-
-    def test_getFoldedSections(self):
-        '''Test ExpandMoreArgs' _getFoldedSections'''
-        # Divert subprocess.Popen
-        subprocess_popen = subprocess.Popen
-        subprocess.Popen = SubprocessPopen(self)
-        config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
-        args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
-        self.assertEqual(args._getFoldedSections(), {'.text.hello': ['.text.hi'], '.text.hi': ['.text.hello']})
-        subprocess.Popen = subprocess_popen
-
-    def test_getOrderedSectionsWithICF(self):
-        '''Test ExpandMoreArgs' _getOrderedSections, with ICF'''
-        # Divert subprocess.Popen
-        subprocess_popen = subprocess.Popen
-        subprocess.Popen = SubprocessPopen(self)
-        config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
-        config.OBJ_SUFFIX = '.o'
-        config.LIB_SUFFIX = '.a'
-        config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
-        args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
-        self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hello', '.text.hi', '.text.hot._Z6barbazv'])
-        self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv'])
-        subprocess.Popen = subprocess_popen
-
-
-if __name__ == '__main__':
-    mozunit.main(runwith='unittest')
--- a/python/mozbuild/mozbuild/backend/common.py
+++ b/python/mozbuild/mozbuild/backend/common.py
@@ -22,30 +22,37 @@ from mozbuild.frontend.data import (
     BaseProgram,
     ChromeManifestEntry,
     ConfigFileSubstitution,
     Exports,
     FinalTargetPreprocessedFiles,
     FinalTargetFiles,
     GeneratedSources,
     GnProjectData,
+    HostLibrary,
+    HostRustLibrary,
     IPDLCollection,
+    RustLibrary,
     SharedLibrary,
+    StaticLibrary,
     UnifiedSources,
     XPIDLFile,
     WebIDLCollection,
 )
 from mozbuild.jar import (
     DeprecatedJarManifest,
     JarManifestParser,
 )
 from mozbuild.preprocessor import Preprocessor
 from mozpack.chrome.manifest import parse_manifest_line
 
-from mozbuild.util import group_unified_files
+from mozbuild.util import (
+    group_unified_files,
+    mkdir,
+)
 
 class XPIDLManager(object):
     """Helps manage XPCOM IDLs in the context of the build system."""
     def __init__(self, config):
         self.config = config
         self.topsrcdir = config.topsrcdir
         self.topobjdir = config.topobjdir
 
@@ -191,16 +198,97 @@ class CommonBackend(BuildBackend):
 
         # Write out a file listing generated sources.
         with self._write_file(mozpath.join(topobjdir, 'generated-sources.json')) as fh:
             d = {
                 'sources': sorted(self._generated_sources),
             }
             json.dump(d, fh, sort_keys=True, indent=4)
 
+    def _expand_libs(self, input_bin):
+        os_libs = []
+        shared_libs = []
+        static_libs = []
+        objs = []
+
+        seen_objs = set()
+        seen_libs = set()
+
+        def add_objs(lib):
+            for o in lib.objs:
+                if o not in seen_objs:
+                    seen_objs.add(o)
+                    objs.append(o)
+
+        def expand(lib, recurse_objs, system_libs):
+            if isinstance(lib, StaticLibrary):
+                if lib.no_expand_lib:
+                    static_libs.append(lib)
+                    recurse_objs = False
+                elif recurse_objs:
+                    add_objs(lib)
+
+                for l in lib.linked_libraries:
+                    expand(l, recurse_objs, system_libs)
+
+                if system_libs:
+                    for l in lib.linked_system_libs:
+                        if l not in seen_libs:
+                            seen_libs.add(l)
+                            os_libs.append(l)
+
+            elif isinstance(lib, SharedLibrary):
+                if lib not in seen_libs:
+                    seen_libs.add(lib)
+                    shared_libs.append(lib)
+
+        add_objs(input_bin)
+
+        system_libs = not isinstance(input_bin, StaticLibrary)
+        for lib in input_bin.linked_libraries:
+            if isinstance(lib, RustLibrary):
+                continue
+            elif isinstance(lib, StaticLibrary):
+                expand(lib, True, system_libs)
+            elif isinstance(lib, SharedLibrary):
+                if lib not in seen_libs:
+                    seen_libs.add(lib)
+                    shared_libs.append(lib)
+
+        for lib in input_bin.linked_system_libs:
+            if lib not in seen_libs:
+                seen_libs.add(lib)
+                os_libs.append(lib)
+
+        return objs, shared_libs, os_libs, static_libs
+
+    def _make_list_file(self, objdir, objs, name):
+        if not objs:
+            return None
+        list_style = self.environment.substs.get('EXPAND_LIBS_LIST_STYLE')
+        list_file_path = mozpath.join(objdir, name)
+        objs = [os.path.relpath(o, objdir) for o in objs]
+        if list_style == 'linkerscript':
+            ref = list_file_path
+            content = '\n'.join('INPUT("%s")' % o for o in objs)
+        elif list_style == 'filelist':
+            ref = "-Wl,-filelist," + list_file_path
+            content = '\n'.join(objs)
+        elif list_style == 'list':
+            ref = "@" + list_file_path
+            content = '\n'.join(objs)
+        else:
+            return None
+
+        mkdir(objdir)
+        with self._write_file(list_file_path) as fh:
+            fh.write(content)
+
+        return ref
+
     def _handle_generated_sources(self, files):
         self._generated_sources.update(mozpath.relpath(f, self.environment.topobjdir) for f in files)
 
     def _handle_webidl_collection(self, webidls):
 
         bindings_dir = mozpath.join(self.environment.topobjdir, 'dom', 'bindings')
 
         all_inputs = set(webidls.all_static_sources())
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -1312,77 +1312,78 @@ class RecursiveMakeBackend(CommonBackend
     def _process_host_library(self, libdef, backend_file):
         backend_file.write('HOST_LIBRARY_NAME = %s\n' % libdef.basename)
 
     def _build_target_for_obj(self, obj):
         return '%s/%s' % (mozpath.relpath(obj.objdir,
             self.environment.topobjdir), obj.KIND)
 
     def _process_linked_libraries(self, obj, backend_file):
-        def write_shared_and_system_libs(lib):
-            for l in lib.linked_libraries:
-                if isinstance(l, (StaticLibrary, RustLibrary)):
-                    write_shared_and_system_libs(l)
-                else:
-                    backend_file.write_once('SHARED_LIBS += %s/%s\n'
-                        % (pretty_relpath(l), l.import_name))
-            for l in lib.linked_system_libs:
-                backend_file.write_once('OS_LIBS += %s\n' % l)
-
         def pretty_relpath(lib):
             return '$(DEPTH)/%s' % mozpath.relpath(lib.objdir, topobjdir)
 
         topobjdir = mozpath.normsep(obj.topobjdir)
         # This will create the node even if there aren't any linked libraries.
         build_target = self._build_target_for_obj(obj)
         self._compile_graph[build_target]
 
+        objs, shared_libs, os_libs, static_libs = self._expand_libs(obj)
+
+        if obj.KIND == 'target':
+            obj_target = obj.name
+            if isinstance(obj, Program):
+                obj_target = self._pretty_path(obj.output_path, backend_file)
+
+            objs_ref = ' \\\n    '.join(os.path.relpath(o, obj.objdir)
+                                        for o in objs)
+            # Don't bother with a list file if we're only linking objects built
+            # in this directory or building a real static library. This
+            # accommodates clang-plugin, where we would otherwise pass an
+            # incorrect list file format to the host compiler as well as when
+            # creating an archive with AR, which doesn't understand list files.
+            if (objs == obj.objs and not isinstance(obj, StaticLibrary) or
+                isinstance(obj, StaticLibrary) and obj.no_expand_lib):
+                backend_file.write_once('%s_OBJS := %s\n' %
+                                        (obj.name, objs_ref))
+                backend_file.write_once('%s: %s\n' % (obj_target, objs_ref))
+            elif not isinstance(obj, StaticLibrary):
+                list_file_path = '%s.list' % obj.name.replace('.', '_')
+                list_file_ref = self._make_list_file(obj.objdir, objs,
+                                                     list_file_path)
+                backend_file.write_once('%s_OBJS := %s\n' %
+                                        (obj.name, list_file_ref))
+                backend_file.write_once('%s: %s\n' % (obj_target, list_file_path))
+                backend_file.write_once('%s: %s\n' % (obj_target, objs_ref))
+
+        for lib in shared_libs:
+            backend_file.write_once('SHARED_LIBS += %s/%s\n' %
+                                    (pretty_relpath(lib), lib.import_name))
+        for lib in static_libs:
+            backend_file.write_once('STATIC_LIBS += %s/%s\n' %
+                                    (pretty_relpath(lib), lib.import_name))
+        for lib in os_libs:
+            if obj.KIND == 'target':
+                backend_file.write_once('OS_LIBS += %s\n' % lib)
+            else:
+                backend_file.write_once('HOST_EXTRA_LIBS += %s\n' % lib)
+
         for lib in obj.linked_libraries:
             if not isinstance(lib, ExternalLibrary):
                 self._compile_graph[build_target].add(
                     self._build_target_for_obj(lib))
-            relpath = pretty_relpath(lib)
-            if isinstance(obj, Library):
-                if isinstance(lib, RustLibrary):
-                    # We don't need to do anything here; we will handle
-                    # linkage for any RustLibrary elsewhere.
-                    continue
-                elif isinstance(lib, StaticLibrary):
-                    backend_file.write_once('STATIC_LIBS += %s/%s\n'
-                                        % (relpath, lib.import_name))
-                    if isinstance(obj, SharedLibrary):
-                        write_shared_and_system_libs(lib)
-                elif isinstance(obj, SharedLibrary):
-                    backend_file.write_once('SHARED_LIBS += %s/%s\n'
-                                        % (relpath, lib.import_name))
-            elif isinstance(obj, (Program, SimpleProgram)):
-                if isinstance(lib, StaticLibrary):
-                    backend_file.write_once('STATIC_LIBS += %s/%s\n'
-                                        % (relpath, lib.import_name))
-                    write_shared_and_system_libs(lib)
-                else:
-                    backend_file.write_once('SHARED_LIBS += %s/%s\n'
-                                        % (relpath, lib.import_name))
-            elif isinstance(obj, (HostLibrary, HostProgram, HostSimpleProgram)):
-                assert isinstance(lib, (HostLibrary, HostRustLibrary))
-                backend_file.write_once('HOST_LIBS += %s/%s\n'
-                                   % (relpath, lib.import_name))
+            if isinstance(lib, (HostLibrary, HostRustLibrary)):
+                backend_file.write_once('HOST_LIBS += %s/%s\n' %
+                                        (pretty_relpath(lib), lib.import_name))
 
         # We have to link any Rust libraries after all intermediate static
         # libraries have been listed to ensure that the Rust libraries are
         # searched after the C/C++ objects that might reference Rust symbols.
         if isinstance(obj, SharedLibrary):
             self._process_rust_libraries(obj, backend_file, pretty_relpath)
 
-        for lib in obj.linked_system_libs:
-            if obj.KIND == 'target':
-                backend_file.write_once('OS_LIBS += %s\n' % lib)
-            else:
-                backend_file.write_once('HOST_EXTRA_LIBS += %s\n' % lib)
-
         # Process library-based defines
         self._process_defines(obj.lib_defines, backend_file)
 
     def _process_rust_libraries(self, obj, backend_file, pretty_relpath):
         assert isinstance(obj, SharedLibrary)
 
         # If this library does not depend on any Rust libraries, then we are done.
         direct_linked = [l for l in obj.linked_libraries if isinstance(l, RustLibrary)]
--- a/python/mozbuild/mozbuild/test/backend/common.py
+++ b/python/mozbuild/mozbuild/test/backend/common.py
@@ -209,16 +209,17 @@ CONFIGS = defaultdict(lambda: {
         'defines': {},
         'non_global_defines': [],
         'substs': {
             'COMPILE_ENVIRONMENT': '1',
             'LIB_SUFFIX': 'a',
             'BIN_SUFFIX': '.exe',
             'DLL_SUFFIX': '.so',
             'OBJ_SUFFIX': 'o',
+            'EXPAND_LIBS_LIST_STYLE': 'list',
         },
     },
 })
 
 
 class BackendTester(unittest.TestCase):
     def setUp(self):
         self._old_env = dict(os.environ)
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/moz.build
@@ -0,0 +1,8 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SOURCES += [
+    'bar_helper1.cpp',
+]
+
+FINAL_LIBRARY = 'bar'
\ No newline at end of file
--- a/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build
@@ -1,8 +1,13 @@
 # Any copyright is dedicated to the Public Domain.
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 SOURCES += [
     'bar1.cc',
+    'bar2.cc',
+]
+
+DIRS += [
+    'bar_helper',
 ]
 
 FINAL_LIBRARY = 'bar'
\ No newline at end of file
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -992,40 +992,34 @@ class TestRecursiveMakeBackend(BackendTe
 
             for line in lines:
                 self.assertNotIn('LIB_IS_C_ONLY', line)
 
     def test_linkage(self):
         env = self._consume('linkage', RecursiveMakeBackend)
         expected_linkage = {
             'prog': {
-                'SHARED_LIBS': ['$(DEPTH)/shared/baz', '$(DEPTH)/prog/qux/qux'],
-                'STATIC_LIBS': ['$(DEPTH)/static/bar%s' % env.lib_suffix],
+                'SHARED_LIBS': ['$(DEPTH)/prog/qux/qux.so',
+                                '$(DEPTH)/shared/baz.so'],
+                'STATIC_LIBS': ['$(DEPTH)/real/foo.a'],
                 'OS_LIBS': ['-lfoo', '-lbaz', '-lbar'],
             },
             'shared': {
                 'OS_LIBS': ['-lfoo'],
-                'SHARED_LIBS': ['$(DEPTH)/prog/qux/qux'],
-                'STATIC_LIBS': ['$(DEPTH)/shared/baz/shared_baz%s' %
-                                env.lib_suffix],
+                'SHARED_LIBS': ['$(DEPTH)/prog/qux/qux.so'],
+                'STATIC_LIBS': [],
             },
             'static': {
-                'STATIC_LIBS': [
-                    '$(DEPTH)/static/bar/static_bar.a',
-                    '$(DEPTH)/real/foo.a',
-                ],
+                'STATIC_LIBS': ['$(DEPTH)/real/foo.a'],
                 'OS_LIBS': ['-lbar'],
-                'SHARED_LIBS': [],
+                'SHARED_LIBS': ['$(DEPTH)/prog/qux/qux.so'],
             },
             'real': {
-                'STATIC_LIBS': [
-                    '$(DEPTH)/shared/baz_s%s' % env.lib_suffix,
-                    '$(DEPTH)/real/foo/real_foo%s' % env.lib_suffix,
-                ],
-                'SHARED_LIBS': [],
+                'STATIC_LIBS': [],
+                'SHARED_LIBS': ['$(DEPTH)/prog/qux/qux.so'],
                 'OS_LIBS': ['-lbaz'],
             }
         }
         actual_linkage = {}
         for name in expected_linkage.keys():
             with open(os.path.join(env.topobjdir, name, 'backend.mk'), 'rb') as fh:
                 actual_linkage[name] = [line.rstrip() for line in fh.readlines()]
         for name in expected_linkage:
@@ -1033,16 +1027,44 @@ class TestRecursiveMakeBackend(BackendTe
                 for val in expected_linkage[name][var]:
                     line = '%s += %s' % (var, val)
                     self.assertIn(line,
                                   actual_linkage[name])
                     actual_linkage[name].remove(line)
                 for line in actual_linkage[name]:
                     self.assertNotIn('%s +=' % var, line)
 
+    def test_list_files(self):
+        env = self._consume('linkage', RecursiveMakeBackend)
+        expected_list_files = {
+            'prog/MyProgram_exe.list': [
+                '../static/bar/bar1.o',
+                '../static/bar/bar2.o',
+                '../static/bar/bar_helper/bar_helper1.o',
+            ],
+            'shared/baz_so.list': [
+                'baz/baz1.o',
+            ],
+        }
+        actual_list_files = {}
+        for name in expected_list_files.keys():
+            with open(os.path.join(env.topobjdir, name), 'rb') as fh:
+                actual_list_files[name] = [mozpath.normsep(line.rstrip())
+                                           for line in fh.readlines()]
+        for name in expected_list_files:
+            self.assertEqual(actual_list_files[name],
+                             expected_list_files[name])
+
+        # We don't produce a list file for a shared library composed only of
+        # object files in its directory, but instead list them in a variable.
+        with open(os.path.join(env.topobjdir, 'prog', 'qux', 'backend.mk'), 'rb') as fh:
+            lines = [line.rstrip() for line in fh.readlines()]
+
+        self.assertIn('qux.so_OBJS := qux1.o', lines)
+
     def test_jar_manifests(self):
         env = self._consume('jar-manifests', RecursiveMakeBackend)
 
         with open(os.path.join(env.topobjdir, 'backend.mk'), 'rb') as fh:
             lines = fh.readlines()
 
         lines = [line.rstrip() for line in lines]