# Copyright © 2010-2013 Piotr Ożarowski # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import logging import re from os import makedirs, chmod from os.path import basename, exists, join, dirname from sys import argv from dhpython import DEPENDS_SUBSTVARS, PKG_NAME_TPLS, RT_LOCATIONS, RT_TPLS log = logging.getLogger('dhpython') parse_dep = re.compile('''[,\s]* (?P[^ ]+) \s* \(?(?P([>=<]{2,}|=)\s*[^\)]+)?\)? \s* (?:\[(?P[^\]]+)\])? ''', re.VERBOSE).match class DebHelper: """Reinvents the wheel / some dh functionality (Perl is ugly ;-P)""" def __init__(self, options, impl='cpython3'): self.options = options self.packages = {} self.build_depends = {} self.python_version = None source_section = True binary_package = None build_depends_line = '' inside_bdepends_field = False # Note that each DebHelper instance supports ONE interpreter type only # it's not possible to mix cpython2, cpython3 and pypy here self.impl = impl skip_tpl = set() for name, tpls in PKG_NAME_TPLS.items(): if name != impl: skip_tpl.update(tpls) skip_tpl = tuple(skip_tpl) substvar = DEPENDS_SUBSTVARS[impl] pkgs = options.package skip_pkgs = options.no_package try: fp = open('debian/control', 'r', encoding='utf-8') except IOError: raise Exception('cannot find debian/control file') for line in fp: if line.startswith('#'): continue if not line.strip(): source_section = False binary_package = None inside_depends_field = False continue line_l = line.lower() # field names are case-insensitive if binary_package: if binary_package not in self.packages: continue if line_l.startswith('architecture:'): arch = line[13:].strip() if options.arch is False and arch != 'all' or\ options.arch is True and arch == 'all': # TODO: check also if arch matches current architecture: del self.packages[binary_package] else: self.packages[binary_package]['arch'] = arch continue if not binary_package.startswith(PKG_NAME_TPLS[impl]): # package doesn't have common prefix (python-, python3-, pypy-) # so lets check if Depends contains appropriate substvar if line_l.startswith('depends:'): if substvar in line: continue inside_depends_field = True elif inside_depends_field: # multiline continuation if not line.startswith((' ', '\t')): inside_depends_field = False log.debug('skipping package %s (missing %s in Depends)', binary_package, substvar) del self.packages[binary_package] elif substvar in line: inside_depends_field = None elif line_l.startswith('package:'): binary_package = line[8:].strip() if skip_tpl and binary_package.startswith(skip_tpl): log.debug('skipping package: %s', binary_package) continue if pkgs and binary_package not in pkgs: continue if skip_pkgs and binary_package in skip_pkgs: continue self.packages[binary_package] = {'substvars': {}, 'autoscripts': {}, 'rtupdates': [], 'arch': 'any'} elif line_l.startswith('source:'): self.source_name = line[7:].strip() elif source_section and self.impl == 'cpython3' and line_l.startswith('x-python3-version:'): self.python_version = line[18:] if len(self.python_version.split(',')) > 2: raise ValueError('too many arguments provided for X-Python3-Version: min and max only.') elif source_section and self.impl == 'cpython2': if line_l.startswith('xs-python-version:'): if not self.python_version: self.python_version = line[18:].strip() if line_l.startswith('x-python-version:'): self.python_version = line[17:].strip() elif source_section and line_l.startswith(('build-depends:', 'build-depends-indep:')): inside_bdepends_field = True build_depends_line += ',' + line.split(':', 1)[1].strip(', \t\n') elif inside_bdepends_field: # multiline continuation if not line.startswith((' ', '\t', '#')): inside_bdepends_field = False elif not line.strip().startswith('#'): build_depends_line += ',' + line.strip(', \t\n') for dep1 in build_depends_line.strip(', \t').split(','): for dep2 in dep1.split('|'): details = parse_dep(dep2) if details: details = details.groupdict() if details['arch']: architectures = details['arch'].split() else: architectures = [None] for arch in architectures: self.build_depends.setdefault(details['name'], {})[arch] = details['version'] fp.close() log.debug('source=%s, binary packages=%s', self.source_name, list(self.packages.keys())) def addsubstvar(self, package, name, value): """debhelper's addsubstvar""" self.packages[package]['substvars'].setdefault(name, []).append(value) def autoscript(self, package, when, template, args): """debhelper's autoscript""" self.packages[package]['autoscripts'].setdefault(when, {})\ .setdefault(template, []).append(args) def add_rtupdate(self, package, value): self.packages[package]['rtupdates'].append(value) def save_autoscripts(self): for package, settings in self.packages.items(): autoscripts = settings.get('autoscripts') if not autoscripts: continue for when, templates in autoscripts.items(): fn = "debian/%s.%s.debhelper" % (package, when) if exists(fn): with open(fn, 'r', encoding='utf-8') as datafile: data = datafile.read() else: data = '' new_data = '' for tpl_name, args in templates.items(): for i in args: # try local one first (useful while testing dh_python3) fpath = join(dirname(__file__), '..', "autoscripts/%s" % tpl_name) if not exists(fpath): fpath = "/usr/share/debhelper/autoscripts/%s" % tpl_name with open(fpath, 'r', encoding='utf-8') as tplfile: tpl = tplfile.read() if self.options.compile_all and args: # TODO: should args be checked to contain dir name? tpl = tpl.replace('-p #PACKAGE#', '') else: tpl = tpl.replace('#PACKAGE#', package) tpl = tpl.replace('#ARGS#', i) if tpl not in data and tpl not in new_data: new_data += "\n%s" % tpl if new_data: data += '\n# Automatically added by {}:'.format(basename(argv[0])) +\ '{}\n# End automatically added section\n'.format(new_data) fp = open(fn, 'w', encoding='utf-8') fp.write(data) fp.close() def save_substvars(self): for package, settings in self.packages.items(): substvars = settings.get('substvars') if not substvars: continue fn = "debian/%s.substvars" % package if exists(fn): with open(fn, 'r', encoding='utf-8') as datafile: data = datafile.read() else: data = '' for name, values in substvars.items(): p = data.find("%s=" % name) if p > -1: # parse the line and remove it from data e = data[p:].find('\n') line = data[p + len("%s=" % name): p + e if e > -1 else None] items = [i.strip() for i in line.split(',') if i] if e > -1 and data[p + e:].strip(): data = "%s\n%s" % (data[:p], data[p + e:]) else: data = data[:p] else: items = [] for j in values: if j not in items: items.append(j) if items: if data: data += '\n' data += "%s=%s\n" % (name, ', '.join(items)) data = data.replace('\n\n', '\n') if data: fp = open(fn, 'w', encoding='utf-8') fp.write(data) fp.close() def save_rtupdate(self): for package, settings in self.packages.items(): pkg_arg = '' if self.options.compile_all else "-p %s" % package values = settings.get('rtupdates') if not values: continue d = 'debian/{}/{}'.format(package, RT_LOCATIONS[self.impl]) if not exists(d): makedirs(d) fn = "%s/%s.rtupdate" % (d, package) if exists(fn): data = open(fn, 'r', encoding='utf-8').read() else: data = "#! /bin/sh\nset -e" for dname, args in values: cmd = RT_TPLS[self.impl].format(pkg_arg=pkg_arg, dname=dname, args=args) if cmd not in data: data += "\n%s" % cmd if data: fp = open(fn, 'w', encoding='utf-8') fp.write(data) fp.close() chmod(fn, 0o755) def save(self): self.save_substvars() self.save_autoscripts() self.save_rtupdate()