~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to config/JarMaker.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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/.
4
 
 
5
 
'''jarmaker.py provides a python class to package up chrome content by
6
 
processing jar.mn files.
7
 
 
8
 
See the documentation for jar.mn on MDC for further details on the format.
9
 
'''
10
 
 
11
 
import sys
12
 
import os
13
 
import os.path
14
 
import errno
15
 
import re
16
 
import logging
17
 
from time import localtime
18
 
from optparse import OptionParser
19
 
from MozZipFile import ZipFile
20
 
from cStringIO import StringIO
21
 
from datetime import datetime
22
 
 
23
 
from utils import pushback_iter, lockFile
24
 
from Preprocessor import Preprocessor
25
 
from buildlist import addEntriesToListFile
26
 
if sys.platform == "win32":
27
 
  from ctypes import windll, WinError
28
 
  CreateHardLink = windll.kernel32.CreateHardLinkA
29
 
 
30
 
__all__ = ['JarMaker']
31
 
 
32
 
class ZipEntry:
33
 
  '''Helper class for jar output.
34
 
 
35
 
  This class defines a simple file-like object for a zipfile.ZipEntry
36
 
  so that we can consecutively write to it and then close it.
37
 
  This methods hooks into ZipFile.writestr on close().
38
 
  '''
39
 
  def __init__(self, name, zipfile):
40
 
    self._zipfile = zipfile
41
 
    self._name = name
42
 
    self._inner = StringIO()
43
 
 
44
 
  def write(self, content):
45
 
    'Append the given content to this zip entry'
46
 
    self._inner.write(content)
47
 
    return
48
 
 
49
 
  def close(self):
50
 
    'The close method writes the content back to the zip file.'
51
 
    self._zipfile.writestr(self._name, self._inner.getvalue())
52
 
 
53
 
def getModTime(aPath):
54
 
  if not os.path.isfile(aPath):
55
 
    return 0
56
 
  mtime = os.stat(aPath).st_mtime
57
 
  return localtime(mtime)
58
 
 
59
 
 
60
 
class JarMaker(object):
61
 
  '''JarMaker reads jar.mn files and process those into jar files or
62
 
  flat directories, along with chrome.manifest files.
63
 
  '''
64
 
 
65
 
  ignore = re.compile('\s*(\#.*)?$')
66
 
  jarline = re.compile('(?:(?P<jarfile>[\w\d.\-\_\\\/]+).jar\:)|(?:\s*(\#.*)?)\s*$')
67
 
  regline = re.compile('\%\s+(.*)$')
68
 
  entryre = '(?P<optPreprocess>\*)?(?P<optOverwrite>\+?)\s+'
69
 
  entryline = re.compile(entryre + '(?P<output>[\w\d.\-\_\\\/\+]+)\s*(\((?P<locale>\%?)(?P<source>[\w\d.\-\_\\\/]+)\))?\s*$')
70
 
 
71
 
  def __init__(self, outputFormat = 'flat', useJarfileManifest = True,
72
 
               useChromeManifest = False):
73
 
    self.outputFormat = outputFormat
74
 
    self.useJarfileManifest = useJarfileManifest
75
 
    self.useChromeManifest = useChromeManifest
76
 
    self.pp = Preprocessor()
77
 
 
78
 
  def getCommandLineParser(self):
79
 
    '''Get a optparse.OptionParser for jarmaker.
80
 
 
81
 
    This OptionParser has the options for jarmaker as well as
82
 
    the options for the inner PreProcessor.
83
 
    '''
84
 
    # HACK, we need to unescape the string variables we get,
85
 
    # the perl versions didn't grok strings right
86
 
    p = self.pp.getCommandLineParser(unescapeDefines = True)
87
 
    p.add_option('-f', type="choice", default="jar",
88
 
                 choices=('jar', 'flat', 'symlink'),
89
 
                 help="fileformat used for output", metavar="[jar, flat, symlink]")
90
 
    p.add_option('-v', action="store_true", dest="verbose",
91
 
                 help="verbose output")
92
 
    p.add_option('-q', action="store_false", dest="verbose",
93
 
                 help="verbose output")
94
 
    p.add_option('-e', action="store_true",
95
 
                 help="create chrome.manifest instead of jarfile.manifest")
96
 
    p.add_option('--both-manifests', action="store_true",
97
 
                 dest="bothManifests",
98
 
                 help="create chrome.manifest and jarfile.manifest")
