Merge the last PGO-green inbound changeset to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 03 Feb 2013 12:33:19 -0800
changeset 130572 847e28c7ba6738b22cdf7c7021f423509464356d
parent 130571 4fe38a0ee8c5a3af493e78627228d0c067ac934f (current diff)
parent 130483 31268d71c33c693ea118f09e28b393adb40b4549 (diff)
child 130598 2a8e243711a98a8cc6b7b4a9e6bbbd5e41d7ad28
child 138170 2c403f93ae466012a3e1acf7130cacf643094771
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
847e28c7ba67 / 21.0a1 / 20130204030941 / files
nightly linux64
847e28c7ba67 / 21.0a1 / 20130204030941 / files
nightly mac
847e28c7ba67 / 21.0a1 / 20130204030941 / files
nightly win32
847e28c7ba67 / 21.0a1 / 20130204030941 / files
nightly win64
847e28c7ba67 / 21.0a1 / 20130204030941 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge the last PGO-green inbound changeset to m-c
--- a/python/mozbuild/mozpack/chrome/flags.py
+++ b/python/mozbuild/mozpack/chrome/flags.py
@@ -146,17 +146,17 @@ class VersionFlag(object):
         '''
         assert(definition.startswith(self.name))
         value = definition[len(self.name):]
         if value.startswith('='):
             self.values.append(('==', LooseVersion(value[1:])))
         elif len(value) > 1 and value[0] in ['<', '>']:
             if value[1] == '=':
                 if len(value) < 3:
-                   return errors.fatal('Malformed flag: %s' % definition)
+                    return errors.fatal('Malformed flag: %s' % definition)
                 self.values.append((value[0:2], LooseVersion(value[2:])))
             else:
                 self.values.append((value[0], LooseVersion(value[1:])))
         else:
             return errors.fatal('Malformed flag: %s' % definition)
 
     def matches(self, value):
         '''
--- a/python/mozbuild/mozpack/chrome/manifest.py
+++ b/python/mozbuild/mozpack/chrome/manifest.py
@@ -319,16 +319,17 @@ class ManifestContract(ManifestEntry):
 
 # All manifest classes by their type name.
 MANIFESTS_TYPES = dict([(c.type, c) for c in globals().values()
                        if type(c) == type and issubclass(c, ManifestEntry)
                        and hasattr(c, 'type') and c.type])
 
 MANIFEST_RE = re.compile(r'\s*#.*$')
 
+
 def parse_manifest_line(base, line):
     '''
     Parse a line from a manifest file with the given base directory and
     return the corresponding ManifestEntry instance.
     '''
     # Remove comments
     cmd = MANIFEST_RE.sub('', line).strip().split()
     if not cmd:
--- a/python/mozbuild/mozpack/copier.py
+++ b/python/mozbuild/mozpack/copier.py
@@ -125,17 +125,17 @@ class FileRegistry(object):
         return self._files.iteritems()
 
 
 class FileCopier(FileRegistry):
     '''
     FileRegistry with the ability to copy the registered files to a separate
     directory.
     '''
-    def copy(self, destination):
+    def copy(self, destination, skip_if_older=True):
         '''
         Copy all registered files to the given destination path. The given
         destination can be an existing directory, or not exist at all. It
         can't be e.g. a file.
         The copy process acts a bit like rsync: files are not copied when they
         don't need to (see mozpack.files for details on file.copy), and files
         existing in the destination directory that aren't registered are
         removed.
@@ -143,17 +143,17 @@ class FileCopier(FileRegistry):
         assert isinstance(destination, basestring)
         assert not os.path.exists(destination) or os.path.isdir(destination)
         destination = os.path.normpath(destination)
         dest_files = set()
         for path, file in self:
             destfile = os.path.normpath(os.path.join(destination, path))
             dest_files.add(destfile)
             ensure_parent_dir(destfile)
-            file.copy(destfile)
+            file.copy(destfile, skip_if_older)
 
         actual_dest_files = set()
         for root, dirs, files in os.walk(destination):
             for f in files:
                 actual_dest_files.add(os.path.normpath(os.path.join(root, f)))
         for f in actual_dest_files - dest_files:
             os.remove(f)
         for root, dirs, files in os.walk(destination):
@@ -171,17 +171,17 @@ class Jarrer(FileRegistry, BaseFile):
         Create a Jarrer instance. See mozpack.mozjar.JarWriter documentation
         for details on the compress and optimize arguments.
         '''
         self.compress = compress
         self.optimize = optimize
         self._preload = []
         FileRegistry.__init__(self)
 
-    def copy(self, dest):
+    def copy(self, dest, skip_if_older=True):
         '''
         Pack all registered files in the given destination jar. The given
         destination jar may be a path to jar file, or a Dest instance for
         a jar file.
         If the destination jar file exists, its (compressed) contents are used
         instead of the registered BaseFile instances when appropriate.
         '''
         class DeflaterDest(Dest):
@@ -229,17 +229,17 @@ class Jarrer(FileRegistry, BaseFile):
 
         with JarWriter(fileobj=dest, compress=self.compress,
                        optimize=self.optimize) as jar:
             for path, file in self:
                 if path in old_contents:
                     deflater = DeflaterDest(old_contents[path], self.compress)
                 else:
                     deflater = DeflaterDest(compress=self.compress)
-                file.copy(deflater)
+                file.copy(deflater, skip_if_older)
                 jar.add(path, deflater.deflater)
             if self._preload:
                 jar.preload(self._preload)
 
     def open(self):
         raise RuntimeError('unsupported')
 
     def preload(self, paths):
--- a/python/mozbuild/mozpack/errors.py
+++ b/python/mozbuild/mozpack/errors.py
@@ -123,10 +123,15 @@ class ErrorCollector(object):
         assert self._count is None
         self._count = 0
         yield
         count = self._count
         self._count = None
         if count:
             raise AccumulatedErrors()
 
+    @property
+    def count(self):
+        # _count can be None.
+        return self._count if self._count else 0
+
 
 errors = ErrorCollector()
--- a/python/mozbuild/mozpack/files.py
+++ b/python/mozbuild/mozpack/files.py
@@ -10,17 +10,19 @@ from mozpack.executables import (
     may_strip,
     strip,
     may_elfhack,
     elfhack,
 )
 from mozpack.chrome.manifest import ManifestEntry
 from io import BytesIO
 from mozpack.errors import ErrorMessage
+from mozpack.mozjar import JarReader
 import mozpack.path
