python/mozbuild/mozpack/packager/formats.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/python/mozbuild/mozpack/packager/formats.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,285 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +from mozpack.chrome.manifest import (
     1.9 +    Manifest,
    1.10 +    ManifestInterfaces,
    1.11 +    ManifestChrome,
    1.12 +    ManifestBinaryComponent,
    1.13 +    ManifestResource,
    1.14 +)
    1.15 +from urlparse import urlparse
    1.16 +import mozpack.path
    1.17 +from mozpack.files import (
    1.18 +    ManifestFile,
    1.19 +    XPTFile,
    1.20 +)
    1.21 +from mozpack.copier import (
    1.22 +    FileRegistry,
    1.23 +    Jarrer,
    1.24 +)
    1.25 +
    1.26 +STARTUP_CACHE_PATHS = [
    1.27 +    'jsloader',
    1.28 +    'jssubloader',
    1.29 +]
    1.30 +
    1.31 +'''
    1.32 +Formatters are classes receiving packaging instructions and creating the
    1.33 +appropriate package layout.
    1.34 +
    1.35 +There are three distinct formatters, each handling one of the different chrome
    1.36 +formats:
    1.37 +    - flat: essentially, copies files from the source with the same file system
    1.38 +      layout. Manifests entries are grouped in a single manifest per directory,
    1.39 +      as well as XPT interfaces.
    1.40 +    - jar: chrome content is packaged in jar files.
    1.41 +    - omni: chrome content, modules, non-binary components, and many other
    1.42 +      elements are packaged in an omnijar file for each base directory.
    1.43 +
    1.44 +The base interface provides the following methods:
    1.45 +    - add_base(path)
    1.46 +        Register a base directory for an application or GRE. Base directories
    1.47 +        usually contain a root manifest (manifests not included in any other
    1.48 +        manifest) named chrome.manifest.
    1.49 +    - add(path, content)
    1.50 +        Add the given content (BaseFile instance) at the given virtual path
    1.51 +    - add_interfaces(path, content)
    1.52 +        Add the given content (BaseFile instance) and link it to other
    1.53 +        interfaces in the parent directory of the given virtual path.
    1.54 +    - add_manifest(entry)
    1.55 +        Add a ManifestEntry.
    1.56 +    - contains(path)
    1.57 +        Returns whether the given virtual path is known of the formatter.
    1.58 +
    1.59 +The virtual paths mentioned above are paths as they would be with a flat
    1.60 +chrome.
    1.61 +
    1.62 +Formatters all take a FileCopier instance they will fill with the packaged
    1.63 +data.
    1.64 +'''
    1.65 +
    1.66 +
    1.67 +class FlatFormatter(object):
    1.68 +    '''
    1.69 +    Formatter for the flat package format.
    1.70 +    '''
    1.71 +    def __init__(self, copier):
    1.72 +        assert isinstance(copier, FileRegistry)
    1.73 +        self.copier = copier
    1.74 +        self._bases = ['']
    1.75 +        self._frozen_bases = False
    1.76 +
    1.77 +    def add_base(self, base):
    1.78 +        # Only allow to add a base directory before calls to _get_base()
    1.79 +        assert not self._frozen_bases
    1.80 +        if not base in self._bases:
    1.81 +            self._bases.append(base)
    1.82 +
    1.83 +    def _get_base(self, path):
    1.84 +        '''
    1.85 +        Return the deepest base directory containing the given path.
    1.86 +        '''
    1.87 +        self._frozen_bases = True
    1.88 +        return mozpack.path.basedir(path, self._bases)
    1.89 +
    1.90 +    def add(self, path, content):
    1.91 +        self.copier.add(path, content)
    1.92 +
    1.93 +    def add_manifest(self, entry):
    1.94 +        # Store manifest entries in a single manifest per directory, named
    1.95 +        # after their parent directory, except for root manifests, all named
    1.96 +        # chrome.manifest.
    1.97 +        base = self._get_base(entry.base)
    1.98 +        if entry.base == base:
    1.99 +            name = 'chrome'
   1.100 +        else:
   1.101 +            name = mozpack.path.basename(entry.base)
   1.102 +        path = mozpack.path.normpath(mozpack.path.join(entry.base,
   1.103 +                                                       '%s.manifest' % name))
   1.104 +        if not self.copier.contains(path):
   1.105 +            assert mozpack.path.basedir(entry.base, [base]) == base
   1.106 +            # Add a reference to the manifest file in the parent manifest, if
   1.107 +            # the manifest file is not a root manifest.
   1.108 +            if len(entry.base) > len(base):
   1.109 +                parent = mozpack.path.dirname(entry.base)
   1.110 +                relbase = mozpack.path.basename(entry.base)
   1.111 +                relpath = mozpack.path.join(relbase,
   1.112 +                                            mozpack.path.basename(path))
   1.113 +                FlatFormatter.add_manifest(self, Manifest(parent, relpath))
   1.114 +            self.copier.add(path, ManifestFile(entry.base))
   1.115 +        self.copier[path].add(entry)
   1.116 +
   1.117 +    def add_interfaces(self, path, content):
   1.118 +        # Interfaces in the same directory are all linked together in an
   1.119 +        # interfaces.xpt file.
   1.120 +        interfaces_path = mozpack.path.join(mozpack.path.dirname(path),
   1.121 +                                            'interfaces.xpt')
   1.122 +        if not self.copier.contains(interfaces_path):
   1.123 +            FlatFormatter.add_manifest(self, ManifestInterfaces(
   1.124 +                mozpack.path.dirname(path), 'interfaces.xpt'))
   1.125 +            self.copier.add(interfaces_path, XPTFile())
   1.126 +        self.copier[interfaces_path].add(content)
   1.127 +
   1.128 +    def contains(self, path):
   1.129 +        assert '*' not in path
   1.130 +        return self.copier.contains(path)
   1.131 +
   1.132 +
   1.133 +class JarFormatter(FlatFormatter):
   1.134 +    '''
   1.135 +    Formatter for the jar package format. Assumes manifest entries related to
   1.136 +    chrome are registered before the chrome data files are added. Also assumes
   1.137 +    manifest entries for resources are registered after chrome manifest
   1.138 +    entries.
   1.139 +    '''
   1.140 +    def __init__(self, copier, compress=True, optimize=True):
   1.141 +        FlatFormatter.__init__(self, copier)
   1.142 +        self._chrome = set()
   1.143 +        self._frozen_chrome = False
   1.144 +        self._compress = compress
   1.145 +        self._optimize = optimize
   1.146 +
   1.147 +    def _chromepath(self, path):
   1.148 +        '''
   1.149 +        Return the chrome base directory under which the given path is. Used to
   1.150 +        detect under which .jar (if any) the path should go.
   1.151 +        '''
   1.152 +        self._frozen_chrome = True
   1.153 +        return mozpack.path.basedir(path, self._chrome)
   1.154 +
   1.155 +    def add(self, path, content):
   1.156 +        chrome = self._chromepath(path)
   1.157 +        if chrome:
   1.158 +            jar = chrome + '.jar'
   1.159 +            if not self.copier.contains(jar):
   1.160 +                self.copier.add(jar, Jarrer(self._compress, self._optimize))
   1.161 +            if not self.copier[jar].contains(mozpack.path.relpath(path,
   1.162 +                                                                  chrome)):
   1.163 +                self.copier[jar].add(mozpack.path.relpath(path, chrome),
   1.164 +                                     content)
   1.165 +        else:
   1.166 +            FlatFormatter.add(self, path, content)
   1.167 +
   1.168 +    def _jarize(self, entry, relpath):
   1.169 +        '''
   1.170 +        Transform a manifest entry in one pointing to chrome data in a jar.
   1.171 +        Return the corresponding chrome path and the new entry.
   1.172 +        '''
   1.173 +        base = entry.base
   1.174 +        basepath = mozpack.path.split(relpath)[0]
   1.175 +        chromepath = mozpack.path.join(base, basepath)
   1.176 +        entry = entry.rebase(chromepath) \
   1.177 +            .move(mozpack.path.join(base, 'jar:%s.jar!' % basepath)) \
   1.178 +            .rebase(base)
   1.179 +        return chromepath, entry
   1.180 +
   1.181 +    def add_manifest(self, entry):
   1.182 +        if isinstance(entry, ManifestChrome) and \
   1.183 +                not urlparse(entry.relpath).scheme:
   1.184 +            chromepath, entry = self._jarize(entry, entry.relpath)
   1.185 +            assert not self._frozen_chrome
   1.186 +            self._chrome.add(chromepath)
   1.187 +        elif isinstance(entry, ManifestResource) and \
   1.188 +                not urlparse(entry.target).scheme:
   1.189 +            chromepath, new_entry = self._jarize(entry, entry.target)
   1.190 +            if chromepath in self._chrome:
   1.191 +                entry = new_entry
   1.192 +        FlatFormatter.add_manifest(self, entry)
   1.193 +
   1.194 +    def contains(self, path):
   1.195 +        assert '*' not in path
   1.196 +        chrome = self._chromepath(path)
   1.197 +        if not chrome:
   1.198 +            return self.copier.contains(path)
   1.199 +        if not self.copier.contains(chrome + '.jar'):
   1.200 +            return False
   1.201 +        return self.copier[chrome + '.jar']. \
   1.202 +            contains(mozpack.path.relpath(path, chrome))
   1.203 +
   1.204 +
   1.205 +class OmniJarFormatter(FlatFormatter):
   1.206 +    '''
   1.207 +    Formatter for the omnijar package format.
   1.208 +    '''
   1.209 +    def __init__(self, copier, omnijar_name, compress=True, optimize=True,
   1.210 +                 non_resources=[]):
   1.211 +        FlatFormatter.__init__(self, copier)
   1.212 +        self.omnijars = {}
   1.213 +        self._omnijar_name = omnijar_name
   1.214 +        self._compress = compress
   1.215 +        self._optimize = optimize
   1.216 +        self._non_resources = non_resources
   1.217 +
   1.218 +    def _get_omnijar(self, path, create=True):
   1.219 +        '''
   1.220 +        Return the omnijar corresponding to the given path, its base directory
   1.221 +        and the path translated to be under the omnijar..
   1.222 +        '''
   1.223 +        base = self._get_base(path)
   1.224 +        if not base in self.omnijars:
   1.225 +            if not create:
   1.226 +                return None, '', path
   1.227 +            omnijar = Jarrer(self._compress, self._optimize)
   1.228 +            self.omnijars[base] = FlatFormatter(omnijar)
   1.229 +            self.copier.add(mozpack.path.join(base, self._omnijar_name),
   1.230 +                            omnijar)
   1.231 +        return self.omnijars[base], base, mozpack.path.relpath(path, base)
   1.232 +
   1.233 +    def add(self, path, content):
   1.234 +        if self.is_resource(path):
   1.235 +            formatter, base, path = self._get_omnijar(path)
   1.236 +        else:
   1.237 +            formatter = self
   1.238 +        FlatFormatter.add(formatter, path, content)
   1.239 +
   1.240 +    def add_manifest(self, entry):
   1.241 +        if isinstance(entry, ManifestBinaryComponent):
   1.242 +            formatter, base = self, ''
   1.243 +        else:
   1.244 +            formatter, base, path = self._get_omnijar(entry.base)
   1.245 +        entry = entry.move(mozpack.path.relpath(entry.base, base))
   1.246 +        FlatFormatter.add_manifest(formatter, entry)
   1.247 +
   1.248 +    def add_interfaces(self, path, content):
   1.249 +        formatter, base, path = self._get_omnijar(path)
   1.250 +        FlatFormatter.add_interfaces(formatter, path, content)
   1.251 +
   1.252 +    def contains(self, path):
   1.253 +        assert '*' not in path
   1.254 +        if self.copier.contains(path):
   1.255 +            return True
   1.256 +        for base, copier in self.omnijars.iteritems():
   1.257 +            if copier.contains(mozpack.path.relpath(path, base)):
   1.258 +                return True
   1.259 +        return False
   1.260 +
   1.261 +    def is_resource(self, path):
   1.262 +        '''
   1.263 +        Return whether the given path corresponds to a resource to be put in an
   1.264 +        omnijar archive.
   1.265 +        '''
   1.266 +        base = self._get_base(path)
   1.267 +        path = mozpack.path.relpath(path, base)
   1.268 +        if any(mozpack.path.match(path, p.replace('*', '**'))
   1.269 +               for p in self._non_resources):
   1.270 +            return False
   1.271 +        path = mozpack.path.split(path)
   1.272 +        if path[0] == 'chrome':
   1.273 +            return len(path) == 1 or path[1] != 'icons'
   1.274 +        if path[0] == 'components':
   1.275 +            return path[-1].endswith('.js')
   1.276 +        if path[0] == 'res':
   1.277 +            return len(path) == 1 or \
   1.278 +                (path[1] != 'cursors' and path[1] != 'MainMenu.nib')
   1.279 +        if path[0] == 'defaults':
   1.280 +            return len(path) != 3 or \
   1.281 +                not (path[2] == 'channel-prefs.js' and
   1.282 +                     path[1] in ['pref', 'preferences'])
   1.283 +        return path[0] in [
   1.284 +            'modules',
   1.285 +            'greprefs.js',
   1.286 +            'hyphenation',
   1.287 +            'update.locale',
   1.288 +        ] or path[0] in STARTUP_CACHE_PATHS

mercurial