99
 
    p.add_option('-s', type="string", action="append", default=[],
100
 
                 help="source directory")
101
 
    p.add_option('-t', type="string",
102
 
                 help="top source directory")
103
 
    p.add_option('-c', '--l10n-src', type="string", action="append",
104
 
                 help="localization directory")
105
 
    p.add_option('--l10n-base', type="string", action="append", default=[],
106
 
                 help="base directory to be used for localization (multiple)")
107
 
    p.add_option('-j', type="string",
108
 
                 help="jarfile directory")
109
 
    # backwards compat, not needed
110
 
    p.add_option('-a', action="store_false", default=True,
111
 
                 help="NOT SUPPORTED, turn auto-registration of chrome off (installed-chrome.txt)")
112
 
    p.add_option('-d', type="string",
113
 
                 help="UNUSED, chrome directory")
114
 
    p.add_option('-o', help="cross compile for auto-registration, ignored")
115
 
    p.add_option('-l', action="store_true",
116
 
                 help="ignored (used to switch off locks)")
117
 
    p.add_option('-x', action="store_true",
118
 
                 help="force Unix")
119
 
    p.add_option('-z', help="backwards compat, ignored")
120
 
    p.add_option('-p', help="backwards compat, ignored")
121
 
    return p
122
 
 
123
 
  def processIncludes(self, includes):
124
 
    '''Process given includes with the inner PreProcessor.
125
 
 
126
 
    Only use this for #defines, the includes shouldn't generate
127
 
    content.
128
 
    '''
129
 
    self.pp.out = StringIO()
130
 
    for inc in includes:
131
 
      self.pp.do_include(inc)
132
 
    includesvalue = self.pp.out.getvalue()
133
 
    if includesvalue:
134
 
      logging.info("WARNING: Includes produce non-empty output")
135
 
    self.pp.out = None
136
 
    pass
137
 
 
138
 
  def finalizeJar(self, jarPath, chromebasepath, register,
139
 
                  doZip=True):
140
 
    '''Helper method to write out the chrome registration entries to
141
 
    jarfile.manifest or chrome.manifest, or both.
142
 
 
143
 
    The actual file processing is done in updateManifest.
144
 
    '''
145
 
    # rewrite the manifest, if entries given
146
 
    if not register:
147
 
      return
148
 
 
149
 
    chromeManifest = os.path.join(os.path.dirname(jarPath),
150
 
                                  '..', 'chrome.manifest')
151
 
 
152
 
    if self.useJarfileManifest:
153
 
      self.updateManifest(jarPath + '.manifest', chromebasepath % '',
154
 
                          register)
155
 
      addEntriesToListFile(chromeManifest, ['manifest chrome/%s.manifest' % (os.path.basename(jarPath),)])
156
 
    if self.useChromeManifest:
157
 
      self.updateManifest(chromeManifest, chromebasepath % 'chrome/',
158
 
                          register)
159
 
 
160
 
  def updateManifest(self, manifestPath, chromebasepath, register):
161
 
    '''updateManifest replaces the % in the chrome registration entries
162
 
    with the given chrome base path, and updates the given manifest file.
163
 
    '''
164
 
    lock = lockFile(manifestPath + '.lck')
165
 
    try:
166
 
      myregister = dict.fromkeys(map(lambda s: s.replace('%', chromebasepath),
167
 
                                     register.iterkeys()))
168
 
      manifestExists = os.path.isfile(manifestPath)
169
 
      mode = (manifestExists and 'r+b') or 'wb'
170
 
      mf = open(manifestPath, mode)
171
 
      if manifestExists:
172
 
        # import previous content into hash, ignoring empty ones and comments
173
 
        imf = re.compile('(#.*)?$')
174
 
        for l in re.split('[\r\n]+', mf.read()):
175
 
          if imf.match(l):
176
 
            continue
177
 
          myregister[l] = None
178
 
        mf.seek(0)
179
 
      for k in myregister.iterkeys():
180
 
        mf.write(k + os.linesep)
181
 
      mf.close()
182
 
    finally:
183
 
      lock = None
184
 
  
185
 
  def makeJar(self, infile=None,
186
 
               jardir='',
187
 
               sourcedirs=[], topsourcedir='', localedirs=None):
188
 
    '''makeJar is the main entry point to JarMaker.
189
 
 
190
 
    It takes the input file, the output directory, the source dirs and the
191
 
    top source dir as argument, and optionally the l10n dirs.
192
 
    '''