+from collections import OrderedDict
 
 
 class Dest(object):
     '''
     Helper interface for BaseFile.copy. The interface works as follows:
     - read() and write() can be used to sequentially read/write from the
       underlying file.
     - a call to read() after a write() will re-open the underlying file and
@@ -54,38 +56,39 @@ class Dest(object):
 
 
 class BaseFile(object):
     '''
     Base interface and helper for file copying. Derived class may implement
     their own copy function, or rely on BaseFile.copy using the open() member
     function and/or the path property.
     '''
-    def copy(self, dest):
+    def copy(self, dest, skip_if_older=True):
         '''
         Copy the BaseFile content to the destination given as a string or a
         Dest instance. Avoids replacing existing files if the BaseFile content
         matches that of the destination, or in case of plain files, if the
-        destination is newer than the original file.
+        destination is newer than the original file. This latter behaviour is
+        disabled when skip_if_older is False.
         Returns whether a copy was actually performed (True) or not (False).
         '''
         if isinstance(dest, basestring):
             dest = Dest(dest)
         else:
             assert isinstance(dest, Dest)
 
         can_skip_content_check = False
         if not dest.exists():
             can_skip_content_check = True
         elif getattr(self, 'path', None) and getattr(dest, 'path', None):
             # os.path.getmtime returns a result in seconds with precision up to
             # the microsecond. But microsecond is too precise because
             # shutil.copystat only copies milliseconds, and seconds is not
             # enough precision.
-            if int(os.path.getmtime(self.path) * 1000) \
+            if skip_if_older and int(os.path.getmtime(self.path) * 1000) \
                     <= int(os.path.getmtime(dest.path) * 1000):
                 return False
             elif os.path.getsize(self.path) != os.path.getsize(dest.path):
                 can_skip_content_check = True
 
         if can_skip_content_check:
             if getattr(self, 'path', None) and getattr(dest, 'path', None):
                 shutil.copy2(self.path, dest.path)
@@ -132,19 +135,24 @@ class File(BaseFile):
         self.path = path
 
 
 class ExecutableFile(File):
     '''
     File class for executable and library files on OS/2, OS/X and ELF systems.
     (see mozpack.executables.is_executable documentation).
     '''
-    def copy(self, dest):
+    def copy(self, dest, skip_if_older=True):
         assert isinstance(dest, basestring)
-        File.copy(self, dest)
+        # If File.copy didn't actually copy because dest is newer, check the
+        # file sizes. If dest is smaller, it means it is already stripped and
+        # elfhacked, so we can skip.
+        if not File.copy(self, dest, skip_if_older) and \
+                os.path.getsize(self.path) > os.path.getsize(dest):
+            return False
         try:
             if may_strip(dest):
                 strip(dest)
             if may_elfhack(dest):
                 elfhack(dest)
         except ErrorMessage:
             os.remove(dest)
             raise
@@ -197,22 +205,23 @@ class XPTFile(GeneratedFile):
     def remove(self, xpt):
         '''
         Remove the given XPT file (as a BaseFile instance) from the list of
         XPTs to link.
         '''
         assert isinstance(xpt, BaseFile)
         self._files.remove(xpt)
 
-    def copy(self, dest):
+    def copy(self, dest, skip_if_older=True):
         '''
         Link the registered XPTs and place the resulting linked XPT at the
         destination given as a string or a Dest instance. Avoids an expensive
         XPT linking if the interfaces in an existing destination match those of
         the individual XPTs to link.
+        skip_if_older is ignored.
         '''
         if isinstance(dest, basestring):
             dest = Dest(dest)
         assert isinstance(dest, Dest)
 
         from xpt import xpt_link, Typelib, Interface
         all_typelibs = [Typelib.read(f.open()) for f in self._files]
         if dest.exists():
@@ -283,17 +292,17 @@ class ManifestFile(BaseFile):
         self._entries.remove(entry)
 
     def open(self):
         '''
         Return a file-like object allowing to read() the serialized content of
         the manifest.
         '''
         return BytesIO(''.join('%s\n' % e.rebase(self._base)
-                                for e in self._entries))
+                               for e in self._entries))
 
     def __iter__(self):
         '''
         Iterate over entries in the manifest file.
         '''
         return iter(self._entries)
 
     def isempty(self):
@@ -313,49 +322,93 @@ class MinifiedProperties(BaseFile):
         self._file = file
 
     def open(self):
         '''
         Return a file-like object allowing to read() the minified content of
         the properties file.
         '''
         return BytesIO(''.join(l for l in self._file.open().readlines()
-                                if not l.startswith('#')))
+                               if not l.startswith('#')))
 
 
-class FileFinder(object):
-    '''
-    Helper to get appropriate BaseFile instances from the file system.
-    '''
+class BaseFinder(object):
     def __init__(self, base, minify=False):
         '''
-        Create a FileFinder for files under the given base directory. The
+        Initializes the instance with a reference base directory. The
         optional minify argument specifies whether file types supporting
         minification (currently only "*.properties") should be minified.
         '''
         self.base = base
         self._minify = minify
 
     def find(self, pattern):
         '''
         Yield path, BaseFile_instance pairs for all files under the base
         directory and its subdirectories that match the given pattern. See the
         mozpack.path.match documentation for a description of the handled
-        patterns. Note all files with a name starting with a '.' are ignored
-        when scanning directories, but are not ignored when explicitely
-        requested.
+        patterns.
         '''
         while pattern.startswith('/'):
             pattern = pattern[1:]
-        return self._find(pattern)
+        for p, f in self._find(pattern):
+            yield p, self._minify_file(p, f)
+
+    def __iter__(self):
+        '''
+        Iterates over all files under the base directory (excluding files
+        starting with a '.' and files at any level under a directory starting
+        with a '.').
+            for path, file in finder:
+                ...
+        '''
+        return self.find('')
+
+    def __contains__(self, pattern):
+        raise RuntimeError("'in' operator forbidden for %s. Use contains()." %
+                           self.__class__.__name__)
+
+    def contains(self, pattern):
+        '''
+        Return whether some files under the base directory match the given
+        pattern. See the mozpack.path.match documentation for a description of
+        the handled patterns.
+        '''
+        return any(self.find(pattern))
+
+    def _minify_file(self, path, file):
+        '''
+        Return an appropriate MinifiedSomething wrapper for the given BaseFile
+        instance (file), according to the file type (determined by the given
+        path), if the FileFinder was created with minification enabled.
+        Otherwise, just return the given BaseFile instance.
+        Currently, only "*.properties" files are handled.
+        '''
+        if self._minify and not isinstance(file, ExecutableFile):
+            if path.endswith('.properties'):
+                return MinifiedProperties(file)
+        return file
+
+
+class FileFinder(BaseFinder):
+    '''
+    Helper to get appropriate BaseFile instances from the file system.
+    '''
+    def __init__(self, base, **kargs):
+        '''
+        Create a FileFinder for files under the given base directory.
+        '''
+        BaseFinder.__init__(self, base, **kargs)
 
     def _find(self, pattern):
         '''
         Actual implementation of FileFinder.find(), dispatching to specialized
         member functions depending on what kind of pattern was given.
