diff options
Diffstat (limited to 'python/mozbuild/mozpack/chrome')
-rw-r--r-- | python/mozbuild/mozpack/chrome/__init__.py | 0 | ||||
-rw-r--r-- | python/mozbuild/mozpack/chrome/flags.py | 258 | ||||
-rw-r--r-- | python/mozbuild/mozpack/chrome/manifest.py | 368 |
3 files changed, 626 insertions, 0 deletions
diff --git a/python/mozbuild/mozpack/chrome/__init__.py b/python/mozbuild/mozpack/chrome/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/python/mozbuild/mozpack/chrome/__init__.py diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py new file mode 100644 index 0000000000..8c5c9a54c0 --- /dev/null +++ b/python/mozbuild/mozpack/chrome/flags.py @@ -0,0 +1,258 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import + +import re +from distutils.version import LooseVersion +from mozpack.errors import errors +from collections import OrderedDict + + +class Flag(object): + ''' + Class for flags in manifest entries in the form: + "flag" (same as "flag=true") + "flag=yes|true|1" + "flag=no|false|0" + ''' + def __init__(self, name): + ''' + Initialize a Flag with the given name. + ''' + self.name = name + self.value = None + + def add_definition(self, definition): + ''' + Add a flag value definition. Replaces any previously set value. + ''' + if definition == self.name: + self.value = True + return + assert(definition.startswith(self.name)) + if definition[len(self.name)] != '=': + return errors.fatal('Malformed flag: %s' % definition) + value = definition[len(self.name) + 1:] + if value in ('yes', 'true', '1', 'no', 'false', '0'): + self.value = value + else: + return errors.fatal('Unknown value in: %s' % definition) + + def matches(self, value): + ''' + Return whether the flag value matches the given value. The values + are canonicalized for comparison. + ''' + if value in ('yes', 'true', '1', True): + return self.value in ('yes', 'true', '1', True) + if value in ('no', 'false', '0', False): + return self.value in ('no', 'false', '0', False, None) + raise RuntimeError('Invalid value: %s' % value) + + def __str__(self): + ''' + Serialize the flag value in the same form given to the last + add_definition() call. + ''' + if self.value is None: + return '' + if self.value is True: + return self.name + return '%s=%s' % (self.name, self.value) + + +class StringFlag(object): + ''' + Class for string flags in manifest entries in the form: + "flag=string" + "flag!=string" + ''' + def __init__(self, name): + ''' + Initialize a StringFlag with the given name. + ''' + self.name = name + self.values = [] + + def add_definition(self, definition): + ''' + Add a string flag definition. + ''' + assert(definition.startswith(self.name)) + value = definition[len(self.name):] + if value.startswith('='): + self.values.append(('==', value[1:])) + elif value.startswith('!='): + self.values.append(('!=', value[2:])) + else: + return errors.fatal('Malformed flag: %s' % definition) + + def matches(self, value): + ''' + Return whether one of the string flag definitions matches the given + value. + For example, + flag = StringFlag('foo') + flag.add_definition('foo!=bar') + flag.matches('bar') returns False + flag.matches('qux') returns True + flag = StringFlag('foo') + flag.add_definition('foo=bar') + flag.add_definition('foo=baz') + flag.matches('bar') returns True + flag.matches('baz') returns True + flag.matches('qux') returns False + ''' + if not self.values: + return True + for comparison, val in self.values: + if eval('value %s val' % comparison): + return True + return False + + def __str__(self): + ''' + Serialize the flag definitions in the same form given to each + add_definition() call. + ''' + res = [] + for comparison, val in self.values: + if comparison == '==': + res.append('%s=%s' % (self.name, val)) + else: + res.append('%s!=%s' % (self.name, val)) + return ' '.join(res) + + +class VersionFlag(object): + ''' + Class for version flags in manifest entries in the form: + "flag=version" + "flag<=version" + "flag<version" + "flag>=version" + "flag>version" + ''' + def __init__(self, name): + ''' + Initialize a VersionFlag with the given name. + ''' + self.name = name + self.values = [] + + def add_definition(self, definition): + ''' + Add a version flag definition. + ''' + assert(definition.startswith(self.name)) + value = definition[len(self.name):] + if value.startswith('='): + self.values.append(('==', LooseVersion(value[1:]))) + elif len(value) > 1 and value[0] in ['<', '>']: + if value[1] == '=': + if len(value) < 3: + return errors.fatal('Malformed flag: %s' % definition) + self.values.append((value[0:2], LooseVersion(value[2:]))) + else: + self.values.append((value[0], LooseVersion(value[1:]))) + else: + return errors.fatal('Malformed flag: %s' % definition) + + def matches(self, value): + ''' + Return whether one of the version flag definitions matches the given + value. + For example, + flag = VersionFlag('foo') + flag.add_definition('foo>=1.0') + flag.matches('1.0') returns True + flag.matches('1.1') returns True + flag.matches('0.9') returns False + flag = VersionFlag('foo') + flag.add_definition('foo>=1.0') + flag.add_definition('foo<0.5') + flag.matches('0.4') returns True + flag.matches('1.0') returns True + flag.matches('0.6') returns False + ''' + value = LooseVersion(value) + if not self.values: + return True + for comparison, val in self.values: + if eval('value %s val' % comparison): + return True + return False + + def __str__(self): + ''' + Serialize the flag definitions in the same form given to each + add_definition() call. + ''' + res = [] + for comparison, val in self.values: + if comparison == '==': + res.append('%s=%s' % (self.name, val)) + else: + res.append('%s%s%s' % (self.name, comparison, val)) + return ' '.join(res) + + +class Flags(OrderedDict): + ''' + Class to handle a set of flags definitions given on a single manifest + entry. + ''' + FLAGS = { + 'application': StringFlag, + 'appversion': VersionFlag, + 'platformversion': VersionFlag, + 'contentaccessible': Flag, + 'os': StringFlag, + 'osversion': VersionFlag, + 'abi': StringFlag, + 'platform': Flag, + 'xpcnativewrappers': Flag, + 'tablet': Flag, + 'process': StringFlag, + } + RE = re.compile(r'([!<>=]+)') + + def __init__(self, *flags): + ''' + Initialize a set of flags given in string form. + flags = Flags('contentaccessible=yes', 'appversion>=3.5') + ''' + OrderedDict.__init__(self) + for f in flags: + name = self.RE.split(f) + name = name[0] + if not name in self.FLAGS: + errors.fatal('Unknown flag: %s' % name) + continue + if not name in self: + self[name] = self.FLAGS[name](name) + self[name].add_definition(f) + + def __str__(self): + ''' + Serialize the set of flags. + ''' + return ' '.join(str(self[k]) for k in self) + + def match(self, **filter): + ''' + Return whether the set of flags match the set of given filters. + flags = Flags('contentaccessible=yes', 'appversion>=3.5', + 'application=foo') + flags.match(application='foo') returns True + flags.match(application='foo', appversion='3.5') returns True + flags.match(application='foo', appversion='3.0') returns False + ''' + for name, value in filter.iteritems(): + if not name in self: + continue + if not self[name].matches(value): + return False + return True diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py new file mode 100644 index 0000000000..71241764da --- /dev/null +++ b/python/mozbuild/mozpack/chrome/manifest.py @@ -0,0 +1,368 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import + +import re +import os +from urlparse import urlparse +import mozpack.path as mozpath +from mozpack.chrome.flags import Flags +from mozpack.errors import errors + + +class ManifestEntry(object): + ''' + Base class for all manifest entry types. + Subclasses may define the following class or member variables: + - localized: indicates whether the manifest entry is used for localized + data. + - type: the manifest entry type (e.g. 'content' in + 'content global content/global/') + - allowed_flags: a set of flags allowed to be defined for the given + manifest entry type. + + A manifest entry is attached to a base path, defining where the manifest + entry is bound to, and that is used to find relative paths defined in + entries. + ''' + localized = False + type = None + allowed_flags = [ + 'application', + 'platformversion', + 'os', + 'osversion', + 'abi', + 'xpcnativewrappers', + 'tablet', + 'process', + ] + + def __init__(self, base, *flags): + ''' + Initialize a manifest entry with the given base path and flags. + ''' + self.base = base + self.flags = Flags(*flags) + if not all(f in self.allowed_flags for f in self.flags): + errors.fatal('%s unsupported for %s manifest entries' % + (','.join(f for f in self.flags + if not f in self.allowed_flags), self.type)) + + def serialize(self, *args): + ''' + Serialize the manifest entry. + ''' + entry = [self.type] + list(args) + flags = str(self.flags) + if flags: + entry.append(flags) + return ' '.join(entry) + + def __eq__(self, other): + return self.base == other.base and str(self) == str(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return '<%s@%s>' % (str(self), self.base) + + def move(self, base): + ''' + Return a new manifest entry with a different base path. + ''' + return parse_manifest_line(base, str(self)) + + def rebase(self, base): + ''' + Return a new manifest entry with all relative paths defined in the + entry relative to a new base directory. + The base class doesn't define relative paths, so it is equivalent to + move(). + ''' + return self.move(base) + + +class ManifestEntryWithRelPath(ManifestEntry): + ''' + Abstract manifest entry type with a relative path definition. + ''' + def __init__(self, base, relpath, *flags): + ManifestEntry.__init__(self, base, *flags) + self.relpath = relpath + + def __str__(self): + return self.serialize(self.relpath) + + def rebase(self, base): + ''' + Return a new manifest entry with all relative paths defined in the + entry relative to a new base directory. + ''' + clone = ManifestEntry.rebase(self, base) + clone.relpath = mozpath.rebase(self.base, base, self.relpath) + return clone + + @property + def path(self): + return mozpath.normpath(mozpath.join(self.base, + self.relpath)) + + +class Manifest(ManifestEntryWithRelPath): + ''' + Class for 'manifest' entries. + manifest some/path/to/another.manifest + ''' + type = 'manifest' + + +class ManifestChrome(ManifestEntryWithRelPath): + ''' + Abstract class for chrome entries. + ''' + def __init__(self, base, name, relpath, *flags): + ManifestEntryWithRelPath.__init__(self, base, relpath, *flags) + self.name = name + + @property + def location(self): + return mozpath.join(self.base, self.relpath) + + +class ManifestContent(ManifestChrome): + ''' + Class for 'content' entries. + content global content/global/ + ''' + type = 'content' + allowed_flags = ManifestChrome.allowed_flags + [ + 'contentaccessible', + 'platform', + ] + + def __str__(self): + return self.serialize(self.name, self.relpath) + + +class ManifestMultiContent(ManifestChrome): + ''' + Abstract class for chrome entries with multiple definitions. + Used for locale and skin entries. + ''' + type = None + + def __init__(self, base, name, id, relpath, *flags): + ManifestChrome.__init__(self, base, name, relpath, *flags) + self.id = id + + def __str__(self): + return self.serialize(self.name, self.id, self.relpath) + + +class ManifestLocale(ManifestMultiContent): + ''' + Class for 'locale' entries. + locale global en-US content/en-US/ + locale global fr content/fr/ + ''' + localized = True + type = 'locale' + + +class ManifestSkin(ManifestMultiContent): + ''' + Class for 'skin' entries. + skin global classic/1.0 content/skin/classic/ + ''' + type = 'skin' + + +class ManifestOverload(ManifestEntry): + ''' + Abstract class for chrome entries defining some kind of overloading. + Used for overlay, override or style entries. + ''' + type = None + + def __init__(self, base, overloaded, overload, *flags): + ManifestEntry.__init__(self, base, *flags) + self.overloaded = overloaded + self.overload = overload + + def __str__(self): + return self.serialize(self.overloaded, self.overload) + + @property + def localized(self): + u = urlparse(self.overload) + return u.scheme == 'chrome' and \ + u.path.split('/')[0:2] == ['', 'locale'] + + +class ManifestOverlay(ManifestOverload): + ''' + Class for 'overlay' entries. + overlay chrome://global/content/viewSource.xul \ + chrome://browser/content/viewSourceOverlay.xul + ''' + type = 'overlay' + + +class ManifestStyle(ManifestOverload): + ''' + Class for 'style' entries. + style chrome://global/content/customizeToolbar.xul \ + chrome://browser/skin/ + ''' + type = 'style' + + +class ManifestOverride(ManifestOverload): + ''' + Class for 'override' entries. + override chrome://global/locale/netError.dtd \ + chrome://browser/locale/netError.dtd + ''' + type = 'override' + + +class ManifestResource(ManifestEntry): + ''' + Class for 'resource' entries. + resource gre-resources toolkit/res/ + resource services-sync resource://gre/modules/services-sync/ + + The target may be a relative path or a resource or chrome url. + ''' + type = 'resource' + + def __init__(self, base, name, target, *flags): + ManifestEntry.__init__(self, base, *flags) + self.name = name + self.target = target + + def __str__(self): + return self.serialize(self.name, self.target) + + def rebase(self, base): + u = urlparse(self.target) + if u.scheme and u.scheme != 'jar': + return ManifestEntry.rebase(self, base) + clone = ManifestEntry.rebase(self, base) + clone.target = mozpath.rebase(self.base, base, self.target) + return clone + + +class ManifestBinaryComponent(ManifestEntryWithRelPath): + ''' + Class for 'binary-component' entries. + binary-component some/path/to/a/component.dll + ''' + type = 'binary-component' + + +class ManifestComponent(ManifestEntryWithRelPath): + ''' + Class for 'component' entries. + component {b2bba4df-057d-41ea-b6b1-94a10a8ede68} foo.js + ''' + type = 'component' + + def __init__(self, base, cid, file, *flags): + ManifestEntryWithRelPath.__init__(self, base, file, *flags) + self.cid = cid + + def __str__(self): + return self.serialize(self.cid, self.relpath) + + +class ManifestInterfaces(ManifestEntryWithRelPath): + ''' + Class for 'interfaces' entries. + interfaces foo.xpt + ''' + type = 'interfaces' + + +class ManifestCategory(ManifestEntry): + ''' + Class for 'category' entries. + category command-line-handler m-browser @mozilla.org/browser/clh; + ''' + type = 'category' + + def __init__(self, base, category, name, value, *flags): + ManifestEntry.__init__(self, base, *flags) + self.category = category + self.name = name + self.value = value + + def __str__(self): + return self.serialize(self.category, self.name, self.value) + + +class ManifestContract(ManifestEntry): + ''' + Class for 'contract' entries. + contract @mozilla.org/foo;1 {b2bba4df-057d-41ea-b6b1-94a10a8ede68} + ''' + type = 'contract' + + def __init__(self, base, contractID, cid, *flags): + ManifestEntry.__init__(self, base, *flags) + self.contractID = contractID + self.cid = cid + + def __str__(self): + return self.serialize(self.contractID, self.cid) + +# All manifest classes by their type name. +MANIFESTS_TYPES = dict([(c.type, c) for c in globals().values() + if type(c) == type and issubclass(c, ManifestEntry) + and hasattr(c, 'type') and c.type]) + +MANIFEST_RE = re.compile(r'^#.*$') + + +def parse_manifest_line(base, line): + ''' + Parse a line from a manifest file with the given base directory and + return the corresponding ManifestEntry instance. + ''' + # Remove comments + cmd = MANIFEST_RE.sub('', line).strip().split() + if not cmd: + return None + if not cmd[0] in MANIFESTS_TYPES: + return errors.fatal('Unknown manifest directive: %s' % cmd[0]) + return MANIFESTS_TYPES[cmd[0]](base, *cmd[1:]) + + +def parse_manifest(root, path, fileobj=None): + ''' + Parse a manifest file. + ''' + base = mozpath.dirname(path) + if root: + path = os.path.normpath(os.path.abspath(os.path.join(root, path))) + if not fileobj: + fileobj = open(path) + linenum = 0 + for line in fileobj: + linenum += 1 + with errors.context(path, linenum): + e = parse_manifest_line(base, line) + if e: + yield e + + +def is_manifest(path): + ''' + Return whether the given path is that of a manifest file. + ''' + return path.endswith('.manifest') and not path.endswith('.CRT.manifest') \ + and not path.endswith('.exe.manifest') |