xref: /aosp_15_r20/external/deqp/external/vulkancts/scripts/gen_vulkan_header.py (revision 35238bce31c2a825756842865a792f8cf7f89930)
1#!/usr/bin/python3
2#
3# Copyright (c) 2018 The Khronos Group Inc.
4# Copyright (c) 2018 Google Inc.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18import xml.etree.ElementTree as ET
19import argparse
20import os
21import sys
22import subprocess
23import importlib
24
25currentheader = ""
26baseextensions = {}
27supersetextensions = []
28sources = [
29    "cgenerator.py",
30    "generator.py",
31    "reg.py",
32    "vk.xml"
33    ]
34
35def get_current_header(header):
36    global currentheader
37    if os.path.exists(header):
38        currentheader = open(header).read()
39
40def get_spec_ver(n):
41    specver = "?"
42    req = n.findall("require")
43    if len(req) > 0:
44        enum = req[0].findall("enum")
45        if len(enum) > 0:
46            for y in enum:
47                if "_SPEC_VERSION" in y.get("name"):
48                    specver = y.get("value")
49    return specver
50
51def get_base_extensions(xmlfile):
52    global baseextensions
53    if os.path.exists(xmlfile):
54        missing = []
55        tree = ET.parse(xmlfile)
56        root = tree.getroot()
57        extroot = root.findall("extensions")
58        ext = []
59        if len(extroot) > 0 :
60            ext = extroot[0].getchildren()
61        for x in ext:
62            name = x.get("name")
63            specver = get_spec_ver(x)
64            if specver not in "0:":
65                baseextensions[name] = specver
66
67            if name not in currentheader:
68                if specver not in "0?":
69                    missing.append(name)
70            else:
71                if specver is "0":
72                    print ("!! Warning: Current header contains extension with version 0:", name)
73                if specver is "?":
74                    print ("!! Warning: Current header contains extension with unknown version:", name)
75        if len(missing) > 0:
76            print ("!! Warning: current header does not include following base extension(s)")
77            for x in missing:
78                print ("!!  ", x)
79            print ("!! These will be included in generated header.")
80
81def parse_superset_extensions(xmlfile):
82    global supersetextensions
83    global baseextensions
84    if os.path.exists(xmlfile):
85        tree = ET.parse(xmlfile)
86        root = tree.getroot()
87        extroot = root.findall("extensions")
88        ext = []
89        if len(extroot) > 0 :
90            ext = extroot[0].getchildren()
91        for x in ext:
92            name = x.get("name")
93            specver = get_spec_ver(x)
94            if name in baseextensions:
95                if baseextensions[name] != specver:
96                    print ("!! Warning: base and superset versions for extension", name, "differ: ", baseextensions[name], "!=", specver)
97                    print ("!! The superset version ", specver, " will be included in generated header.")
98            else:
99                if specver not in "0?":
100                    supersetextensions.append([name, name in currentheader, specver])
101
102def print_menu():
103    global supersetextensions
104    index = 0
105    print()
106    for x in supersetextensions:
107        print (index, ") Output:", x[1],"-", x[0], "(ver " + x[2] + ")")
108        index += 1
109    print ("q ) Quit without saving")
110    print ("go ) Generate new header with selected superset extensions")
111
112
113def generate(args):
114    # Dynamically import generator functions (since we downloaded the scripts)
115    sys.path.insert(0, os.getcwd())
116    importlib.invalidate_caches()
117    reg_py = importlib.import_module("reg")
118    cgenerator_py = importlib.import_module("cgenerator")
119
120    reg = reg_py.Registry()
121    tree = ET.parse(args.supersetxml)
122    reg.loadElementTree(tree)
123    createGenerator = cgenerator_py.COutputGenerator
124
125    # Copyright text prefixing all headers (list of strings).
126    prefixStrings = [
127        '/*',
128        '** Copyright (c) 2015-2018 The Khronos Group Inc.',
129        '**',
130        '** Licensed under the Apache License, Version 2.0 (the "License");',
131        '** you may not use this file except in compliance with the License.',
132        '** You may obtain a copy of the License at',
133        '**',
134        '**     http://www.apache.org/licenses/LICENSE-2.0',
135        '**',
136        '** Unless required by applicable law or agreed to in writing, software',
137        '** distributed under the License is distributed on an "AS IS" BASIS,',
138        '** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.',
139        '** See the License for the specific language governing permissions and',
140        '** limitations under the License.',
141        '*/',
142        ''
143    ]
144
145    # Text specific to Vulkan headers
146    vkPrefixStrings = [
147        '/*',
148        '** This header is generated from the Khronos Vulkan XML API Registry.',
149        '** DO NOT EDIT MANUALLY. Use the following script to generate this file:',
150        '** external/vulkancts/scripts/gen_vulkan_header.py',
151        '*/',
152        ''
153    ]
154
155    # Emit everything except the extensions specifically disabled
156    removeExtensionsPat = ''
157    for x in supersetextensions:
158        if not x[1]:
159            if removeExtensionsPat != '':
160                removeExtensionsPat += '|'
161            removeExtensionsPat += x[0]
162    if removeExtensionsPat != '':
163        removeExtensionsPat = "^(" + removeExtensionsPat + ")$"
164    else:
165        removeExtensionsPat = None
166
167    options = cgenerator_py.CGeneratorOptions(
168        filename          = args.header,
169        directory         = '.',
170        apiname           = 'vulkan',
171        profile           = None,
172        versions          = '.*',
173        emitversions      = '.*',
174        defaultExtensions = 'vulkan',
175        addExtensions     = None,
176        removeExtensions  = removeExtensionsPat,
177        emitExtensions    = '.*',
178        prefixText        = prefixStrings + vkPrefixStrings,
179        genFuncPointers   = True,
180        protectFile       = True,
181        protectFeature    = False,
182        protectProto      = '#ifndef',
183        protectProtoStr   = 'VK_NO_PROTOTYPES',
184        apicall           = 'VKAPI_ATTR ',
185        apientry          = 'VKAPI_CALL ',
186        apientryp         = 'VKAPI_PTR *',
187        alignFuncParam    = 48)
188    gen = createGenerator(diagFile=None)
189    reg.setGenerator(gen)
190    reg.apiGen(options)
191    print("Done")
192
193def cleanup(args):
194    if args.nofetch or args.nocleanup:
195        print("Skipping cleanup")
196    else:
197        for x in sources:
198            if os.path.exists(x):
199                os.remove(x)
200
201def fetch_sources(args):
202    if not args.nofetch:
203        for x in sources:
204            if os.path.exists(x):
205                os.remove(x)
206            command = ["wget", "https://raw.github.com/KhronosGroup/Vulkan-Docs/master/xml/" + x]
207            if not args.noquietwget:
208                command.append("--quiet")
209            print("Fetching", x)
210            subprocess.call(command)
211            if not os.path.exists(x):
212                print("!! Error: Could not fetch", x)
213                if args.noquietwget:
214                    print("!! Re-run with -noquietwget for diagnostic information")
215                quit()
216    else:
217        for x in sources:
218            if not os.path.exists(x):
219                print("!! Error: Can't find the file",x)
220                print("!! please re-run without -skipfetch")
221                quit()
222
223if __name__ == '__main__':
224    parser = argparse.ArgumentParser()
225    parser.add_argument('-go', action='store_true',
226                        default=False,
227                        help='Enable execution.')
228    parser.add_argument('-noquietwget', action='store_true',
229                        default=False,
230                        help='Let wget output diagnostic information.')
231    parser.add_argument('-nofetch', action='store_true',
232                        default=False,
233                        help='Skip fetching required sources from github.')
234    parser.add_argument('-nocleanup', action='store_true',
235                        default=False,
236                        help='Do not remove fetched files after run.')
237    parser.add_argument('-basexml', action='store',
238                        default='vk.xml',
239                        help='Specify the base xml file with vulkan API information. Defaults to vk.xml.')
240    parser.add_argument('-supersetxml', action='store',
241                        default='vk.xml',
242                        help='Specify the xml file with superset information. Defaults to vk.xml.')
243    parser.add_argument('-header', action='store',
244                        default='external/vulkancts/scripts/src/vulkan.h.in',
245                        help='Specify the current header file. Defaults to external/vulkancts/scripts/src/invulkan.h.in.')
246    args = parser.parse_args()
247
248    if not args.go:
249        print(
250"""
251This script is used to generate the Vulkan header file for the Vulkan CTS from
252the vk.xml specification. It can optionally take a superset XML file and the
253user can interactively select which of the superset extensions to use in
254generation.
255
256The script automatically fetches the current vk.xml as well as the header
257generation scripts from github.
258
259For help with options, run with -h. To execute the script, run with -go.
260""")
261        quit()
262
263    fetch_sources(args)
264
265    if not os.path.exists(args.basexml):
266        print("!! Error: Can't find base xml file", args.basexml)
267        quit()
268    if not os.path.exists(args.supersetxml):
269        print("!! Error: Can't find superset xml file", args.supersetxml)
270        quit()
271    if not os.path.exists(args.header):
272        print("!! Error: Can't find header file", args.header)
273        quit()
274
275    get_current_header(args.header)
276    get_base_extensions(args.basexml)
277    parse_superset_extensions(args.supersetxml)
278
279    while True:
280        print_menu()
281        i = input("Option: ")
282        if i != "":
283            if i in "qQ":
284                print ("Quiting without changes")
285                cleanup(args)
286                quit()
287            if i == "go":
288                print ("Generating new header")
289                generate(args)
290                cleanup(args)
291                quit()
292            if not i.isdigit() or int(i) >= len(supersetextensions):
293                print ("Invalid input '"+i+"'")
294            else:
295                supersetextensions[int(i)][1] = not supersetextensions[int(i)][1]
296