+        Note all files with a name starting with a '.' are ignored when
+        scanning directories, but are not ignored when explicitely requested.
         '''
         if '*' in pattern:
             return self._find_glob('', mozpack.path.split(pattern))
         elif os.path.isdir(os.path.join(self.base, pattern)):
             return self._find_dir(pattern)
         else:
             return self._find_file(pattern)
 
@@ -379,17 +432,17 @@ class FileFinder(object):
         '''
         srcpath = os.path.join(self.base, path)
         if not os.path.exists(srcpath):
             return
 
         if is_executable(srcpath):
             yield path, ExecutableFile(srcpath)
         else:
-            yield path, self._minify_file(srcpath, File(srcpath))
+            yield path, File(srcpath)
 
     def _find_glob(self, base, pattern):
         '''
         Actual implementation of FileFinder.find() when the given pattern
         contains globbing patterns ('*' or '**'). This is meant to be an
         equivalent of:
             for p, f in self:
                 if mozpack.path.match(p, pattern):
@@ -413,42 +466,40 @@ class FileFinder(object):
                     for p_, f in self._find_glob(mozpack.path.join(base, p),
                                                  pattern[1:]):
                         yield p_, f
         else:
             for p, f in self._find_glob(mozpack.path.join(base, pattern[0]),
                                         pattern[1:]):
                 yield p, f
 
-    def __iter__(self):
-        '''
-        Iterates over all files under the base directory (excluding files
-        starting with a '.' and files at any level under a directory starting
-        with a '.').
-            for path, file in finder:
-                ...
+
+class JarFinder(BaseFinder):
+    '''
+    Helper to get appropriate DeflatedFile instances from a JarReader.
+    '''
+    def __init__(self, base, reader, **kargs):
         '''
-        return self.find('')
+        Create a JarFinder for files in the given JarReader. The base argument
+        is used as an indication of the Jar file location.
+        '''
+        assert isinstance(reader, JarReader)
+        BaseFinder.__init__(self, base, **kargs)
+        self._files = OrderedDict((f.filename, f) for f in reader)
 
-    def __contains__(self, pattern):
-        raise RuntimeError("'in' operator forbidden for %s. Use contains()." %
-                           self.__class__.__name__)
-
-    def contains(self, pattern):
+    def _find(self, pattern):
         '''
-        Return whether some files under the base directory match the given
-        pattern. See the mozpack.path.match documentation for a description of
-        the handled patterns.
-        '''
-        return any(self.find(pattern))
-
-    def _minify_file(self, path, file):
+        Actual implementation of JarFinder.find(), dispatching to specialized
+        member functions depending on what kind of pattern was given.
         '''
-        Return an appropriate MinifiedSomething wrapper for the given BaseFile
-        instance (file), according to the file type (determined by the given
-        path), if the FileFinder was created with minification enabled.
-        Otherwise, just return the given BaseFile instance.
-        Currently, only "*.properties" files are handled.
-        '''
-        if self._minify:
-            if path.endswith('.properties'):
-                return MinifiedProperties(file)
-        return file
+        if '*' in pattern:
+            for p in self._files:
+                if mozpack.path.match(p, pattern):
+                    yield p, DeflatedFile(self._files[p])
+        elif pattern == '':
+            for p in self._files:
+                yield p, DeflatedFile(self._files[p])
+        elif pattern in self._files:
+            yield pattern, DeflatedFile(self._files[pattern])
+        else:
+            for p in self._files:
+                if mozpack.path.basedir(p, [pattern]) == pattern:
+                    yield p, DeflatedFile(self._files[p])
--- a/python/mozbuild/mozpack/mozjar.py
+++ b/python/mozbuild/mozpack/mozjar.py
@@ -60,17 +60,17 @@ class JarStruct(object):
 
     def __init__(self, data=None):
         '''
         Create an instance from the given data. Data may be omitted to create
         an instance with empty fields.
         '''
         assert self.MAGIC and isinstance(self.STRUCT, OrderedDict)
         self.size_fields = set(t for t in self.STRUCT.itervalues()
-                              if not t in JarStruct.TYPE_MAPPING)
+                               if not t in JarStruct.TYPE_MAPPING)
         self._values = {}
         if data:
             self._init_data(data)
         else:
             self._init_empty()
 
     def _init_data(self, data):
         '''
@@ -272,17 +272,17 @@ class JarFileReader(object):
         '''
         return self.uncompressed_data.read(length)
 
     def readlines(self):
         '''
         Return a list containing all the lines of data in the uncompressed
         data.
         '''
-        return self.read().splitlines()
+        return self.read().splitlines(True)
 
     def seek(self, pos, whence=os.SEEK_SET):
         '''
         Change the current position in the uncompressed data. Subsequent reads
         will start from there.
         '''
         return self.uncompressed_data.seek(pos, whence)
 
@@ -370,17 +370,17 @@ class JarReader(object):
             # Creator host system. 0 is MSDOS, 3 is Unix
             host = entry['creator_version'] >> 16
             # External attributes values depend on host above. On Unix the
             # higher bits are the stat.st_mode value. On MSDOS, the lower bits
             # are the FAT attributes.
             xattr = entry['external_attr']
             # Skip directories
             if (host == 0 and xattr & 0x10) or (host == 3 and
-                    xattr & (040000 << 16)):
+                                                xattr & (040000 << 16)):
                 continue
             entries[entry['filename']] = entry
             if entry['offset'] < preload:
                 self._last_preloaded = entry['filename']
         self._entries = entries
         return entries
 
     @property
