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