[game-data-packager] 20/25: Move control-file writing from PackagingTask to PackagingSystem

Simon McVittie smcv at debian.org
Sun Oct 9 21:26:07 UTC 2016


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to branch master
in repository game-data-packager.

commit d1fa5149058ac5545f3e64aff35a543394af5b88
Author: Simon McVittie <smcv at debian.org>
Date:   Sun Oct 9 18:46:36 2016 +0100

    Move control-file writing from PackagingTask to PackagingSystem
---
 game_data_packager/build.py              | 427 +------------------------------
 game_data_packager/packaging/__init__.py |  81 ++++++
 game_data_packager/packaging/arch.py     |  39 +++
 game_data_packager/packaging/deb.py      | 165 ++++++++++++
 game_data_packager/packaging/rpm.py      | 123 +++++++++
 5 files changed, 419 insertions(+), 416 deletions(-)

diff --git a/game_data_packager/build.py b/game_data_packager/build.py
index 43f5ad3..4469ae1 100644
--- a/game_data_packager/build.py
+++ b/game_data_packager/build.py
@@ -1277,209 +1277,6 @@ class PackagingTask(object):
     def fill_extra_files(self, package, destdir):
         pass
 
-    def fill_dest_dir_arch(self, package, destdir, compress, arch):
-        PKGINFO = os.path.join(destdir, '.PKGINFO')
-        short_desc, _ = self.generate_description(package)
-        size = check_output(['du','-bs','.'], cwd=destdir)
-        size = int(size.split()[0])
-        with open(PKGINFO, 'w',  encoding='utf-8') as pkginfo:
-            pkginfo.write('# Generated by game-data-packager %s\n' % package.version.split('+')[-1])
-            pkginfo.write('# using fakeroot version %s\n' %
-                    self.builder_packaging.current_version('fakeroot'))
-            pkginfo.write('# %s\n' % time.strftime("%a %b %d %H:%M:%S UTC %Y", time.gmtime()))
-            pkginfo.write('pkgname = %s\n' % package.name)
-            pkginfo.write('pkgver = %s-1\n' % package.version)
-            pkginfo.write('pkgdesc = %s\n' % short_desc)
-            pkginfo.write('url = https://wiki.debian.org/Games/GameDataPackager\n')
-            pkginfo.write('builddate = %i\n' % int(time.time()))
-            pkginfo.write('packager = Alexandre Detiste <alexandre at detiste.be>\n')
-            pkginfo.write('size = %i\n' % size)
-            pkginfo.write('arch = %s\n' % arch)
-            if os.path.isdir(os.path.join(destdir, 'usr/share/licenses')):
-                pkginfo.write('license = custom\n')
-            pkginfo.write('group = games\n')
-            if package.expansion_for:
-                pkginfo.write('depend = %s\n' % package.expansion_for)
-            else:
-                engine = self.packaging.substitute(
-                        package.engine or self.game.engine,
-                        package.name)
-
-                if engine and len(engine.split()) == 1:
-                    pkginfo.write('depend = %s\n' % engine)
-
-        files = set()
-        for dirpath, dirnames, filenames in os.walk(destdir):
-                for fn in filenames:
-                    full = os.path.join(dirpath, fn)
-                    full = full[len(destdir)+1:]
-                    files.add(full)
-
-        MTREE = os.path.join(destdir, '.MTREE')
-        subprocess.check_call(['fakeroot', 'bsdtar', '-czf', MTREE, '--format=mtree',
-                 '--options=!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link']
-                 + sorted(files), env={'LANG':'C'}, cwd=destdir)
-
-    def __merge_relations(self, package, rel):
-        return set(self.packaging.format_relations(package.relations[rel]))
-
-    def fill_dest_dir_rpm(self, package, destdir, compress, architecture, release):
-        specfile = os.path.join(self.get_workdir(), '%s.spec' % package.name)
-        short_desc, long_desc = self.generate_description(package)
-        short_desc = short_desc[0].upper() + short_desc[1:]
-
-        if self.game.wikibase:
-            url = self.game.wikibase + (self.game.wiki or '')
-        elif self.game.wikipedia:
-            url = self.game.wikipedia
-        else:
-            url = 'https://wiki.debian.org/Games/GameDataPackager'
-
-        # /usr/games & /usr/share/games should only
-        # be seen in rpm's built for Mageia
-        SYSTEM_DIRS = set(['/usr',
-                           '/usr/bin',
-                           '/usr/games',
-                           '/usr/lib',
-                           '/usr/share',
-                           '/usr/share/applications',
-                           '/usr/share/doc',
-                           '/usr/share/doc/packages',
-                           '/usr/share/games',
-                           '/usr/share/icons',
-                           '/usr/share/icons/hicolor',
-                           '/usr/share/icons/hicolor/scalable',
-                           '/usr/share/icons/hicolor/scalable/apps',
-                           '/usr/share/licenses',
-                           '/usr/share/pixmaps'])
-
-        files = set()
-        for dirpath, dirnames, filenames in os.walk(destdir):
-             dir = dirpath[len(destdir):]
-             if not dir:
-                 # /
-                 continue
-             elif dir in SYSTEM_DIRS:
-                 for fn in filenames + dirnames:
-                     full = os.path.join(dirpath, fn)
-                     file = full[len(destdir):]
-                     if file not in SYSTEM_DIRS:
-                         files.add(file)
-             else:
-                 for file in files:
-                     if dir.startswith(file):
-                         break
-                 else:
-                     files.add(dir)
-
-        logger.debug('%%files in specfile:\n%s', '\n'.join(sorted(files)))
-
-        with open(specfile, 'w', encoding='utf-8') as spec:
-            spec.write('Summary: %s\n' % short_desc)
-            spec.write('Name: %s\n' % package.name)
-            spec.write('Version: %s\n' % package.version)
-            spec.write('Url: %s\n' % url)
-            spec.write('Release: %s\n' % release)
-            spec.write('License: Commercial\n')
-            if self.packaging.derives_from('mageia'):
-                spec.write('Packager: game-data-packager\n')
-                spec.write('Group: Games/%s\n' % self.game.genre)
-            else:
-                spec.write('Group: Amusements/Games\n')
-            spec.write('BuildArch: %s\n' % architecture)
-
-            for p in self.__merge_relations(package, 'provides'):
-                spec.write('Provides: %s\n' % p)
-
-                if package.mutually_exclusive:
-                    spec.write('Conflicts: %s\n' % p)
-
-            if package.expansion_for:
-                spec.write('Requires: %s\n' % package.expansion_for)
-            else:
-                engine = self.packaging.substitute(
-                        package.engine or self.game.engine,
-                        package.name)
-
-                if engine and len(engine.split()) == 1:
-                    spec.write('Requires: %s\n' % engine)
-
-            for p in self.__merge_relations(package, 'depends'):
-                spec.write('Requires: %s\n' % p)
-
-            for p in (self.__merge_relations(package, 'conflicts') |
-                    self.__merge_relations(package, 'breaks')):
-                spec.write('Conflicts: %s\n' % p)
-
-            for p in self.__merge_relations(package, 'recommends'):
-                # FIXME: some RPM distributions do have recommends;
-                # which ones?
-                pass
-
-            for p in self.__merge_relations(package, 'suggests'):
-                # FIXME: likewise
-                pass
-
-            # FIXME: replaces?
-
-            if not compress or not self.compress_deb or package.rip_cd:
-                spec.write('%define _binary_payload w0.gzdio\n')
-            elif self.compress_deb == ['-Zgzip', '-z1']:
-                spec.write('%define _binary_payload w1.gzdio\n')
-            spec.write('%description\n')
-            spec.write('%s\n' % long_desc)
-            spec.write('%files\n')
-            spec.write('\n'.join(files))
-            spec.write('\n\n')
-
-            spec.write('%changelog\n')
-            try:
-                login = os.getlogin()
-            except FileNotFoundError:
-                login = 'game-data-packager'
-            spec.write('* %s %s@%s - %s-%s\n' %
-                        (time.strftime("%a %b %d %Y", time.gmtime()),
-                         login, os.uname()[1], package.version, release))
-            spec.write('- Package generated by game-data-packager'
-                       ' for local use only\n')
-
-        return specfile
-
-    def fill_dest_dir_deb(self, package, destdir):
-        if package.component == 'local':
-             self.packaging.override_lintian(destdir, package.name,
-                     'unknown-section', 'local/%s' % package.section)
-
-        # same output as in dh_md5sums
-
-        # we only compute here the md5 we don't have yet,
-        # for the (small) GDP-generated files
-        for dirpath, dirnames, filenames in os.walk(destdir):
-            if os.path.basename(dirpath) == 'DEBIAN':
-                continue
-            for fn in filenames:
-                full = os.path.join(dirpath, fn)
-                if os.path.islink(full):
-                    continue
-                file = full[len(destdir)+1:]
-                if file not in package.md5sums:
-                    with open(full, 'rb') as opened:
-                        hf = HashedFile.from_file(full, opened)
-                        package.md5sums[file] = hf.md5
-
-        debdir = os.path.join(destdir, 'DEBIAN')
-        mkdir_p(debdir)
-        md5sums = os.path.join(destdir, 'DEBIAN/md5sums')
-        with open(md5sums, 'w', encoding='utf8') as outfile:
-            for file in sorted(package.md5sums.keys()):
-                outfile.write('%s  %s\n' % (package.md5sums[file], file))
-        os.chmod(md5sums, 0o644)
-
-        control = os.path.join(destdir, 'DEBIAN/control')
-        self.generate_control(package, destdir).dump(fd=open(control, 'wb'),
-                                                encoding='utf-8')
-        os.chmod(control, 0o644)
-
     def fill_dest_dir(self, package, destdir):
         pkgdocdir = self.packaging.substitute('$pkgdocdir', package.name)
         dest_pkgdocdir = os.path.join(destdir, pkgdocdir.strip('/'))