193
 
    if isinstance(infile, basestring):
194
 
      logging.info("processing " + infile)
195
 
    pp = self.pp.clone()
196
 
    pp.out = StringIO()
197
 
    pp.do_include(infile)
198
 
    lines = pushback_iter(pp.out.getvalue().splitlines())
199
 
    try:
200
 
      while True:
201
 
        l = lines.next()
202
 
        m = self.jarline.match(l)
203
 
        if not m:
204
 
          raise RuntimeError(l)
205
 
        if m.group('jarfile') is None:
206
 
          # comment
207
 
          continue
208
 
        self.processJarSection(m.group('jarfile'), lines,
209
 
                               jardir, sourcedirs, topsourcedir,
210
 
                               localedirs)
211
 
    except StopIteration:
212
 
      # we read the file
213
 
      pass
214
 
    return
215
 
 
216
 
  def makeJars(self, infiles, l10nbases,
217
 
               jardir='',
218
 
               sourcedirs=[], topsourcedir='', localedirs=None):
219
 
    '''makeJars is the second main entry point to JarMaker.
220
 
 
221
 
    It takes an iterable sequence of input file names, the l10nbases,
222
 
    the output directory, the source dirs and the
223
 
    top source dir as argument, and optionally the l10n dirs.
224
 
 
225
 
    It iterates over all inputs, guesses srcdir and l10ndir from the
226
 
    path and topsourcedir and calls into makeJar.
227
 
 
228
 
    The l10ndirs are created by guessing the relativesrcdir, and resolving
229
 
    that against the l10nbases. l10nbases can either be path strings, or 
230
 
    callables. In the latter case, that will be called with the 
231
 
    relativesrcdir as argument, and is expected to return a path string.
232
 
    This logic is disabled if the jar.mn path is not inside the topsrcdir.
233
 
    '''
234
 
    topsourcedir = os.path.normpath(os.path.abspath(topsourcedir))
235
 
    def resolveL10nBase(relpath):
236
 
      def _resolve(base):
237
 
        if isinstance(base, basestring):
238
 
          return os.path.join(base, relpath)
239
 
        if callable(base):
240
 
          return base(relpath)
241
 
        return base
242
 
      return _resolve
243
 
    for infile in infiles:
244
 
      srcdir = os.path.normpath(os.path.abspath(os.path.dirname(infile)))
245
 
      l10ndir = srcdir
246
 
      if os.path.basename(srcdir) == 'locales':
247
 
        l10ndir = os.path.dirname(l10ndir)
248
 
 
249
 
      l10ndirs = None
250
 
      # srcdir may not be a child of topsourcedir, in which case
251
 
      # we assume that the caller passed in suitable sourcedirs,
252
 
      # and just skip passing in localedirs
253
 
      if srcdir.startswith(topsourcedir):
254
 
        rell10ndir = l10ndir[len(topsourcedir):].lstrip(os.sep)
255
 
 
256
 
        l10ndirs = map(resolveL10nBase(rell10ndir), l10nbases)
257
 
        if localedirs is not None:
258
 
          l10ndirs += [os.path.normpath(os.path.abspath(s))
259
 
                       for s in localedirs]
260
 
      srcdirs = [os.path.normpath(os.path.abspath(s))
261
 
                 for s in sourcedirs] + [srcdir]
262
 
      self.makeJar(infile=infile,
263
 
                   sourcedirs=srcdirs, topsourcedir=topsourcedir,
264
 
                   localedirs=l10ndirs,
265
 
                   jardir=jardir)
266
 
 
267
 
 
268
 
  def processJarSection(self, jarfile, lines,
269
 
                        jardir, sourcedirs, topsourcedir, localedirs):
270
 
    '''Internal method called by makeJar to actually process a section
271
 
    of a jar.mn file.
272
 
 
273
 
    jarfile is the basename of the jarfile or the directory name for 
274
 
    flat output, lines is a pushback_iterator of the lines of jar.mn,
275
 
    the remaining options are carried over from makeJar.
276
 
    '''
277
 
 
278
 
    # chromebasepath is used for chrome registration manifests
279
 
    # %s is getting replaced with chrome/ for chrome.manifest, and with
280
 
    # an empty string for jarfile.manifest
281
 
    chromebasepath = '%s' + os.path.basename(jarfile)
