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
18from reg import *
19from generator import write
20from cgenerator import CGeneratorOptions, COutputGenerator
21from docgenerator import DocGeneratorOptions, DocOutputGenerator
22from extensionmetadocgenerator import ExtensionMetaDocGeneratorOptions, ExtensionMetaDocOutputGenerator
23from pygenerator import PyOutputGenerator
24from validitygenerator import ValidityOutputGenerator
25from hostsyncgenerator import HostSynchronizationOutputGenerator
26from extensionStubSource import ExtensionStubSourceOutputGenerator
27
28# Simple timer functions
29startTime = None
30
31def startTimer(timeit):
32    global startTime
33    if timeit:
34        startTime = time.process_time()
35
36def endTimer(timeit, msg):
37    global startTime
38    if timeit:
39        endTime = time.process_time()
40        write(msg, endTime - startTime, file=sys.stderr)
41        startTime = None
42
43# Turn a list of strings into a regexp string matching exactly those strings
44def makeREstring(list, default = None):
45    if len(list) > 0 or default == None:
46        return '^(' + '|'.join(list) + ')$'
47    else:
48        return default
49
50# Returns a directory of [ generator function, generator options ] indexed
51# by specified short names. The generator options incorporate the following
52# parameters:
53#
54# args is an parsed argument object; see below for the fields that are used.
55def makeGenOpts(args):
56    global genOpts
57    genOpts = {}
58
59    # Default class of extensions to include, or None
60    defaultExtensions = args.defaultExtensions
61
62    # Additional extensions to include (list of extensions)
63    extensions = args.extension
64
65    # Extensions to remove (list of extensions)
66    removeExtensions = args.removeExtensions
67
68    # Extensions to emit (list of extensions)
69    emitExtensions = args.emitExtensions
70
71    # Features to include (list of features)
72    features = args.feature
73
74    # Whether to disable inclusion protect in headers
75    protect = args.protect
76
77    # Output target directory
78    directory = args.directory
79
80    # Descriptive names for various regexp patterns used to select
81    # versions and extensions
82    allFeatures     = allExtensions = '.*'
83    noFeatures      = noExtensions = None
84
85    # Turn lists of names/patterns into matching regular expressions
86    addExtensionsPat     = makeREstring(extensions, None)
87    removeExtensionsPat  = makeREstring(removeExtensions, None)
88    emitExtensionsPat    = makeREstring(emitExtensions, allExtensions)
89    featuresPat          = makeREstring(features, allFeatures)
90
91    # Copyright text prefixing all headers (list of strings).
92    prefixStrings = [
93        '/*',
94        '** Copyright (c) 2015-2019 The Khronos Group Inc.',
95        '**',
96        '** Licensed under the Apache License, Version 2.0 (the "License");',
97        '** you may not use this file except in compliance with the License.',
98        '** You may obtain a copy of the License at',
99        '**',
100        '**     http://www.apache.org/licenses/LICENSE-2.0',
101        '**',
102        '** Unless required by applicable law or agreed to in writing, software',
103        '** distributed under the License is distributed on an "AS IS" BASIS,',
104        '** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.',
105        '** See the License for the specific language governing permissions and',
106        '** limitations under the License.',
107        '*/',
108        ''
109    ]
110
111    # Text specific to Vulkan headers
112    vkPrefixStrings = [
113        '/*',
114        '** This header is generated from the Khronos Vulkan XML API Registry.',
115        '**',
116        '*/',
117        ''
118    ]
119
120    # Defaults for generating re-inclusion protection wrappers (or not)
121    protectFile = protect
122    protectFeature = protect
123    protectProto = protect
124
125    # API include files for spec and ref pages
126    # Overwrites include subdirectories in spec source tree
127    # The generated include files do not include the calling convention
128    # macros (apientry etc.), unlike the header files.
129    # Because the 1.0 core branch includes ref pages for extensions,
130    # all the extension interfaces need to be generated, even though
131    # none are used by the core spec itself.
132    genOpts['apiinc'] = [
133          DocOutputGenerator,
134          DocGeneratorOptions(
135            filename          = 'timeMarker',
136            directory         = directory,
137            apiname           = 'vulkan',
138            profile           = None,
139            versions          = featuresPat,
140            emitversions      = featuresPat,
141            defaultExtensions = None,
142            addExtensions     = addExtensionsPat,
143            removeExtensions  = removeExtensionsPat,
144            emitExtensions    = emitExtensionsPat,
145            prefixText        = prefixStrings + vkPrefixStrings,
146            apicall           = '',
147            apientry          = '',
148            apientryp         = '*',
149            alignFuncParam    = 48,
150            expandEnumerants  = False)
151        ]
152
153    # API names to validate man/api spec includes & links
154    genOpts['vkapi.py'] = [
155          PyOutputGenerator,
156          DocGeneratorOptions(
157            filename          = 'vkapi.py',
158            directory         = directory,
159            apiname           = 'vulkan',
160            profile           = None,
161            versions          = featuresPat,
162            emitversions      = featuresPat,
163            defaultExtensions = None,
164            addExtensions     = addExtensionsPat,
165            removeExtensions  = removeExtensionsPat,
166            emitExtensions    = emitExtensionsPat)
167        ]
168
169    # API validity files for spec
170    genOpts['validinc'] = [
171          ValidityOutputGenerator,
172          DocGeneratorOptions(
173            filename          = 'timeMarker',
174            directory         = directory,
175            apiname           = 'vulkan',
176            profile           = None,
177            versions          = featuresPat,
178            emitversions      = featuresPat,
179            defaultExtensions = None,
180            addExtensions     = addExtensionsPat,
181            removeExtensions  = removeExtensionsPat,
182            emitExtensions    = emitExtensionsPat)
183        ]
184
185    # API host sync table files for spec
186    genOpts['hostsyncinc'] = [
187          HostSynchronizationOutputGenerator,
188          DocGeneratorOptions(
189            filename          = 'timeMarker',
190            directory         = directory,
191            apiname           = 'vulkan',
192            profile           = None,
193            versions          = featuresPat,
194            emitversions      = featuresPat,
195            defaultExtensions = None,
196            addExtensions     = addExtensionsPat,
197            removeExtensions  = removeExtensionsPat,
198            emitExtensions    = emitExtensionsPat)
199        ]
200
201    # Extension stub source dispatcher
202    # This target is no longer maintained and supported.
203    # See README.adoc for discussion.
204    genOpts['vulkan_ext.c'] = [
205          ExtensionStubSourceOutputGenerator,
206          CGeneratorOptions(
207            filename          = 'vulkan_ext.c',
208            directory         = directory,
209            apiname           = 'vulkan',
210            profile           = None,
211            versions          = featuresPat,
212            emitversions      = None,
213            defaultExtensions = None,
214            addExtensions     = '.*',
215            removeExtensions  = removeExtensionsPat,
216            emitExtensions    = emitExtensionsPat,
217            prefixText        = prefixStrings + vkPrefixStrings,
218            alignFuncParam    = 48)
219        ]
220
221    # Extension metainformation for spec extension appendices
222    genOpts['extinc'] = [
223          ExtensionMetaDocOutputGenerator,
224          ExtensionMetaDocGeneratorOptions(
225            filename          = 'timeMarker',
226            directory         = directory,
227            apiname           = 'vulkan',
228            profile           = None,
229            versions          = featuresPat,
230            emitversions      = None,
231            defaultExtensions = defaultExtensions,
232            addExtensions     = None,
233            removeExtensions  = None,
234            emitExtensions    = emitExtensionsPat)
235        ]
236
237    # Platform extensions, in their own header files
238    # Each element of the platforms[] array defines information for
239    # generating a single platform:
240    #   [0] is the generated header file name
241    #   [1] is the set of platform extensions to generate
242    #   [2] is additional extensions whose interfaces should be considered,
243    #   but suppressed in the output, to avoid duplicate definitions of
244    #   dependent types like VkDisplayKHR and VkSurfaceKHR which come from
245    #   non-platform extensions.
246
247    # Track all platform extensions, for exclusion from vulkan_core.h
248    allPlatformExtensions = []
249
250    # Extensions suppressed for all platforms.
251    # Covers common WSI extension types.
252    commonSuppressExtensions = [ 'VK_KHR_display', 'VK_KHR_swapchain' ]
253
254    platforms = [
255        [ 'vulkan_android.h',     [ 'VK_KHR_android_surface',
256                                    'VK_ANDROID_external_memory_android_hardware_buffer'
257                                                                  ], commonSuppressExtensions ],
258        [ 'vulkan_fuchsia.h',     [ 'VK_FUCHSIA_imagepipe_surface'], commonSuppressExtensions ],
259        [ 'vulkan_ios.h',         [ 'VK_MVK_ios_surface'          ], commonSuppressExtensions ],
260        [ 'vulkan_macos.h',       [ 'VK_MVK_macos_surface'        ], commonSuppressExtensions ],
261        [ 'vulkan_vi.h',          [ 'VK_NN_vi_surface'            ], commonSuppressExtensions ],
262        [ 'vulkan_wayland.h',     [ 'VK_KHR_wayland_surface'      ], commonSuppressExtensions ],
263        [ 'vulkan_win32.h',       [ 'VK_.*_win32(|_.*)'           ], commonSuppressExtensions + [ 'VK_KHR_external_semaphore', 'VK_KHR_external_memory_capabilities', 'VK_KHR_external_fence', 'VK_KHR_external_fence_capabilities', 'VK_NV_external_memory_capabilities' ] ],
264        [ 'vulkan_xcb.h',         [ 'VK_KHR_xcb_surface'          ], commonSuppressExtensions ],
265        [ 'vulkan_xlib.h',        [ 'VK_KHR_xlib_surface'         ], commonSuppressExtensions ],
266        [ 'vulkan_xlib_xrandr.h', [ 'VK_EXT_acquire_xlib_display' ], commonSuppressExtensions ],
267        [ 'vulkan_metal.h',       [ 'VK_EXT_metal_surface'        ], commonSuppressExtensions ],
268    ]
269
270    for platform in platforms:
271        headername = platform[0]
272
273        allPlatformExtensions += platform[1]
274
275        addPlatformExtensionsRE = makeREstring(platform[1] + platform[2])
276        emitPlatformExtensionsRE = makeREstring(platform[1])
277
278        opts = CGeneratorOptions(
279            filename          = headername,
280            directory         = directory,
281            apiname           = 'vulkan',
282            profile           = None,
283            versions          = featuresPat,
284            emitversions      = None,
285            defaultExtensions = None,
286            addExtensions     = addPlatformExtensionsRE,
287            removeExtensions  = None,
288            emitExtensions    = emitPlatformExtensionsRE,
289            prefixText        = prefixStrings + vkPrefixStrings,
290            genFuncPointers   = True,
291            protectFile       = protectFile,
292            protectFeature    = False,
293            protectProto      = '#ifndef',
294            protectProtoStr   = 'VK_NO_PROTOTYPES',
295            apicall           = 'VKAPI_ATTR ',
296            apientry          = 'VKAPI_CALL ',
297            apientryp         = 'VKAPI_PTR *',
298            alignFuncParam    = 48)
299
300        genOpts[headername] = [ COutputGenerator, opts ]
301
302    # Header for core API + extensions.
303    # To generate just the core API,
304    # change to 'defaultExtensions = None' below.
305    #
306    # By default this adds all enabled, non-platform extensions.
307    # It removes all platform extensions (from the platform headers options
308    # constructed above) as well as any explicitly specified removals.
309
310    removeExtensionsPat = makeREstring(allPlatformExtensions + removeExtensions, None)
311
312    genOpts['vulkan_core.h'] = [
313          COutputGenerator,
314          CGeneratorOptions(
315            filename          = 'vulkan_core.h',
316            directory         = directory,
317            apiname           = 'vulkan',
318            profile           = None,
319            versions          = featuresPat,
320            emitversions      = featuresPat,
321            defaultExtensions = defaultExtensions,
322            addExtensions     = None,
323            removeExtensions  = removeExtensionsPat,
324            emitExtensions    = emitExtensionsPat,
325            prefixText        = prefixStrings + vkPrefixStrings,
326            genFuncPointers   = True,
327            protectFile       = protectFile,
328            protectFeature    = False,
329            protectProto      = '#ifndef',
330            protectProtoStr   = 'VK_NO_PROTOTYPES',
331            apicall           = 'VKAPI_ATTR ',
332            apientry          = 'VKAPI_CALL ',
333            apientryp         = 'VKAPI_PTR *',
334            alignFuncParam    = 48)
335        ]
336
337    # Unused - vulkan10.h target.
338    # It is possible to generate a header with just the Vulkan 1.0 +
339    # extension interfaces defined, but since the promoted KHR extensions
340    # are now defined in terms of the 1.1 interfaces, such a header is very
341    # similar to vulkan_core.h.
342    genOpts['vulkan10.h'] = [
343          COutputGenerator,
344          CGeneratorOptions(
345            filename          = 'vulkan10.h',
346            directory         = directory,
347            apiname           = 'vulkan',
348            profile           = None,
349            versions          = 'VK_VERSION_1_0',
350            emitversions      = 'VK_VERSION_1_0',
351            defaultExtensions = defaultExtensions,
352            addExtensions     = None,
353            removeExtensions  = removeExtensionsPat,
354            emitExtensions    = emitExtensionsPat,
355            prefixText        = prefixStrings + vkPrefixStrings,
356            genFuncPointers   = True,
357            protectFile       = protectFile,
358            protectFeature    = False,
359            protectProto      = '#ifndef',
360            protectProtoStr   = 'VK_NO_PROTOTYPES',
361            apicall           = 'VKAPI_ATTR ',
362            apientry          = 'VKAPI_CALL ',
363            apientryp         = 'VKAPI_PTR *',
364            alignFuncParam    = 48)
365        ]
366
367    genOpts['alias.h'] = [
368          COutputGenerator,
369          CGeneratorOptions(
370            filename          = 'alias.h',
371            directory         = directory,
372            apiname           = 'vulkan',
373            profile           = None,
374            versions          = featuresPat,
375            emitversions      = featuresPat,
376            defaultExtensions = defaultExtensions,
377            addExtensions     = None,
378            removeExtensions  = removeExtensionsPat,
379            emitExtensions    = emitExtensionsPat,
380            prefixText        = None,
381            genFuncPointers   = False,
382            protectFile       = False,
383            protectFeature    = False,
384            protectProto      = '',
385            protectProtoStr   = '',
386            apicall           = '',
387            apientry          = '',
388            apientryp         = '',
389            alignFuncParam    = 36)
390        ]
391
392# Generate a target based on the options in the matching genOpts{} object.
393# This is encapsulated in a function so it can be profiled and/or timed.
394# The args parameter is an parsed argument object containing the following
395# fields that are used:
396#   target - target to generate
397#   directory - directory to generate it in
398#   protect - True if re-inclusion wrappers should be created
399#   extensions - list of additional extensions to include in generated
400#   interfaces
401def genTarget(args):
402    global genOpts
403
404    # Create generator options with specified parameters
405    makeGenOpts(args)
406
407    if (args.target in genOpts.keys()):
408        createGenerator = genOpts[args.target][0]
409        options = genOpts[args.target][1]
410
411        if not args.quiet:
412            write('* Building', options.filename, file=sys.stderr)
413            write('* options.versions          =', options.versions, file=sys.stderr)
414            write('* options.emitversions      =', options.emitversions, file=sys.stderr)
415            write('* options.defaultExtensions =', options.defaultExtensions, file=sys.stderr)
416            write('* options.addExtensions     =', options.addExtensions, file=sys.stderr)
417            write('* options.removeExtensions  =', options.removeExtensions, file=sys.stderr)
418            write('* options.emitExtensions    =', options.emitExtensions, file=sys.stderr)
419
420        startTimer(args.time)
421        gen = createGenerator(errFile=errWarn,
422                              warnFile=errWarn,
423                              diagFile=diag)
424        reg.setGenerator(gen)
425        reg.apiGen(options)
426
427        if not args.quiet:
428            write('* Generated', options.filename, file=sys.stderr)
429        endTimer(args.time, '* Time to generate ' + options.filename + ' =')
430    else:
431        write('No generator options for unknown target:',
432              args.target, file=sys.stderr)
433
434# -feature name
435# -extension name
436# For both, "name" may be a single name, or a space-separated list
437# of names, or a regular expression.
438if __name__ == '__main__':
439    parser = argparse.ArgumentParser()
440
441    parser.add_argument('-defaultExtensions', action='store',
442                        default='vulkan',
443                        help='Specify a single class of extensions to add to targets')
444    parser.add_argument('-extension', action='append',
445                        default=[],
446                        help='Specify an extension or extensions to add to targets')
447    parser.add_argument('-removeExtensions', action='append',
448                        default=[],
449                        help='Specify an extension or extensions to remove from targets')
450    parser.add_argument('-emitExtensions', action='append',
451                        default=[],
452                        help='Specify an extension or extensions to emit in targets')
453    parser.add_argument('-feature', action='append',
454                        default=[],
455                        help='Specify a core API feature name or names to add to targets')
456    parser.add_argument('-debug', action='store_true',
457                        help='Enable debugging')
458    parser.add_argument('-dump', action='store_true',
459                        help='Enable dump to stderr')
460    parser.add_argument('-diagfile', action='store',
461                        default=None,
462                        help='Write diagnostics to specified file')
463    parser.add_argument('-errfile', action='store',
464                        default=None,
465                        help='Write errors and warnings to specified file instead of stderr')
466    parser.add_argument('-noprotect', dest='protect', action='store_false',
467                        help='Disable inclusion protection in output headers')
468    parser.add_argument('-profile', action='store_true',
469                        help='Enable profiling')
470    parser.add_argument('-registry', action='store',
471                        default='vk.xml',
472                        help='Use specified registry file instead of vk.xml')
473    parser.add_argument('-time', action='store_true',
474                        help='Enable timing')
475    parser.add_argument('-validate', action='store_true',
476                        help='Enable group validation')
477    parser.add_argument('-o', action='store', dest='directory',
478                        default='.',
479                        help='Create target and related files in specified directory')
480    parser.add_argument('target', metavar='target', nargs='?',
481                        help='Specify target')
482    parser.add_argument('-quiet', action='store_true', default=True,
483                        help='Suppress script output during normal execution.')
484    parser.add_argument('-verbose', action='store_false', dest='quiet', default=True,
485                        help='Enable script output during normal execution.')
486
487    args = parser.parse_args()
488
489    # This splits arguments which are space-separated lists
490    args.feature = [name for arg in args.feature for name in arg.split()]
491    args.extension = [name for arg in args.extension for name in arg.split()]
492
493    # Load & parse registry
494    reg = Registry()
495
496    startTimer(args.time)
497    tree = etree.parse(args.registry)
498    endTimer(args.time, '* Time to make ElementTree =')
499
500    if args.debug:
501        pdb.run('reg.loadElementTree(tree)')
502    else:
503        startTimer(args.time)
504        reg.loadElementTree(tree)
505        endTimer(args.time, '* Time to parse ElementTree =')
506
507    if (args.validate):
508        reg.validateGroups()
509
510    if (args.dump):
511        write('* Dumping registry to regdump.txt', file=sys.stderr)
512        reg.dumpReg(filehandle = open('regdump.txt', 'w', encoding='utf-8'))
513
514    # create error/warning & diagnostic files
515    if (args.errfile):
516        errWarn = open(args.errfile, 'w', encoding='utf-8')
517    else:
518        errWarn = sys.stderr
519
520    if (args.diagfile):
521        diag = open(args.diagfile, 'w', encoding='utf-8')
522    else:
523        diag = None
524
525    if (args.debug):
526        pdb.run('genTarget(args)')
527    elif (args.profile):
528        import cProfile, pstats
529        cProfile.run('genTarget(args)', 'profile.txt')
530        p = pstats.Stats('profile.txt')
531        p.strip_dirs().sort_stats('time').print_stats(50)
532    else:
533        genTarget(args)
534