@@ -1588,212 +1385,6 @@ class PackagingTask(object):
 
         self.fill_extra_files(package, destdir)
 
-    def generate_control(self, package, destdir):
-        # import lazily, it's only needed for Debian packages
-        from debian.deb822 import Deb822
-
-        try:
-            control_in = open(os.path.join(DATADIR,
-                              package.name + '.control.in'), encoding='utf-8')
-            control = Deb822(control_in)
-            for key in control.keys():
-                assert key == 'Description', 'specify "%s" only in YAML' % key
-        except FileNotFoundError:
-            control = Deb822()
-
-        control['Package'] = package.name
-        control['Version'] = package.version
-        control['Priority'] = 'optional'
-        control['Maintainer'] = 'Debian Games Team <pkg-games-devel at lists.alioth.debian.org>'
-
-        installed_size = 0
-        # algorithm from https://bugs.debian.org/650077 designed to be
-        # filesystem-independent
-        for dirpath, dirnames, filenames in os.walk(destdir):
-            if dirpath == destdir and 'DEBIAN' in dirnames:
-                dirnames.remove('DEBIAN')
-            # estimate 1 KiB per directory
-            installed_size += len(dirnames)
-            for f in filenames:
-                stat_res = os.lstat(os.path.join(dirpath, f))
-                if (stat.S_ISLNK(stat_res.st_mode) or
-                        stat.S_ISREG(stat_res.st_mode)):
-                    # take the real size and round up to next 1 KiB
-                    installed_size += ((stat_res.st_size + 1023) // 1024)
-                else:
-                    # this will probably never happen in gdp, but assume
-                    # 1 KiB per non-regular, non-directory, non-symlink file
-                    installed_size += 1
-        control['Installed-Size'] = str(installed_size)
-
-        if package.component == 'main':
-            control['Section'] = package.section
-        else:
-            control['Section'] = package.component + '/' + package.section
-
-        if package.architecture == 'all':
-            control['Architecture'] = 'all'
-            control['Multi-Arch'] = 'foreign'
-        else:
-            control['Architecture'] = self.packaging.get_architecture(package.architecture)
-
-        dep = dict()
-
-        for rel in package.relations:
-            if rel == 'build_depends':
-                continue
-
-            dep[rel] = self.__merge_relations(package, rel)
-            logger.debug('%s %s %s', package.name, rel, ', '.join(dep[rel]))
-
-        if package.mutually_exclusive:
-            dep['conflicts'] |= package.demo_for
-            dep['conflicts'] |= package.better_versions
-
-        if package.mutually_exclusive:
-            dep['replaces'] |= dep['provides']
-
-        engine = self.packaging.substitute(
-                package.engine or self.game.engine,
-                package.name)
-
-        if engine and '>=' in engine:
-            engine, ver = engine.split(maxsplit=1)
-            ver = ver.strip('(>=) ')
-            dep['breaks'].add('%s (<< %s~)' % (engine, ver))
-
-        # We only 'recommends' & not 'depends'; to avoid
-        # that GDP-generated packages get removed
-        # if engine goes through some gcc/png/ffmpeg/... migration
-        # and must be temporarily removed.
-        # It's not like 'apt-get install ...' can revert this removal;
-        # user may need to dig again for the original media....
-        if package.engine:
-            dep['recommends'].add(engine)
-        elif not package.expansion_for and self.game.engine:
-            dep['recommends'].add(engine)
-
-        if package.expansion_for:
-            # check if default heuristic has been overriden in yaml
-            for p in dep['depends']:
-                if package.expansion_for == p.split()[0]:
-                    break
-            else:
-                dep['depends'].add(package.expansion_for)
-
-        # dependencies derived from *other* package's data
-        for other_package in self.game.packages.values():
-            if other_package.expansion_for:
-                if package.name == other_package.expansion_for:
-                    dep['suggests'].add(other_package.name)
-                else:
-                    for p in package.relations['provides']:
-                        if p.package == other_package.expansion_for:
-                            dep['suggests'].add(other_package.name)
-
-            if other_package.mutually_exclusive:
-                if package.name in other_package.better_versions:
-                    dep['replaces'].add(other_package.name)
-
-                if package.name in other_package.demo_for:
-                    dep['replaces'].add(other_package.name)
-
-        # Shortcut: if A Replaces B, A automatically Conflicts B
-        dep['conflicts'] |= dep['replaces']
-
-        # keep only strongest depedency
-        dep['recommends'] -= dep['depends']
-        dep['suggests'] -= dep['recommends']
-        dep['suggests'] -= dep['depends']
-
-        for k, v in dep.items():
-            if v:
-                control[k.title()] = ', '.join(sorted(v))
-
-        if 'Description' not in control:
-            short_desc, long_desc = self.generate_description(package)
-            control['Description'] = short_desc + '\n ' + long_desc.replace('\n', '\n ')
-
-        return control
-
-    def generate_description(self, package):
-        longname = package.longname or self.game.longname
-
-        if package.short_description is not None:
-            short_desc = package.short_description
-        elif package.section == 'games':
-            short_desc = 'game %s for %s' % (package.data_type, longname)
-        else:
-            short_desc = longname
-
-        if package.long_description is not None:
-            long_desc = package.long_description
-            long_desc = long_desc.rstrip('\n')
-            return (short_desc, long_desc)
-
-        long_desc =  'This package was built using game-data-packager.\n'
-        if package.component == 'local':
-            long_desc += 'It contains proprietary game data and must not be redistributed.\n'
-            long_desc += '.\n'
-        elif package.component == 'non-free':
-            long_desc += 'It contains proprietary game data that may be redistributed\n'
-            long_desc += 'only under some conditions.\n'
-            long_desc += '.\n'
-        else:
-            long_desc += 'It contains free game data and may be redistributed.\n'
-            long_desc += '.\n'
-
-        if package.description:
-            for line in package.description.splitlines():
-                line = line.rstrip() or '.'
-                long_desc += (line + '\n')
-            long_desc += '.\n'
-
-        if self.game.genre:
-            long_desc += ' Genre: ' + self.game.genre + '\n'
-
-        if package.section == 'doc':
-            long_desc += ' Documentation: ' + longname + '\n'
-        elif package.expansion_for and package.expansion_for in self.game.packages:
-            game_name = (self.game.packages[package.expansion_for].longname
-                         or self.game.longname)
-            if game_name not in long_desc:
-                long_desc += ' Game: ' + game_name + '\n'
-            if longname != game_name:
-                long_desc += ' Expansion: ' + longname + '\n'
-        else:
-            long_desc += ' Game: ' + longname + '\n'
-
-        copyright = package.copyright or self.game.copyright
-        copyright = copyright.split(' ', 2)[2]
-        if copyright not in long_desc:
-            long_desc += ' Published by: ' + copyright
-
-        engine = self.packaging.substitute(
-                package.engine or self.game.engine,
-                package.name)
-
-        if engine and package.data_type not in ('music', 'documentation'):
-            long_desc += '\n.\n'
-            if '|' in engine:
-                virtual = engine.split('|')[-1].strip()
-                has_virtual = (virtual.split('-')[-1] == 'engine')
-            else:
-                has_virtual = False
-            engine = engine.split('|')[0].split('(')[0].strip()
-            if engine.startswith('gemrb'):
-                engine = 'gemrb'
-            if has_virtual:
-                long_desc += 'Intended for use with some ' + virtual + ',\n'
-                long_desc += 'such as for example: ' + engine
-            else:
-                long_desc += 'Intended for use with: ' + engine
-
-        if package.used_sources:
-            long_desc += '\nBuilt from: ' + ', '.join(package.used_sources)
-
-        return (short_desc, long_desc)
-
     def look_for_engines(self, packages, force=False):
         engines = set()
 
@@ -2228,7 +1819,7 @@ class PackagingTask(object):
                     possible.discard(package)
 
         for package in set(possible):
-            build_depends = self.__merge_relations(package, 'build_depends')
+            build_depends = self.packaging.merge_relations(package, 'build_depends')
             for tool in build_depends:
                 tool = tool.strip()
 
@@ -2265,13 +1856,13 @@ class PackagingTask(object):
                     continue
 
                 # keep only preferred language for this virtual package
-                provides = self.__merge_relations(package, 'provides')
+                provides = self.packaging.merge_relations(package, 'provides')
 
                 if provides:
                     for other_p in possible:
                         if other_p.name == package.name:
                             continue
-                        other_provides = self.__merge_relations(other_p,
+                        other_provides = self.packaging.merge_relations(other_p,
                                 'provides')
                         if other_provides - provides:
                             # it provides something this one doesn't
@@ -2570,7 +2161,7 @@ class PackagingTask(object):
 
         self.check_component(package)
         self.fill_dest_dir(package, destdir)
-        self.fill_dest_dir_deb(package, destdir)
+        self.packaging.fill_dest_dir_deb(game, package, destdir)
         normalize_permissions(destdir)
 
         # it had better have a /usr and a DEBIAN directory or
@@ -2611,7 +2202,8 @@ class PackagingTask(object):
     def build_arch(self, package, arch, destination, compress=True):
         destdir = os.path.join(self.get_workdir(), '%s.pkg.d' % package.name)
         self.fill_dest_dir(package, destdir)
-        self.fill_dest_dir_arch(package, destdir, compress, arch)
+        self.packaging.fill_dest_dir_arch(game, package, destdir, compress,
+                arch)
         normalize_permissions(destdir)
 
         assert os.path.isdir(os.path.join(destdir, 'usr')), destdir
@@ -2667,8 +2259,11 @@ class PackagingTask(object):
         if self.packaging.distro is not None:
             release = release + '.' + self.packaging.distro
 
-        specfile = self.fill_dest_dir_rpm(package, destdir, compress,
-                                          arch, release)
+        if compress:
+            compress = self.compress_deb
+
+        specfile = self.packaging.fill_dest_dir_rpm(game, package,
+                self.get_workdir(), destdir, compress, arch, release)
         normalize_permissions(destdir)
 
         assert os.path.isdir(os.path.join(destdir, 'usr')), destdir
diff --git a/game_data_packager/packaging/__init__.py b/game_data_packager/packaging/__init__.py
index 09e72a1..0e04501 100644
--- a/game_data_packager/packaging/__init__.py
+++ b/game_data_packager/packaging/__init__.py
@@ -215,6 +215,87 @@ class PackagingSystem(metaclass=ABCMeta):
                 return k
         return package
 
+    def merge_relations(self, package, rel):
+        return set(self.format_relations(package.relations[rel]))
+
+    def generate_description(self, game, package):
+        longname = package.longname or game.longname
+
+        if package.short_description is not None:
+            short_desc = package.short_description
+        elif package.section == 'games':
+            short_desc = 'game %s for %s' % (package.data_type, longname)
+        else:
+            short_desc = longname
+
+        if package.long_description is not None:
+            long_desc = package.long_description
+            long_desc = long_desc.rstrip('\n')
+            return (short_desc, long_desc)
+
+        long_desc =  'This package was built using game-data-packager.\n'
+        if package.component == 'local':
+            long_desc += 'It contains proprietary game data and must not be redistributed.\n'
+            long_desc += '.\n'
+        elif package.component == 'non-free':
+            long_desc += 'It contains proprietary game data that may be redistributed\n'
+            long_desc += 'only under some conditions.\n'
+            long_desc += '.\n'
+        else:
+            long_desc += 'It contains free game data and may be redistributed.\n'
+            long_desc += '.\n'
+
+        if package.description:
+            for line in package.description.splitlines():
+                line = line.rstrip() or '.'
+                long_desc += (line + '\n')
+            long_desc += '.\n'
+
+        if game.genre:
+            long_desc += ' Genre: ' + game.genre + '\n'
+
+        if package.section == 'doc':
+            long_desc += ' Documentation: ' + longname + '\n'
+        elif package.expansion_for and package.expansion_for in game.packages:
+            game_name = (game.packages[package.expansion_for].longname
+                         or game.longname)
+            if game_name not in long_desc:
+                long_desc += ' Game: ' + game_name + '\n'
+            if longname != game_name:
+                long_desc += ' Expansion: ' + longname + '\n'
+        else:
+            long_desc += ' Game: ' + longname + '\n'
+
+        copyright = package.copyright or game.copyright
+        copyright = copyright.split(' ', 2)[2]
+        if copyright not in long_desc:
+            long_desc += ' Published by: ' + copyright
+
+        engine = self.substitute(
+                package.engine or game.engine,
+                package.name)
+
+        if engine and package.data_type not in ('music', 'documentation'):
+            long_desc += '\n.\n'
+            if '|' in engine:
+                virtual = engine.split('|')[-1].strip()
+                has_virtual = (virtual.split('-')[-1] == 'engine')
+            else:
+                has_virtual = False
+            engine = engine.split('|')[0].split('(')[0].strip()
+            if engine.startswith('gemrb'):
+                engine = 'gemrb'
+            if has_virtual:
+                long_desc += 'Intended for use with some ' + virtual + ',\n'
+                long_desc += 'such as for example: ' + engine
+            else:
+                long_desc += 'Intended for use with: ' + engine
+
+        if package.used_sources:
+            long_desc += '\nBuilt from: ' + ', '.join(package.used_sources)
+
+        return (short_desc, long_desc)
+
 def get_packaging_system(format, distro=None):
     mod = 'game_data_packager.packaging.{}'.format(format)
     return importlib.import_module(mod).get_packaging_system(distro)
diff --git a/game_data_packager/packaging/arch.py b/game_data_packager/packaging/arch.py
index 351aa06..b1e9627 100644
--- a/game_data_packager/packaging/arch.py
+++ b/game_data_packager/packaging/arch.py
@@ -109,5 +109,44 @@ class ArchPackaging(PackagingSystem):
 
         return self.rename_package(pr.package)
 
+    def fill_dest_dir_arch(self, game, package, destdir, compress, arch):
+        PKGINFO = os.path.join(destdir, '.PKGINFO')
+        short_desc, _ = self.generate_description(game, package)
+        size = check_output(['du','-bs','.'], cwd=destdir)
+        size = int(size.split()[0])
+        with open(PKGINFO, 'w',  encoding='utf-8') as pkginfo:
+            pkginfo.write('pkgname = %s\n' % package.name)
+            pkginfo.write('pkgver = %s-1\n' % package.version)
+            pkginfo.write('pkgdesc = %s\n' % short_desc)
+            pkginfo.write('url = https://wiki.debian.org/Games/GameDataPackager\n')
+            pkginfo.write('builddate = %i\n' % int(time.time()))
+            pkginfo.write('packager = Alexandre Detiste <alexandre at detiste.be>\n')
+            pkginfo.write('size = %i\n' % size)
+            pkginfo.write('arch = %s\n' % arch)
+            if os.path.isdir(os.path.join(destdir, 'usr/share/licenses')):
+                pkginfo.write('license = custom\n')
+            pkginfo.write('group = games\n')
+            if package.expansion_for:
+                pkginfo.write('depend = %s\n' % package.expansion_for)
+            else:
+                engine = self.substitute(
+                        package.engine or game.engine,
+                        package.name)
+
+                if engine and len(engine.split()) == 1:
+                    pkginfo.write('depend = %s\n' % engine)
+
+        files = set()
+        for dirpath, dirnames, filenames in os.walk(destdir):
+                for fn in filenames:
+                    full = os.path.join(dirpath, fn)
+                    full = full[len(destdir)+1:]
+                    files.add(full)
+
+        MTREE = os.path.join(destdir, '.MTREE')
+        subprocess.check_call(['fakeroot', 'bsdtar', '-czf', MTREE, '--format=mtree',
+                 '--options=!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link']
+                 + sorted(files), env={'LANG':'C'}, cwd=destdir)
+
 def get_packaging_system(distro=None):
     return ArchPackaging()
diff --git a/game_data_packager/packaging/deb.py b/game_data_packager/packaging/deb.py
index 3780895..a2b28b2 100644
--- a/game_data_packager/packaging/deb.py
+++ b/game_data_packager/packaging/deb.py
@@ -207,5 +207,170 @@ class DebPackaging(PackagingSystem):
 
         return self.rename_package(pr.package)
 
+    def __generate_control(self, game, package, destdir):
+        if Deb822 is None:
+            raise FileNotFoundError('Cannot generate .deb packages without '
+                    'python3-debian')
+
+        try:
+            control_in = open(os.path.join(DATADIR,
+                              package.name + '.control.in'), encoding='utf-8')
+            control = Deb822(control_in)
+            for key in control.keys():
+                assert key == 'Description', 'specify "%s" only in YAML' % key
+        except FileNotFoundError:
+            control = Deb822()
+
+        control['Package'] = package.name
+        control['Version'] = package.version
+        control['Priority'] = 'optional'
+        control['Maintainer'] = 'Debian Games Team <pkg-games-devel at lists.alioth.debian.org>'
+
+        installed_size = 0
+        # algorithm from https://bugs.debian.org/650077 designed to be
+        # filesystem-independent
+        for dirpath, dirnames, filenames in os.walk(destdir):
+            if dirpath == destdir and 'DEBIAN' in dirnames:
+                dirnames.remove('DEBIAN')
+            # estimate 1 KiB per directory
+            installed_size += len(dirnames)
+            for f in filenames:
+                stat_res = os.lstat(os.path.join(dirpath, f))
+                if (stat.S_ISLNK(stat_res.st_mode) or
+                        stat.S_ISREG(stat_res.st_mode)):
+                    # take the real size and round up to next 1 KiB
+                    installed_size += ((stat_res.st_size + 1023) // 1024)
+                else:
+                    # this will probably never happen in gdp, but assume
+                    # 1 KiB per non-regular, non-directory, non-symlink file
+                    installed_size += 1
+        control['Installed-Size'] = str(installed_size)
+
+        if package.component == 'main':
+            control['Section'] = package.section
+        else:
+            control['Section'] = package.component + '/' + package.section
+
+        if package.architecture == 'all':
+            control['Architecture'] = 'all'
+            control['Multi-Arch'] = 'foreign'
+        else:
+            control['Architecture'] = self.get_architecture(
+                    package.architecture)
+
+        dep = dict()
+
+        for rel in package.relations:
+            if rel == 'build_depends':
+                continue
+
+            dep[rel] = self.merge_relations(package, rel)
+            logger.debug('%s %s %s', package.name, rel, ', '.join(dep[rel]))
+
+        if package.mutually_exclusive:
+            dep['conflicts'] |= package.demo_for
+            dep['conflicts'] |= package.better_versions
+
+        if package.mutually_exclusive:
+            dep['replaces'] |= dep['provides']
+
+        engine = self.substitute(
+                package.engine or game.engine,
+                package.name)
+
+        if engine and '>=' in engine:
+            engine, ver = engine.split(maxsplit=1)
+            ver = ver.strip('(>=) ')
+            dep['breaks'].add('%s (<< %s~)' % (engine, ver))
+
+        # We only 'recommends' & not 'depends'; to avoid
+        # that GDP-generated packages get removed
+        # if engine goes through some gcc/png/ffmpeg/... migration
+        # and must be temporarily removed.
+        # It's not like 'apt-get install ...' can revert this removal;
+        # user may need to dig again for the original media....
+        if package.engine:
+            dep['recommends'].add(engine)
+        elif not package.expansion_for and game.engine:
+            dep['recommends'].add(engine)
+
+        if package.expansion_for:
+            # check if default heuristic has been overriden in yaml
+            for p in dep['depends']:
+                if package.expansion_for == p.split()[0]:
+                    break
+            else:
+                dep['depends'].add(package.expansion_for)
+
+        # dependencies derived from *other* package's data
+        for other_package in game.packages.values():
+            if other_package.expansion_for:
+                if package.name == other_package.expansion_for:
+                    dep['suggests'].add(other_package.name)
+                else:
+                    for p in package.relations['provides']:
+                        if p.package == other_package.expansion_for:
+                            dep['suggests'].add(other_package.name)
+
+            if other_package.mutually_exclusive:
+                if package.name in other_package.better_versions:
+                    dep['replaces'].add(other_package.name)
+
+                if package.name in other_package.demo_for:
+                    dep['replaces'].add(other_package.name)
+
+        # Shortcut: if A Replaces B, A automatically Conflicts B
+        dep['conflicts'] |= dep['replaces']
+
+        # keep only strongest depedency
+        dep['recommends'] -= dep['depends']
+        dep['suggests'] -= dep['recommends']
+        dep['suggests'] -= dep['depends']
+
+        for k, v in dep.items():
+            if v:
+                control[k.title()] = ', '.join(sorted(v))
+
+        if 'Description' not in control:
+            short_desc, long_desc = self.generate_description(game, package)
+            control['Description'] = short_desc + '\n ' + long_desc.replace('\n', '\n ')
+
+        return control
+
+    def fill_dest_dir_deb(self, game, package, destdir):
+        if package.component == 'local':
+             self.override_lintian(destdir, package.name,
+                     'unknown-section', 'local/%s' % package.section)
+
+        # same output as in dh_md5sums
+
+        # we only compute here the md5 we don't have yet,
+        # for the (small) GDP-generated files
+        for dirpath, dirnames, filenames in os.walk(destdir):
+            if os.path.basename(dirpath) == 'DEBIAN':
+                continue
+            for fn in filenames:
+                full = os.path.join(dirpath, fn)
+                if os.path.islink(full):
+                    continue
+                file = full[len(destdir)+1:]
+                if file not in package.md5sums:
+                    with open(full, 'rb') as opened:
+                        hf = HashedFile.from_file(full, opened)
+                        package.md5sums[file] = hf.md5
+
+        debdir = os.path.join(destdir, 'DEBIAN')
+        mkdir_p(debdir)
+        md5sums = os.path.join(destdir, 'DEBIAN/md5sums')
+        with open(md5sums, 'w', encoding='utf8') as outfile:
+            for file in sorted(package.md5sums.keys()):
+                outfile.write('%s  %s\n' % (package.md5sums[file], file))
+        os.chmod(md5sums, 0o644)
+
+        control = os.path.join(destdir, 'DEBIAN/control')
+        self.__generate_control(game, package, destdir).dump(
+                fd=open(control, 'wb'), encoding='utf-8')
+        os.chmod(control, 0o644)
+
 def get_packaging_system(distro=None):
     return DebPackaging()
diff --git a/game_data_packager/packaging/rpm.py b/game_data_packager/packaging/rpm.py
index ddb9b6a..128d972 100644
--- a/game_data_packager/packaging/rpm.py
+++ b/game_data_packager/packaging/rpm.py
@@ -101,6 +101,129 @@ class RpmPackaging(PackagingSystem):
 
         return self.rename_package(pr.package)
 
+    def fill_dest_dir_rpm(self, game, package, workdir, destdir, compress,
+            architecture, release):
+        specfile = os.path.join(workdir, '%s.spec' % package.name)
+        short_desc, long_desc = self.generate_description(game, package)
+        short_desc = short_desc[0].upper() + short_desc[1:]
+
+        if game.wikibase:
+            url = game.wikibase + (game.wiki or '')
+        elif game.wikipedia:
+            url = game.wikipedia
+        else:
+            url = 'https://wiki.debian.org/Games/GameDataPackager'
+
+        # /usr/games & /usr/share/games should only
+        # be seen in rpm's built for Mageia
+        SYSTEM_DIRS = set(['/usr',
+                           '/usr/bin',
+                           '/usr/games',
+                           '/usr/lib',
+                           '/usr/share',
+                           '/usr/share/applications',
+                           '/usr/share/doc',
+                           '/usr/share/doc/packages',
+                           '/usr/share/games',
+                           '/usr/share/icons',
+                           '/usr/share/icons/hicolor',
+                           '/usr/share/icons/hicolor/scalable',
+                           '/usr/share/icons/hicolor/scalable/apps',
+                           '/usr/share/licenses',
+                           '/usr/share/pixmaps'])
+
+        files = set()
+        for dirpath, dirnames, filenames in os.walk(destdir):
+             dir = dirpath[len(destdir):]
+             if not dir:
+                 # /
+                 continue
+             elif dir in SYSTEM_DIRS:
+                 for fn in filenames + dirnames:
+                     full = os.path.join(dirpath, fn)
+                     file = full[len(destdir):]
+                     if file not in SYSTEM_DIRS:
+                         files.add(file)
+             else:
+                 for file in files:
+                     if dir.startswith(file):
+                         break
+                 else:
+                     files.add(dir)
+
+        logger.debug('%%files in specfile:\n%s', '\n'.join(sorted(files)))
+
+        with open(specfile, 'w', encoding='utf-8') as spec:
+            spec.write('Summary: %s\n' % short_desc)
+            spec.write('Name: %s\n' % package.name)
+            spec.write('Version: %s\n' % package.version)
+            spec.write('Url: %s\n' % url)
+            spec.write('Release: %s\n' % release)
+            spec.write('License: Commercial\n')
+            if self.derives_from('mageia'):
+                spec.write('Packager: game-data-packager\n')
+                spec.write('Group: Games/%s\n' % game.genre)
+            else:
+                spec.write('Group: Amusements/Games\n')
+            spec.write('BuildArch: %s\n' % architecture)
+
+            for p in self.merge_relations(package, 'provides'):
+                spec.write('Provides: %s\n' % p)
+
+                if package.mutually_exclusive:
+                    spec.write('Conflicts: %s\n' % p)
+
+            if package.expansion_for:
+                spec.write('Requires: %s\n' % package.expansion_for)
+            else:
+                engine = self.substitute(
+                        package.engine or game.engine,
+                        package.name)
+
+                if engine and len(engine.split()) == 1:
+                    spec.write('Requires: %s\n' % engine)
+
+            for p in self.merge_relations(package, 'depends'):
+                spec.write('Requires: %s\n' % p)
+
+            for p in (self.merge_relations(package, 'conflicts') |
+                    self.merge_relations(package, 'breaks')):
+                spec.write('Conflicts: %s\n' % p)
+
+            for p in self.merge_relations(package, 'recommends'):
+                # FIXME: some RPM distributions do have recommends;
+                # which ones?
+                pass
+
+            for p in self.merge_relations(package, 'suggests'):
+                # FIXME: likewise
+                pass
+
+            # FIXME: replaces?
+
+            if not compress or package.rip_cd:
+                spec.write('%define _binary_payload w0.gzdio\n')
+            elif compress == ['-Zgzip', '-z1']:
+                spec.write('%define _binary_payload w1.gzdio\n')
+            spec.write('%description\n')
+            spec.write('%s\n' % long_desc)
+            spec.write('%files\n')
+            spec.write('\n'.join(files))
+            spec.write('\n\n')
+
+            spec.write('%changelog\n')
+            try:
+                login = os.getlogin()
+            except FileNotFoundError:
+                login = 'game-data-packager'
+            spec.write('* %s %s@%s - %s-%s\n' %
+                        (time.strftime("%a %b %d %Y", time.gmtime()),
+                         login, os.uname()[1], package.version, release))
+            spec.write('- Package generated by game-data-packager'
+                       ' for local use only\n')
+
+        return specfile
+
 # XXX: dnf is written in python3 and has a stable public api,
 #      it is likely faster to use it instead of calling 'dnf' pgm.
 #

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/game-data-packager.git



More information about the Pkg-games-commits mailing list