282
 
    if self.outputFormat == 'jar':
283
 
      chromebasepath = 'jar:' + chromebasepath + '.jar!'
284
 
    chromebasepath += '/'
285
 
 
286
 
    jarfile = os.path.join(jardir, jarfile)
287
 
    jf = None
288
 
    if self.outputFormat == 'jar':
289
 
      #jar
290
 
      jarfilepath = jarfile + '.jar'
291
 
      try:
292
 
        os.makedirs(os.path.dirname(jarfilepath))
293
 
      except OSError, error:
294
 
        if error.errno != errno.EEXIST:
295
 
          raise
296
 
      jf = ZipFile(jarfilepath, 'a', lock = True)
297
 
      outHelper = self.OutputHelper_jar(jf)
298
 
    else:
299
 
      outHelper = getattr(self, 'OutputHelper_' + self.outputFormat)(jarfile)
300
 
    register = {}
301
 
    # This loop exits on either
302
 
    # - the end of the jar.mn file
303
 
    # - an line in the jar.mn file that's not part of a jar section
304
 
    # - on an exception raised, close the jf in that case in a finally
305
 
    try:
306
 
      while True:
307
 
        try:
308
 
          l = lines.next()
309
 
        except StopIteration:
310
 
          # we're done with this jar.mn, and this jar section
311
 
          self.finalizeJar(jarfile, chromebasepath, register)
312
 
          if jf is not None:
313
 
            jf.close()
314
 
          # reraise the StopIteration for makeJar
315
 
          raise
316
 
        if self.ignore.match(l):
317
 
          continue
318
 
        m = self.regline.match(l)
319
 
        if  m:
320
 
          rline = m.group(1)
321
 
          register[rline] = 1
322
 
          continue
323
 
        m = self.entryline.match(l)
324
 
        if not m:
325
 
          # neither an entry line nor chrome reg, this jar section is done
326
 
          self.finalizeJar(jarfile, chromebasepath, register)
327
 
          if jf is not None:
328
 
            jf.close()
329
 
          lines.pushback(l)
330
 
          return
331
 
        self._processEntryLine(m, sourcedirs, topsourcedir, localedirs,
332
 
                              outHelper, jf)
333
 
    finally:
334
 
      if jf is not None:
335
 
        jf.close()
336
 
    return
337
 
 
338
 
  def _processEntryLine(self, m, 
339
 
                        sourcedirs, topsourcedir, localedirs,
340
 
                        outHelper, jf):
341
 
      out = m.group('output')
342
 
      src = m.group('source') or os.path.basename(out)
343
 
      # pick the right sourcedir -- l10n, topsrc or src
344
 
      if m.group('locale'):
345
 
        src_base = localedirs
346
 
      elif src.startswith('/'):
347
 
        # path/in/jar/file_name.xul     (/path/in/sourcetree/file_name.xul)
348
 
        # refers to a path relative to topsourcedir, use that as base
349
 
        # and strip the leading '/'
350
 
        src_base = [topsourcedir]
351
 
        src = src[1:]
352
 
      else:
353
 
        # use srcdirs and the objdir (current working dir) for relative paths
354
 
        src_base = sourcedirs + [os.getcwd()]
355
 
      # check if the source file exists
356
 
      realsrc = None
357
 
      for _srcdir in src_base:
358
 
        if os.path.isfile(os.path.join(_srcdir, src)):
359
 
          realsrc = os.path.join(_srcdir, src)
360
 
          break
361
 
      if realsrc is None:
362
 
        if jf is not None:
363
 
          jf.close()
364
 
        raise RuntimeError('File "%s" not found in %s' % (src, ', '.join(src_base)))
365
 
      if m.group('optPreprocess'):
366
 
        outf = outHelper.getOutput(out)
367
 
        inf = open(realsrc)
368
 
        pp = self.pp.clone()
369
 
        if src[-4:] == '.css':
370
 
          pp.setMarker('%')
371
 
        pp.out = outf
372
 
        pp.do_include(inf)
373
 
        pp.warnUnused(realsrc)
374
 
        outf.close()
375
 
        inf.close()
376
 
        return
377
 
      # copy or symlink if newer or overwrite
378
 
      if (m.group('optOverwrite')
379
 
          or (getModTime(realsrc) >
380
 
              outHelper.getDestModTime(m.group('output')))):
381
 
        if self.outputFormat == 'symlink':
382
 
          outHelper.symlink(realsrc, out)
