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