Fri, 16 Jan 2015 18:13:44 +0100
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)