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 re |
michael@0 | 6 | import os |
michael@0 | 7 | from urlparse import urlparse |
michael@0 | 8 | import mozpack.path |
michael@0 | 9 | from mozpack.chrome.flags import Flags |
michael@0 | 10 | from mozpack.errors import errors |
michael@0 | 11 | |
michael@0 | 12 | |
michael@0 | 13 | class ManifestEntry(object): |
michael@0 | 14 | ''' |
michael@0 | 15 | Base class for all manifest entry types. |
michael@0 | 16 | Subclasses may define the following class or member variables: |
michael@0 | 17 | - localized: indicates whether the manifest entry is used for localized |
michael@0 | 18 | data. |
michael@0 | 19 | - type: the manifest entry type (e.g. 'content' in |
michael@0 | 20 | 'content global content/global/') |
michael@0 | 21 | - allowed_flags: a set of flags allowed to be defined for the given |
michael@0 | 22 | manifest entry type. |
michael@0 | 23 | |
michael@0 | 24 | A manifest entry is attached to a base path, defining where the manifest |
michael@0 | 25 | entry is bound to, and that is used to find relative paths defined in |
michael@0 | 26 | entries. |
michael@0 | 27 | ''' |
michael@0 | 28 | localized = False |
michael@0 | 29 | type = None |
michael@0 | 30 | allowed_flags = [ |
michael@0 | 31 | 'application', |
michael@0 | 32 | 'platformversion', |
michael@0 | 33 | 'os', |
michael@0 | 34 | 'osversion', |
michael@0 | 35 | 'abi', |
michael@0 | 36 | 'xpcnativewrappers', |
michael@0 | 37 | 'tablet', |
michael@0 | 38 | ] |
michael@0 | 39 | |
michael@0 | 40 | def __init__(self, base, *flags): |
michael@0 | 41 | ''' |
michael@0 | 42 | Initialize a manifest entry with the given base path and flags. |
michael@0 | 43 | ''' |
michael@0 | 44 | self.base = base |
michael@0 | 45 | self.flags = Flags(*flags) |
michael@0 | 46 | if not all(f in self.allowed_flags for f in self.flags): |
michael@0 | 47 | errors.fatal('%s unsupported for %s manifest entries' % |
michael@0 | 48 | (','.join(f for f in self.flags |
michael@0 | 49 | if not f in self.allowed_flags), self.type)) |
michael@0 | 50 | |
michael@0 | 51 | def serialize(self, *args): |
michael@0 | 52 | ''' |
michael@0 | 53 | Serialize the manifest entry. |
michael@0 | 54 | ''' |
michael@0 | 55 | entry = [self.type] + list(args) |
michael@0 | 56 | flags = str(self.flags) |
michael@0 | 57 | if flags: |
michael@0 | 58 | entry.append(flags) |
michael@0 | 59 | return ' '.join(entry) |
michael@0 | 60 | |
michael@0 | 61 | def __eq__(self, other): |
michael@0 | 62 | return self.base == other.base and str(self) == str(other) |
michael@0 | 63 | |
michael@0 | 64 | def __ne__(self, other): |
michael@0 | 65 | return not self.__eq__(other) |
michael@0 | 66 | |
michael@0 | 67 | def __repr__(self): |
michael@0 | 68 | return '<%s@%s>' % (str(self), self.base) |
michael@0 | 69 | |
michael@0 | 70 | def move(self, base): |
michael@0 | 71 | ''' |
michael@0 | 72 | Return a new manifest entry with a different base path. |
michael@0 | 73 | ''' |
michael@0 | 74 | return parse_manifest_line(base, str(self)) |
michael@0 | 75 | |
michael@0 | 76 | def rebase(self, base): |
michael@0 | 77 | ''' |
michael@0 | 78 | Return a new manifest entry with all relative paths defined in the |
michael@0 | 79 | entry relative to a new base directory. |
michael@0 | 80 | The base class doesn't define relative paths, so it is equivalent to |
michael@0 | 81 | move(). |
michael@0 | 82 | ''' |
michael@0 | 83 | return self.move(base) |
michael@0 | 84 | |
michael@0 | 85 | |
michael@0 | 86 | class ManifestEntryWithRelPath(ManifestEntry): |
michael@0 | 87 | ''' |
michael@0 | 88 | Abstract manifest entry type with a relative path definition. |
michael@0 | 89 | ''' |
michael@0 | 90 | def __init__(self, base, relpath, *flags): |
michael@0 | 91 | ManifestEntry.__init__(self, base, *flags) |
michael@0 | 92 | self.relpath = relpath |
michael@0 | 93 | |
michael@0 | 94 | def __str__(self): |
michael@0 | 95 | return self.serialize(self.relpath) |
michael@0 | 96 | |
michael@0 | 97 | def rebase(self, base): |
michael@0 | 98 | ''' |
michael@0 | 99 | Return a new manifest entry with all relative paths defined in the |
michael@0 | 100 | entry relative to a new base directory. |
michael@0 | 101 | ''' |
michael@0 | 102 | clone = ManifestEntry.rebase(self, base) |
michael@0 | 103 | clone.relpath = mozpack.path.rebase(self.base, base, self.relpath) |
michael@0 | 104 | return clone |
michael@0 | 105 | |
michael@0 | 106 | @property |
michael@0 | 107 | def path(self): |
michael@0 | 108 | return mozpack.path.normpath(mozpack.path.join(self.base, |
michael@0 | 109 | self.relpath)) |
michael@0 | 110 | |
michael@0 | 111 | |
michael@0 | 112 | class Manifest(ManifestEntryWithRelPath): |
michael@0 | 113 | ''' |
michael@0 | 114 | Class for 'manifest' entries. |
michael@0 | 115 | manifest some/path/to/another.manifest |
michael@0 | 116 | ''' |
michael@0 | 117 | type = 'manifest' |
michael@0 | 118 | |
michael@0 | 119 | |
michael@0 | 120 | class ManifestChrome(ManifestEntryWithRelPath): |
michael@0 | 121 | ''' |
michael@0 | 122 | Abstract class for chrome entries. |
michael@0 | 123 | ''' |
michael@0 | 124 | def __init__(self, base, name, relpath, *flags): |
michael@0 | 125 | ManifestEntryWithRelPath.__init__(self, base, relpath, *flags) |
michael@0 | 126 | self.name = name |
michael@0 | 127 | |
michael@0 | 128 | @property |
michael@0 | 129 | def location(self): |
michael@0 | 130 | return mozpack.path.join(self.base, self.relpath) |
michael@0 | 131 | |
michael@0 | 132 | |
michael@0 | 133 | class ManifestContent(ManifestChrome): |
michael@0 | 134 | ''' |
michael@0 | 135 | Class for 'content' entries. |
michael@0 | 136 | content global content/global/ |
michael@0 | 137 | ''' |
michael@0 | 138 | type = 'content' |
michael@0 | 139 | allowed_flags = ManifestChrome.allowed_flags + [ |
michael@0 | 140 | 'contentaccessible', |
michael@0 | 141 | 'platform', |
michael@0 | 142 | ] |
michael@0 | 143 | |
michael@0 | 144 | def __str__(self): |
michael@0 | 145 | return self.serialize(self.name, self.relpath) |
michael@0 | 146 | |
michael@0 | 147 | |
michael@0 | 148 | class ManifestMultiContent(ManifestChrome): |
michael@0 | 149 | ''' |
michael@0 | 150 | Abstract class for chrome entries with multiple definitions. |
michael@0 | 151 | Used for locale and skin entries. |
michael@0 | 152 | ''' |
michael@0 | 153 | type = None |
michael@0 | 154 | |
michael@0 | 155 | def __init__(self, base, name, id, relpath, *flags): |
michael@0 | 156 | ManifestChrome.__init__(self, base, name, relpath, *flags) |
michael@0 | 157 | self.id = id |
michael@0 | 158 | |
michael@0 | 159 | def __str__(self): |
michael@0 | 160 | return self.serialize(self.name, self.id, self.relpath) |
michael@0 | 161 | |
michael@0 | 162 | |
michael@0 | 163 | class ManifestLocale(ManifestMultiContent): |
michael@0 | 164 | ''' |
michael@0 | 165 | Class for 'locale' entries. |
michael@0 | 166 | locale global en-US content/en-US/ |
michael@0 | 167 | locale global fr content/fr/ |
michael@0 | 168 | ''' |
michael@0 | 169 | localized = True |
michael@0 | 170 | type = 'locale' |
michael@0 | 171 | |
michael@0 | 172 | |
michael@0 | 173 | class ManifestSkin(ManifestMultiContent): |
michael@0 | 174 | ''' |
michael@0 | 175 | Class for 'skin' entries. |
michael@0 | 176 | skin global classic/1.0 content/skin/classic/ |
michael@0 | 177 | ''' |
michael@0 | 178 | type = 'skin' |
michael@0 | 179 | |
michael@0 | 180 | |
michael@0 | 181 | class ManifestOverload(ManifestEntry): |
michael@0 | 182 | ''' |
michael@0 | 183 | Abstract class for chrome entries defining some kind of overloading. |
michael@0 | 184 | Used for overlay, override or style entries. |
michael@0 | 185 | ''' |
michael@0 | 186 | type = None |
michael@0 | 187 | |
michael@0 | 188 | def __init__(self, base, overloaded, overload, *flags): |
michael@0 | 189 | ManifestEntry.__init__(self, base, *flags) |
michael@0 | 190 | self.overloaded = overloaded |
michael@0 | 191 | self.overload = overload |
michael@0 | 192 | |
michael@0 | 193 | def __str__(self): |
michael@0 | 194 | return self.serialize(self.overloaded, self.overload) |
michael@0 | 195 | |
michael@0 | 196 | @property |
michael@0 | 197 | def localized(self): |
michael@0 | 198 | u = urlparse(self.overload) |
michael@0 | 199 | return u.scheme == 'chrome' and \ |
michael@0 | 200 | u.path.split('/')[0:2] == ['', 'locale'] |
michael@0 | 201 | |
michael@0 | 202 | |
michael@0 | 203 | class ManifestOverlay(ManifestOverload): |
michael@0 | 204 | ''' |
michael@0 | 205 | Class for 'overlay' entries. |
michael@0 | 206 | overlay chrome://global/content/viewSource.xul \ |
michael@0 | 207 | chrome://browser/content/viewSourceOverlay.xul |
michael@0 | 208 | ''' |
michael@0 | 209 | type = 'overlay' |
michael@0 | 210 | |
michael@0 | 211 | |
michael@0 | 212 | class ManifestStyle(ManifestOverload): |
michael@0 | 213 | ''' |
michael@0 | 214 | Class for 'style' entries. |
michael@0 | 215 | style chrome://global/content/customizeToolbar.xul \ |
michael@0 | 216 | chrome://browser/skin/ |
michael@0 | 217 | ''' |
michael@0 | 218 | type = 'style' |
michael@0 | 219 | |
michael@0 | 220 | |
michael@0 | 221 | class ManifestOverride(ManifestOverload): |
michael@0 | 222 | ''' |
michael@0 | 223 | Class for 'override' entries. |
michael@0 | 224 | override chrome://global/locale/netError.dtd \ |
michael@0 | 225 | chrome://browser/locale/netError.dtd |
michael@0 | 226 | ''' |
michael@0 | 227 | type = 'override' |
michael@0 | 228 | |
michael@0 | 229 | |
michael@0 | 230 | class ManifestResource(ManifestEntry): |
michael@0 | 231 | ''' |
michael@0 | 232 | Class for 'resource' entries. |
michael@0 | 233 | resource gre-resources toolkit/res/ |
michael@0 | 234 | resource services-sync resource://gre/modules/services-sync/ |
michael@0 | 235 | |
michael@0 | 236 | The target may be a relative path or a resource or chrome url. |
michael@0 | 237 | ''' |
michael@0 | 238 | type = 'resource' |
michael@0 | 239 | |
michael@0 | 240 | def __init__(self, base, name, target, *flags): |
michael@0 | 241 | ManifestEntry.__init__(self, base, *flags) |
michael@0 | 242 | self.name = name |
michael@0 | 243 | self.target = target |
michael@0 | 244 | |
michael@0 | 245 | def __str__(self): |
michael@0 | 246 | return self.serialize(self.name, self.target) |
michael@0 | 247 | |
michael@0 | 248 | def rebase(self, base): |
michael@0 | 249 | u = urlparse(self.target) |
michael@0 | 250 | if u.scheme and u.scheme != 'jar': |
michael@0 | 251 | return ManifestEntry.rebase(self, base) |
michael@0 | 252 | clone = ManifestEntry.rebase(self, base) |
michael@0 | 253 | clone.target = mozpack.path.rebase(self.base, base, self.target) |
michael@0 | 254 | return clone |
michael@0 | 255 | |
michael@0 | 256 | |
michael@0 | 257 | class ManifestBinaryComponent(ManifestEntryWithRelPath): |
michael@0 | 258 | ''' |
michael@0 | 259 | Class for 'binary-component' entries. |
michael@0 | 260 | binary-component some/path/to/a/component.dll |
michael@0 | 261 | ''' |
michael@0 | 262 | type = 'binary-component' |
michael@0 | 263 | |
michael@0 | 264 | |
michael@0 | 265 | class ManifestComponent(ManifestEntryWithRelPath): |
michael@0 | 266 | ''' |
michael@0 | 267 | Class for 'component' entries. |
michael@0 | 268 | component {b2bba4df-057d-41ea-b6b1-94a10a8ede68} foo.js |
michael@0 | 269 | ''' |
michael@0 | 270 | type = 'component' |
michael@0 | 271 | |
michael@0 | 272 | def __init__(self, base, cid, file, *flags): |
michael@0 | 273 | ManifestEntryWithRelPath.__init__(self, base, file, *flags) |
michael@0 | 274 | self.cid = cid |
michael@0 | 275 | |
michael@0 | 276 | def __str__(self): |
michael@0 | 277 | return self.serialize(self.cid, self.relpath) |
michael@0 | 278 | |
michael@0 | 279 | |
michael@0 | 280 | class ManifestInterfaces(ManifestEntryWithRelPath): |
michael@0 | 281 | ''' |
michael@0 | 282 | Class for 'interfaces' entries. |
michael@0 | 283 | interfaces foo.xpt |
michael@0 | 284 | ''' |
michael@0 | 285 | type = 'interfaces' |
michael@0 | 286 | |
michael@0 | 287 | |
michael@0 | 288 | class ManifestCategory(ManifestEntry): |
michael@0 | 289 | ''' |
michael@0 | 290 | Class for 'category' entries. |
michael@0 | 291 | category command-line-handler m-browser @mozilla.org/browser/clh; |
michael@0 | 292 | ''' |
michael@0 | 293 | type = 'category' |
michael@0 | 294 | |
michael@0 | 295 | def __init__(self, base, category, name, value, *flags): |
michael@0 | 296 | ManifestEntry.__init__(self, base, *flags) |
michael@0 | 297 | self.category = category |
michael@0 | 298 | self.name = name |
michael@0 | 299 | self.value = value |
michael@0 | 300 | |
michael@0 | 301 | def __str__(self): |
michael@0 | 302 | return self.serialize(self.category, self.name, self.value) |
michael@0 | 303 | |
michael@0 | 304 | |
michael@0 | 305 | class ManifestContract(ManifestEntry): |
michael@0 | 306 | ''' |
michael@0 | 307 | Class for 'contract' entries. |
michael@0 | 308 | contract @mozilla.org/foo;1 {b2bba4df-057d-41ea-b6b1-94a10a8ede68} |
michael@0 | 309 | ''' |
michael@0 | 310 | type = 'contract' |
michael@0 | 311 | |
michael@0 | 312 | def __init__(self, base, contractID, cid, *flags): |
michael@0 | 313 | ManifestEntry.__init__(self, base, *flags) |
michael@0 | 314 | self.contractID = contractID |
michael@0 | 315 | self.cid = cid |
michael@0 | 316 | |
michael@0 | 317 | def __str__(self): |
michael@0 | 318 | return self.serialize(self.contractID, self.cid) |
michael@0 | 319 | |
michael@0 | 320 | # All manifest classes by their type name. |
michael@0 | 321 | MANIFESTS_TYPES = dict([(c.type, c) for c in globals().values() |
michael@0 | 322 | if type(c) == type and issubclass(c, ManifestEntry) |
michael@0 | 323 | and hasattr(c, 'type') and c.type]) |
michael@0 | 324 | |
michael@0 | 325 | MANIFEST_RE = re.compile(r'\s*#.*$') |
michael@0 | 326 | |
michael@0 | 327 | |
michael@0 | 328 | def parse_manifest_line(base, line): |
michael@0 | 329 | ''' |
michael@0 | 330 | Parse a line from a manifest file with the given base directory and |
michael@0 | 331 | return the corresponding ManifestEntry instance. |
michael@0 | 332 | ''' |
michael@0 | 333 | # Remove comments |
michael@0 | 334 | cmd = MANIFEST_RE.sub('', line).strip().split() |
michael@0 | 335 | if not cmd: |
michael@0 | 336 | return None |
michael@0 | 337 | if not cmd[0] in MANIFESTS_TYPES: |
michael@0 | 338 | return errors.fatal('Unknown manifest directive: %s' % cmd[0]) |
michael@0 | 339 | return MANIFESTS_TYPES[cmd[0]](base, *cmd[1:]) |
michael@0 | 340 | |
michael@0 | 341 | |
michael@0 | 342 | def parse_manifest(root, path, fileobj=None): |
michael@0 | 343 | ''' |
michael@0 | 344 | Parse a manifest file. |
michael@0 | 345 | ''' |
michael@0 | 346 | base = mozpack.path.dirname(path) |
michael@0 | 347 | if root: |
michael@0 | 348 | path = os.path.normpath(os.path.abspath(os.path.join(root, path))) |
michael@0 | 349 | if not fileobj: |
michael@0 | 350 | fileobj = open(path) |
michael@0 | 351 | linenum = 0 |
michael@0 | 352 | for line in fileobj: |
michael@0 | 353 | linenum += 1 |
michael@0 | 354 | with errors.context(path, linenum): |
michael@0 | 355 | e = parse_manifest_line(base, line) |
michael@0 | 356 | if e: |
michael@0 | 357 | yield e |
michael@0 | 358 | |
michael@0 | 359 | |
michael@0 | 360 | def is_manifest(path): |
michael@0 | 361 | ''' |
michael@0 | 362 | Return whether the given path is that of a manifest file. |
michael@0 | 363 | ''' |
michael@0 | 364 | return path.endswith('.manifest') and not path.endswith('.CRT.manifest') \ |
michael@0 | 365 | and not path.endswith('.exe.manifest') |