michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: from mozpack.chrome.manifest import ( michael@0: Manifest, michael@0: ManifestInterfaces, michael@0: ManifestChrome, michael@0: ManifestBinaryComponent, michael@0: ManifestResource, michael@0: ) michael@0: from urlparse import urlparse michael@0: import mozpack.path michael@0: from mozpack.files import ( michael@0: ManifestFile, michael@0: XPTFile, michael@0: ) michael@0: from mozpack.copier import ( michael@0: FileRegistry, michael@0: Jarrer, michael@0: ) michael@0: michael@0: STARTUP_CACHE_PATHS = [ michael@0: 'jsloader', michael@0: 'jssubloader', michael@0: ] michael@0: michael@0: ''' michael@0: Formatters are classes receiving packaging instructions and creating the michael@0: appropriate package layout. michael@0: michael@0: There are three distinct formatters, each handling one of the different chrome michael@0: formats: michael@0: - flat: essentially, copies files from the source with the same file system michael@0: layout. Manifests entries are grouped in a single manifest per directory, michael@0: as well as XPT interfaces. michael@0: - jar: chrome content is packaged in jar files. michael@0: - omni: chrome content, modules, non-binary components, and many other michael@0: elements are packaged in an omnijar file for each base directory. michael@0: michael@0: The base interface provides the following methods: michael@0: - add_base(path) michael@0: Register a base directory for an application or GRE. Base directories michael@0: usually contain a root manifest (manifests not included in any other michael@0: manifest) named chrome.manifest. michael@0: - add(path, content) michael@0: Add the given content (BaseFile instance) at the given virtual path michael@0: - add_interfaces(path, content) michael@0: Add the given content (BaseFile instance) and link it to other michael@0: interfaces in the parent directory of the given virtual path. michael@0: - add_manifest(entry) michael@0: Add a ManifestEntry. michael@0: - contains(path) michael@0: Returns whether the given virtual path is known of the formatter. michael@0: michael@0: The virtual paths mentioned above are paths as they would be with a flat michael@0: chrome. michael@0: michael@0: Formatters all take a FileCopier instance they will fill with the packaged michael@0: data. michael@0: ''' michael@0: michael@0: michael@0: class FlatFormatter(object): michael@0: ''' michael@0: Formatter for the flat package format. michael@0: ''' michael@0: def __init__(self, copier): michael@0: assert isinstance(copier, FileRegistry) michael@0: self.copier = copier michael@0: self._bases = [''] michael@0: self._frozen_bases = False michael@0: michael@0: def add_base(self, base): michael@0: # Only allow to add a base directory before calls to _get_base() michael@0: assert not self._frozen_bases michael@0: if not base in self._bases: michael@0: self._bases.append(base) michael@0: michael@0: def _get_base(self, path): michael@0: ''' michael@0: Return the deepest base directory containing the given path. michael@0: ''' michael@0: self._frozen_bases = True michael@0: return mozpack.path.basedir(path, self._bases) michael@0: michael@0: def add(self, path, content): michael@0: self.copier.add(path, content) michael@0: michael@0: def add_manifest(self, entry): michael@0: # Store manifest entries in a single manifest per directory, named michael@0: # after their parent directory, except for root manifests, all named michael@0: # chrome.manifest. michael@0: base = self._get_base(entry.base) michael@0: if entry.base == base: michael@0: name = 'chrome' michael@0: else: michael@0: name = mozpack.path.basename(entry.base) michael@0: path = mozpack.path.normpath(mozpack.path.join(entry.base, michael@0: '%s.manifest' % name)) michael@0: if not self.copier.contains(path): michael@0: assert mozpack.path.basedir(entry.base, [base]) == base michael@0: # Add a reference to the manifest file in the parent manifest, if michael@0: # the manifest file is not a root manifest. michael@0: if len(entry.base) > len(base): michael@0: parent = mozpack.path.dirname(entry.base) michael@0: relbase = mozpack.path.basename(entry.base) michael@0: relpath = mozpack.path.join(relbase, michael@0: mozpack.path.basename(path)) michael@0: FlatFormatter.add_manifest(self, Manifest(parent, relpath)) michael@0: self.copier.add(path, ManifestFile(entry.base)) michael@0: self.copier[path].add(entry) michael@0: michael@0: def add_interfaces(self, path, content): michael@0: # Interfaces in the same directory are all linked together in an michael@0: # interfaces.xpt file. michael@0: interfaces_path = mozpack.path.join(mozpack.path.dirname(path), michael@0: 'interfaces.xpt') michael@0: if not self.copier.contains(interfaces_path): michael@0: FlatFormatter.add_manifest(self, ManifestInterfaces( michael@0: mozpack.path.dirname(path), 'interfaces.xpt')) michael@0: self.copier.add(interfaces_path, XPTFile()) michael@0: self.copier[interfaces_path].add(content) michael@0: michael@0: def contains(self, path): michael@0: assert '*' not in path michael@0: return self.copier.contains(path) michael@0: michael@0: michael@0: class JarFormatter(FlatFormatter): michael@0: ''' michael@0: Formatter for the jar package format. Assumes manifest entries related to michael@0: chrome are registered before the chrome data files are added. Also assumes michael@0: manifest entries for resources are registered after chrome manifest michael@0: entries. michael@0: ''' michael@0: def __init__(self, copier, compress=True, optimize=True): michael@0: FlatFormatter.__init__(self, copier) michael@0: self._chrome = set() michael@0: self._frozen_chrome = False michael@0: self._compress = compress michael@0: self._optimize = optimize michael@0: michael@0: def _chromepath(self, path): michael@0: ''' michael@0: Return the chrome base directory under which the given path is. Used to michael@0: detect under which .jar (if any) the path should go. michael@0: ''' michael@0: self._frozen_chrome = True michael@0: return mozpack.path.basedir(path, self._chrome) michael@0: michael@0: def add(self, path, content): michael@0: chrome = self._chromepath(path) michael@0: if chrome: michael@0: jar = chrome + '.jar' michael@0: if not self.copier.contains(jar): michael@0: self.copier.add(jar, Jarrer(self._compress, self._optimize)) michael@0: if not self.copier[jar].contains(mozpack.path.relpath(path, michael@0: chrome)): michael@0: self.copier[jar].add(mozpack.path.relpath(path, chrome), michael@0: content) michael@0: else: michael@0: FlatFormatter.add(self, path, content) michael@0: michael@0: def _jarize(self, entry, relpath): michael@0: ''' michael@0: Transform a manifest entry in one pointing to chrome data in a jar. michael@0: Return the corresponding chrome path and the new entry. michael@0: ''' michael@0: base = entry.base michael@0: basepath = mozpack.path.split(relpath)[0] michael@0: chromepath = mozpack.path.join(base, basepath) michael@0: entry = entry.rebase(chromepath) \ michael@0: .move(mozpack.path.join(base, 'jar:%s.jar!' % basepath)) \ michael@0: .rebase(base) michael@0: return chromepath, entry michael@0: michael@0: def add_manifest(self, entry): michael@0: if isinstance(entry, ManifestChrome) and \ michael@0: not urlparse(entry.relpath).scheme: michael@0: chromepath, entry = self._jarize(entry, entry.relpath) michael@0: assert not self._frozen_chrome michael@0: self._chrome.add(chromepath) michael@0: elif isinstance(entry, ManifestResource) and \ michael@0: not urlparse(entry.target).scheme: michael@0: chromepath, new_entry = self._jarize(entry, entry.target) michael@0: if chromepath in self._chrome: michael@0: entry = new_entry michael@0: FlatFormatter.add_manifest(self, entry) michael@0: michael@0: def contains(self, path): michael@0: assert '*' not in path michael@0: chrome = self._chromepath(path) michael@0: if not chrome: michael@0: return self.copier.contains(path) michael@0: if not self.copier.contains(chrome + '.jar'): michael@0: return False michael@0: return self.copier[chrome + '.jar']. \ michael@0: contains(mozpack.path.relpath(path, chrome)) michael@0: michael@0: michael@0: class OmniJarFormatter(FlatFormatter): michael@0: ''' michael@0: Formatter for the omnijar package format. michael@0: ''' michael@0: def __init__(self, copier, omnijar_name, compress=True, optimize=True, michael@0: non_resources=[]): michael@0: FlatFormatter.__init__(self, copier) michael@0: self.omnijars = {} michael@0: self._omnijar_name = omnijar_name michael@0: self._compress = compress michael@0: self._optimize = optimize michael@0: self._non_resources = non_resources michael@0: michael@0: def _get_omnijar(self, path, create=True): michael@0: ''' michael@0: Return the omnijar corresponding to the given path, its base directory michael@0: and the path translated to be under the omnijar.. michael@0: ''' michael@0: base = self._get_base(path) michael@0: if not base in self.omnijars: michael@0: if not create: michael@0: return None, '', path michael@0: omnijar = Jarrer(self._compress, self._optimize) michael@0: self.omnijars[base] = FlatFormatter(omnijar) michael@0: self.copier.add(mozpack.path.join(base, self._omnijar_name), michael@0: omnijar) michael@0: return self.omnijars[base], base, mozpack.path.relpath(path, base) michael@0: michael@0: def add(self, path, content): michael@0: if self.is_resource(path): michael@0: formatter, base, path = self._get_omnijar(path) michael@0: else: michael@0: formatter = self michael@0: FlatFormatter.add(formatter, path, content) michael@0: michael@0: def add_manifest(self, entry): michael@0: if isinstance(entry, ManifestBinaryComponent): michael@0: formatter, base = self, '' michael@0: else: michael@0: formatter, base, path = self._get_omnijar(entry.base) michael@0: entry = entry.move(mozpack.path.relpath(entry.base, base)) michael@0: FlatFormatter.add_manifest(formatter, entry) michael@0: michael@0: def add_interfaces(self, path, content): michael@0: formatter, base, path = self._get_omnijar(path) michael@0: FlatFormatter.add_interfaces(formatter, path, content) michael@0: michael@0: def contains(self, path): michael@0: assert '*' not in path michael@0: if self.copier.contains(path): michael@0: return True michael@0: for base, copier in self.omnijars.iteritems(): michael@0: if copier.contains(mozpack.path.relpath(path, base)): michael@0: return True michael@0: return False michael@0: michael@0: def is_resource(self, path): michael@0: ''' michael@0: Return whether the given path corresponds to a resource to be put in an michael@0: omnijar archive. michael@0: ''' michael@0: base = self._get_base(path) michael@0: path = mozpack.path.relpath(path, base) michael@0: if any(mozpack.path.match(path, p.replace('*', '**')) michael@0: for p in self._non_resources): michael@0: return False michael@0: path = mozpack.path.split(path) michael@0: if path[0] == 'chrome': michael@0: return len(path) == 1 or path[1] != 'icons' michael@0: if path[0] == 'components': michael@0: return path[-1].endswith('.js') michael@0: if path[0] == 'res': michael@0: return len(path) == 1 or \ michael@0: (path[1] != 'cursors' and path[1] != 'MainMenu.nib') michael@0: if path[0] == 'defaults': michael@0: return len(path) != 3 or \ michael@0: not (path[2] == 'channel-prefs.js' and michael@0: path[1] in ['pref', 'preferences']) michael@0: return path[0] in [ michael@0: 'modules', michael@0: 'greprefs.js', michael@0: 'hyphenation', michael@0: 'update.locale', michael@0: ] or path[0] in STARTUP_CACHE_PATHS