| # Copyright 2015 The Bazel Authors. All rights reserved. | 
 | # | 
 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
 | # you may not use this file except in compliance with the License. | 
 | # You may obtain a copy of the License at | 
 | # | 
 | #    http://www.apache.org/licenses/LICENSE-2.0 | 
 | # | 
 | # Unless required by applicable law or agreed to in writing, software | 
 | # distributed under the License is distributed on an "AS IS" BASIS, | 
 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | # See the License for the specific language governing permissions and | 
 | # limitations under the License. | 
 | """A simple cross-platform helper to create a debian package.""" | 
 |  | 
 | import gzip | 
 | import hashlib | 
 | from io import BytesIO | 
 | import os.path | 
 | import sys | 
 | import tarfile | 
 | import textwrap | 
 | import time | 
 |  | 
 | from third_party.py import gflags | 
 |  | 
 | # list of debian fields : (name, mandatory, wrap[, default]) | 
 | # see http://www.debian.org/doc/debian-policy/ch-controlfields.html | 
 | DEBIAN_FIELDS = [ | 
 |     ('Package', True, False), | 
 |     ('Version', True, False), | 
 |     ('Section', False, False, 'contrib/devel'), | 
 |     ('Priority', False, False, 'optional'), | 
 |     ('Architecture', False, False, 'all'), | 
 |     ('Depends', False, True, []), | 
 |     ('Recommends', False, True, []), | 
 |     ('Suggests', False, True, []), | 
 |     ('Enhances', False, True, []), | 
 |     ('Conflicts', False, True, []), | 
 |     ('Pre-Depends', False, True, []), | 
 |     ('Installed-Size', False, False), | 
 |     ('Maintainer', True, False), | 
 |     ('Description', True, True), | 
 |     ('Homepage', False, False), | 
 |     ('Built-Using', False, False, 'Bazel'), | 
 |     ('Distribution', False, False, 'unstable'), | 
 |     ('Urgency', False, False, 'medium'), | 
 | ] | 
 |  | 
 | gflags.DEFINE_string('output', None, 'The output file, mandatory') | 
 | gflags.MarkFlagAsRequired('output') | 
 |  | 
 | gflags.DEFINE_string('changes', None, 'The changes output file, mandatory.') | 
 | gflags.MarkFlagAsRequired('changes') | 
 |  | 
 | gflags.DEFINE_string('data', None, | 
 |                      'Path to the data tarball, mandatory') | 
 | gflags.MarkFlagAsRequired('data') | 
 |  | 
 | gflags.DEFINE_string('preinst', None, | 
 |                      'The preinst script (prefix with @ to provide a path).') | 
 | gflags.DEFINE_string('postinst', None, | 
 |                      'The postinst script (prefix with @ to provide a path).') | 
 | gflags.DEFINE_string('prerm', None, | 
 |                      'The prerm script (prefix with @ to provide a path).') | 
 | gflags.DEFINE_string('postrm', None, | 
 |                      'The postrm script (prefix with @ to provide a path).') | 
 |  | 
 |  | 
 | # see | 
 | # https://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.en.html#s-conffile | 
 | gflags.DEFINE_multistring( | 
 |     'conffile', None, | 
 |     'List of conffiles (prefix item with @ to provide a path)') | 
 |  | 
 |  | 
 | def MakeGflags(): | 
 |   for field in DEBIAN_FIELDS: | 
 |     fieldname = field[0].replace('-', '_').lower() | 
 |     msg = 'The value for the %s content header entry.' % field[0] | 
 |     if len(field) > 3: | 
 |       if type(field[3]) is list: | 
 |         gflags.DEFINE_multistring(fieldname, field[3], msg) | 
 |       else: | 
 |         gflags.DEFINE_string(fieldname, field[3], msg) | 
 |     else: | 
 |       gflags.DEFINE_string(fieldname, None, msg) | 
 |     if field[1]: | 
 |       gflags.MarkFlagAsRequired(fieldname) | 
 |  | 
 |  | 
 | def AddArFileEntry(fileobj, filename, | 
 |                    content='', timestamp=0, | 
 |                    owner_id=0, group_id=0, mode=0o644): | 
 |   """Add a AR file entry to fileobj.""" | 
 |   inputs = [ | 
 |       (filename + '/').ljust(16),  # filename (SysV) | 
 |       str(timestamp).ljust(12),  # timestamp | 
 |       str(owner_id).ljust(6),  # owner id | 
 |       str(group_id).ljust(6),  # group id | 
 |       oct(mode).ljust(8),  # mode | 
 |       str(len(content)).ljust(10),  # size | 
 |       '\x60\x0a',  # end of file entry | 
 |   ] | 
 |   for i in inputs: | 
 |     fileobj.write(i.encode('ascii')) | 
 |   fileobj.write(content) | 
 |   if len(content) % 2 != 0: | 
 |     fileobj.write(b'\n')  # 2-byte alignment padding | 
 |  | 
 |  | 
 | def MakeDebianControlField(name, value, wrap=False): | 
 |   """Add a field to a debian control file.""" | 
 |   result = name + ': ' | 
 |   if type(value) is list: | 
 |     value = ', '.join(value) | 
 |   if wrap: | 
 |     result += ' '.join(value.split('\n')) | 
 |     result = textwrap.fill(result, | 
 |                            break_on_hyphens=False, | 
 |                            break_long_words=False) | 
 |   else: | 
 |     result += value | 
 |   return result.replace('\n', '\n ') + '\n' | 
 |  | 
 |  | 
 | def CreateDebControl(extrafiles=None, **kwargs): | 
 |   """Create the control.tar.gz file.""" | 
 |   # create the control file | 
 |   controlfile = '' | 
 |   for values in DEBIAN_FIELDS: | 
 |     fieldname = values[0] | 
 |     key = fieldname[0].lower() + fieldname[1:].replace('-', '') | 
 |     if values[1] or (key in kwargs and kwargs[key]): | 
 |       controlfile += MakeDebianControlField(fieldname, kwargs[key], values[2]) | 
 |   # Create the control.tar file | 
 |   tar = BytesIO() | 
 |   with gzip.GzipFile('control.tar.gz', mode='w', fileobj=tar, mtime=0) as gz: | 
 |     with tarfile.open('control.tar.gz', mode='w', fileobj=gz) as f: | 
 |       tarinfo = tarfile.TarInfo('control') | 
 |       tarinfo.size = len(controlfile) | 
 |       f.addfile(tarinfo, fileobj=BytesIO(controlfile.encode('utf-8'))) | 
 |       if extrafiles: | 
 |         for name, (data, mode) in extrafiles.items(): | 
 |           tarinfo = tarfile.TarInfo(name) | 
 |           tarinfo.size = len(data) | 
 |           tarinfo.mode = mode | 
 |           f.addfile(tarinfo, fileobj=BytesIO(data.encode('utf-8'))) | 
 |   control = tar.getvalue() | 
 |   tar.close() | 
 |   return control | 
 |  | 
 |  | 
 | def CreateDeb(output, | 
 |               data, | 
 |               preinst=None, | 
 |               postinst=None, | 
 |               prerm=None, | 
 |               postrm=None, | 
 |               conffiles=None, | 
 |               **kwargs): | 
 |   """Create a full debian package.""" | 
 |   extrafiles = {} | 
 |   if preinst: | 
 |     extrafiles['preinst'] = (preinst, 0o755) | 
 |   if postinst: | 
 |     extrafiles['postinst'] = (postinst, 0o755) | 
 |   if prerm: | 
 |     extrafiles['prerm'] = (prerm, 0o755) | 
 |   if postrm: | 
 |     extrafiles['postrm'] = (postrm, 0o755) | 
 |   if conffiles: | 
 |     extrafiles['conffiles'] = ('\n'.join(conffiles) + '\n', 0o644) | 
 |   control = CreateDebControl(extrafiles=extrafiles, **kwargs) | 
 |  | 
 |   # Write the final AR archive (the deb package) | 
 |   with open(output, 'wb') as f: | 
 |     f.write(b'!<arch>\n')  # Magic AR header | 
 |     AddArFileEntry(f, 'debian-binary', b'2.0\n') | 
 |     AddArFileEntry(f, 'control.tar.gz', control) | 
 |     # Tries to preserve the extension name | 
 |     ext = os.path.basename(data).split('.')[-2:] | 
 |     if len(ext) < 2: | 
 |       ext = 'tar' | 
 |     elif ext[1] == 'tgz': | 
 |       ext = 'tar.gz' | 
 |     elif ext[1] == 'tar.bzip2': | 
 |       ext = 'tar.bz2' | 
 |     else: | 
 |       ext = '.'.join(ext) | 
 |       if ext not in ['tar.bz2', 'tar.gz', 'tar.xz', 'tar.lzma']: | 
 |         ext = 'tar' | 
 |     with open(data, 'rb') as datafile: | 
 |       data = datafile.read() | 
 |     AddArFileEntry(f, 'data.' + ext, data) | 
 |  | 
 |  | 
 | def GetChecksumsFromFile(filename, hash_fns=None): | 
 |   """Computes MD5 and/or other checksums of a file. | 
 |  | 
 |   Args: | 
 |     filename: Name of the file. | 
 |     hash_fns: Mapping of hash functions. | 
 |               Default is {'md5': hashlib.md5} | 
 |  | 
 |   Returns: | 
 |     Mapping of hash names to hexdigest strings. | 
 |     { <hashname>: <hexdigest>, ... } | 
 |   """ | 
 |   hash_fns = hash_fns or {'md5': hashlib.md5} | 
 |   checksums = {k: fn() for (k, fn) in hash_fns.items()} | 
 |  | 
 |   with open(filename, 'rb') as file_handle: | 
 |     while True: | 
 |       buf = file_handle.read(1048576)  # 1 MiB | 
 |       if not buf: | 
 |         break | 
 |       for hashfn in checksums.values(): | 
 |         hashfn.update(buf) | 
 |  | 
 |   return {k: fn.hexdigest() for (k, fn) in checksums.items()} | 
 |  | 
 |  | 
 | def CreateChanges(output, | 
 |                   deb_file, | 
 |                   architecture, | 
 |                   short_description, | 
 |                   maintainer, | 
 |                   package, | 
 |                   version, | 
 |                   section, | 
 |                   priority, | 
 |                   distribution, | 
 |                   urgency, | 
 |                   timestamp=0): | 
 |   """Create the changes file.""" | 
 |   checksums = GetChecksumsFromFile(deb_file, {'md5': hashlib.md5, | 
 |                                               'sha1': hashlib.sha1, | 
 |                                               'sha256': hashlib.sha256}) | 
 |   debsize = str(os.path.getsize(deb_file)) | 
 |   deb_basename = os.path.basename(deb_file) | 
 |  | 
 |   changesdata = ''.join( | 
 |       MakeDebianControlField(*x) | 
 |       for x in [('Format', '1.8'), ('Date', time.ctime(timestamp)), ( | 
 |           'Source', package | 
 |       ), ('Binary', package | 
 |          ), ('Architecture', architecture), ('Version', version), ( | 
 |              'Distribution', distribution | 
 |          ), ('Urgency', urgency), ('Maintainer', maintainer), ( | 
 |              'Changed-By', maintainer | 
 |          ), ('Description', '\n%s - %s' % (package, short_description) | 
 |             ), ('Changes', ('\n%s (%s) %s; urgency=%s' | 
 |                             '\nChanges are tracked in revision control.' | 
 |                            ) % (package, version, distribution, urgency) | 
 |                ), ('Files', '\n' + ' '.join( | 
 |                    [checksums['md5'], debsize, section, priority, deb_basename]) | 
 |                   ), ('Checksums-Sha1', '\n' + ' '.join( | 
 |                       [checksums['sha1'], debsize, deb_basename]) | 
 |                      ), ('Checksums-Sha256', '\n' + ' '.join( | 
 |                          [checksums['sha256'], debsize, deb_basename]))]) | 
 |   with open(output, 'w') as changes_fh: | 
 |     changes_fh.write(changesdata) | 
 |  | 
 |  | 
 | def GetFlagValue(flagvalue, strip=True): | 
 |   if flagvalue: | 
 |     if flagvalue[0] == '@': | 
 |       with open(flagvalue[1:], 'r') as f: | 
 |         flagvalue = f.read() | 
 |     if strip: | 
 |       return flagvalue.strip() | 
 |   return flagvalue | 
 |  | 
 |  | 
 | def GetFlagValues(flagvalues): | 
 |   if flagvalues: | 
 |     return [GetFlagValue(f, False) for f in flagvalues] | 
 |   else: | 
 |     return None | 
 |  | 
 |  | 
 | def main(unused_argv): | 
 |   CreateDeb( | 
 |       FLAGS.output, | 
 |       FLAGS.data, | 
 |       preinst=GetFlagValue(FLAGS.preinst, False), | 
 |       postinst=GetFlagValue(FLAGS.postinst, False), | 
 |       prerm=GetFlagValue(FLAGS.prerm, False), | 
 |       postrm=GetFlagValue(FLAGS.postrm, False), | 
 |       conffiles=GetFlagValues(FLAGS.conffile), | 
 |       package=FLAGS.package, | 
 |       version=GetFlagValue(FLAGS.version), | 
 |       description=GetFlagValue(FLAGS.description), | 
 |       maintainer=FLAGS.maintainer, | 
 |       section=FLAGS.section, | 
 |       architecture=FLAGS.architecture, | 
 |       depends=FLAGS.depends, | 
 |       suggests=FLAGS.suggests, | 
 |       enhances=FLAGS.enhances, | 
 |       preDepends=FLAGS.pre_depends, | 
 |       recommends=FLAGS.recommends, | 
 |       homepage=FLAGS.homepage, | 
 |       builtUsing=GetFlagValue(FLAGS.built_using), | 
 |       priority=FLAGS.priority, | 
 |       conflicts=FLAGS.conflicts, | 
 |       installedSize=GetFlagValue(FLAGS.installed_size)) | 
 |   CreateChanges( | 
 |       output=FLAGS.changes, | 
 |       deb_file=FLAGS.output, | 
 |       architecture=FLAGS.architecture, | 
 |       short_description=GetFlagValue(FLAGS.description).split('\n')[0], | 
 |       maintainer=FLAGS.maintainer, package=FLAGS.package, | 
 |       version=GetFlagValue(FLAGS.version), section=FLAGS.section, | 
 |       priority=FLAGS.priority, distribution=FLAGS.distribution, | 
 |       urgency=FLAGS.urgency) | 
 |  | 
 | if __name__ == '__main__': | 
 |   MakeGflags() | 
 |   FLAGS = gflags.FLAGS | 
 |   main(FLAGS(sys.argv)) |