python/mozbuild/mozpack/packager/unpack.py

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this
     3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 import mozpack.path
     6 from mozpack.files import (
     7     FileFinder,
     8     DeflatedFile,
     9     ManifestFile,
    10 )
    11 from mozpack.chrome.manifest import (
    12     parse_manifest,
    13     ManifestEntryWithRelPath,
    14     ManifestResource,
    15     is_manifest,
    16 )
    17 from mozpack.mozjar import JarReader
    18 from mozpack.copier import (
    19     FileRegistry,
    20     FileCopier,
    21 )
    22 from mozpack.packager import SimplePackager
    23 from mozpack.packager.formats import (
    24     FlatFormatter,
    25     STARTUP_CACHE_PATHS,
    26 )
    27 from urlparse import urlparse
    30 class UnpackFinder(FileFinder):
    31     '''
    32     Special FileFinder that treats the source package directory as if it were
    33     in the flat chrome format, whatever chrome format it actually is in.
    35     This means that for example, paths like chrome/browser/content/... match
    36     files under jar:chrome/browser.jar!/content/... in case of jar chrome
    37     format.
    38     '''
    39     def __init__(self, *args, **kargs):
    40         FileFinder.__init__(self, *args, **kargs)
    41         self.files = FileRegistry()
    42         self.kind = 'flat'
    43         self.omnijar = None
    44         self.jarlogs = {}
    45         self.optimizedjars = False
    47         jars = set()
    49         for p, f in FileFinder.find(self, '*'):
    50             # Skip the precomplete file, which is generated at packaging time.
    51             if p == 'precomplete':
    52                 continue
    53             base = mozpack.path.dirname(p)
    54             # If the file is a zip/jar that is not a .xpi, and contains a
    55             # chrome.manifest, it is an omnijar. All the files it contains
    56             # go in the directory containing the omnijar. Manifests are merged
    57             # if there is a corresponding manifest in the directory.
    58             if not p.endswith('.xpi') and self._maybe_zip(f) and \
    59                     (mozpack.path.basename(p) == self.omnijar or
    60                      not self.omnijar):
    61                 jar = self._open_jar(p, f)
    62                 if 'chrome.manifest' in jar:
    63                     self.kind = 'omni'
    64                     self.omnijar = mozpack.path.basename(p)
    65                     self._fill_with_omnijar(base, jar)
    66                     continue
    67             # If the file is a manifest, scan its entries for some referencing
    68             # jar: urls. If there are some, the files contained in the jar they
    69             # point to, go under a directory named after the jar.
    70             if is_manifest(p):
    71                 m = self.files[p] if self.files.contains(p) \
    72                     else ManifestFile(base)
    73                 for e in parse_manifest(self.base, p, f.open()):
    74                     m.add(self._handle_manifest_entry(e, jars))
    75                 if self.files.contains(p):
    76                     continue
    77                 f = m
    78             if not p in jars:
    79                 self.files.add(p, f)
    81     def _fill_with_omnijar(self, base, jar):
    82         for j in jar:
    83             path = mozpack.path.join(base, j.filename)
    84             if is_manifest(j.filename):
    85                 m = self.files[path] if self.files.contains(path) \
    86                     else ManifestFile(mozpack.path.dirname(path))
    87                 for e in parse_manifest(None, path, j):
    88                     m.add(e)
    89                 if not self.files.contains(path):
    90                     self.files.add(path, m)
    91                 continue
    92             else:
    93                 self.files.add(path, DeflatedFile(j))
    95     def _handle_manifest_entry(self, entry, jars):
    96         jarpath = None
    97         if isinstance(entry, ManifestEntryWithRelPath) and \
    98                 urlparse(entry.relpath).scheme == 'jar':
    99             jarpath, entry = self._unjarize(entry, entry.relpath)
   100         elif isinstance(entry, ManifestResource) and \
   101                 urlparse(entry.target).scheme == 'jar':
   102             jarpath, entry = self._unjarize(entry, entry.target)
   103         if jarpath:
   104             # Don't defer unpacking the jar file. If we already saw
   105             # it, take (and remove) it from the registry. If we
   106             # haven't, try to find it now.
   107             if self.files.contains(jarpath):
   108                 jar = self.files[jarpath]
   109                 self.files.remove(jarpath)
   110             else:
   111                 jar = [f for p, f in FileFinder.find(self, jarpath)]
   112                 assert len(jar) == 1
   113                 jar = jar[0]
   114             if not jarpath in jars:
   115                 base = mozpack.path.splitext(jarpath)[0]
   116                 for j in self._open_jar(jarpath, jar):
   117                     self.files.add(mozpack.path.join(base,
   118                                                      j.filename),
   119                                    DeflatedFile(j))
   120             jars.add(jarpath)
   121             self.kind = 'jar'
   122         return entry
   124     def _open_jar(self, path, file):
   125         '''
   126         Return a JarReader for the given BaseFile instance, keeping a log of
   127         the preloaded entries it has.
   128         '''
   129         jar = JarReader(fileobj=file.open())
   130         if jar.is_optimized:
   131             self.optimizedjars = True
   132         if jar.last_preloaded:
   133             jarlog = jar.entries.keys()
   134             self.jarlogs[path] = jarlog[:jarlog.index(jar.last_preloaded) + 1]
   135         return jar
   137     def find(self, path):
   138         for p in self.files.match(path):
   139             yield p, self.files[p]
   141     def _maybe_zip(self, file):
   142         '''
   143         Return whether the given BaseFile looks like a ZIP/Jar.
   144         '''
   145         header = file.open().read(8)
   146         return len(header) == 8 and (header[0:2] == 'PK' or
   147                                      header[4:6] == 'PK')
   149     def _unjarize(self, entry, relpath):
   150         '''
   151         Transform a manifest entry pointing to chrome data in a jar in one
   152         pointing to the corresponding unpacked path. Return the jar path and
   153         the new entry.
   154         '''
   155         base = entry.base
   156         jar, relpath = urlparse(relpath).path.split('!', 1)
   157         entry = entry.rebase(mozpack.path.join(base, 'jar:%s!' % jar)) \
   158             .move(mozpack.path.join(base, mozpack.path.splitext(jar)[0])) \
   159             .rebase(base)
   160         return mozpack.path.join(base, jar), entry
   163 def unpack(source):
   164     '''
   165     Transform a jar chrome or omnijar packaged directory into a flat package.
   166     '''
   167     copier = FileCopier()
   168     finder = UnpackFinder(source)
   169     packager = SimplePackager(FlatFormatter(copier))
   170     for p, f in finder.find('*'):
   171         if mozpack.path.split(p)[0] not in STARTUP_CACHE_PATHS:
   172             packager.add(p, f)
   173     packager.close()
   174     copier.copy(source, skip_if_older=False)

mercurial