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