Bug 1210687 - Separate out jar.mn parsing in a separate class. r=gps

This commit is contained in:
Mike Hommey 2015-10-02 14:15:59 +09:00
parent ccf3953f83
commit e127dc20b1

View File

@ -18,16 +18,17 @@ import logging
from time import localtime
from MozZipFile import ZipFile
from cStringIO import StringIO
from collections import defaultdict
from mozbuild.util import (
ensureParentDir,
lock_file,
PushbackIter,
)
from mozbuild.preprocessor import Preprocessor
from mozbuild.action.buildlist import addEntriesToListFile
from mozpack.files import FileFinder
import mozpack.path as mozpath
if sys.platform == 'win32':
from ctypes import windll, WinError
CreateHardLink = windll.kernel32.CreateHardLinkA
@ -67,13 +68,28 @@ def getModTime(aPath):
return localtime(mtime)
class JarMaker(object):
'''JarMaker reads jar.mn files and process those into jar files or
flat directories, along with chrome.manifest files.
'''
class JarManifestEntry(object):
def __init__(self, output, source, is_locale=False, preprocess=False,
overwrite=False):
self.output = output
self.source = source
self.is_locale = is_locale
self.preprocess = preprocess
self.overwrite = overwrite
class JarInfo(object):
def __init__(self, name):
self.name = name
self.relativesrcdir = None
self.chrome_manifests = []
self.entries = []
class JarManifestParser(object):
ignore = re.compile('\s*(\#.*)?$')
jarline = re.compile('(?:(?P<jarfile>[\w\d.\-\_\\\/{}]+).jar\:)|(?:\s*(\#.*)?)\s*$')
jarline = re.compile('(?P<jarfile>[\w\d.\-\_\\\/{}]+).jar\:$')
relsrcline = re.compile('relativesrcdir\s+(?P<relativesrcdir>.+?):')
regline = re.compile('\%\s+(.*)$')
entryre = '(?P<optPreprocess>\*)?(?P<optOverwrite>\+?)\s+'
@ -81,6 +97,85 @@ class JarMaker(object):
+ '(?P<output>[\w\d.\-\_\\\/\+\@]+)\s*(\((?P<locale>\%?)(?P<source>[\w\d.\-\_\\\/\@\*]+)\))?\s*$'
)
def __init__(self):
self._current_jar = None
self._jars = []
def write(self, line):
# A Preprocessor instance feeds the parser through calls to this method.
# Ignore comments and empty lines
if self.ignore.match(line):
return
# A jar manifest file can declare several different sections, each of
# which applies to a given "jar file". Each of those sections starts
# with "<name>.jar:".
if self._current_jar is None:
m = self.jarline.match(line)
if not m:
raise RuntimeError(line)
self._current_jar = JarInfo(m.group('jarfile'))
self._jars.append(self._current_jar)
return
# Within each section, there can be three different types of entries:
# - indications of the relative source directory we pretend to be in
# when considering localization files, in the following form;
# "relativesrcdir <path>:"
m = self.relsrcline.match(line)
if m:
if self._current_jar.chrome_manifests or self._current_jar.entries:
self._current_jar = JarInfo(self._current_jar.name)
self._jars.append(self._current_jar)
self._current_jar.relativesrcdir = m.group('relativesrcdir')
return
# - chrome manifest entries, prefixed with "%".
m = self.regline.match(line)
if m:
rline = m.group(1)
if rline not in self._current_jar.chrome_manifests:
self._current_jar.chrome_manifests.append(rline)
return
# - entries indicating files to be part of the given jar. They are
# formed thusly:
# "<dest_path>"
# or
# "<dest_path> (<source_path>)"
# The <dest_path> is where the file(s) will be put in the chrome jar.
# The <source_path> is where the file(s) can be found in the source
# directory. The <source_path> may start with a "%" for files part
# of a localization directory, in which case the "%" counts as the
# locale.
# Each entry can be prefixed with "*" for preprocessing and "+" to
# always overwrite the destination independently of file timestamps
# (the usefulness of the latter is dubious in the modern days).
m = self.entryline.match(line)
if m:
self._current_jar.entries.append(JarManifestEntry(
m.group('output'),
m.group('source') or mozpath.basename(m.group('output')),
is_locale=bool(m.group('locale')),
preprocess=bool(m.group('optPreprocess')),
overwrite=bool(m.group('optOverwrite')),
))
return
self._current_jar = None
self.write(line)
def __iter__(self):
return iter(self._jars)
class JarMaker(object):
'''JarMaker reads jar.mn files and process those into jar files or
flat directories, along with chrome.manifest files.
'''
def __init__(self, outputFormat='flat', useJarfileManifest=True,
useChromeManifest=False):
@ -205,7 +300,7 @@ class JarMaker(object):
lock = lock_file(manifestPath + '.lck')
try:
myregister = dict.fromkeys(map(lambda s: s.replace('%',
chromebasepath), register.iterkeys()))
chromebasepath), register))
manifestExists = os.path.isfile(manifestPath)
mode = manifestExists and 'r+b' or 'wb'
mf = open(manifestPath, mode)
@ -243,24 +338,11 @@ class JarMaker(object):
logging.info('processing ' + infile)
self.sourcedirs.append(_normpath(os.path.dirname(infile)))
pp = self.pp.clone()
pp.out = StringIO()
pp.out = JarManifestParser()
pp.do_include(infile)
lines = PushbackIter(pp.out.getvalue().splitlines())
try:
while True:
l = lines.next()
m = self.jarline.match(l)
if not m:
raise RuntimeError(l)
if m.group('jarfile') is None:
# comment
continue
self.processJarSection(m.group('jarfile'), lines,
jardir)
except StopIteration:
# we read the file
pass
return
for info in pp.out:
self.processJarSection(info, jardir)
def generateLocaleDirs(self, relativesrcdir):
if os.path.basename(relativesrcdir) == 'locales':
@ -281,25 +363,21 @@ class JarMaker(object):
relativesrcdir, 'en-US'))
return locdirs
def processJarSection(self, jarfile, lines, jardir):
def processJarSection(self, jarinfo, jardir):
'''Internal method called by makeJar to actually process a section
of a jar.mn file.
jarfile is the basename of the jarfile or the directory name for
flat output, lines is a PushbackIter of the lines of jar.mn,
the remaining options are carried over from makeJar.
'''
# chromebasepath is used for chrome registration manifests
# {0} is getting replaced with chrome/ for chrome.manifest, and with
# an empty string for jarfile.manifest
chromebasepath = '{0}' + os.path.basename(jarfile)
chromebasepath = '{0}' + os.path.basename(jarinfo.name)
if self.outputFormat == 'jar':
chromebasepath = 'jar:' + chromebasepath + '.jar!'
chromebasepath += '/'
jarfile = os.path.join(jardir, jarfile)
jarfile = os.path.join(jardir, jarinfo.name)
jf = None
if self.outputFormat == 'jar':
# jar
@ -314,59 +392,24 @@ class JarMaker(object):
else:
outHelper = getattr(self, 'OutputHelper_'
+ self.outputFormat)(jarfile)
register = {}
# This loop exits on either
# - the end of the jar.mn file
# - an line in the jar.mn file that's not part of a jar section
# - on an exception raised, close the jf in that case in a finally
if jarinfo.relativesrcdir:
self.localedirs = self.generateLocaleDirs(jarinfo.relativesrcdir)
try:
while True:
try:
l = lines.next()
except StopIteration:
# we're done with this jar.mn, and this jar section
self.finalizeJar(jarfile, chromebasepath, register)
if jf is not None:
jf.close()
for e in jarinfo.entries:
self._processEntryLine(e, outHelper, jf)
# reraise the StopIteration for makeJar
raise
if self.ignore.match(l):
continue
m = self.relsrcline.match(l)
if m:
relativesrcdir = m.group('relativesrcdir')
self.localedirs = \
self.generateLocaleDirs(relativesrcdir)
continue
m = self.regline.match(l)
if m:
rline = m.group(1)
register[rline] = 1
continue
m = self.entryline.match(l)
if not m:
# neither an entry line nor chrome reg, this jar section is done
self.finalizeJar(jarfile, chromebasepath, register)
if jf is not None:
jf.close()
lines.pushback(l)
return
self._processEntryLine(m, outHelper, jf)
finally:
if jf is not None:
jf.close()
return
self.finalizeJar(jarfile, chromebasepath, jarinfo.chrome_manifests)
if jf is not None:
jf.close()
def _processEntryLine(self, m, outHelper, jf):
out = m.group('output')
src = m.group('source') or os.path.basename(out)
def _processEntryLine(self, e, outHelper, jf):
out = e.output
src = e.source
# pick the right sourcedir -- l10n, topsrc or src
if m.group('locale'):
if e.is_locale:
src_base = self.localedirs
elif src.startswith('/'):
# path/in/jar/file_name.xul (/path/in/sourcetree/file_name.xul)
@ -379,26 +422,22 @@ class JarMaker(object):
src_base = self.sourcedirs + [os.getcwd()]
if '*' in src:
if not out.endswith('/'):
out += '/'
def _prefix(s):
for p in s.split('/'):
if '*' not in p:
yield p + '/'
prefix = ''.join(_prefix(src))
fmt = '%s%s %s%%s (%s%%s)' % (
m.group('optPreprocess') or '',
m.group('optOverwrite') or '',
out,
m.group('locale').replace('%', '%%') or '',
)
for _srcdir in src_base:
finder = FileFinder(_srcdir, find_executables=False)
for path, _ in finder.find(src):
line = fmt % (path[len(prefix):], path)
m = self.entryline.match(line)
if m:
self._processEntryLine(m, outHelper, jf)
e = JarManifestEntry(
mozpath.join(out, path[len(prefix):]),
path,
is_locale=e.is_locale,
preprocess=e.preprocess,
overwrite=e.overwrite
)
self._processEntryLine(e, outHelper, jf)
return
# check if the source file exists
@ -412,7 +451,7 @@ class JarMaker(object):
jf.close()
raise RuntimeError('File "{0}" not found in {1}'.format(src,
', '.join(src_base)))
if m.group('optPreprocess'):
if e.preprocess:
outf = outHelper.getOutput(out)
inf = open(realsrc)
pp = self.pp.clone()
@ -427,8 +466,8 @@ class JarMaker(object):
# copy or symlink if newer or overwrite
if m.group('optOverwrite') or getModTime(realsrc) \
> outHelper.getDestModTime(m.group('output')):
if e.overwrite or getModTime(realsrc) \
> outHelper.getDestModTime(e.output):
if self.outputFormat == 'symlink':
outHelper.symlink(realsrc, out)
return