|
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/. |
|
4 |
|
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 |
|
28 |
|
29 |
|
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. |
|
34 |
|
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 |
|
46 |
|
47 jars = set() |
|
48 |
|
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) |
|
80 |
|
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)) |
|
94 |
|
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 |
|
123 |
|
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 |
|
136 |
|
137 def find(self, path): |
|
138 for p in self.files.match(path): |
|
139 yield p, self.files[p] |
|
140 |
|
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') |
|
148 |
|
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 |
|
161 |
|
162 |
|
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) |