@@ -411,17 +411,18 @@ class JarReader(object):
         Helper to create a JarFileReader corresponding to the given central
         directory entry.
         '''
         header = JarLocalFileHeader(self._data[entry['offset']:])
         for key, value in entry:
             if key in header and header[key] != value:
                 raise JarReaderError('Central directory and file header ' +
                                      'mismatch. Corrupted archive?')
-        return JarFileReader(header, self._data[entry['offset'] + header.size:])
+        return JarFileReader(header,
+                             self._data[entry['offset'] + header.size:])
 
     def __iter__(self):
         '''
         Iterate over all files in the Jar archive, in the form of
         JarFileReaders.
             for file in jarReader:
                 ...
         '''
@@ -524,17 +525,17 @@ class JarWriter(object):
             if entry['filename'] == self._last_preloaded:
                 preload_size = offset
             headers[entry] = header
         # Prepare end of central directory
         end = JarCdirEnd()
         end['disk_entries'] = len(self._contents)
         end['cdir_entries'] = end['disk_entries']
         end['cdir_size'] = reduce(lambda x, y: x + y[0].size,
-                               self._contents.values(), 0)
+                                  self._contents.values(), 0)
         # On optimized archives, store the preloaded size and the central
         # directory entries, followed by the first end of central directory.
         if self._optimize:
             end['cdir_offset'] = 4
             offset = end['cdir_size'] + end['cdir_offset'] + end.size
             if preload_size:
                 preload_size += offset
             self._data.write(struct.pack('<I', preload_size))
@@ -579,17 +580,18 @@ class JarWriter(object):
         else:
             deflater = Deflater(compress)
             if isinstance(data, basestring):
                 deflater.write(data)
             elif hasattr(data, 'read'):
                 data.seek(0)
                 deflater.write(data.read())
             else:
-                raise JarWriterError("Don't know how to handle %s" % type(data))
+                raise JarWriterError("Don't know how to handle %s" %
+                                     type(data))
         # Fill a central directory entry for this new member.
         entry = JarCdirEntry()
         # Not storing as created on unix, which avoids having to deal with
         # st_mode.
         entry['creator_version'] = 20
         if deflater.compressed:
             entry['min_version'] = 20  # Version 2.0 supports deflated streams
             entry['general_flag'] = 2  # Max compression
--- a/python/mozbuild/mozpack/packager/formats.py
+++ b/python/mozbuild/mozpack/packager/formats.py
@@ -14,17 +14,16 @@ import mozpack.path
 from mozpack.files import (
     ManifestFile,
     XPTFile,
 )
 from mozpack.copier import (
     FileRegistry,
     Jarrer,
 )
-from mozpack.errors import errors
 
 STARTUP_CACHE_PATHS = [
     'jsloader',
     'jssubloader',
 ]
 
 '''
 Formatters are classes receiving packaging instructions and creating the
@@ -56,16 +55,17 @@ The base interface provides the followin
 
 The virtual paths mentioned above are paths as they would be with a flat
 chrome.
 
 Formatters all take a FileCopier instance they will fill with the packaged
 data.
 '''
 
+
 class FlatFormatter(object):
     '''
     Formatter for the flat package format.
     '''
     def __init__(self, copier):
         assert isinstance(copier, FileRegistry)
         self.copier = copier
         self._bases = ['']
@@ -187,18 +187,18 @@ class JarFormatter(FlatFormatter):
 
     def contains(self, path):
         assert '*' not in path
         chrome = self._chromepath(path)
         if not chrome:
             return self.copier.contains(path)
         if not self.copier.contains(chrome + '.jar'):
             return False
-        return self.copier[chrome + '.jar'].contains(mozpack.path.relpath(path,
-                                                                          chrome))
+        return self.copier[chrome + '.jar']. \
+            contains(mozpack.path.relpath(path, chrome))
 
 
 class OmniJarFormatter(FlatFormatter):
     '''
     Formatter for the omnijar package format.
     '''
     def __init__(self, copier, omnijar_name, compress=True, optimize=True,
                  non_resources=[]):
--- a/python/mozbuild/mozpack/packager/unpack.py
+++ b/python/mozbuild/mozpack/packager/unpack.py
@@ -20,17 +20,16 @@ from mozpack.copier import (
     FileCopier,
 )
 from mozpack.packager import SimplePackager
 from mozpack.packager.formats import (
     FlatFormatter,
     STARTUP_CACHE_PATHS,
 )
 from urlparse import urlparse
-from collections import OrderedDict
 
 
 class UnpackFinder(FileFinder):
     '''
     Special FileFinder that treats the source package directory as if it were
     in the flat chrome format, whatever chrome format it actually is in.
 
     This means that for example, paths like chrome/browser/content/... match