383
 
          return
384
 
        outf = outHelper.getOutput(out)
385
 
        # open in binary mode, this can be images etc
386
 
        inf = open(realsrc, 'rb')
387
 
        outf.write(inf.read())
388
 
        outf.close()
389
 
        inf.close()
390
 
    
391
 
 
392
 
  class OutputHelper_jar(object):
393
 
    '''Provide getDestModTime and getOutput for a given jarfile.
394
 
    '''
395
 
    def __init__(self, jarfile):
396
 
      self.jarfile = jarfile
397
 
    def getDestModTime(self, aPath):
398
 
      try :
399
 
        info = self.jarfile.getinfo(aPath)
400
 
        return info.date_time
401
 
      except:
402
 
        return 0
403
 
    def getOutput(self, name):
404
 
      return ZipEntry(name, self.jarfile)
405
 
 
406
 
  class OutputHelper_flat(object):
407
 
    '''Provide getDestModTime and getOutput for a given flat
408
 
    output directory. The helper method ensureDirFor is used by
409
 
    the symlink subclass.
410
 
    '''
411
 
    def __init__(self, basepath):
412
 
      self.basepath = basepath
413
 
    def getDestModTime(self, aPath):
414
 
      return getModTime(os.path.join(self.basepath, aPath))
415
 
    def getOutput(self, name):
416
 
      out = self.ensureDirFor(name)
417
 
      # remove previous link or file
418
 
      try:
419
 
        os.remove(out)
420
 
      except OSError, e:
421
 
        if e.errno != errno.ENOENT:
422
 
          raise
423
 
      return open(out, 'wb')
424
 
    def ensureDirFor(self, name):
425
 
      out = os.path.join(self.basepath, name)
426
 
      outdir = os.path.dirname(out)
427
 
      if not os.path.isdir(outdir):
428
 
        try:
429
 
          os.makedirs(outdir)
430
 
        except OSError, error:
431
 
          if error.errno != errno.EEXIST:
432
 
            raise
433
 
      return out
434
 
 
435
 
  class OutputHelper_symlink(OutputHelper_flat):
436
 
    '''Subclass of OutputHelper_flat that provides a helper for
437
 
    creating a symlink including creating the parent directories.
438
 
    '''
439
 
    def symlink(self, src, dest):
440
 
      out = self.ensureDirFor(dest)
441
 
      # remove previous link or file
442
 
      try:
443
 
        os.remove(out)
444
 
      except OSError, e:
445
 
        if e.errno != errno.ENOENT:
446
 
          raise
447
 
      if sys.platform != "win32":
448
 
        os.symlink(src, out)
449
 
      else:
450
 
        # On Win32, use ctypes to create a hardlink
451
 
        rv = CreateHardLink(out, src, None)
452
 
        if rv == 0:
453
 
          raise WinError()
454
 
 
455
 
def main():
456
 
  jm = JarMaker()
457
 
  p = jm.getCommandLineParser()
458
 
  (options, args) = p.parse_args()
459
 
  jm.processIncludes(options.I)
460
 
  jm.outputFormat = options.f
461
 
  if options.e:
462
 
    jm.useChromeManifest = True
463
 
    jm.useJarfileManifest = False
464
 
  if options.bothManifests:
465
 
    jm.useChromeManifest = True
466
 
    jm.useJarfileManifest = True
467
 
  noise = logging.INFO
468
 
  if options.verbose is not None:
469
 
    noise = (options.verbose and logging.DEBUG) or logging.WARN
470
 
  if sys.version_info[:2] > (2,3):
471
 
    logging.basicConfig(format = "%(message)s")
472
 
  else:
473
 
    logging.basicConfig()
474
 
  logging.getLogger().setLevel(noise)
475
 
  topsrc = options.t
476
 
  topsrc = os.path.normpath(os.path.abspath(topsrc))
477
 
  if not args:
478
 
    jm.makeJar(infile=sys.stdin,
479
 
               sourcedirs=options.s, topsourcedir=topsrc,
480
 
               localedirs=options.l10n_src,
481
 
               jardir=options.j)
482
 
  else:
483
 
    jm.makeJars(args, options.l10n_base,
484
 
                jardir=options.j,
485
 
                sourcedirs=options.s, topsourcedir=topsrc,
486
 
                localedirs=options.l10n_src)
487
 
 
488
 
if __name__ == "__main__":
489
 
  main()