1#!/usr/bin/python3
2#
3# Copyright (c) 2013-2019 The Khronos Group Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import argparse, cProfile, pdb, string, sys, time, os
18
19# Simple timer functions
20startTime = None
21
22def startTimer(timeit):
23    global startTime
24    if timeit:
25        startTime = time.process_time()
26
27def endTimer(timeit, msg):
28    global startTime
29    if timeit:
30        endTime = time.process_time()
31        write(msg, endTime - startTime, file=sys.stderr)
32        startTime = None
33
34# Turn a list of strings into a regexp string matching exactly those strings
35def makeREstring(list, default = None):
36    if len(list) > 0 or default is None:
37        return '^(' + '|'.join(list) + ')$'
38    else:
39        return default
40
41# Returns a directory of [ generator function, generator options ] indexed
42# by specified short names. The generator options incorporate the following
43# parameters:
44#
45# args is an parsed argument object; see below for the fields that are used.
46def makeGenOpts(args):
47    global genOpts
48    genOpts = {}
49
50    # Default class of extensions to include, or None
51    defaultExtensions = args.defaultExtensions
52
53    # Additional extensions to include (list of extensions)
54    extensions = args.extension
55
56    # Extensions to remove (list of extensions)
57    removeExtensions = args.removeExtensions
58
59    # Extensions to emit (list of extensions)
60    emitExtensions = args.emitExtensions
61
62    # Features to include (list of features)
63    features = args.feature
64
65    # Whether to disable inclusion protect in headers
66    protect = args.protect
67
68    # Output target directory
69    directory = args.directory
70
71    # Path to generated files, particularly api.py
72    genpath = args.genpath
73
74    # Descriptive names for various regexp patterns used to select
75    # versions and extensions
76    allFeatures     = allExtensions = '.*'
77    noFeatures      = noExtensions = None
78
79    # Turn lists of names/patterns into matching regular expressions
80    addExtensionsPat     = makeREstring(extensions, None)
81    removeExtensionsPat  = makeREstring(removeExtensions, None)
82    emitExtensionsPat    = makeREstring(emitExtensions, allExtensions)
83    featuresPat          = makeREstring(features, allFeatures)
84
85    # Copyright text prefixing all headers (list of strings).
86    prefixStrings = [
87        '/*',
88        '** Copyright (c) 2015-2019 The Khronos Group Inc.',
89        '**',
90        '** Licensed under the Apache License, Version 2.0 (the "License");',
91        '** you may not use this file except in compliance with the License.',
92        '** You may obtain a copy of the License at',
93        '**',
94        '**     http://www.apache.org/licenses/LICENSE-2.0',
95        '**',
96        '** Unless required by applicable law or agreed to in writing, software',
97        '** distributed under the License is distributed on an "AS IS" BASIS,',
98        '** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.',
99        '** See the License for the specific language governing permissions and',
100        '** limitations under the License.',
101        '*/',
102        ''
103    ]
104
105    # Text specific to Vulkan headers
106    vkPrefixStrings = [
107        '/*',
108        '** This header is generated from the Khronos Vulkan XML API Registry.',
109        '**',
110        '*/',
111        ''
112    ]
113
114    # Defaults for generating re-inclusion protection wrappers (or not)
115    protectFeature = protect
116
117    # An API style conventions object
118    conventions = VulkanConventions()
119
120    # Loader Generators
121    # Options for dispatch table helper generator
122    genOpts['vk_dispatch_table_helper.h'] = [
123          DispatchTableHelperOutputGenerator,
124          DispatchTableHelperOutputGeneratorOptions(
125            conventions       = conventions,
126            filename          = 'vk_dispatch_table_helper.h',
127            directory         = directory,
128            genpath           = None,
129            apiname           = 'vulkan',
130            profile           = None,
131            versions          = featuresPat,
132            emitversions      = featuresPat,
133            defaultExtensions = 'vulkan',
134            addExtensions     = addExtensionsPat,
135            removeExtensions  = removeExtensionsPat,
136            emitExtensions    = emitExtensionsPat,
137            prefixText        = prefixStrings + vkPrefixStrings,
138            apicall           = 'VKAPI_ATTR ',
139            apientry          = 'VKAPI_CALL ',
140            apientryp         = 'VKAPI_PTR *',
141            alignFuncParam    = 48,
142            expandEnumerants = False)
143        ]
144
145    # Options for Layer dispatch table generator
146    genOpts['vk_layer_dispatch_table.h'] = [
147          LoaderExtensionOutputGenerator,
148          LoaderExtensionGeneratorOptions(
149            conventions       = conventions,
150            filename          = 'vk_layer_dispatch_table.h',
151            directory         = directory,
152            genpath           = None,
153            apiname           = 'vulkan',
154            profile           = None,
155            versions          = featuresPat,
156            emitversions      = featuresPat,
157            defaultExtensions = 'vulkan',
158            addExtensions     = addExtensionsPat,
159            removeExtensions  = removeExtensionsPat,
160            emitExtensions    = emitExtensionsPat,
161            prefixText        = prefixStrings + vkPrefixStrings,
162            apicall           = 'VKAPI_ATTR ',
163            apientry          = 'VKAPI_CALL ',
164            apientryp         = 'VKAPI_PTR *',
165            alignFuncParam    = 48,
166            expandEnumerants = False)
167        ]
168
169    # Options for loader extension source generator
170    genOpts['vk_loader_extensions.h'] = [
171          LoaderExtensionOutputGenerator,
172          LoaderExtensionGeneratorOptions(
173            conventions       = conventions,
174            filename          = 'vk_loader_extensions.h',
175            directory         = directory,
176            genpath           = None,
177            apiname           = 'vulkan',
178            profile           = None,
179            versions          = featuresPat,
180            emitversions      = featuresPat,
181            defaultExtensions = 'vulkan',
182            addExtensions     = addExtensionsPat,
183            removeExtensions  = removeExtensionsPat,
184            emitExtensions    = emitExtensionsPat,
185            prefixText        = prefixStrings + vkPrefixStrings,
186            apicall           = 'VKAPI_ATTR ',
187            apientry          = 'VKAPI_CALL ',
188            apientryp         = 'VKAPI_PTR *',
189            alignFuncParam    = 48,
190            expandEnumerants = False)
191        ]
192
193    # Options for loader extension source generator
194    genOpts['vk_loader_extensions.c'] = [
195          LoaderExtensionOutputGenerator,
196          LoaderExtensionGeneratorOptions(
197            conventions       = conventions,
198            filename          = 'vk_loader_extensions.c',
199            directory         = directory,
200            genpath           = None,
201            apiname           = 'vulkan',
202            profile           = None,
203            versions          = featuresPat,
204            emitversions      = featuresPat,
205            defaultExtensions = 'vulkan',
206            addExtensions     = addExtensionsPat,
207            removeExtensions  = removeExtensionsPat,
208            emitExtensions    = emitExtensionsPat,
209            prefixText        = prefixStrings + vkPrefixStrings,
210            apicall           = 'VKAPI_ATTR ',
211            apientry          = 'VKAPI_CALL ',
212            apientryp         = 'VKAPI_PTR *',
213            alignFuncParam    = 48,
214            expandEnumerants = False)
215        ]
216
217    # Helper file generator options for vk_object_types.h
218    genOpts['vk_object_types.h'] = [
219          HelperFileOutputGenerator,
220          HelperFileOutputGeneratorOptions(
221            conventions       = conventions,
222            filename          = 'vk_object_types.h',
223            directory         = directory,
224            genpath           = None,
225            apiname           = 'vulkan',
226            profile           = None,
227            versions          = featuresPat,
228            emitversions      = featuresPat,
229            defaultExtensions = 'vulkan',
230            addExtensions     = addExtensionsPat,
231            removeExtensions  = removeExtensionsPat,
232            emitExtensions    = emitExtensionsPat,
233            prefixText        = prefixStrings + vkPrefixStrings,
234            apicall           = 'VKAPI_ATTR ',
235            apientry          = 'VKAPI_CALL ',
236            apientryp         = 'VKAPI_PTR *',
237            alignFuncParam    = 48,
238            expandEnumerants  = False,
239            helper_file_type  = 'object_types_header')
240        ]
241
242    # Loader Generated Header Version Options for loader_generated_header_version.cmake
243    genOpts['loader_generated_header_version.cmake'] = [
244          LoaderVersioningGenerator,
245          LoaderVersioningGeneratorOptions(
246            conventions       = conventions,
247            filename          = 'loader_generated_header_version.cmake',
248            directory         = directory,
249            genpath           = None,
250            apiname           = 'vulkan',
251            profile           = None,
252            versions          = featuresPat,
253            emitversions      = featuresPat,
254            defaultExtensions = 'vulkan',
255            addExtensions     = addExtensionsPat,
256            removeExtensions  = removeExtensionsPat,
257            emitExtensions    = emitExtensionsPat,
258            prefixText        = prefixStrings + vkPrefixStrings,
259            apicall           = 'VKAPI_ATTR ',
260            apientry          = 'VKAPI_CALL ',
261            apientryp         = 'VKAPI_PTR *',
262            alignFuncParam    = 48,
263            expandEnumerants  = False)
264        ]
265
266# Create an API generator and corresponding generator options based on
267# the requested target and command line options.
268# This is encapsulated in a function so it can be profiled and/or timed.
269# The args parameter is an parsed argument object containing the following
270# fields that are used:
271#   target - target to generate
272#   directory - directory to generate it in
273#   protect - True if re-inclusion wrappers should be created
274#   extensions - list of additional extensions to include in generated
275#   interfaces
276def genTarget(args):
277    global genOpts
278
279    # Create generator options with parameters specified on command line
280    makeGenOpts(args)
281
282    # Select a generator matching the requested target
283    if (args.target in genOpts.keys()):
284        createGenerator = genOpts[args.target][0]
285        options = genOpts[args.target][1]
286
287        if not args.quiet:
288            write('* Building', options.filename, file=sys.stderr)
289            write('* options.versions          =', options.versions, file=sys.stderr)
290            write('* options.emitversions      =', options.emitversions, file=sys.stderr)
291            write('* options.defaultExtensions =', options.defaultExtensions, file=sys.stderr)
292            write('* options.addExtensions     =', options.addExtensions, file=sys.stderr)
293            write('* options.removeExtensions  =', options.removeExtensions, file=sys.stderr)
294            write('* options.emitExtensions    =', options.emitExtensions, file=sys.stderr)
295
296        gen = createGenerator(errFile=errWarn,
297                              warnFile=errWarn,
298                              diagFile=diag)
299        if not args.quiet:
300            write('* Generated', options.filename, file=sys.stderr)
301        return (gen, options)
302    else:
303        write('No generator options for unknown target:', args.target, file=sys.stderr)
304        return none
305
306# -feature name
307# -extension name
308# For both, "name" may be a single name, or a space-separated list
309# of names, or a regular expression.
310if __name__ == '__main__':
311    parser = argparse.ArgumentParser()
312
313    parser.add_argument('-defaultExtensions', action='store',
314                        default='vulkan',
315                        help='Specify a single class of extensions to add to targets')
316    parser.add_argument('-extension', action='append',
317                        default=[],
318                        help='Specify an extension or extensions to add to targets')
319    parser.add_argument('-removeExtensions', action='append',
320                        default=[],
321                        help='Specify an extension or extensions to remove from targets')
322    parser.add_argument('-emitExtensions', action='append',
323                        default=[],
324                        help='Specify an extension or extensions to emit in targets')
325    parser.add_argument('-feature', action='append',
326                        default=[],
327                        help='Specify a core API feature name or names to add to targets')
328    parser.add_argument('-debug', action='store_true',
329                        help='Enable debugging')
330    parser.add_argument('-dump', action='store_true',
331                        help='Enable dump to stderr')
332    parser.add_argument('-diagfile', action='store',
333                        default=None,
334                        help='Write diagnostics to specified file')
335    parser.add_argument('-errfile', action='store',
336                        default=None,
337                        help='Write errors and warnings to specified file instead of stderr')
338    parser.add_argument('-noprotect', dest='protect', action='store_false',
339                        help='Disable inclusion protection in output headers')
340    parser.add_argument('-profile', action='store_true',
341                        help='Enable profiling')
342    parser.add_argument('-registry', action='store',
343                        default='vk.xml',
344                        help='Use specified registry file instead of vk.xml')
345    parser.add_argument('-time', action='store_true',
346                        help='Enable timing')
347    parser.add_argument('-validate', action='store_true',
348                        help='Enable XML group validation')
349    parser.add_argument('-genpath', action='store', default='gen',
350                        help='Path to generated files')
351    parser.add_argument('-o', action='store', dest='directory',
352                        default='.',
353                        help='Create target and related files in specified directory')
354    parser.add_argument('target', metavar='target', nargs='?',
355                        help='Specify target')
356    parser.add_argument('-quiet', action='store_true', default=True,
357                        help='Suppress script output during normal execution.')
358    parser.add_argument('-verbose', action='store_false', dest='quiet', default=True,
359                        help='Enable script output during normal execution.')
360
361    # This argument tells us where to load the script from the Vulkan-Headers registry
362    parser.add_argument('-scripts', action='store',
363                        help='Find additional scripts in this directory')
364
365    args = parser.parse_args()
366
367    # default scripts path to be same as registry
368    if not args.scripts:
369        args.scripts = os.path.dirname(args.registry)
370        print(args.scripts)
371
372    scripts_dir = os.path.dirname(os.path.abspath(__file__))
373    registry_dir = os.path.join(scripts_dir, args.scripts)
374    sys.path.insert(0, registry_dir)
375
376    # The imports need to be done here so that they can be picked up from Vulkan-Headers
377    from reg import *
378    from generator import write
379    from cgenerator import CGeneratorOptions, COutputGenerator
380
381    from dispatch_table_helper_generator import DispatchTableHelperOutputGenerator, DispatchTableHelperOutputGeneratorOptions
382    from helper_file_generator import HelperFileOutputGenerator, HelperFileOutputGeneratorOptions
383    from loader_extension_generator import LoaderExtensionOutputGenerator, LoaderExtensionGeneratorOptions
384    from loader_versioning_generator import LoaderVersioningGenerator, LoaderVersioningGeneratorOptions
385
386    # Temporary workaround for vkconventions python2 compatibility
387    import abc; abc.ABC = abc.ABCMeta('ABC', (object,), {})
388    from vkconventions import VulkanConventions
389
390    # This splits arguments which are space-separated lists
391    args.feature = [name for arg in args.feature for name in arg.split()]
392    args.extension = [name for arg in args.extension for name in arg.split()]
393
394    # create error/warning & diagnostic files
395    if args.errfile:
396        errWarn = open(args.errfile, 'w', encoding='utf-8')
397    else:
398        errWarn = sys.stderr
399
400    if args.diagfile:
401        diag = open(args.diagfile, 'w', encoding='utf-8')
402    else:
403        diag = None
404
405    # Create the API generator & generator options
406    (gen, options) = genTarget(args)
407
408    # Create the registry object with the specified generator and generator
409    # options. The options are set before XML loading as they may affect it.
410    reg = Registry(gen, options)
411
412    # Parse the specified registry XML into an ElementTree objec
413    startTimer(args.time)
414    tree = etree.parse(args.registry)
415    endTimer(args.time, '* Time to make ElementTree =')
416
417    # Load the XML tree into the registry object
418    startTimer(args.time)
419    reg.loadElementTree(tree)
420    endTimer(args.time, '* Time to parse ElementTree =')
421
422    if (args.validate):
423        reg.validateGroups()
424
425    if (args.dump):
426        write('* Dumping registry to regdump.txt', file=sys.stderr)
427        reg.dumpReg(filehandle = open('regdump.txt', 'w', encoding='utf-8'))
428
429    # Finally, use the output generator to create the requested targe
430    if (args.debug):
431        pdb.run('reg.apiGen()')
432    else:
433        startTimer(args.time)
434        reg.apiGen()
435        endTimer(args.time, '* Time to generate ' + options.filename + ' =')
436
437    if not args.quiet:
438        write('* Generated', options.filename, file=sys.stderr)
439