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)