@@ -167,9 +166,9 @@ def unpack(source):
     '''
     copier = FileCopier()
     finder = UnpackFinder(source)
     packager = SimplePackager(FlatFormatter(copier))
     for p, f in finder.find('*'):
         if mozpack.path.split(p)[0] not in STARTUP_CACHE_PATHS:
             packager.add(p, f)
     packager.close()
-    copier.copy(source)
+    copier.copy(source, skip_if_older=False)
--- a/python/mozbuild/mozpack/test/test_copier.py
+++ b/python/mozbuild/mozpack/test/test_copier.py
@@ -54,16 +54,17 @@ class TestFileRegistry(MatchTestTemplate
 
         self.assertEqual(self.registry.paths(), ['foo', 'bar'])
 
         self.registry.remove('foo')
         self.assertEqual(self.registry.paths(), ['bar'])
         self.registry.remove('bar')
         self.assertEqual(self.registry.paths(), [])
 
+        self.prepare_match_test()
         self.do_match_test()
         self.assertTrue(self.checked)
         self.assertEqual(self.registry.paths(), [
             'bar',
             'foo/bar',
             'foo/baz',
             'foo/qux/1',
             'foo/qux/bar',
--- a/python/mozbuild/mozpack/test/test_files.py
+++ b/python/mozbuild/mozpack/test/test_files.py
@@ -6,16 +6,17 @@ from mozpack.files import (
     Dest,
     File,
     GeneratedFile,
     DeflatedFile,
     ManifestFile,
     XPTFile,
     MinifiedProperties,
     FileFinder,
+    JarFinder,
 )
 from mozpack.mozjar import (
     JarReader,
     JarWriter,
 )
 from mozpack.chrome.manifest import (
     ManifestContent,
     ManifestResource,
@@ -204,16 +205,20 @@ class TestFile(TestWithTmpDir):
         self.assertEqual('test', open(dest, 'rb').read())
 
         # Double check that under conditions where a copy occurs, we would get
         # an exception.
         time = os.path.getmtime(src) - 1
         os.utime(dest, (time, time))
         self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest))
 
+        # skip_if_older=False is expected to force a copy in this situation.
+        f.copy(dest, skip_if_older=False)
+        self.assertEqual('fooo', open(dest, 'rb').read())
+
 
 class TestGeneratedFile(TestWithTmpDir):
     def test_generated_file(self):
         '''
         Check that GeneratedFile.copy yields the proper content in the
         destination file in all situations that trigger different code paths
         (see TestFile.test_file)
         '''
@@ -481,25 +486,29 @@ class TestMinifiedProperties(TestWithTmp
         open(self.tmppath('prop'), 'wb').write('\n'.join(propLines))
         MinifiedProperties(File(self.tmppath('prop'))) \
             .copy(self.tmppath('prop2'))
         self.assertEqual(open(self.tmppath('prop2')).readlines(),
                          ['foo = bar\n', '\n'])
 
 
 class MatchTestTemplate(object):
-    def do_match_test(self):
+    def prepare_match_test(self, with_dotfiles=False):
         self.add('bar')
         self.add('foo/bar')
         self.add('foo/baz')
         self.add('foo/qux/1')
         self.add('foo/qux/bar')
         self.add('foo/qux/2/test')
         self.add('foo/qux/2/test2')
+        if with_dotfiles:
+            self.add('foo/.foo')
+            self.add('foo/.bar/foo')
 
+    def do_match_test(self):
         self.do_check('', [
             'bar', 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
             'foo/qux/2/test', 'foo/qux/2/test2'
         ])
         self.do_check('*', [
             'bar', 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
             'foo/qux/2/test', 'foo/qux/2/test2'
         ])
@@ -528,46 +537,69 @@ class MatchTestTemplate(object):
         self.do_check('**/2/test*', ['foo/qux/2/test', 'foo/qux/2/test2'])
         self.do_check('**/foo', [
             'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
             'foo/qux/2/test', 'foo/qux/2/test2'
         ])
         self.do_check('**/barbaz', [])
         self.do_check('f**/bar', ['foo/bar'])
 
+    def do_finder_test(self, finder):
+        self.assertTrue(finder.contains('foo/.foo'))
+        self.assertTrue(finder.contains('foo/.bar'))
+        self.assertTrue('foo/.foo' in [f for f, c in
+                                       finder.find('foo/.foo')])
+        self.assertTrue('foo/.bar/foo' in [f for f, c in
+                                           finder.find('foo/.bar')])
+        self.assertEqual(sorted([f for f, c in finder.find('foo/.*')]),
+                         ['foo/.bar/foo', 'foo/.foo'])
+        for pattern in ['foo', '**', '**/*', '**/foo', 'foo/*']:
+            self.assertFalse('foo/.foo' in [f for f, c in
+                                            finder.find(pattern)])
+            self.assertFalse('foo/.bar/foo' in [f for f, c in
+                                                finder.find(pattern)])
+            self.assertEqual(sorted([f for f, c in finder.find(pattern)]),
+                             sorted([f for f, c in finder
+                                     if mozpack.path.match(f, pattern)]))
+
+
+def do_check(test, finder, pattern, result):
+    if result:
+        test.assertTrue(finder.contains(pattern))
+    else:
+        test.assertFalse(finder.contains(pattern))
+    test.assertEqual(sorted(list(f for f, c in finder.find(pattern))),
+                     sorted(result))
+
 
 class TestFileFinder(MatchTestTemplate, TestWithTmpDir):
     def add(self, path):
         ensure_parent_dir(self.tmppath(path))
         open(self.tmppath(path), 'wb').write(path)
 
     def do_check(self, pattern, result):
-        if result:
-            self.assertTrue(self.finder.contains(pattern))
-        else:
-            self.assertFalse(self.finder.contains(pattern))
-        self.assertEqual(sorted(list(f for f, c in self.finder.find(pattern))),
-                         sorted(result))
+        do_check(self, self.finder, pattern, result)
 
     def test_file_finder(self):
+        self.prepare_match_test(with_dotfiles=True)
         self.finder = FileFinder(self.tmpdir)
         self.do_match_test()
-        self.add('foo/.foo')
-        self.add('foo/.bar/foo')
-        self.assertTrue(self.finder.contains('foo/.foo'))
-        self.assertTrue(self.finder.contains('foo/.bar'))
-        self.assertTrue('foo/.foo' in [f for f, c in
-                                       self.finder.find('foo/.foo')])
-        self.assertTrue('foo/.bar/foo' in [f for f, c in
-                                           self.finder.find('foo/.bar')])
-        self.assertEqual(sorted([f for f, c in self.finder.find('foo/.*')]),
-                         ['foo/.bar/foo', 'foo/.foo'])
-        for pattern in ['foo', '**', '**/*', '**/foo', 'foo/*']:
-            self.assertFalse('foo/.foo' in [f for f, c in
-                                            self.finder.find(pattern)])
-            self.assertFalse('foo/.bar/foo' in [f for f, c in
-                                                self.finder.find(pattern)])
-            self.assertEqual(sorted([f for f, c in self.finder.find(pattern)]),
-                             sorted([f for f, c in self.finder
-                                     if mozpack.path.match(f, pattern)]))
+        self.do_finder_test(self.finder)
+
+
+class TestJarFinder(MatchTestTemplate, TestWithTmpDir):
+    def add(self, path):
+        self.jar.add(path, path, compress=True)
+
+    def do_check(self, pattern, result):
+        do_check(self, self.finder, pattern, result)
+
+    def test_jar_finder(self):
+        self.jar = JarWriter(file=self.tmppath('test.jar'))
+        self.prepare_match_test()
+        self.jar.finish()
+        reader = JarReader(file=self.tmppath('test.jar'))
+        self.finder = JarFinder(self.tmppath('test.jar'), reader)
+        self.do_match_test()
+
 
 if __name__ == '__main__':
     mozunit.main()
--- a/python/mozbuild/mozpack/test/test_packager_formats.py
+++ b/python/mozbuild/mozpack/test/test_packager_formats.py
@@ -228,39 +228,39 @@ class TestOmniJarFormatter(TestWithTmpDi
         formatter.add('app/foo', GeneratedFile('foo'))
         self.assertEqual(registry.paths(), [
             'omni.foo', 'app/omni.foo', 'chrome.manifest',
             'components/components.manifest', 'components/foo.so',
             'app/chrome.manifest', 'app/components/components.manifest',
             'app/components/foo.so', 'app/foo'
         ])
 
-
     def test_omnijar_is_resource(self):
         registry = FileRegistry()
         f = OmniJarFormatter(registry, 'omni.foo', non_resources=[
             'defaults/messenger/mailViews.dat',
             'defaults/foo/*',
             '*/dummy',
         ])
         f.add_base('app')
         for base in ['', 'app/']:
             self.assertTrue(f.is_resource(base + 'chrome'))
             self.assertTrue(
                 f.is_resource(base + 'chrome/foo/bar/baz.properties'))
             self.assertFalse(f.is_resource(base + 'chrome/icons/foo.png'))
             self.assertTrue(f.is_resource(base + 'components/foo.js'))
             self.assertFalse(f.is_resource(base + 'components/foo.so'))
             self.assertTrue(f.is_resource(base + 'res/foo.css'))
-            self.assertFalse( f.is_resource(base + 'res/cursors/foo.png'))
+            self.assertFalse(f.is_resource(base + 'res/cursors/foo.png'))
             self.assertFalse(f.is_resource(base + 'res/MainMenu.nib/'))
             self.assertTrue(f.is_resource(base + 'defaults/pref/foo.js'))
             self.assertFalse(
                 f.is_resource(base + 'defaults/pref/channel-prefs.js'))
-            self.assertTrue(f.is_resource(base + 'defaults/preferences/foo.js'))
+            self.assertTrue(
+                f.is_resource(base + 'defaults/preferences/foo.js'))
             self.assertFalse(
                 f.is_resource(base + 'defaults/preferences/channel-prefs.js'))
             self.assertTrue(f.is_resource(base + 'modules/foo.jsm'))
             self.assertTrue(f.is_resource(base + 'greprefs.js'))
             self.assertTrue(f.is_resource(base + 'hyphenation/foo'))
             self.assertTrue(f.is_resource(base + 'update.locale'))
             self.assertTrue(
                 f.is_resource(base + 'jsloader/resource/gre/modules/foo.jsm'))
--- a/python/mozbuild/mozpack/test/test_unify.py
+++ b/python/mozbuild/mozpack/test/test_unify.py
@@ -4,18 +4,27 @@
 
 from mozpack.unify import (
     UnifiedFinder,
     UnifiedBuildFinder,
 )
 import mozunit
 from mozpack.test.test_files import TestWithTmpDir
 from mozpack.copier import ensure_parent_dir
+from mozpack.files import FileFinder
+from mozpack.mozjar import JarWriter
+from mozpack.test.test_files import MockDest
+from cStringIO import StringIO
 import os
-from mozpack.errors import ErrorMessage
+import sys
+from mozpack.errors import (
+    ErrorMessage,
+    AccumulatedErrors,
+    errors,
+)
 
 
 class TestUnified(TestWithTmpDir):
     def create_one(self, which, path, content):
         file = self.tmppath(os.path.join(which, path))
         ensure_parent_dir(file)
         open(file, 'wb').write(content)
 
@@ -31,17 +40,18 @@ class TestUnifiedFinder(TestUnified):
         self.create_one('a', 'bar', 'bar')
         self.create_one('b', 'baz', 'baz')
         self.create_one('a', 'qux', 'foobar')
         self.create_one('b', 'qux', 'baz')
         self.create_one('a', 'test/foo', 'a\nb\nc\n')
         self.create_one('b', 'test/foo', 'b\nc\na\n')
         self.create_both('test/bar', 'a\nb\nc\n')
 
-        finder = UnifiedFinder(self.tmppath('a'), self.tmppath('b'),
+        finder = UnifiedFinder(FileFinder(self.tmppath('a')),
+                               FileFinder(self.tmppath('b')),
                                sorted=['test'])
         self.assertEqual(sorted([(f, c.open().read())
                                  for f, c in finder.find('foo')]),
                          [('foo/bar', 'foobar'), ('foo/baz', 'foobaz')])
         self.assertRaises(ErrorMessage, any, finder.find('bar'))
         self.assertRaises(ErrorMessage, any, finder.find('baz'))
         self.assertRaises(ErrorMessage, any, finder.find('qux'))
         self.assertEqual(sorted([(f, c.open().read())
@@ -68,17 +78,18 @@ class TestUnifiedBuildFinder(TestUnified
                         '\n'.join([
                             '<html>',
                             '<body>',
                             '<h1>about:buildconfig</h1>',
                             '<div>bar</div>',
                             '</body>',
                             '</html>',
                         ]))
-        finder = UnifiedBuildFinder(self.tmppath('a'), self.tmppath('b'))
+        finder = UnifiedBuildFinder(FileFinder(self.tmppath('a')),
+                                    FileFinder(self.tmppath('b')))
         self.assertEqual(sorted([(f, c.open().read()) for f, c in
                                  finder.find('**/chrome.manifest')]),
                          [('chrome.manifest', 'a\nb\nc\n'),
                           ('chrome/chrome.manifest', 'a\nb\nc\n')])
 
         self.assertEqual(sorted([(f, c.open().read()) for f, c in
                                  finder.find('**/buildconfig.html')]),
                          [('chrome/browser/foo/buildconfig.html', '\n'.join([
@@ -87,11 +98,30 @@ class TestUnifiedBuildFinder(TestUnified
                              '<h1>about:buildconfig</h1>',
                              '<div>foo</div>',
                              '<hr> </hr>',
                              '<div>bar</div>',
                              '</body>',
                              '</html>',
                          ]))])
 
+        xpi = MockDest()
+        with JarWriter(fileobj=xpi, compress=True) as jar:
+            jar.add('foo', 'foo')
+            jar.add('bar', 'bar')
+        foo_xpi = xpi.read()
+        self.create_both('foo.xpi', foo_xpi)
+
+        with JarWriter(fileobj=xpi, compress=True) as jar:
+            jar.add('foo', 'bar')
+        self.create_one('a', 'bar.xpi', foo_xpi)
+        self.create_one('b', 'bar.xpi', xpi.read())
+
+        errors.out = StringIO()
+        with self.assertRaises(AccumulatedErrors), errors.accumulate():
+            self.assertEqual([(f, c.open().read()) for f, c in
+                              finder.find('*.xpi')],
+                             [('foo.xpi', foo_xpi)])
+        errors.out = sys.stderr
+
 
 if __name__ == '__main__':
     mozunit.main()
--- a/python/mozbuild/mozpack/unify.py
+++ b/python/mozbuild/mozpack/unify.py
@@ -1,27 +1,26 @@
 # 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 mozpack.files import (
-    FileFinder,
+    BaseFinder,
+    JarFinder,
     ExecutableFile,
     BaseFile,
     GeneratedFile,
 )
 from mozpack.executables import (
     MACHO_SIGNATURES,
-    may_strip,
-    strip,
 )
+from mozpack.mozjar import JarReader
 from mozpack.errors import errors
 from tempfile import mkstemp
 import mozpack.path
-import shutil
 import struct
 import os
 import subprocess
 from collections import OrderedDict
 
 
 def may_unify_binary(file):
     '''
