summaryrefslogtreecommitdiffstats
path: root/dhpython/debhelper.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:08:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:08:06 +0000
commitba6d96469df143b52295f8e79da648bf8a597407 (patch)
tree5ea0c3374f5c53209ad02008dcdddfc8ccae92e5 /dhpython/debhelper.py
parentInitial commit. (diff)
downloaddh-python-ba6d96469df143b52295f8e79da648bf8a597407.tar.xz
dh-python-ba6d96469df143b52295f8e79da648bf8a597407.zip
Adding upstream version 5.20230130+deb12u1.upstream/5.20230130+deb12u1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dhpython/debhelper.py')
-rw-r--r--dhpython/debhelper.py327
1 files changed, 327 insertions, 0 deletions
diff --git a/dhpython/debhelper.py b/dhpython/debhelper.py
new file mode 100644
index 0000000..f497a60
--- /dev/null
+++ b/dhpython/debhelper.py
@@ -0,0 +1,327 @@
+# Copyright © 2010-2013 Piotr Ożarowski <piotr@debian.org>
+#
+# 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 errno
+import logging
+import re
+from os import makedirs, chmod, environ
+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<name>[^ :]+)(?::any)?
+ \s*
+ \(?(?P<version>([>=<]{2,}|=)\s*[^\)]+)?\)?
+ \s*
+ (?:\[(?P<arch>[^\]]+)\])?
+ ''', re.VERBOSE).match
+
+
+def build_options(**options):
+ """Build an Options object from kw options"""
+ default_options = {
+ 'arch': None,
+ 'package': [],
+ 'no_package': [],
+ 'write_log': False,
+ 'remaining_packages': False,
+ }
+ built_options = default_options
+ built_options.update(options)
+ return type('Options', (object,), built_options)
+
+
+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
+ # Note that each DebHelper instance supports ONE interpreter type only
+ # it's not possible to mix cpython2, cpython3 and pypy here
+ self.impl = impl
+ self.command = {
+ 'cpython2': 'dh_python2',
+ 'cpython3': 'dh_python3',
+ 'pypy': 'dh_pypy',
+ }[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:
+ with open('debian/control', 'r', encoding='utf-8') as fp:
+ paragraphs = [{}]
+ field = None
+ for lineno, line in enumerate(fp, 1):
+ if line.startswith('#'):
+ continue
+ if not line.strip():
+ if paragraphs[-1]:
+ paragraphs.append({})
+ field = None
+ continue
+ if line[0].isspace(): # Continuation
+ paragraphs[-1][field] += line.rstrip()
+ continue
+ if not ':' in line:
+ raise Exception(
+ 'Unable to parse line %i in debian/control: %s'
+ % (lineno, line))
+ field, value = line.split(':', 1)
+ field = field.lower()
+ paragraphs[-1][field] = value.strip()
+ except IOError:
+ raise Exception('cannot find debian/control file')
+
+ # Trailing new lines?
+ if not paragraphs[-1]:
+ paragraphs.pop()
+
+ if len(paragraphs) < 2:
+ raise Exception('Unable to parse debian/control, found less than '
+ '2 paragraphs')
+
+ self.source_name = paragraphs[0]['source']
+ if self.impl == 'cpython3' and 'x-python3-version' in paragraphs[0]:
+ self.python_version = paragraphs[0]['x-python3-version']
+ if len(self.python_version.split(',')) > 2:
+ raise ValueError('too many arguments provided for '
+ 'X-Python3-Version: min and max only.')
+ elif self.impl == 'cpython2':
+ if 'x-python-version' in paragraphs[0]:
+ self.python_version = paragraphs[0]['x-python-version']
+ elif 'xs-python-version' in paragraphs[0]:
+ self.python_version = paragraphs[0]['xs-python-version']
+
+ build_depends = []
+ for field in ('build-depends', 'build-depends-indep',
+ 'build-depends-arch'):
+ if field in paragraphs[0]:
+ build_depends.append(paragraphs[0][field])
+ build_depends = ', '.join(build_depends)
+ for dep1 in build_depends.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']
+
+ for paragraph_no, paragraph in enumerate(paragraphs[1:], 2):
+ if 'package' not in paragraph:
+ raise Exception('Unable to parse debian/control, paragraph %i '
+ 'missing Package field' % paragraph_no)
+ binary_package = paragraph['package']
+ 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
+ if (options.remaining_packages and
+ self.has_acted_on_package(binary_package)):
+ continue
+ pkg = {
+ 'substvars': {},
+ 'autoscripts': {},
+ 'rtupdates': [],
+ 'arch': paragraph['architecture'],
+ }
+ if (options.arch is False and pkg['arch'] != 'all' or
+ options.arch is True and pkg['arch'] == 'all'):
+ # TODO: check also if arch matches current architecture:
+ continue
+
+ if not binary_package.startswith(PKG_NAME_TPLS[impl]):
+ # package doesn't have common prefix (python-, python3-, pypy-)
+ # so lets check if Depends/Recommends contains the
+ # appropriate substvar
+ if (substvar not in paragraph.get('depends', '')
+ and substvar not in paragraph.get('recommends', '')):
+ log.debug('skipping package %s (missing %s in '
+ 'Depends/Recommends)',
+ binary_package, substvar)
+ continue
+ # Operate on binary_package
+ self.packages[binary_package] = pkg
+
+ fp.close()
+ log.debug('source=%s, binary packages=%s', self.source_name,
+ list(self.packages.keys()))
+
+ def has_acted_on_package(self, package):
+ try:
+ with open('debian/{}.debhelper.log'.format(package),
+ encoding='utf-8') as f:
+ for line in f:
+ if line.strip() == self.command:
+ return True
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ return False
+
+ 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#', '')
+ elif settings['arch'] == 'all':
+ tpl = tpl.replace('#PACKAGE#', package)
+ else:
+ arch = environ['DEB_HOST_ARCH']
+ tpl = tpl.replace('#PACKAGE#', '%s:%s' % (package, arch))
+ 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_log(self):
+ if not self.options.write_log:
+ return
+ for package, settings in self.packages.items():
+ with open('debian/{}.debhelper.log'.format(package),
+ 'a', encoding='utf-8') as f:
+ f.write(self.command + '\n')
+
+ def save(self):
+ self.save_substvars()
+ self.save_autoscripts()
+ self.save_rtupdate()
+ self.save_log()