python/mozbuild/mozpack/packager/unpack.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/python/mozbuild/mozpack/packager/unpack.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,174 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +import mozpack.path
     1.9 +from mozpack.files import (
    1.10 +    FileFinder,
    1.11 +    DeflatedFile,
    1.12 +    ManifestFile,
    1.13 +)
    1.14 +from mozpack.chrome.manifest import (
    1.15 +    parse_manifest,
    1.16 +    ManifestEntryWithRelPath,
    1.17 +    ManifestResource,
    1.18 +    is_manifest,
    1.19 +)
    1.20 +from mozpack.mozjar import JarReader
    1.21 +from mozpack.copier import (
    1.22 +    FileRegistry,
    1.23 +    FileCopier,
    1.24 +)
    1.25 +from mozpack.packager import SimplePackager
    1.26 +from mozpack.packager.formats import (
    1.27 +    FlatFormatter,
    1.28 +    STARTUP_CACHE_PATHS,
    1.29 +)
    1.30 +from urlparse import urlparse
    1.31 +
    1.32 +
    1.33 +class UnpackFinder(FileFinder):
    1.34 +    '''
    1.35 +    Special FileFinder that treats the source package directory as if it were
    1.36 +    in the flat chrome format, whatever chrome format it actually is in.
    1.37 +
    1.38 +    This means that for example, paths like chrome/browser/content/... match
    1.39 +    files under jar:chrome/browser.jar!/content/... in case of jar chrome
    1.40 +    format.
    1.41 +    '''
    1.42 +    def __init__(self, *args, **kargs):
    1.43 +        FileFinder.__init__(self, *args, **kargs)
    1.44 +        self.files = FileRegistry()
    1.45 +        self.kind = 'flat'
    1.46 +        self.omnijar = None
    1.47 +        self.jarlogs = {}
    1.48 +        self.optimizedjars = False
    1.49 +
    1.50 +        jars = set()
    1.51 +
    1.52 +        for p, f in FileFinder.find(self, '*'):
    1.53 +            # Skip the precomplete file, which is generated at packaging time.
    1.54 +            if p == 'precomplete':
    1.55 +                continue
    1.56 +            base = mozpack.path.dirname(p)
    1.57 +            # If the file is a zip/jar that is not a .xpi, and contains a
    1.58 +            # chrome.manifest, it is an omnijar. All the files it contains
    1.59 +            # go in the directory containing the omnijar. Manifests are merged
    1.60 +            # if there is a corresponding manifest in the directory.
    1.61 +            if not p.endswith('.xpi') and self._maybe_zip(f) and \
    1.62 +                    (mozpack.path.basename(p) == self.omnijar or
    1.63 +                     not self.omnijar):
    1.64 +                jar = self._open_jar(p, f)
    1.65 +                if 'chrome.manifest' in jar:
    1.66 +                    self.kind = 'omni'
    1.67 +                    self.omnijar = mozpack.path.basename(p)
    1.68 +                    self._fill_with_omnijar(base, jar)
    1.69 +                    continue
    1.70 +            # If the file is a manifest, scan its entries for some referencing
    1.71 +            # jar: urls. If there are some, the files contained in the jar they
    1.72 +            # point to, go under a directory named after the jar.
    1.73 +            if is_manifest(p):
    1.74 +                m = self.files[p] if self.files.contains(p) \
    1.75 +                    else ManifestFile(base)
    1.76 +                for e in parse_manifest(self.base, p, f.open()):
    1.77 +                    m.add(self._handle_manifest_entry(e, jars))
    1.78 +                if self.files.contains(p):
    1.79 +                    continue
    1.80 +                f = m
    1.81 +            if not p in jars:
    1.82 +                self.files.add(p, f)
    1.83 +
    1.84 +    def _fill_with_omnijar(self, base, jar):
    1.85 +        for j in jar:
    1.86 +            path = mozpack.path.join(base, j.filename)
    1.87 +            if is_manifest(j.filename):
    1.88 +                m = self.files[path] if self.files.contains(path) \
    1.89 +                    else ManifestFile(mozpack.path.dirname(path))
    1.90 +                for e in parse_manifest(None, path, j):
    1.91 +                    m.add(e)
    1.92 +                if not self.files.contains(path):
    1.93 +                    self.files.add(path, m)
    1.94 +                continue
    1.95 +            else:
    1.96 +                self.files.add(path, DeflatedFile(j))
    1.97 +
    1.98 +    def _handle_manifest_entry(self, entry, jars):
    1.99 +        jarpath = None
   1.100 +        if isinstance(entry, ManifestEntryWithRelPath) and \
   1.101 +                urlparse(entry.relpath).scheme == 'jar':
   1.102 +            jarpath, entry = self._unjarize(entry, entry.relpath)
   1.103 +        elif isinstance(entry, ManifestResource) and \
   1.104 +                urlparse(entry.target).scheme == 'jar':
   1.105 +            jarpath, entry = self._unjarize(entry, entry.target)
   1.106 +        if jarpath:
   1.107 +            # Don't defer unpacking the jar file. If we already saw
   1.108 +            # it, take (and remove) it from the registry. If we
   1.109 +            # haven't, try to find it now.
   1.110 +            if self.files.contains(jarpath):
   1.111 +                jar = self.files[jarpath]
   1.112 +                self.files.remove(jarpath)
   1.113 +            else:
   1.114 +                jar = [f for p, f in FileFinder.find(self, jarpath)]
   1.115 +                assert len(jar) == 1
   1.116 +                jar = jar[0]
   1.117 +            if not jarpath in jars:
   1.118 +                base = mozpack.path.splitext(jarpath)[0]
   1.119 +                for j in self._open_jar(jarpath, jar):
   1.120 +                    self.files.add(mozpack.path.join(base,
   1.121 +                                                     j.filename),
   1.122 +                                   DeflatedFile(j))
   1.123 +            jars.add(jarpath)
   1.124 +            self.kind = 'jar'
   1.125 +        return entry
   1.126 +
   1.127 +    def _open_jar(self, path, file):
   1.128 +        '''
   1.129 +        Return a JarReader for the given BaseFile instance, keeping a log of
   1.130 +        the preloaded entries it has.
   1.131 +        '''
   1.132 +        jar = JarReader(fileobj=file.open())
   1.133 +        if jar.is_optimized:
   1.134 +            self.optimizedjars = True
   1.135 +        if jar.last_preloaded:
   1.136 +            jarlog = jar.entries.keys()
   1.137 +            self.jarlogs[path] = jarlog[:jarlog.index(jar.last_preloaded) + 1]
   1.138 +        return jar
   1.139 +
   1.140 +    def find(self, path):
   1.141 +        for p in self.files.match(path):
   1.142 +            yield p, self.files[p]
   1.143 +
   1.144 +    def _maybe_zip(self, file):
   1.145 +        '''
   1.146 +        Return whether the given BaseFile looks like a ZIP/Jar.
   1.147 +        '''
   1.148 +        header = file.open().read(8)
   1.149 +        return len(header) == 8 and (header[0:2] == 'PK' or
   1.150 +                                     header[4:6] == 'PK')
   1.151 +
   1.152 +    def _unjarize(self, entry, relpath):
   1.153 +        '''
   1.154 +        Transform a manifest entry pointing to chrome data in a jar in one
   1.155 +        pointing to the corresponding unpacked path. Return the jar path and
   1.156 +        the new entry.
   1.157 +        '''
   1.158 +        base = entry.base
   1.159 +        jar, relpath = urlparse(relpath).path.split('!', 1)
   1.160 +        entry = entry.rebase(mozpack.path.join(base, 'jar:%s!' % jar)) \
   1.161 +            .move(mozpack.path.join(base, mozpack.path.splitext(jar)[0])) \
   1.162 +            .rebase(base)
   1.163 +        return mozpack.path.join(base, jar), entry
   1.164 +
   1.165 +
   1.166 +def unpack(source):
   1.167 +    '''
   1.168 +    Transform a jar chrome or omnijar packaged directory into a flat package.
   1.169 +    '''
   1.170 +    copier = FileCopier()
   1.171 +    finder = UnpackFinder(source)
   1.172 +    packager = SimplePackager(FlatFormatter(copier))
   1.173 +    for p, f in finder.find('*'):
   1.174 +        if mozpack.path.split(p)[0] not in STARTUP_CACHE_PATHS:
   1.175 +            packager.add(p, f)
   1.176 +    packager.close()
   1.177 +    copier.copy(source, skip_if_older=False)

mercurial