@@ -37,102 +36,109 @@ def may_unify_binary(file):
             return True
     return False
 
 
 class UnifiedExecutableFile(BaseFile):
     '''
     File class for executable and library files that to be unified with 'lipo'.
     '''
-    def __init__(self, path1, path2):
+    def __init__(self, executable1, executable2):
         '''
-        Initialize a UnifiedExecutableFile with the path to both non-fat Mach-O
-        executables to be unified.
+        Initialize a UnifiedExecutableFile with a pair of ExecutableFiles to
+        be unified. They are expected to be non-fat Mach-O executables.
         '''
-        self.path1 = path1
-        self.path2 = path2
+        assert isinstance(executable1, ExecutableFile)
+        assert isinstance(executable2, ExecutableFile)
+        self._executables = (executable1, executable2)
 
-    def copy(self, dest):
+    def copy(self, dest, skip_if_older=True):
+        '''
+        Create a fat executable from the two Mach-O executable given when
+        creating the instance.
+        skip_if_older is ignored.
+        '''
         assert isinstance(dest, basestring)
         tmpfiles = []
         try:
-            for p in [self.path1, self.path2]:
+            for e in self._executables:
                 fd, f = mkstemp()
                 os.close(fd)
                 tmpfiles.append(f)
-                shutil.copy2(p, f)
-                if may_strip(f):
-                    strip(f)
+                e.copy(f, skip_if_older=False)
             subprocess.call(['lipo', '-create'] + tmpfiles + ['-output', dest])
         finally:
             for f in tmpfiles:
                 os.unlink(f)
 
 
