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()
|