File: __main__.py

package info (click to toggle)
python-glad 2.0.2-0.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 6,360 kB
  • sloc: xml: 76,627; ansic: 5,843; python: 2,413; sh: 423; cpp: 248; makefile: 4
file content (194 lines) | stat: -rw-r--r-- 6,609 bytes parent folder | download
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
#!/usr/bin/env python

"""
Uses the official Khronos-XML specs to generate a
Vulkan/GL/GLES/EGL/GLX/WGL Loader made for your needs.


Subcommands have additional help information, query with: `{subcommand} --help`
"""
from itertools import groupby

import logging
import os

import glad.files
from glad.config import Config, ConfigOption
from glad.generator import GenerationInfo
from glad.sink import LoggingSink
from glad.opener import URLOpener
from glad.parse import FeatureSet
from glad.plugin import find_specifications, find_generators
from glad.util import parse_apis


logger = logging.getLogger('glad')


def parse_extensions(value):
    if os.path.isfile(value):
        # it's an extensions file
        with open(value) as f:
            value = f.read()

    value = value.replace(',', ' ')
    return list(filter(None, value.split()))


class GlobalConfig(Config):
    OUT_PATH = ConfigOption(
        required=True,
        description='Output directory for the generated files'
    )
    API = ConfigOption(
        required=True,
        converter=parse_apis,
        description='Comma separated list of APIs in `name:profile=version` pairs '
                    'optionally including a specification `name:profile/spec=version`. '
                    'No version means latest, a profile is only required if the API requires a profile. '
                    'E.g. `gl:core=3.3,gles1/gl=2,gles2'
    )
    EXTENSIONS = ConfigOption(
        converter=parse_extensions,
        default=None,
        description='Path to a file containing a list of extensions or '
                    'a comma separated list of extensions, if missing '
                    'all possible extensions are included.'
    )
    MERGE = ConfigOption(
        converter=bool,
        default=False,
        description='Merge multiple APIs of the same specification into one file.'
    )
    QUIET = ConfigOption(
        converter=bool,
        description='Disable logging.'
    )
    REPRODUCIBLE = ConfigOption(
        converter=bool,
        default=False,
        description='Makes the build reproducible by not fetching the latest '
                    'specification from Khronos.'
    )


def load_specifications(specification_names, opener, specification_classes=None):
    specifications = dict()

    if specification_classes is None:
        specification_classes = find_specifications()

    for name in set(specification_names):
        Specification = specification_classes[name]
        xml_name = name + '.xml'

        if os.path.isfile(xml_name):
            logger.info('using local specification: %s', xml_name)
            specification = Specification.from_file(xml_name, opener=opener)
        else:
            logger.info('getting %r specification from remote location', name)
            specification = Specification.from_remote(opener=opener)

        specifications[name] = specification

    return specifications


def apis_by_specification(api_info, specifications):
    return groupby(api_info.items(),
                   key=lambda api_info: specifications[api_info[1].specification])


def main(args=None):
    from argparse import ArgumentParser
    import sys

    # Initialize logging as early as possible
    if not '--quiet' in (args or sys.argv):
        logging.basicConfig(
            format='[%(asctime)s][%(levelname)s\t][%(name)-7s\t]: %(message)s',
            datefmt='%d.%m.%Y %H:%M:%S', level=logging.DEBUG
        )

    logging_sink = LoggingSink(logger=logger)

    description = __doc__
    parser = ArgumentParser(description=description)

    global_config = GlobalConfig()
    global_config.init_parser(parser)

    subparsers = parser.add_subparsers(
        dest='subparser_name',
        description='Generator to use'
    )
    subparsers.default = 'c'

    configs = dict()
    generators = find_generators()
    for lang, Generator in generators.items():
        config = Generator.Config()
        subparser = subparsers.add_parser(lang)
        config.init_parser(subparser)

        configs[lang] = config

    ns = parser.parse_args(args=args)

    global_config.update_from_object(ns, convert=False, ignore_additional=True)
    config = configs[ns.subparser_name]
    config.update_from_object(ns, convert=False, ignore_additional=True)

    # This should never throw if Config.init_parser is working correctly
    global_config.validate()  # Done before, but doesn't hurt
    config.validate()

    if global_config['REPRODUCIBLE']:
        opener = glad.files.StaticFileOpener()
        gen_info_factory = lambda *a, **kw: GenerationInfo.create(when='-', *a, **kw)
    else:
        opener = URLOpener()
        gen_info_factory = GenerationInfo.create

    specifications = load_specifications(
        [value[0] for value in global_config['API'].values()], opener=opener
    )

    generator = generators[ns.subparser_name](
        global_config['OUT_PATH'], opener=opener, gen_info_factory=gen_info_factory
    )

    invalid_extensions = set(global_config['EXTENSIONS'] or [])
    for specification, apis in apis_by_specification(global_config['API'], specifications):
        for api in apis:
            invalid_extensions = invalid_extensions.difference(specification.extensions[api[0]])

    if not len(invalid_extensions) == 0:
        message = 'invalid extensions or extensions not present in one of the selected APIs: {}\n' \
            .format(', '.join(invalid_extensions))
        parser.exit(11, message)

    def select(specification, api, info):
        logging_sink.info('generating {}:{}/{}={}'.format(api, info.profile, info.specification, info.version))

        extensions = global_config['EXTENSIONS']
        if extensions:
            extensions = [ext for ext in extensions if specification.is_extension(api, ext)]

        return generator.select(specification, api, info.version, info.profile, extensions, config, sink=logging_sink)

    for specification, apis in apis_by_specification(global_config['API'], specifications):
        feature_sets = list(select(specification, api, info) for api, info in apis)

        if global_config['MERGE']:
            logging_sink.info('merging {}'.format(feature_sets))
            feature_sets = [FeatureSet.merge(feature_sets, sink=logging_sink)]
            logging_sink.info('merged into {}'.format(feature_sets[0]))

        for feature_set in feature_sets:
            logging_sink.info('generating feature set {}'.format(feature_set))
            generator.generate(specification, feature_set, config, sink=logging_sink)


if __name__ == '__main__':
    main()