-class UnifiedFinder(FileFinder):
+class UnifiedFinder(BaseFinder):
     '''
     Helper to get unified BaseFile instances from two distinct trees on the
     file system.
     '''
-    def __init__(self, base1, base2, sorted=[], **kargs):
+    def __init__(self, finder1, finder2, sorted=[], **kargs):
         '''
-        Initialize a UnifiedFinder. base1 and base2 are the base directories
-        for the two trees from which files are picked. UnifiedFinder.find()
-        will act as FileFinder.find() but will error out when matches can only
-        be found in one of the two trees and not the other. It will also error
-        out if matches can be found on both ends but their contents are not
-        identical.
+        Initialize a UnifiedFinder. finder1 and finder2 are BaseFinder
+        instances from which files are picked. UnifiedFinder.find() will act as
+        FileFinder.find() but will error out when matches can only be found in
+        one of the two trees and not the other. It will also error out if
+        matches can be found on both ends but their contents are not identical.
 
         The sorted argument gives a list of mozpack.path.match patterns. File
         paths matching one of these patterns will have their contents compared
         with their lines sorted.
         '''
-        self._base1 = FileFinder(base1, **kargs)
-        self._base2 = FileFinder(base2, **kargs)
+        assert isinstance(finder1, BaseFinder)
+        assert isinstance(finder2, BaseFinder)
+        self._finder1 = finder1
+        self._finder2 = finder2
         self._sorted = sorted
+        BaseFinder.__init__(self, finder1.base, **kargs)
 
     def _find(self, path):
         '''
         UnifiedFinder.find() implementation.
         '''
         files1 = OrderedDict()
-        for p, f in self._base1.find(path):
+        for p, f in self._finder1.find(path):
             files1[p] = f
         files2 = set()
-        for p, f in self._base2.find(path):
+        for p, f in self._finder2.find(path):
             files2.add(p)
             if p in files1:
                 if may_unify_binary(files1[p]) and \
                         may_unify_binary(f):
-                    yield p, UnifiedExecutableFile(files1[p].path, f.path)
+                    yield p, UnifiedExecutableFile(files1[p], f)
                 else:
+                    err = errors.count
                     unified = self.unify_file(p, files1[p], f)
                     if unified:
                         yield p, unified
-                    else:
+                    elif err == errors.count:
                         self._report_difference(p, files1[p], f)
             else:
-                errors.error('File missing in %s: %s' % (self._base1.base, p))
+                errors.error('File missing in %s: %s' %
+                             (self._finder1.base, p))
         for p in [p for p in files1 if not p in files2]:
-            errors.error('File missing in %s: %s' % (self._base2.base, p))
+            errors.error('File missing in %s: %s' % (self._finder2.base, p))
 
     def _report_difference(self, path, file1, file2):
         '''
         Report differences between files in both trees.
         '''
         errors.error("Can't unify %s: file differs between %s and %s" %
-                     (path, self._base1.base, self._base2.base))
+                     (path, self._finder1.base, self._finder2.base))
         if not isinstance(file1, ExecutableFile) and \
                 not isinstance(file2, ExecutableFile):
             from difflib import unified_diff
-            import sys
             for line in unified_diff(file1.open().readlines(),
                                      file2.open().readlines(),
-                                     os.path.join(self._base1.base, path),
-                                     os.path.join(self._base2.base, path)):
+                                     os.path.join(self._finder1.base, path),
+                                     os.path.join(self._finder2.base, path)):
                 errors.out.write(line)
 
     def unify_file(self, path, file1, file2):
         '''
         Given two BaseFiles and the path they were found at, check whether
         their content match and return the first BaseFile if they do.
         '''
         content1 = file1.open().readlines()
@@ -148,18 +154,18 @@ class UnifiedFinder(FileFinder):
 
 
 class UnifiedBuildFinder(UnifiedFinder):
     '''
     Specialized UnifiedFinder for Mozilla applications packaging. It allows
     "*.manifest" files to differ in their order, and unifies "buildconfig.html"
     files by merging their content.
     '''
