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 | ''' |
michael@0 | 6 | Replace localized parts of a packaged directory with data from a langpack |
michael@0 | 7 | directory. |
michael@0 | 8 | ''' |
michael@0 | 9 | |
michael@0 | 10 | import os |
michael@0 | 11 | import mozpack.path |
michael@0 | 12 | from mozpack.packager.formats import ( |
michael@0 | 13 | FlatFormatter, |
michael@0 | 14 | JarFormatter, |
michael@0 | 15 | OmniJarFormatter, |
michael@0 | 16 | ) |
michael@0 | 17 | from mozpack.packager import SimplePackager |
michael@0 | 18 | from mozpack.files import ManifestFile |
michael@0 | 19 | from mozpack.copier import ( |
michael@0 | 20 | FileCopier, |
michael@0 | 21 | Jarrer, |
michael@0 | 22 | ) |
michael@0 | 23 | from mozpack.chrome.manifest import ( |
michael@0 | 24 | ManifestLocale, |
michael@0 | 25 | ManifestEntryWithRelPath, |
michael@0 | 26 | is_manifest, |
michael@0 | 27 | ManifestChrome, |
michael@0 | 28 | Manifest, |
michael@0 | 29 | ) |
michael@0 | 30 | from mozpack.errors import errors |
michael@0 | 31 | from mozpack.packager.unpack import UnpackFinder |
michael@0 | 32 | from createprecomplete import generate_precomplete |
michael@0 | 33 | |
michael@0 | 34 | |
michael@0 | 35 | class LocaleManifestFinder(object): |
michael@0 | 36 | def __init__(self, finder): |
michael@0 | 37 | # Read all manifest entries |
michael@0 | 38 | manifests = dict((p, m) for p, m in finder.find('**/*.manifest') |
michael@0 | 39 | if is_manifest(p)) |
michael@0 | 40 | assert all(isinstance(m, ManifestFile) |
michael@0 | 41 | for m in manifests.itervalues()) |
michael@0 | 42 | self.entries = [e for m in manifests.itervalues() |
michael@0 | 43 | for e in m if e.localized] |
michael@0 | 44 | # Find unique locales used in these manifest entries. |
michael@0 | 45 | self.locales = list(set(e.id for e in self.entries |
michael@0 | 46 | if isinstance(e, ManifestLocale))) |
michael@0 | 47 | # Find all paths whose manifest are included by no other manifest. |
michael@0 | 48 | includes = set(mozpack.path.join(e.base, e.relpath) |
michael@0 | 49 | for m in manifests.itervalues() |
michael@0 | 50 | for e in m if isinstance(e, Manifest)) |
michael@0 | 51 | self.bases = [mozpack.path.dirname(p) |
michael@0 | 52 | for p in set(manifests.keys()) - includes] |
michael@0 | 53 | |
michael@0 | 54 | |
michael@0 | 55 | def _repack(app_finder, l10n_finder, copier, formatter, non_chrome=set()): |
michael@0 | 56 | app = LocaleManifestFinder(app_finder) |
michael@0 | 57 | l10n = LocaleManifestFinder(l10n_finder) |
michael@0 | 58 | |
michael@0 | 59 | # The code further below assumes there's only one locale replaced with |
michael@0 | 60 | # another one. |
michael@0 | 61 | if len(app.locales) > 1 or len(l10n.locales) > 1: |
michael@0 | 62 | errors.fatal("Multiple locales aren't supported") |
michael@0 | 63 | locale = app.locales[0] |
michael@0 | 64 | l10n_locale = l10n.locales[0] |
michael@0 | 65 | |
michael@0 | 66 | # For each base directory, store what path a locale chrome package name |
michael@0 | 67 | # corresponds to. |
michael@0 | 68 | # e.g., for the following entry under app/chrome: |
michael@0 | 69 | # locale foo en-US path/to/files |
michael@0 | 70 | # keep track that the locale path for foo in app is |
michael@0 | 71 | # app/chrome/path/to/files. |
michael@0 | 72 | l10n_paths = {} |
michael@0 | 73 | for e in l10n.entries: |
michael@0 | 74 | if isinstance(e, ManifestChrome): |
michael@0 | 75 | base = mozpack.path.basedir(e.path, app.bases) |
michael@0 | 76 | l10n_paths.setdefault(base, {}) |
michael@0 | 77 | l10n_paths[base][e.name] = e.path |
michael@0 | 78 | |
michael@0 | 79 | # For chrome and non chrome files or directories, store what langpack path |
michael@0 | 80 | # corresponds to a package path. |
michael@0 | 81 | paths = dict((e.path, |
michael@0 | 82 | l10n_paths[mozpack.path.basedir(e.path, app.bases)][e.name]) |
michael@0 | 83 | for e in app.entries |
michael@0 | 84 | if isinstance(e, ManifestEntryWithRelPath)) |
michael@0 | 85 | |
michael@0 | 86 | for pattern in non_chrome: |
michael@0 | 87 | for base in app.bases: |
michael@0 | 88 | path = mozpack.path.join(base, pattern) |
michael@0 | 89 | left = set(p for p, f in app_finder.find(path)) |
michael@0 | 90 | right = set(p for p, f in l10n_finder.find(path)) |
michael@0 | 91 | for p in right: |
michael@0 | 92 | paths[p] = p |
michael@0 | 93 | for p in left - right: |
michael@0 | 94 | paths[p] = None |
michael@0 | 95 | |
michael@0 | 96 | # Create a new package, with non localized bits coming from the original |
michael@0 | 97 | # package, and localized bits coming from the langpack. |
michael@0 | 98 | packager = SimplePackager(formatter) |
michael@0 | 99 | for p, f in app_finder: |
michael@0 | 100 | if is_manifest(p): |
michael@0 | 101 | # Remove localized manifest entries. |
michael@0 | 102 | for e in [e for e in f if e.localized]: |
michael@0 | 103 | f.remove(e) |
michael@0 | 104 | # If the path is one that needs a locale replacement, use the |
michael@0 | 105 | # corresponding file from the langpack. |
michael@0 | 106 | path = None |
michael@0 | 107 | if p in paths: |
michael@0 | 108 | path = paths[p] |
michael@0 | 109 | if not path: |
michael@0 | 110 | continue |
michael@0 | 111 | else: |
michael@0 | 112 | base = mozpack.path.basedir(p, paths.keys()) |
michael@0 | 113 | if base: |
michael@0 | 114 | subpath = mozpack.path.relpath(p, base) |
michael@0 | 115 | path = mozpack.path.normpath(mozpack.path.join(paths[base], |
michael@0 | 116 | subpath)) |
michael@0 | 117 | if path: |
michael@0 | 118 | files = [f for p, f in l10n_finder.find(path)] |
michael@0 | 119 | if not len(files): |
michael@0 | 120 | if base not in non_chrome: |
michael@0 | 121 | errors.error("Missing file: %s" % |
michael@0 | 122 | os.path.join(l10n_finder.base, path)) |
michael@0 | 123 | else: |
michael@0 | 124 | packager.add(path, files[0]) |
michael@0 | 125 | else: |
michael@0 | 126 | packager.add(p, f) |
michael@0 | 127 | |
michael@0 | 128 | # Add localized manifest entries from the langpack. |
michael@0 | 129 | l10n_manifests = [] |
michael@0 | 130 | for base in set(e.base for e in l10n.entries): |
michael@0 | 131 | m = ManifestFile(base, [e for e in l10n.entries if e.base == base]) |
michael@0 | 132 | path = mozpack.path.join(base, 'chrome.%s.manifest' % l10n_locale) |
michael@0 | 133 | l10n_manifests.append((path, m)) |
michael@0 | 134 | bases = packager.get_bases() |
michael@0 | 135 | for path, m in l10n_manifests: |
michael@0 | 136 | base = mozpack.path.basedir(path, bases) |
michael@0 | 137 | packager.add(path, m) |
michael@0 | 138 | # Add a "manifest $path" entry in the top manifest under that base. |
michael@0 | 139 | m = ManifestFile(base) |
michael@0 | 140 | m.add(Manifest(base, mozpack.path.relpath(path, base))) |
michael@0 | 141 | packager.add(mozpack.path.join(base, 'chrome.manifest'), m) |
michael@0 | 142 | |
michael@0 | 143 | packager.close() |
michael@0 | 144 | |
michael@0 | 145 | # Add any remaining non chrome files. |
michael@0 | 146 | for pattern in non_chrome: |
michael@0 | 147 | for base in bases: |
michael@0 | 148 | for p, f in l10n_finder.find(mozpack.path.join(base, pattern)): |
michael@0 | 149 | if not formatter.contains(p): |
michael@0 | 150 | formatter.add(p, f) |
michael@0 | 151 | |
michael@0 | 152 | # Transplant jar preloading information. |
michael@0 | 153 | for path, log in app_finder.jarlogs.iteritems(): |
michael@0 | 154 | assert isinstance(copier[path], Jarrer) |
michael@0 | 155 | copier[path].preload([l.replace(locale, l10n_locale) for l in log]) |
michael@0 | 156 | |
michael@0 | 157 | |
michael@0 | 158 | def repack(source, l10n, non_resources=[], non_chrome=set()): |
michael@0 | 159 | app_finder = UnpackFinder(source) |
michael@0 | 160 | l10n_finder = UnpackFinder(l10n) |
michael@0 | 161 | copier = FileCopier() |
michael@0 | 162 | if app_finder.kind == 'flat': |
michael@0 | 163 | formatter = FlatFormatter(copier) |
michael@0 | 164 | elif app_finder.kind == 'jar': |
michael@0 | 165 | formatter = JarFormatter(copier, optimize=app_finder.optimizedjars) |
michael@0 | 166 | elif app_finder.kind == 'omni': |
michael@0 | 167 | formatter = OmniJarFormatter(copier, app_finder.omnijar, |
michael@0 | 168 | optimize=app_finder.optimizedjars, |
michael@0 | 169 | non_resources=non_resources) |
michael@0 | 170 | |
michael@0 | 171 | with errors.accumulate(): |
michael@0 | 172 | _repack(app_finder, l10n_finder, copier, formatter, non_chrome) |
michael@0 | 173 | copier.copy(source, skip_if_older=False) |
michael@0 | 174 | generate_precomplete(source) |