michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import mozpack.path michael@0: from mozpack.files import ( michael@0: FileFinder, michael@0: DeflatedFile, michael@0: ManifestFile, michael@0: ) michael@0: from mozpack.chrome.manifest import ( michael@0: parse_manifest, michael@0: ManifestEntryWithRelPath, michael@0: ManifestResource, michael@0: is_manifest, michael@0: ) michael@0: from mozpack.mozjar import JarReader michael@0: from mozpack.copier import ( michael@0: FileRegistry, michael@0: FileCopier, michael@0: ) michael@0: from mozpack.packager import SimplePackager michael@0: from mozpack.packager.formats import ( michael@0: FlatFormatter, michael@0: STARTUP_CACHE_PATHS, michael@0: ) michael@0: from urlparse import urlparse michael@0: michael@0: michael@0: class UnpackFinder(FileFinder): michael@0: ''' michael@0: Special FileFinder that treats the source package directory as if it were michael@0: in the flat chrome format, whatever chrome format it actually is in. michael@0: michael@0: This means that for example, paths like chrome/browser/content/... match michael@0: files under jar:chrome/browser.jar!/content/... in case of jar chrome michael@0: format. michael@0: ''' michael@0: def __init__(self, *args, **kargs): michael@0: FileFinder.__init__(self, *args, **kargs) michael@0: self.files = FileRegistry() michael@0: self.kind = 'flat' michael@0: self.omnijar = None michael@0: self.jarlogs = {} michael@0: self.optimizedjars = False michael@0: michael@0: jars = set() michael@0: michael@0: for p, f in FileFinder.find(self, '*'): michael@0: # Skip the precomplete file, which is generated at packaging time. michael@0: if p == 'precomplete': michael@0: continue michael@0: base = mozpack.path.dirname(p) michael@0: # If the file is a zip/jar that is not a .xpi, and contains a michael@0: # chrome.manifest, it is an omnijar. All the files it contains michael@0: # go in the directory containing the omnijar. Manifests are merged michael@0: # if there is a corresponding manifest in the directory. michael@0: if not p.endswith('.xpi') and self._maybe_zip(f) and \ michael@0: (mozpack.path.basename(p) == self.omnijar or michael@0: not self.omnijar): michael@0: jar = self._open_jar(p, f) michael@0: if 'chrome.manifest' in jar: michael@0: self.kind = 'omni' michael@0: self.omnijar = mozpack.path.basename(p) michael@0: self._fill_with_omnijar(base, jar) michael@0: continue michael@0: # If the file is a manifest, scan its entries for some referencing michael@0: # jar: urls. If there are some, the files contained in the jar they michael@0: # point to, go under a directory named after the jar. michael@0: if is_manifest(p): michael@0: m = self.files[p] if self.files.contains(p) \ michael@0: else ManifestFile(base) michael@0: for e in parse_manifest(self.base, p, f.open()): michael@0: m.add(self._handle_manifest_entry(e, jars)) michael@0: if self.files.contains(p): michael@0: continue michael@0: f = m michael@0: if not p in jars: michael@0: self.files.add(p, f) michael@0: michael@0: def _fill_with_omnijar(self, base, jar): michael@0: for j in jar: michael@0: path = mozpack.path.join(base, j.filename) michael@0: if is_manifest(j.filename): michael@0: m = self.files[path] if self.files.contains(path) \ michael@0: else ManifestFile(mozpack.path.dirname(path)) michael@0: for e in parse_manifest(None, path, j): michael@0: m.add(e) michael@0: if not self.files.contains(path): michael@0: self.files.add(path, m) michael@0: continue michael@0: else: michael@0: self.files.add(path, DeflatedFile(j)) michael@0: michael@0: def _handle_manifest_entry(self, entry, jars): michael@0: jarpath = None michael@0: if isinstance(entry, ManifestEntryWithRelPath) and \ michael@0: urlparse(entry.relpath).scheme == 'jar': michael@0: jarpath, entry = self._unjarize(entry, entry.relpath) michael@0: elif isinstance(entry, ManifestResource) and \ michael@0: urlparse(entry.target).scheme == 'jar': michael@0: jarpath, entry = self._unjarize(entry, entry.target) michael@0: if jarpath: michael@0: # Don't defer unpacking the jar file. If we already saw michael@0: # it, take (and remove) it from the registry. If we michael@0: # haven't, try to find it now. michael@0: if self.files.contains(jarpath): michael@0: jar = self.files[jarpath] michael@0: self.files.remove(jarpath) michael@0: else: michael@0: jar = [f for p, f in FileFinder.find(self, jarpath)] michael@0: assert len(jar) == 1 michael@0: jar = jar[0] michael@0: if not jarpath in jars: michael@0: base = mozpack.path.splitext(jarpath)[0] michael@0: for j in self._open_jar(jarpath, jar): michael@0: self.files.add(mozpack.path.join(base, michael@0: j.filename), michael@0: DeflatedFile(j)) michael@0: jars.add(jarpath) michael@0: self.kind = 'jar' michael@0: return entry michael@0: michael@0: def _open_jar(self, path, file): michael@0: ''' michael@0: Return a JarReader for the given BaseFile instance, keeping a log of michael@0: the preloaded entries it has. michael@0: ''' michael@0: jar = JarReader(fileobj=file.open()) michael@0: if jar.is_optimized: michael@0: self.optimizedjars = True michael@0: if jar.last_preloaded: michael@0: jarlog = jar.entries.keys() michael@0: self.jarlogs[path] = jarlog[:jarlog.index(jar.last_preloaded) + 1] michael@0: return jar michael@0: michael@0: def find(self, path): michael@0: for p in self.files.match(path): michael@0: yield p, self.files[p] michael@0: michael@0: def _maybe_zip(self, file): michael@0: ''' michael@0: Return whether the given BaseFile looks like a ZIP/Jar. michael@0: ''' michael@0: header = file.open().read(8) michael@0: return len(header) == 8 and (header[0:2] == 'PK' or michael@0: header[4:6] == 'PK') michael@0: michael@0: def _unjarize(self, entry, relpath): michael@0: ''' michael@0: Transform a manifest entry pointing to chrome data in a jar in one michael@0: pointing to the corresponding unpacked path. Return the jar path and michael@0: the new entry. michael@0: ''' michael@0: base = entry.base michael@0: jar, relpath = urlparse(relpath).path.split('!', 1) michael@0: entry = entry.rebase(mozpack.path.join(base, 'jar:%s!' % jar)) \ michael@0: .move(mozpack.path.join(base, mozpack.path.splitext(jar)[0])) \ michael@0: .rebase(base) michael@0: return mozpack.path.join(base, jar), entry michael@0: michael@0: michael@0: def unpack(source): michael@0: ''' michael@0: Transform a jar chrome or omnijar packaged directory into a flat package. michael@0: ''' michael@0: copier = FileCopier() michael@0: finder = UnpackFinder(source) michael@0: packager = SimplePackager(FlatFormatter(copier)) michael@0: for p, f in finder.find('*'): michael@0: if mozpack.path.split(p)[0] not in STARTUP_CACHE_PATHS: michael@0: packager.add(p, f) michael@0: packager.close() michael@0: copier.copy(source, skip_if_older=False)