-    def __init__(self, base1, base2, **kargs):
-        UnifiedFinder.__init__(self, base1, base2,
+    def __init__(self, finder1, finder2, **kargs):
+        UnifiedFinder.__init__(self, finder1, finder2,
                                sorted=['**/*.manifest'], **kargs)
 
     def unify_file(self, path, file1, file2):
         '''
         Unify buildconfig.html contents, or defer to UnifiedFinder.unify_file.
         '''
         if mozpack.path.basename(path) == 'buildconfig.html':
             content1 = file1.open().readlines()
@@ -167,9 +173,20 @@ class UnifiedBuildFinder(UnifiedFinder):
             # Copy everything from the first file up to the end of its <body>,
             # insert a <hr> between the two files and copy the second file's
             # content beginning after its leading <h1>.
             return GeneratedFile(''.join(
                 content1[:content1.index('</body>\n')] +
                 ['<hr> </hr>\n'] +
                 content2[content2.index('<h1>about:buildconfig</h1>\n') + 1:]
             ))
+        if path.endswith('.xpi'):
+            finder1 = JarFinder(os.path.join(self._finder1.base, path),
+                                JarReader(fileobj=file1.open()))
+            finder2 = JarFinder(os.path.join(self._finder2.base, path),
+                                JarReader(fileobj=file2.open()))
+            unifier = UnifiedFinder(finder1, finder2, sorted=self._sorted)
+            err = errors.count
+            all(unifier.find(''))
+            if err == errors.count:
+                return file1
+            return None
         return UnifiedFinder.unify_file(self, path, file1, file2)
--- a/toolkit/mozapps/installer/find-dupes.py
+++ b/toolkit/mozapps/installer/find-dupes.py
@@ -1,19 +1,16 @@
 # 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/.
 
 import sys
 import hashlib
 from mozpack.packager.unpack import UnpackFinder
-try:
-    from collections import OrderedDict
-except ImportError:
-    from simplejson import OrderedDict
+from collections import OrderedDict
 
 '''
 Find files duplicated in a given packaged directory, independently of its
 package format.
 '''
 
 
 def find_dupes(source):
--- a/toolkit/mozapps/installer/l10n-repack.py
+++ b/toolkit/mozapps/installer/l10n-repack.py
@@ -2,17 +2,16 @@
 # 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/.
 
 '''
 Replace localized parts of a packaged directory with data from a langpack
 directory.
 '''
 
-import sys
 import os
 import mozpack.path
 from mozpack.packager.formats import (
     FlatFormatter,
     JarFormatter,
     OmniJarFormatter,
 )
 from mozpack.packager import SimplePackager
@@ -176,17 +175,17 @@ def repack(source, l10n, non_resources=[
             if not formatter.contains(p):
                 formatter.add(p, f)
 
     # Transplant jar preloading information.
     for path, log in finder.jarlogs.iteritems():
         assert isinstance(copier[path], Jarrer)
         copier[path].preload([l.replace(locale, l10n_locale) for l in log])
 
-    copier.copy(source)
+    copier.copy(source, skip_if_older=False)
     generate_precomplete(source)
 
 
 def main():
     parser = ArgumentParser()
     parser.add_argument('build',
                         help='Directory containing the build to repack')
     parser.add_argument('l10n',
--- a/toolkit/mozapps/installer/packager.py
+++ b/toolkit/mozapps/installer/packager.py
@@ -23,18 +23,16 @@ from mozpack.copier import (
 )
 from mozpack.errors import errors
 from mozpack.unify import UnifiedBuildFinder
 import mozpack.path
 import buildconfig
 from argparse import ArgumentParser
 from createprecomplete import generate_precomplete
 import os
-import re
-import sys
 from StringIO import StringIO
 import subprocess
 import platform
 
 # List of libraries to shlibsign.
 SIGN_LIBS = [
     'softokn3',
     'nssdbm3',
@@ -89,22 +87,22 @@ class ToolLauncher(object):
 
 launcher = ToolLauncher()
 
 
 class LibSignFile(File):
     '''
     File class for shlibsign signatures.
     '''
-    def copy(self, dest):
+    def copy(self, dest, skip_if_older=True):
         assert isinstance(dest, basestring)
         # os.path.getmtime returns a result in seconds with precision up to the
         # microsecond. But microsecond is too precise because shutil.copystat
         # only copies milliseconds, and seconds is not enough precision.
-        if os.path.exists(dest) and \
+        if os.path.exists(dest) and skip_if_older and \
                 int(os.path.getmtime(self.path) * 1000) <= \
                 int(os.path.getmtime(dest) * 1000):
             return False
         if launcher.launch(['shlibsign', '-v', '-o', dest, '-i', self.path]):
             errors.fatal('Error while signing %s' % self.path)
 
 
 def precompile_cache(formatter, source_path, gre_path, app_path):
@@ -291,17 +289,18 @@ def main():
                                          key=is_native, reverse=True)
         if is_native(args.source):
             launcher.tooldir = args.source
     elif not buildconfig.substs['CROSS_COMPILE']:
         launcher.tooldir = buildconfig.substs['LIBXUL_DIST']
 
     with errors.accumulate():
         if args.unify:
-            finder = UnifiedBuildFinder(args.source, args.unify,
+            finder = UnifiedBuildFinder(FileFinder(args.source),
+                                        FileFinder(args.unify),
                                         minify=args.minify)
         else:
             finder = FileFinder(args.source, minify=args.minify)
         if 'NO_PKG_FILES' in os.environ:
             sinkformatter = NoPkgFilesRemover(formatter,
                                               args.manifest is not None)
         else:
             sinkformatter = formatter
@@ -323,17 +322,18 @@ def main():
     # shlibsign libraries
     if launcher.can_launch():
         for lib in SIGN_LIBS:
             libbase = mozpack.path.join(binpath, '%s%s') \
                 % (buildconfig.substs['DLL_PREFIX'], lib)
             libname = '%s%s' % (libbase, buildconfig.substs['DLL_SUFFIX'])
             if copier.contains(libname):
                 copier.add(libbase + '.chk',
-                           LibSignFile(os.path.join(args.destination, libname)))
+                           LibSignFile(os.path.join(args.destination,
+                                                    libname)))
 
     # Setup preloading
     if args.jarlogs:
         jarlogs = FileFinder(args.jarlogs)
         for p, log in jarlogs:
             if p.endswith('.log'):
                 p = p[:-4]
             if copier.contains(p) and isinstance(copier[p], Jarrer):
--- a/toolkit/mozapps/installer/unpack.py
+++ b/toolkit/mozapps/installer/unpack.py
@@ -2,16 +2,17 @@
 # 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/.
 
 import sys
 import os
 from mozpack.packager.unpack import unpack
 import buildconfig
 
+
 def main():
     if len(sys.argv) != 2:
         print >>sys.stderr, "Usage: %s directory" % \
                             os.path.basename(sys.argv[0])
         sys.exit(1)
 
     buildconfig.substs['USE_ELF_HACK'] = False
     buildconfig.substs['PKG_SKIP_STRIP'] = True
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -842,29 +842,16 @@ nsXREDirProvider::DoShutdown()
       mozilla::services::GetObserverService();
     NS_ASSERTION(obsSvc, "No observer service?");
     if (obsSvc) {
       static const PRUnichar kShutdownPersist[] =
         {'s','h','u','t','d','o','w','n','-','p','e','r','s','i','s','t','\0'};
       obsSvc->NotifyObservers(nullptr, "profile-change-net-teardown", kShutdownPersist);
       obsSvc->NotifyObservers(nullptr, "profile-change-teardown", kShutdownPersist);
 
-      // Phase 2c: Now that things are torn down, force JS GC so that things which depend on
-      // resources which are about to go away in "profile-before-change" are destroyed first.
-
-      nsCOMPtr<nsIJSRuntimeService> rtsvc
-        (do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
-      if (rtsvc)
-      {
-        JSRuntime *rt = nullptr;
-        rtsvc->GetRuntime(&rt);
-        if (rt)
-          ::JS_GC(rt);
-      }
-
       // Phase 3: Notify observers of a profile change
       obsSvc->NotifyObservers(nullptr, "profile-before-change", kShutdownPersist);
     }
     mProfileNotified = false;
   }
 }
 
 #ifdef XP_WIN