File: __init__.py

package info (click to toggle)
game-data-packager 67
  • links: PTS, VCS
  • area: contrib
  • in suites: bullseye
  • size: 16,188 kB
  • sloc: python: 10,576; sh: 515; makefile: 491
file content (371 lines) | stat: -rw-r--r-- 12,710 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#!/usr/bin/python3
# encoding=utf-8
#
# Copyright © 2014-2016 Simon McVittie <smcv@debian.org>
#           © 2015-2016 Alexandre Detiste <alexandre@detiste.be>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# You can find the GPL license text on a Debian system under
# /usr/share/common-licenses/GPL-2.

from abc import (ABCMeta, abstractmethod)
import importlib
import os
import string

class RecursiveExpansionMap(dict):
    def __getitem__(self, k):
        v = super(RecursiveExpansionMap, self).__getitem__(k)
        return string.Template(v).substitute(self)

class PackagingSystem(metaclass=ABCMeta):
    ASSETS = '$datadir'
    BINDIR = '$prefix/bin'
    DATADIR = '$prefix/share'
    DOCDIR = '$datadir/doc'
    LICENSEDIR = '$datadir/doc'
    PREFIX = '/usr'
    CHECK_CMD = None
    INSTALL_CMD = None

    # Generic tools needed to build packages
    BUILD_DEP = {}

    # Exceptions to our normal heuristic for mapping a tool to a package:
    # the executable tool 'unzip' is in the unzip package, etc.
    #
    # Only exceptions need to be listed.
    #
    # 'NotImplemented' means that this dependency is not packaged by
    # the distro.
    PACKAGE_MAP = {}

    # Exceptions to our normal heuristic for mapping an abstract package name
    # to a package:
    #
    # - the library 'libfoo.so.0' is in a package that Provides libfoo.so.0
    #   (suitable for RPM)
    # - anything else is in the obvious package name
    RENAME_PACKAGES = {}

    # we keep Debian codification as reference, as it
    # - has the most architectures supported
    # - differentiates 'any' from 'all'
    # - is the most tested
    ARCH_DECODE = dict()

    def __init__(self):
        self._architecture = None
        self._foreign_architectures = set()
        # contexts to use when evaluating format- or distro-specific
        # dependencies, in order by preference
        self._contexts = ('generic',)

    def derives_from(self, context):
        return context in self._contexts

    def read_architecture(self):
        arch = os.uname()[4]
        self._architecture = { 'armv7l': 'armhf',
                               'armhfp': 'armhf',
                               'i586': 'i386',
                               'i686': 'i386',
                               'x86_64': 'amd64',
                             }.get(arch, arch)

    def get_architecture(self, archs=''):
        if self._architecture is None:
            self.read_architecture()

        if archs:
            # In theory this should deal with wildcards like linux-any,
            # but it's unlikely to be relevant in practice.
            archs = archs.split()

            if self._architecture in archs or 'any' in archs:
                return self._architecture

            for arch in archs:
                if arch in self._foreign_architectures:
                    return arch

        return self._architecture

    def is_installed(self, package):
        """Return boolean: is a package with the given name installed?"""
        return (self.current_version(package) is not None)

    def is_available(self, package):
        """Return boolean: is a package with the given name available
        to apt or equivalent?
        """
        try:
            self.available_version(package)
        except:
            return False
        else:
            return True

    @abstractmethod
    def current_version(self, package):
        """Return the version number of the given package as a string,
        or None.
        """
        raise NotImplementedError

    @abstractmethod
    def available_version(self, package):
        """Return the version number of the given package available in
        apt or equivalent, or raise an exception if unavailable.
        """
        raise NotImplementedError

    @abstractmethod
    def install_packages(self, packages, method=None, gain_root='su'):
        """Install one or more packages (a list of filenames)."""
        raise NotImplementedError

    def substitute(self, template, package, **kwargs):
        if isinstance(template, dict):
            for c in self._contexts:
                if c in template:
                    template = template[c]
                    break
            else:
                return None

        if template is None:
            return template

        if '$' not in template:
            return template

        return string.Template(template).substitute(
                RecursiveExpansionMap(
                    assets=self.ASSETS,
                    bindir=self.BINDIR,
                    datadir=self.DATADIR,
                    docdir=self.DOCDIR,
                    licensedir=self.LICENSEDIR,
                    pkgdocdir=self._get_pkgdocdir(package),
                    pkglicensedir=self._get_pkglicensedir(package),
                    prefix=self.PREFIX,
                    **kwargs))

        return template

    def _get_pkgdocdir(self, package):
        return '/'.join((self.DOCDIR, package))

    def _get_pkglicensedir(self, package):
        return '/'.join((self.LICENSEDIR, package))

    def override_lintian(self, destdir, package, tag, args=None):
        pass

    def format_relations(self, relations):
        """Yield a native dependency representation for this packaging system
        for each gdp.data.PackagingRelation in relations.
        """
        for pr in relations:
            if pr.contextual:
                for c in self._contexts:
                    if c in pr.contextual:
                        for x in self.format_relations([pr.contextual[c]]):
                            yield x

                        break
            else:
                yield self.format_relation(pr)

    @abstractmethod
    def format_relation(self, pr):
        """Return a native dependency representation for this packaging system
        and the given gdp.data.PackagingRelation. It is guaranteed
        that pr.contextual is empty.
        """
        raise NotImplementedError

    def rename_package(self, dependency):
        """Given an abstract package name, return the corresponding
        package name in this packaging system.

        Abstract package names are mostly the same as for Debian,
        except that libraries are represented as libfoo.so.0.
        """
        return self.RENAME_PACKAGES.get(dependency, dependency)

    def package_for_tool(self, tool):
        """Given an executable name, return the corresponding
        package name in this packaging system.
        """
        return self.PACKAGE_MAP.get(tool, tool)

    def tool_for_package(self, package):
        """Given a package name, return the corresponding
        main/unique executable in this packaging system.
        """
        for k,v in self.PACKAGE_MAP.items():
            if v == package:
                return k
        return package

    def merge_relations(self, package, rel):
        return set(self.format_relations(package.relations[rel]))

    def generate_description(self, game, package, component=None):
        longname = package.longname or game.longname
        if component is None:
            component = package.component

        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:
            return (short_desc, package.long_description.splitlines())

        long_desc = []
        long_desc.append('This package was built using game-data-packager.')

        if component == 'local':
            long_desc.append(
                'It contains proprietary game data and must not be redistributed.')
        elif component == 'non-free':
            long_desc.append(
                'It contains proprietary game data that may be redistributed')
            long_desc.append('only under some conditions.')
        else:
            long_desc.append('It contains free game data and may be redistributed.')

        long_desc.append('')

        if package.description:
            for line in package.description.splitlines():
                long_desc.append(line.rstrip())

        long_desc.append('')

        if game.genre:
            long_desc.append(' Genre: ' + game.genre)

        if package.section == 'doc':
            long_desc.append(' Documentation: ' + longname)
        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.append(' Game: ' + game_name)
            if longname != game_name:
                long_desc.append(' Expansion: ' + longname)
        else:
            long_desc.append(' Game: ' + longname)

        copyright = package.copyright or game.copyright
        copyright = copyright.split(' ', 2)[2]
        if copyright not in long_desc:
            long_desc.append(' Published by: ' + copyright)

        engine = self.substitute(
                package.engine or game.engine,
                package.name)

        if engine and package.data_type not in ('music', 'documentation'):
            if long_desc[-1] != '':
                long_desc.append('')

            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.append('Intended for use with some ' + virtual + ',')
                long_desc.append('such as for example: ' + engine)
            else:
                long_desc.append('Intended for use with: ' + engine)

        if package.used_sources:
            if long_desc[-1] != '':
                long_desc.append('')

            long_desc.append('Built from: ' + ', '.join(package.used_sources))

        return (short_desc, long_desc)

    def get_effective_architecture(self, package):
        arch = package.architecture
        if arch != 'all':
            arch = self.get_architecture(arch)
        return self.ARCH_DECODE.get(arch, arch)

    @abstractmethod
    def build_package(self, per_package_dir, game, package,
            destination, compress=True, md5sums=None, component=None):
        """Build the .deb or equivalent in destination, and return its
        filename.

        per_package_dir may be used as scratch space. It already contains
        a subdirectory named DESTDIR which has been populated with the files
        to be packaged (so it contains DESTDIR/usr, etc.)

        game and package are a GameData and a Package respectively.

        md5sums is either None, or a map like
        { 'usr/share/games/quake3-data/baseq3/pak0.pk3': '1197ca...' }
        """
        raise NotImplementedError

class NoPackaging(PackagingSystem):
    """
    A stub PackagingSystem used while checking consistency.
    """

    def __init__(self):
        super(NoPackaging, self).__init__()
        self._contexts = ('generic',)

    def current_version(self, package):
        return None

    def available_version(self, package):
        return None

    def install_packages(self, packages, method=None, gain_root='su'):
        pass

    def build_package(self, per_package_dir, game, package,
            destination, compress=True, md5sums=None, component=None):
        pass

    def format_relation(self, pr):
        assert not pr.contextual
        assert not pr.alternatives

        if pr.version is not None:
            return '%s (%s %s)' % (pr.package, pr.version_operator, pr.version)

        return pr.package

def get_packaging_system(format, distro=None):
    mod = 'game_data_packager.packaging.{}'.format(format)
    return importlib.import_module(mod).get_packaging_system(distro)

def get_native_packaging_system():
    # lazy import when actually needed
    from ..version import (FORMAT, DISTRO)
    return get_packaging_system(FORMAT, DISTRO)