import json
import os
from createprecomplete import generate_precomplete
import mozpack.path as mozpath
from mozpack.chrome.manifest import (
Manifest,
ManifestChrome,
ManifestEntryWithRelPath,
ManifestLocale,
is_manifest,
)
from mozpack.copier import FileCopier, Jarrer
from mozpack.errors import errors
from mozpack.files import ComposedFinder, GeneratedFile, ManifestFile
from mozpack.mozjar import JAR_DEFLATED
from mozpack.packager import Component, SimpleManifestSink, SimplePackager
from mozpack.packager.formats import FlatFormatter, JarFormatter, OmniJarFormatter
from mozpack.packager.unpack import UnpackFinder
class LocaleManifestFinder:
def __init__(self, finder):
entries = self.entries = []
bases = self.bases = []
class MockFormatter:
def add_interfaces(self, path, content):
pass
def add(self, path, content):
pass
def add_manifest(self, entry):
if entry.localized:
entries.append(entry)
def add_base(self, base, addon=False):
bases.append(base)
class WrapFinder:
def __init__(self, finder):
self._finder = finder
def find(self, pattern):
for p, f in self._finder.find(pattern):
if isinstance(f, ManifestFile):
unwanted = [
e for e in f._entries if isinstance(e, Manifest) and e.flags
]
if unwanted:
f = ManifestFile(
f._base, [e for e in f._entries if e not in unwanted]
)
yield p, f
sink = SimpleManifestSink(WrapFinder(finder), MockFormatter())
sink.add(Component(""), "*")
sink.close(False)
self.locales = list(
set(e.id for e in self.entries if isinstance(e, ManifestLocale))
)
class L10NRepackFormatterMixin:
def __init__(self, *args, **kwargs):
super(L10NRepackFormatterMixin, self).__init__(*args, **kwargs)
self._dictionaries = {}
def add(self, path, file):
base, relpath = self._get_base(path)
if path.endswith(".dic"):
if relpath.startswith("dictionaries/"):
root, ext = mozpath.splitext(mozpath.basename(path))
self._dictionaries[root] = path
elif path.endswith("/built_in_addons.json"):
data = json.loads(file.open().read())
data["dictionaries"] = self._dictionaries
file = GeneratedFile(lambda: json.dumps(data))
elif relpath.startswith("META-INF/"):
return
super(L10NRepackFormatterMixin, self).add(path, file)
def L10NRepackFormatter(klass):
class L10NRepackFormatter(L10NRepackFormatterMixin, klass):
pass
return L10NRepackFormatter
FlatFormatter = L10NRepackFormatter(FlatFormatter)
JarFormatter = L10NRepackFormatter(JarFormatter)
OmniJarFormatter = L10NRepackFormatter(OmniJarFormatter)
def _repack(app_finder, l10n_finder, copier, formatter, non_chrome=set()):
app = LocaleManifestFinder(app_finder)
l10n = LocaleManifestFinder(l10n_finder)
if len(app.locales) > 1:
errors.fatal("Multiple app locales aren't supported: " + ",".join(app.locales))
if len(l10n.locales) > 1:
errors.fatal(
"Multiple l10n locales aren't supported: " + ",".join(l10n.locales)
)
locale = app.locales[0]
l10n_locale = l10n.locales[0]
def key(entry):
if entry.flags:
return "%s %s" % (entry.name, entry.flags)
return entry.name
l10n_paths = {}
for e in l10n.entries:
if isinstance(e, ManifestChrome):
base = mozpath.basedir(e.path, app.bases)
l10n_paths.setdefault(base, {})
l10n_paths[base][key(e)] = e.path
paths = {}
for e in app.entries:
if isinstance(e, ManifestEntryWithRelPath):
base = mozpath.basedir(e.path, app.bases)
if base not in l10n_paths:
errors.fatal("Locale doesn't contain %s/" % base)
continue
if key(e) not in l10n_paths[base]:
errors.fatal("Locale doesn't have a manifest entry for '%s'" % e.name)
continue
paths[e.path] = l10n_paths[base][key(e)]
for pattern in non_chrome:
for base in app.bases:
path = mozpath.join(base, pattern)
left = set(p for p, f in app_finder.find(path))
right = set(p for p, f in l10n_finder.find(path))
for p in right:
paths[p] = p
for p in left - right:
paths[p] = None
packager = SimplePackager(formatter)
for p, f in app_finder:
if is_manifest(p):
for e in [e for e in f if e.localized]:
f.remove(e)
path = None
if p in paths:
path = paths[p]
if not path:
continue
else:
base = mozpath.basedir(p, paths.keys())
if base:
subpath = mozpath.relpath(p, base)
path = mozpath.normpath(mozpath.join(paths[base], subpath))
if path:
files = [f for p, f in l10n_finder.find(path)]
if not files:
if base not in non_chrome:
finderBase = ""
if hasattr(l10n_finder, "base"):
finderBase = l10n_finder.base
errors.error("Missing file: %s" % os.path.join(finderBase, path))
else:
packager.add(path, files[0])
else:
packager.add(p, f)
l10n_manifests = []
for base in set(e.base for e in l10n.entries):
m = ManifestFile(base, [e for e in l10n.entries if e.base == base])
path = mozpath.join(base, "chrome.%s.manifest" % l10n_locale)
l10n_manifests.append((path, m))
bases = packager.get_bases()
for path, m in l10n_manifests:
base = mozpath.basedir(path, bases)
packager.add(path, m)
m = ManifestFile(base)
m.add(Manifest(base, mozpath.relpath(path, base)))
packager.add(mozpath.join(base, "chrome.manifest"), m)
packager.close()
for pattern in non_chrome:
for base in bases:
for p, f in l10n_finder.find(mozpath.join(base, pattern)):
if not formatter.contains(p):
formatter.add(p, f)
for p, f in l10n_finder.find("**/localization"):
if not formatter.contains(p):
formatter.add(p, f)
for path, log in app_finder.jarlogs.items():
assert isinstance(copier[path], Jarrer)
copier[path].preload([l.replace(locale, l10n_locale) for l in log])
def repack(
source, l10n, extra_l10n={}, non_resources=[], non_chrome=set(), minify=False
):
app_finder = UnpackFinder(source, minify=minify)
l10n_finder = UnpackFinder(l10n, minify=minify)
if extra_l10n:
finders = {
"": l10n_finder,
}
for base, path in extra_l10n.items():
finders[base] = UnpackFinder(path, minify=minify)
l10n_finder = ComposedFinder(finders)
copier = FileCopier()
compress = min(app_finder.compressed, JAR_DEFLATED)
if app_finder.kind == "flat":
formatter = FlatFormatter(copier)
elif app_finder.kind == "jar":
formatter = JarFormatter(copier, compress=compress)
elif app_finder.kind == "omni":
formatter = OmniJarFormatter(
copier, app_finder.omnijar, compress=compress, non_resources=non_resources
)
with errors.accumulate():
_repack(app_finder, l10n_finder, copier, formatter, non_chrome)
copier.copy(source, skip_if_older=False)
generate_precomplete(source)