blob: e2595094f142336d47aad6028a727852b0de681f [file] [log] [blame]
Tobin Ehlis3198ba32017-04-19 17:30:52 -06001#!/usr/bin/python -i
Tobin Ehlis5ade0692016-10-05 17:18:15 -06002
3import sys
Tobin Ehlis98d109a2017-05-11 14:42:38 -06004#import xml.etree.ElementTree as etree
Tobin Ehlis5ade0692016-10-05 17:18:15 -06005import urllib2
Tobin Ehlis3198ba32017-04-19 17:30:52 -06006from bs4 import BeautifulSoup
Tobin Ehlis98d109a2017-05-11 14:42:38 -06007import json
8import vuid_mapping
Tobin Ehlis5ade0692016-10-05 17:18:15 -06009
10#############################
11# spec.py script
12#
13# Overview - this script is intended to generate validation error codes and message strings from the xhtml version of
14# the specification. In addition to generating the header file, it provides a number of corrollary services to aid in
15# generating/updating the header.
16#
17# Ideal flow - Not there currently, but the ideal flow for this script would be that you run the script, it pulls the
18# latest spec, compares it to the current set of generated error codes, and makes any updates as needed
19#
20# Current flow - the current flow acheives all of the ideal flow goals, but with more steps than are desired
21# 1. Get the spec - right now spec has to be manually generated or pulled from the web
22# 2. Generate header from spec - This is done in a single command line
23# 3. Generate database file from spec - Can be done along with step #2 above, the database file contains a list of
24# all error enums and message strings, along with some other info on if those errors are implemented/tested
25# 4. Update header using a given database file as the root and a new spec file as goal - This makes sure that existing
26# errors keep the same enum identifier while also making sure that new errors get a unique_id that continues on
27# from the end of the previous highest unique_id.
28#
29# TODO:
30# 1. Improve string matching to add more automation for figuring out which messages are changed vs. completely new
Tobin Ehlis5ade0692016-10-05 17:18:15 -060031#
32#############################
33
34
35spec_filename = "vkspec.html" # can override w/ '-spec <filename>' option
36out_filename = "vk_validation_error_messages.h" # can override w/ '-out <filename>' option
37db_filename = "vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option
Tobin Ehlis2c932132017-05-19 16:32:15 -060038json_filename = None # con pass in w/ '-json <filename> option
Tobin Ehlis5ade0692016-10-05 17:18:15 -060039gen_db = False # set to True when '-gendb <filename>' option provided
40spec_compare = False # set to True with '-compare <db_filename>' option
Tobin Ehlis2c932132017-05-19 16:32:15 -060041json_compare = False # compare existing DB to json file input
Tobin Ehlis828781a2017-05-23 15:23:40 -060042json_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/validation/validusage.json"
43read_json = False
Tobin Ehlis5ade0692016-10-05 17:18:15 -060044# This is the root spec link that is used in error messages to point users to spec sections
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -060045#old_spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html"
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060046spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html"
Tobin Ehlis828781a2017-05-23 15:23:40 -060047core_url = "https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html"
48ext_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060049# After the custom validation error message, this is the prefix for the standard message that includes the
50# spec valid usage language as well as the link to nearest section of spec to that language
51error_msg_prefix = "For more information refer to Vulkan Spec Section "
52ns = {'ns': 'http://www.w3.org/1999/xhtml'}
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060053validation_error_enum_name = "VALIDATION_ERROR_"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060054# Dict of new enum values that should be forced to remap to old handles, explicitly set by -remap option
55remap_dict = {}
Tobin Ehlis2c932132017-05-19 16:32:15 -060056# Custom map to override known-bad values
57struct_to_api_map = {
58'VkDeviceQueueCreateInfo' : 'vkCreateDevice',
59}
Tobin Ehlis5ade0692016-10-05 17:18:15 -060060
61def printHelp():
Tobin Ehlis98d109a2017-05-11 14:42:38 -060062 print ("Usage: python spec.py [-spec <specfile.html>] [-out <headerfile.h>] [-gendb <databasefile.txt>] [-compare <databasefile.txt>] [-update] [-remap <new_id-old_id,count>] [-json <json_file>] [-help]")
Tobin Ehlis3198ba32017-04-19 17:30:52 -060063 print ("\n Default script behavior is to parse the specfile and generate a header of unique error enums and corresponding error messages based on the specfile.\n")
64 print (" Default specfile is from online at %s" % (spec_url))
65 print (" Default headerfile is %s" % (out_filename))
66 print (" Default databasefile is %s" % (db_filename))
67 print ("\nIf '-gendb' option is specified then a database file is generated to default file or <databasefile.txt> if supplied. The database file stores")
68 print (" the list of enums and their error messages.")
69 print ("\nIf '-compare' option is specified then the given database file will be read in as the baseline for generating the new specfile")
70 print ("\nIf '-update' option is specified this triggers the master flow to automate updating header and database files using default db file as baseline")
71 print (" and online spec file as the latest. The default header and database files will be updated in-place for review and commit to the git repo.")
72 print ("\nIf '-remap' option is specified it supplies forced remapping from new enum ids to old enum ids. This should only be specified along with -update")
73 print (" option. Starting at newid and remapping to oldid, count ids will be remapped. Default count is '1' and use ':' to specify multiple remappings.")
Tobin Ehlis828781a2017-05-23 15:23:40 -060074 print ("\nIf '-json' option is used trigger the script to load in data from a json file.")
75 print ("\nIf '-json-file' option is it will point to a local json file, else '%s' is used from the web." % (json_url))
Tobin Ehlis5ade0692016-10-05 17:18:15 -060076
77class Specification:
78 def __init__(self):
79 self.tree = None
Tobin Ehlise7560e72016-10-19 15:59:38 -060080 self.val_error_dict = {} # string for enum is key that references 'error_msg' and 'api'
Tobin Ehlis5ade0692016-10-05 17:18:15 -060081 self.error_db_dict = {} # dict of previous error values read in from database file
82 self.delimiter = '~^~' # delimiter for db file
Tobin Ehlis2a176b12017-01-11 16:18:20 -070083 self.implicit_count = 0
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060084 # Global dicts used for tracking spec updates from old to new VUs
85 self.orig_full_msg_dict = {} # Original full error msg to ID mapping
86 self.orig_no_link_msg_dict = {} # Pair of API,Original msg w/o spec link to ID list mapping
87 self.orig_core_msg_dict = {} # Pair of API,Original core msg (no link or section) to ID list mapping
88 self.last_mapped_id = -10 # start as negative so we don't hit an accidental sequence
89 self.orig_test_imp_enums = set() # Track old enums w/ tests and/or implementation to flag any that aren't carried fwd
Tobin Ehlis2c932132017-05-19 16:32:15 -060090 # Dict of data from json DB
91 # Key is API,<short_msg> which leads to dict w/ following values
92 # 'ext' -> <core|<ext_name>>
93 # 'string_vuid' -> <string_vuid>
94 # 'number_vuid' -> <numerical_vuid>
95 self.json_db = {}
96 self.json_missing = 0
97 self.struct_to_func_map = {} # Map structs to the API func that they fall under in the spec
98 self.duplicate_json_key_count = 0
Tobin Ehlis5ade0692016-10-05 17:18:15 -060099 self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */
100
101/*
102 * Vulkan
103 *
104 * Copyright (c) 2016 Google Inc.
Mark Lobodzinski629d47b2016-10-18 13:34:58 -0600105 * Copyright (c) 2016 LunarG, Inc.
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600106 *
107 * Licensed under the Apache License, Version 2.0 (the "License");
108 * you may not use this file except in compliance with the License.
109 * You may obtain a copy of the License at
110 *
111 * http://www.apache.org/licenses/LICENSE-2.0
112 *
113 * Unless required by applicable law or agreed to in writing, software
114 * distributed under the License is distributed on an "AS IS" BASIS,
115 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
116 * See the License for the specific language governing permissions and
117 * limitations under the License.
118 *
119 * Author: Tobin Ehlis <tobine@google.com>
120 */"""
121 def _checkInternetSpec(self):
122 """Verify that we can access the spec online"""
123 try:
124 online = urllib2.urlopen(spec_url,timeout=1)
125 return True
126 except urllib2.URLError as err:
127 return False
128 return False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600129 def soupLoadFile(self, online=True, spec_file=spec_filename):
130 """Load a spec file into BeutifulSoup"""
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600131 if (online and self._checkInternetSpec()):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600132 print ("Making soup from spec online at %s, this will take a minute" % (spec_url))
133 self.soup = BeautifulSoup(urllib2.urlopen(spec_url), 'html.parser')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600134 else:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600135 print ("Making soup from local spec %s, this will take a minute" % (spec_file))
Tobin Ehlisec45e422017-05-19 08:24:04 -0600136 with open(spec_file, "r") as sf:
137 self.soup = BeautifulSoup(sf, 'html.parser')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600138 self.parseSoup()
139 #print(self.soup.prettify())
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600140 def updateDict(self, updated_dict):
141 """Assign internal dict to use updated_dict"""
142 self.val_error_dict = updated_dict
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600143
Tobin Ehlis828781a2017-05-23 15:23:40 -0600144 def readJSON(self):
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600145 """Read in JSON file"""
Tobin Ehlis828781a2017-05-23 15:23:40 -0600146 if json_filename is not None:
147 with open(json_filename) as jsf:
148 self.json_data = json.load(jsf)
149 else:
150 self.json_data = json.load(urllib2.urlopen(json_url))
151
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600152 def parseJSON(self):
153 """Parse JSON VUIDs into data struct"""
154 # Format of JSON file is:
155 # "API": { "core|EXT": [ {"vuid": "<id>", "text": "<VU txt>"}]},
156 # "VK_KHX_external_memory" & "VK_KHX_device_group" - extension case (vs. "core")
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600157 for api in sorted(self.json_data):
158 for ext in sorted(self.json_data[api]):
159 for vu_txt_dict in self.json_data[api][ext]:
160 vuid = vu_txt_dict['vuid']
161 vutxt = vu_txt_dict['text']
162 #print ("%s:%s:%s:%s" % (api, ext, vuid, vutxt))
Tobin Ehlis828781a2017-05-23 15:23:40 -0600163 #print ("VUTXT orig:%s" % (vutxt))
Tobin Ehlis2c932132017-05-19 16:32:15 -0600164 just_txt = BeautifulSoup(vutxt, 'html.parser')
165 #print ("VUTXT only:%s" % (just_txt.get_text()))
166 num_vuid = vuid_mapping.convertVUID(vuid)
167 function = api
168 if function in self.struct_to_func_map:
169 function = self.struct_to_func_map[function]
170 key = "%s,'%s'" % (function, just_txt.get_text())
171 if key in self.json_db:
172 print ("Key '%s' is already in json_db!" % (key))
173 self.duplicate_json_key_count = self.duplicate_json_key_count + 1
174 #sys.exit()
Tobin Ehlis828781a2017-05-23 15:23:40 -0600175 self.json_db[vuid] = {}
176 self.json_db[vuid]['ext'] = ext
177 #self.json_db[key]['string_vuid'] = vuid
178 self.json_db[vuid]['number_vuid'] = num_vuid
179 self.json_db[vuid]['struct_func'] = api
180 just_txt = just_txt.get_text().strip()
181 unicode_map = {
182 u"\u2019" : "'",
183 u"\u2192" : "->",
184 }
185 for um in unicode_map:
186 just_txt = just_txt.replace(um, unicode_map[um])
187 self.json_db[vuid]['vu_txt'] = just_txt
188 print ("Spec vu txt:%s" % (self.json_db[vuid]['vu_txt']))
189# if 'must specify aspects present in the calling command' in key:
190# print "Found KEY:%s" % (key)
Tobin Ehlis2c932132017-05-19 16:32:15 -0600191 #sys.exit()
Tobin Ehlis828781a2017-05-23 15:23:40 -0600192 #sys.exit()
Tobin Ehlis2c932132017-05-19 16:32:15 -0600193
194 def compareJSON(self):
195 """Compare parsed json file with existing data read in from DB file"""
Tobin Ehlis828781a2017-05-23 15:23:40 -0600196 json_db_set = set()
197 for vuid in self.json_db: # pull entries out and see which fields we're missing from error_db
198 json_db_set.add(vuid)
Tobin Ehlis2c932132017-05-19 16:32:15 -0600199 for enum in self.error_db_dict:
Tobin Ehlis828781a2017-05-23 15:23:40 -0600200 vuid_string = self.error_db_dict[enum]['vuid_string']
201 if vuid_string not in self.json_db:
202 #print ("Full string for %s is:%s" % (enum, full_error_string))
203 print ("WARN: Couldn't find vuid_string in json db:%s" % (vuid_string))
Tobin Ehlis2c932132017-05-19 16:32:15 -0600204 self.json_missing = self.json_missing + 1
Tobin Ehlis828781a2017-05-23 15:23:40 -0600205 self.error_db_dict[enum]['ext'] = 'core'
206 #sys.exit()
207 else:
208 json_db_set.remove(vuid_string)
209 # TMP: Add ext details to error db
210 self.error_db_dict[enum]['ext'] = self.json_db[vuid_string]['ext']
211 if 'core' == self.json_db[vuid_string]['ext'] or '!' in self.json_db[vuid_string]['ext']:
212 spec_link = "%s#%s" % (core_url, vuid_string)
213 else:
214 spec_link = "%s#%s" % (ext_url, vuid_string)
215 self.error_db_dict[enum]['error_msg'] = "The spec valid usage text states '%s' (%s)" % (self.json_db[vuid_string]['vu_txt'], spec_link)
216 print ("Updated error_db error_msg:%s" % (self.error_db_dict[enum]['error_msg']))
217 #sys.exit()
218 print ("These json DB entries are not in error DB:")
219 for extra_vuid in json_db_set:
220 print ("\t%s" % (extra_vuid))
221 # Add these missing entries into the error_db
222 # Create link into core or ext spec as needed
223 if 'core' == self.json_db[extra_vuid]['ext'] or '!' in self.json_db[extra_vuid]['ext']:
224 spec_link = "%s#%s" % (core_url, extra_vuid)
225 else:
226 spec_link = "%s#%s" % (ext_url, extra_vuid)
227 error_enum = "VALIDATION_ERROR_%d" % (self.json_db[extra_vuid]['number_vuid'])
228 self.error_db_dict[error_enum] = {}
229 self.error_db_dict[error_enum]['check_implemented'] = 'N'
230 self.error_db_dict[error_enum]['testname'] = 'None'
231 self.error_db_dict[error_enum]['api'] = self.json_db[extra_vuid]['struct_func']
232 self.error_db_dict[error_enum]['vuid_string'] = extra_vuid
233 self.error_db_dict[error_enum]['error_msg'] = "The spec valid usage text states '%s' (%s)" % (self.json_db[extra_vuid]['vu_txt'], spec_link)
234 self.error_db_dict[error_enum]['note'] = ''
235 self.error_db_dict[error_enum]['ext'] = self.json_db[extra_vuid]['ext']
236 # Enable this code if you want to reset val_error_dict & assign to db dict
237# self.val_error_dict = {}
238# for enum in self.error_db_dict:
239# self.val_error_dict[enum] = {}
240# self.val_error_dict[enum]['check_implemented'] = self.error_db_dict[enum]['check_implemented']
241# self.val_error_dict[enum]['testname'] = self.error_db_dict[enum]['testname']
242# self.val_error_dict[enum]['api'] = self.error_db_dict[enum]['api']
243# self.val_error_dict[enum]['vuid_string'] = self.error_db_dict[enum]['vuid_string']
244# self.val_error_dict[enum]['ext'] = self.error_db_dict[enum]['ext']
245# self.val_error_dict[enum]['error_msg'] = self.error_db_dict[enum]['error_msg']
246# self.val_error_dict[enum]['note'] = self.error_db_dict[enum]['note']
247# implicit = False
248# if extra_vuid.split("_")[-1].isdigit():
249# implicit = True
250# self.val_error_dict[enum]['implicit'] = implicit
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600251
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600252 def parseSoup(self):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600253 """Parse the registry Element, once created"""
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600254 print ("Parsing spec file...")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600255 unique_enum_id = 0
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600256 #self.root = self.tree.getroot()
257 #print ("ROOT: %s") % self.root
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600258 prev_heading = '' # Last seen section heading or sub-heading
259 prev_link = '' # Last seen link id within the spec
Tobin Ehlise7560e72016-10-19 15:59:38 -0600260 api_function = '' # API call that a check appears under
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600261 error_strings = set() # Flag any exact duplicate error strings and skip them
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600262 for tag in self.soup.find_all(True):#self.root.iter(): # iterate down tree
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600263 # Grab most recent section heading and link
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600264 #print ("tag.name is %s and class is %s" % (tag.name, tag.get('class')))
265 if tag.name in ['h2', 'h3', 'h4']:
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800266 #if tag.get('class') != 'title':
267 # continue
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600268 #print ("Found heading %s w/ string %s" % (tag.name, tag.string))
269 if None == tag.string:
270 prev_heading = ""
271 else:
272 prev_heading = "".join(tag.string)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600273 # Insert a space between heading number & title
274 sh_list = prev_heading.rsplit('.', 1)
275 prev_heading = '. '.join(sh_list)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600276 prev_link = tag['id']
277 #print ("Set prev_heading %s to have link of %s" % (prev_heading.encode("ascii", "ignore"), prev_link.encode("ascii", "ignore")))
278 elif tag.name == 'a': # grab any intermediate links
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600279 if tag.get('id') != None:
280 prev_link = tag.get('id')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600281 #print ("Updated prev link to %s" % (prev_link))
282 elif tag.name == 'div' and tag.get('class') is not None and tag['class'][0] == 'listingblock':
Tobin Ehlise7560e72016-10-19 15:59:38 -0600283 # Check and see if this is API function
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600284 code_text = "".join(tag.strings).replace('\n', '')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600285 code_text_list = code_text.split()
286 if len(code_text_list) > 1 and code_text_list[1].startswith('vk'):
287 api_function = code_text_list[1].strip('(')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600288 #print ("Found API function: %s" % (api_function))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600289 prev_link = api_function
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600290 #print ("Updated prev link to %s" % (prev_link))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600291 elif tag.get('id') != None:
292 prev_link = tag.get('id')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600293 #print ("Updated prev link to %s" % (prev_link))
Tobin Ehlis2c932132017-05-19 16:32:15 -0600294 if tag.get('id').startswith('Vk') and api_function != '':
295 #if len(code_text_list) > 1 and code_text_list[1].startswith('Vk'):
296 self.struct_to_func_map[tag.get('id')] = api_function
297 print ("Added mapping from struct:%s to API:%s" % (tag.get('id'), api_function))
298
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600299 #elif tag.name == '{http://www.w3.org/1999/xhtml}div' and tag.get('class') == 'sidebar':
300 elif tag.name == 'div' and tag.get('class') is not None and tag['class'][0] == 'content':
301 #print("Parsing down a div content tag")
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600302 # parse down sidebar to check for valid usage cases
303 valid_usage = False
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700304 implicit = False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600305 for elem in tag.find_all(True):
306 #print(" elem is %s w/ string %s" % (elem.name, elem.string))
307 if elem.name == 'div' and None != elem.string and 'Valid Usage' in elem.string:
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600308 valid_usage = True
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600309 if '(Implicit)' in elem.string:
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700310 implicit = True
311 else:
312 implicit = False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600313 elif valid_usage and elem.name == 'li': # grab actual valid usage requirements
Tobin Ehlis2c932132017-05-19 16:32:15 -0600314 # Grab link
315 prev_link = elem.a.get('id')
316 if 'VUID' not in prev_link:
317 print ("Found VU link that doesn't have 'VUID':%s" % (prev_link))
318 sys.exit()
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600319 #print("I think this is a VU w/ elem.strings is %s" % (elem.strings))
320 error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(elem.strings).replace('\n', ' ').strip(), spec_url, prev_link)
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600321 # Some txt has multiple spaces so split on whitespace and join w/ single space
322 error_msg_str = " ".join(error_msg_str.split())
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600323 if error_msg_str in error_strings:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600324 print ("WARNING: SKIPPING adding repeat entry for string. Please review spec and file issue as appropriate. Repeat string is: %s" % (error_msg_str))
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600325 else:
326 error_strings.add(error_msg_str)
327 enum_str = "%s%05d" % (validation_error_enum_name, unique_enum_id)
328 # TODO : '\' chars in spec error messages are most likely bad spec txt that needs to be updated
Tobin Ehlise7560e72016-10-19 15:59:38 -0600329 self.val_error_dict[enum_str] = {}
330 self.val_error_dict[enum_str]['error_msg'] = error_msg_str.encode("ascii", "ignore").replace("\\", "/")
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600331 self.val_error_dict[enum_str]['api'] = api_function.encode("ascii", "ignore")
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700332 self.val_error_dict[enum_str]['implicit'] = False
Tobin Ehlis2c932132017-05-19 16:32:15 -0600333 self.val_error_dict[enum_str]['vuid_string'] = prev_link
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700334 if implicit:
335 self.val_error_dict[enum_str]['implicit'] = True
336 self.implicit_count = self.implicit_count + 1
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600337 unique_enum_id = unique_enum_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600338 #print ("Validation Error Dict has a total of %d unique errors and contents are:\n%s" % (unique_enum_id, self.val_error_dict))
Tobin Ehlis2c932132017-05-19 16:32:15 -0600339 # TEMP : override struct to api mapping with manual data
340 for struct in struct_to_api_map:
341 self.struct_to_func_map[struct] = struct_to_api_map[struct]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600342 print ("Validation Error Dict has a total of %d unique errors" % (unique_enum_id))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600343 def genHeader(self, header_file):
344 """Generate a header file based on the contents of a parsed spec"""
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600345 print ("Generating header %s..." % (header_file))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600346 file_contents = []
347 file_contents.append(self.copyright)
348 file_contents.append('\n#pragma once')
Mark Lobodzinski267a7cf2017-01-25 09:33:25 -0700349 file_contents.append('\n// Disable auto-formatting for generated file')
350 file_contents.append('// clang-format off')
351 file_contents.append('\n#include <unordered_map>')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600352 file_contents.append('\n// enum values for unique validation error codes')
353 file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below')
354 file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode')
355 file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function')
Tobin Ehlis387fd632016-12-08 13:32:05 -0700356 enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {\n VALIDATION_ERROR_UNDEFINED = -1,']
Tobin Ehlisbf98b692016-10-06 12:58:06 -0600357 error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{']
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800358 enum_value = 0
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600359 for enum in sorted(self.val_error_dict):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600360 #print ("Header enum is %s" % (enum))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800361 enum_value = int(enum.split('_')[-1])
362 enum_decl.append(' %s = %d,' % (enum, enum_value))
Tobin Ehlise7560e72016-10-19 15:59:38 -0600363 error_string_map.append(' {%s, "%s"},' % (enum, self.val_error_dict[enum]['error_msg']))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800364 enum_decl.append(' %sMAX_ENUM = %d,' % (validation_error_enum_name, enum_value + 1))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600365 enum_decl.append('};')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600366 error_string_map.append('};\n')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600367 file_contents.extend(enum_decl)
368 file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message')
369 file_contents.append('// The error message should be appended to the end of a custom error message that is passed')
370 file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function')
371 file_contents.extend(error_string_map)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600372 #print ("File contents: %s" % (file_contents))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600373 with open(header_file, "w") as outfile:
374 outfile.write("\n".join(file_contents))
375 def analyze(self):
376 """Print out some stats on the valid usage dict"""
377 # Create dict for # of occurences of identical strings
378 str_count_dict = {}
379 unique_id_count = 0
380 for enum in self.val_error_dict:
Tobin Ehlise7560e72016-10-19 15:59:38 -0600381 err_str = self.val_error_dict[enum]['error_msg']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600382 if err_str in str_count_dict:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600383 print ("Found repeat error string")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600384 str_count_dict[err_str] = str_count_dict[err_str] + 1
385 else:
386 str_count_dict[err_str] = 1
387 unique_id_count = unique_id_count + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600388 print ("Processed %d unique_ids" % (unique_id_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600389 repeat_string = 0
390 for es in str_count_dict:
391 if str_count_dict[es] > 1:
392 repeat_string = repeat_string + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600393 print ("String '%s' repeated %d times" % (es, repeat_string))
394 print ("Found %d repeat strings" % (repeat_string))
395 print ("Found %d implicit checks" % (self.implicit_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600396 def genDB(self, db_file):
Tobin Ehlis828781a2017-05-23 15:23:40 -0600397 """Generate a database of check_enum, check_coded?, testname, API, VUID_string, core|ext, error_string, notes"""
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600398 db_lines = []
399 # Write header for database file
400 db_lines.append("# This is a database file with validation error check information")
401 db_lines.append("# Comments are denoted with '#' char")
402 db_lines.append("# The format of the lines is:")
Tobin Ehlis828781a2017-05-23 15:23:40 -0600403 db_lines.append("# <error_enum>%s<check_implemented>%s<testname>%s<api>%s<vuid_string>%s<core|ext>%s<errormsg>%s<note>" % (self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter))
Mark Lobodzinski629d47b2016-10-18 13:34:58 -0600404 db_lines.append("# error_enum: Unique error enum for this check of format %s<uniqueid>" % validation_error_enum_name)
Mike Weiblenfe186122017-02-03 12:44:53 -0700405 db_lines.append("# check_implemented: 'Y' if check has been implemented in layers, or 'N' for not implemented")
Dave Houlton14f7e662017-05-17 13:25:53 -0600406 db_lines.append("# testname: Name of validation test for this check, 'Unknown' for unknown, 'None' if not implemented, or 'NotTestable' if cannot be implemented")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600407 db_lines.append("# api: Vulkan API function that this check is related to")
Tobin Ehlis2c932132017-05-19 16:32:15 -0600408 db_lines.append("# vuid_string: Unique string to identify this check")
Tobin Ehlis828781a2017-05-23 15:23:40 -0600409 db_lines.append("# core|ext: Either 'core' for core spec or some extension string that indicates the extension required for this VU to be relevant")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600410 db_lines.append("# errormsg: The unique error message for this check that includes spec language and link")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600411 db_lines.append("# note: Free txt field with any custom notes related to the check in question")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600412 for enum in sorted(self.val_error_dict):
Mike Weiblenfe186122017-02-03 12:44:53 -0700413 # Default check/test implementation status to N/Unknown, then update below if appropriate
414 implemented = 'N'
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600415 testname = 'Unknown'
Tobin Ehlis70980c02016-10-25 14:00:20 -0600416 note = ''
Tobin Ehlis828781a2017-05-23 15:23:40 -0600417 core_ext = 'core'
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700418 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600419 # If we have an existing db entry for this enum, use its implemented/testname values
420 if enum in self.error_db_dict:
421 implemented = self.error_db_dict[enum]['check_implemented']
422 testname = self.error_db_dict[enum]['testname']
Tobin Ehlis70980c02016-10-25 14:00:20 -0600423 note = self.error_db_dict[enum]['note']
Tobin Ehlis828781a2017-05-23 15:23:40 -0600424 core_ext = self.error_db_dict[enum]['ext']
425 self.val_error_dict[enum]['vuid_string'] = self.error_db_dict[enum]['vuid_string']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700426 if implicit and 'implicit' not in note: # add implicit note
427 if '' != note:
428 note = "implicit, %s" % (note)
429 else:
430 note = "implicit"
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600431 #print ("delimiter: %s, id: %s, str: %s" % (self.delimiter, enum, self.val_error_dict[enum])
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600432 # No existing entry so default to N for implemented and None for testname
Tobin Ehlis828781a2017-05-23 15:23:40 -0600433 db_lines.append("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % (enum, self.delimiter, implemented, self.delimiter, testname, self.delimiter, self.val_error_dict[enum]['api'], self.delimiter, self.val_error_dict[enum]['vuid_string'], self.delimiter, core_ext, self.delimiter, self.val_error_dict[enum]['error_msg'], self.delimiter, note))
Tobin Ehlisaf75f7c2016-10-31 11:10:38 -0600434 db_lines.append("\n") # newline at end of file
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600435 print ("Generating database file %s" % (db_file))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600436 with open(db_file, "w") as outfile:
437 outfile.write("\n".join(db_lines))
438 def readDB(self, db_file):
Tobin Ehlis2c932132017-05-19 16:32:15 -0600439 """Read a db file into a dict, refer to genDB function above for format of each line"""
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600440 db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
441 max_id = 0
442 with open(db_file, "r") as infile:
443 for line in infile:
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600444 line = line.strip()
Tobin Ehlisf4245cb2016-10-31 07:55:19 -0600445 if line.startswith('#') or '' == line:
446 continue
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600447 db_line = line.split(self.delimiter)
Tobin Ehlis828781a2017-05-23 15:23:40 -0600448 if len(db_line) != 8:
449 print ("ERROR: Bad database line doesn't have 8 elements: %s" % (line))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600450 error_enum = db_line[0]
451 implemented = db_line[1]
452 testname = db_line[2]
Tobin Ehlis70980c02016-10-25 14:00:20 -0600453 api = db_line[3]
Tobin Ehlis828781a2017-05-23 15:23:40 -0600454 vuid_str = db_line[4]
455 core_ext = db_line[5]
456 error_str = db_line[6]
457 note = db_line[7]
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600458 db_dict[error_enum] = error_str
459 # Also read complete database contents into our class var for later use
460 self.error_db_dict[error_enum] = {}
461 self.error_db_dict[error_enum]['check_implemented'] = implemented
462 self.error_db_dict[error_enum]['testname'] = testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600463 self.error_db_dict[error_enum]['api'] = api
Tobin Ehlis828781a2017-05-23 15:23:40 -0600464 self.error_db_dict[error_enum]['vuid_string'] = vuid_str
465 self.error_db_dict[error_enum]['core_ext'] = core_ext
466 self.error_db_dict[error_enum]['error_msg'] = error_str
Tobin Ehlis70980c02016-10-25 14:00:20 -0600467 self.error_db_dict[error_enum]['note'] = note
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600468 unique_id = int(db_line[0].split('_')[-1])
469 if unique_id > max_id:
470 max_id = unique_id
471 return (db_dict, max_id)
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600472 # This is a helper function to do bookkeeping on data structs when comparing original
473 # error ids to current error ids
474 # It tracks all updated enums in mapped_enums and removes those enums from any lists
475 # in the no_link and core dicts
476 def _updateMappedEnum(self, mapped_enums, enum):
477 mapped_enums.add(enum)
478 # When looking for ID to map, we favor sequences so track last ID mapped
479 self.last_mapped_id = int(enum.split('_')[-1])
480 for msg in self.orig_no_link_msg_dict:
481 if enum in self.orig_no_link_msg_dict[msg]:
482 self.orig_no_link_msg_dict[msg].remove(enum)
483 for msg in self.orig_core_msg_dict:
484 if enum in self.orig_core_msg_dict[msg]:
485 self.orig_core_msg_dict[msg].remove(enum)
486 return mapped_enums
487 # Check all ids in given id list to see if one is in sequence from last mapped id
488 def findSeqID(self, id_list):
489 next_seq_id = self.last_mapped_id + 1
490 for map_id in id_list:
491 id_num = int(map_id.split('_')[-1])
492 if id_num == next_seq_id:
493 return True
494 return False
495 # Use the next ID in sequence. This should only be called if findSeqID() just returned True
496 def useSeqID(self, id_list, mapped_enums):
497 next_seq_id = self.last_mapped_id + 1
498 mapped_id = ''
499 for map_id in id_list:
500 id_num = int(map_id.split('_')[-1])
501 if id_num == next_seq_id:
502 mapped_id = map_id
503 self._updateMappedEnum(mapped_enums, mapped_id)
504 return (mapped_enums, mapped_id)
505 return (mapped_enums, mapped_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600506 # Compare unique ids from original database to data generated from updated spec
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600507 # First, make 3 separate mappings of original error messages:
508 # 1. Map the full error message to its id. There should only be 1 ID per full message (orig_full_msg_dict)
509 # 2. Map the intial portion of the message w/o link to list of IDs. There May be a little aliasing here (orig_no_link_msg_dict)
510 # 3. Map the core spec message w/o link or section info to list of IDs. There will be lots of aliasing here (orig_core_msg_dict)
511 # Also store a set of all IDs that have been mapped to that will serve 2 purposes:
512 # 1. Pull IDs out of the above dicts as they're remapped since we know they won't be used
513 # 2. Make sure that we don't re-use an ID
514 # The general algorithm for remapping from new IDs to old IDs is:
515 # 1. If there is a user-specified remapping, use that above all else
516 # 2. Elif the new error message hits in orig_full_msg_dict then use that ID
517 # 3. Elif the new error message hits orig_no_link_msg_dict then
518 # a. If only a single ID, use it
519 # b. Elif multiple IDs & one matches last used ID in sequence, use it
520 # c. Else assign a new ID and flag for manual remapping
521 # 4. Elif the new error message hits orig_core_msg_dict then
522 # a. If only a single ID, use it
523 # b. Elif multiple IDs & one matches last used ID in sequence, use it
524 # c. Else assign a new ID and flag for manual remapping
525 # 5. Else - No matches use a new ID
Tobin Ehlise7560e72016-10-19 15:59:38 -0600526 def compareDB(self, orig_error_msg_dict, max_id):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600527 """Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec"""
528 # First create reverse dicts of err_strings to IDs
529 next_id = max_id + 1
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600530 ids_parsed = 0
531 mapped_enums = set() # store all enums that have been mapped to avoid re-use
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600532 # Create an updated dict in-place that will be assigned to self.val_error_dict when done
533 updated_val_error_dict = {}
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600534 # Create a few separate mappings of error msg formats to associated ID(s)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600535 for enum in orig_error_msg_dict:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600536 api = self.error_db_dict[enum]['api']
537 original_full_msg = orig_error_msg_dict[enum]
538 orig_no_link_msg = "%s,%s" % (api, original_full_msg.split('(https', 1)[0])
539 orig_core_msg = "%s,%s" % (api, orig_no_link_msg.split(' which states ', 1)[-1])
540 orig_core_msg_period = "%s.' " % (orig_core_msg[:-2])
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600541 print ("Orig core msg:%s\nOrig cw/o per:%s" % (orig_core_msg, orig_core_msg_period))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600542
543 # First store mapping of full error msg to ID, shouldn't have duplicates
544 if original_full_msg in self.orig_full_msg_dict:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600545 print ("ERROR: Found duplicate full msg in original full error messages: %s" % (original_full_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600546 self.orig_full_msg_dict[original_full_msg] = enum
547 # Now map API,no_link_msg to list of IDs
548 if orig_no_link_msg in self.orig_no_link_msg_dict:
549 self.orig_no_link_msg_dict[orig_no_link_msg].append(enum)
550 else:
551 self.orig_no_link_msg_dict[orig_no_link_msg] = [enum]
552 # Finally map API,core_msg to list of IDs
553 if orig_core_msg in self.orig_core_msg_dict:
554 self.orig_core_msg_dict[orig_core_msg].append(enum)
555 else:
556 self.orig_core_msg_dict[orig_core_msg] = [enum]
557 if orig_core_msg_period in self.orig_core_msg_dict:
558 self.orig_core_msg_dict[orig_core_msg_period].append(enum)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600559 print ("Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600560 else:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600561 print ("Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600562 self.orig_core_msg_dict[orig_core_msg_period] = [enum]
563 # Also capture all enums that have a test and/or implementation
Dave Houlton14f7e662017-05-17 13:25:53 -0600564 if self.error_db_dict[enum]['check_implemented'] == 'Y' or self.error_db_dict[enum]['testname'] not in ['None','Unknown','NotTestable']:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600565 print ("Recording %s with implemented value %s and testname %s" % (enum, self.error_db_dict[enum]['check_implemented'], self.error_db_dict[enum]['testname']))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600566 self.orig_test_imp_enums.add(enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600567 # Values to be used for the update dict
568 update_enum = ''
569 update_msg = ''
570 update_api = ''
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600571 # Now parse through new dict and figure out what to do with non-matching things
572 for enum in sorted(self.val_error_dict):
573 ids_parsed = ids_parsed + 1
574 enum_list = enum.split('_') # grab sections of enum for use below
Tobin Ehlise7560e72016-10-19 15:59:38 -0600575 # Default update values to be the same
576 update_enum = enum
577 update_msg = self.val_error_dict[enum]['error_msg']
578 update_api = self.val_error_dict[enum]['api']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700579 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlis2c932132017-05-19 16:32:15 -0600580 vuid_string = self.val_error_dict[enum]['vuid_string']
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600581 new_full_msg = update_msg
582 new_no_link_msg = "%s,%s" % (update_api, new_full_msg.split('(https', 1)[0])
583 new_core_msg = "%s,%s" % (update_api, new_no_link_msg.split(' which states ', 1)[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600584 # Any user-forced remap takes precendence
585 if enum_list[-1] in remap_dict:
586 enum_list[-1] = remap_dict[enum_list[-1]]
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600587 self.last_mapped_id = int(enum_list[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600588 new_enum = "_".join(enum_list)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600589 print ("NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum))
590 mapped_enums = self._updateMappedEnum(mapped_enums, new_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600591 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600592 elif new_full_msg in self.orig_full_msg_dict:
593 orig_enum = self.orig_full_msg_dict[new_full_msg]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600594 print ("Found exact match for full error msg so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600595 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
596 update_enum = orig_enum
597 elif new_no_link_msg in self.orig_no_link_msg_dict:
598 # Try to get single ID to map to from no_link matches
599 if len(self.orig_no_link_msg_dict[new_no_link_msg]) == 1: # Only 1 id, use it!
600 orig_enum = self.orig_no_link_msg_dict[new_no_link_msg][0]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600601 print ("Found no-link err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600602 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
603 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600604 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600605 if self.findSeqID(self.orig_no_link_msg_dict[new_no_link_msg]): # If we have an id in sequence, use it!
606 (mapped_enums, update_enum) = self.useSeqID(self.orig_no_link_msg_dict[new_no_link_msg], mapped_enums)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600607 print ("Found no-link err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600608 else:
609 enum_list[-1] = "%05d" % (next_id)
610 new_enum = "_".join(enum_list)
611 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600612 print ("Found no-link msg match but have multiple matched IDs w/o a sequence ID, updating ID %s to unique ID %s for msg %s" % (enum, new_enum, new_no_link_msg))
Tobin Ehlise7560e72016-10-19 15:59:38 -0600613 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600614 elif new_core_msg in self.orig_core_msg_dict:
615 # Do similar stuff here
616 if len(self.orig_core_msg_dict[new_core_msg]) == 1:
617 orig_enum = self.orig_core_msg_dict[new_core_msg][0]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600618 print ("Found core err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600619 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
620 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600621 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600622 if self.findSeqID(self.orig_core_msg_dict[new_core_msg]):
623 (mapped_enums, update_enum) = self.useSeqID(self.orig_core_msg_dict[new_core_msg], mapped_enums)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600624 print ("Found core err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600625 else:
626 enum_list[-1] = "%05d" % (next_id)
627 new_enum = "_".join(enum_list)
628 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600629 print ("Found core msg match but have multiple matched IDs w/o a sequence ID, updating ID %s to unique ID %s for msg %s" % (enum, new_enum, new_no_link_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600630 update_enum = new_enum
631 # This seems to be a new error so need to pick it up from end of original unique ids & flag for review
632 else:
633 enum_list[-1] = "%05d" % (next_id)
634 new_enum = "_".join(enum_list)
635 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600636 print ("Completely new id and error code, update new id from %s to unique %s for core message:%s" % (enum, new_enum, new_core_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600637 update_enum = new_enum
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600638 if update_enum in updated_val_error_dict:
639 print ("ERROR: About to OVERWRITE entry for %s" % update_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600640 updated_val_error_dict[update_enum] = {}
641 updated_val_error_dict[update_enum]['error_msg'] = update_msg
642 updated_val_error_dict[update_enum]['api'] = update_api
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700643 updated_val_error_dict[update_enum]['implicit'] = implicit
Tobin Ehlis2c932132017-05-19 16:32:15 -0600644 updated_val_error_dict[update_enum]['vuid_string'] = vuid_string
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600645 # Assign parsed dict to be the updated dict based on db compare
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600646 print ("In compareDB parsed %d entries" % (ids_parsed))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600647 return updated_val_error_dict
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600648
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600649 def validateUpdateDict(self, update_dict):
650 """Compare original dict vs. update dict and make sure that all of the checks are still there"""
651 # Currently just make sure that the same # of checks as the original checks are there
652 #orig_ids = {}
653 orig_id_count = len(self.val_error_dict)
654 #update_ids = {}
655 update_id_count = len(update_dict)
656 if orig_id_count != update_id_count:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600657 print ("Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600658 return False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600659 print ("Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600660 # Now flag any original dict enums that had tests and/or checks that are missing from updated
661 for enum in update_dict:
662 if enum in self.orig_test_imp_enums:
663 self.orig_test_imp_enums.remove(enum)
664 if len(self.orig_test_imp_enums) > 0:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600665 print ("TODO: Have some enums with tests and/or checks implemented that are missing in update:")
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600666 for enum in sorted(self.orig_test_imp_enums):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600667 print ("\t%s") % enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600668 return True
669 # TODO : include some more analysis
670
671# User passes in arg of form <new_id1>-<old_id1>[,count1]:<new_id2>-<old_id2>[,count2]:...
672# new_id# = the new enum id that was assigned to an error
673# old_id# = the previous enum id that was assigned to the same error
674# [,count#] = The number of ids to remap starting at new_id#=old_id# and ending at new_id[#+count#-1]=old_id[#+count#-1]
675# If not supplied, then ,1 is assumed, which will only update a single id
676def updateRemapDict(remap_string):
677 """Set up global remap_dict based on user input"""
678 remap_list = remap_string.split(":")
679 for rmap in remap_list:
680 count = 1 # Default count if none supplied
681 id_count_list = rmap.split(',')
682 if len(id_count_list) > 1:
683 count = int(id_count_list[1])
684 new_old_id_list = id_count_list[0].split('-')
685 for offset in range(count):
686 remap_dict["%05d" % (int(new_old_id_list[0]) + offset)] = "%05d" % (int(new_old_id_list[1]) + offset)
687 for new_id in sorted(remap_dict):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600688 print ("Set to remap new id %s to old id %s" % (new_id, remap_dict[new_id]))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600689
690if __name__ == "__main__":
691 i = 1
692 use_online = True # Attempt to grab spec from online by default
693 update_option = False
694 while (i < len(sys.argv)):
695 arg = sys.argv[i]
696 i = i + 1
697 if (arg == '-spec'):
698 spec_filename = sys.argv[i]
699 # If user specifies local specfile, skip online
700 use_online = False
701 i = i + 1
Tobin Ehlis828781a2017-05-23 15:23:40 -0600702 elif (arg == '-json-file'):
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600703 json_filename = sys.argv[i]
704 i = i + 1
Tobin Ehlis828781a2017-05-23 15:23:40 -0600705 elif (arg == '-json'):
706 read_json = True
Tobin Ehlis2c932132017-05-19 16:32:15 -0600707 elif (arg == '-json-compare'):
708 json_compare = True
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600709 elif (arg == '-out'):
710 out_filename = sys.argv[i]
711 i = i + 1
712 elif (arg == '-gendb'):
713 gen_db = True
714 # Set filename if supplied, else use default
715 if i < len(sys.argv) and not sys.argv[i].startswith('-'):
716 db_filename = sys.argv[i]
717 i = i + 1
718 elif (arg == '-compare'):
719 db_filename = sys.argv[i]
720 spec_compare = True
721 i = i + 1
722 elif (arg == '-update'):
723 update_option = True
724 spec_compare = True
725 gen_db = True
726 elif (arg == '-remap'):
727 updateRemapDict(sys.argv[i])
728 i = i + 1
729 elif (arg in ['-help', '-h']):
730 printHelp()
731 sys.exit()
732 if len(remap_dict) > 1 and not update_option:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600733 print ("ERROR: '-remap' option can only be used along with '-update' option. Exiting.")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600734 sys.exit()
735 spec = Specification()
Tobin Ehlis2c932132017-05-19 16:32:15 -0600736 spec.soupLoadFile(use_online, spec_filename)
737 spec.analyze()
Tobin Ehlis828781a2017-05-23 15:23:40 -0600738 if read_json:
739 spec.readJSON()
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600740 spec.parseJSON()
Tobin Ehlis2c932132017-05-19 16:32:15 -0600741 #sys.exit()
742 if (json_compare):
743 # Read in current spec info from db file
744 (orig_err_msg_dict, max_id) = spec.readDB(db_filename)
745 spec.compareJSON()
746 print ("Found %d missing db entries in json db" % (spec.json_missing))
747 print ("Found %d duplicate json entries" % (spec.duplicate_json_key_count))
Tobin Ehlis828781a2017-05-23 15:23:40 -0600748 spec.genDB("json_vk_validation_error_database.txt")
Tobin Ehlis98d109a2017-05-11 14:42:38 -0600749 sys.exit()
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600750 if (spec_compare):
751 # Read in old spec info from db file
Tobin Ehlise7560e72016-10-19 15:59:38 -0600752 (orig_err_msg_dict, max_id) = spec.readDB(db_filename)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600753 # New spec data should already be read into self.val_error_dict
Tobin Ehlise7560e72016-10-19 15:59:38 -0600754 updated_dict = spec.compareDB(orig_err_msg_dict, max_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600755 update_valid = spec.validateUpdateDict(updated_dict)
756 if update_valid:
757 spec.updateDict(updated_dict)
758 else:
759 sys.exit()
760 if (gen_db):
761 spec.genDB(db_filename)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600762 print ("Writing out file (-out) to '%s'" % (out_filename))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600763 spec.genHeader(out_filename)
764
765##### Example dataset
766# <div class="sidebar">
767# <div class="titlepage">
768# <div>
769# <div>
770# <p class="title">
771# <strong>Valid Usage</strong> # When we get to this guy, we know we're under interesting sidebar
772# </p>
773# </div>
774# </div>
775# </div>
776# <div class="itemizedlist">
777# <ul class="itemizedlist" style="list-style-type: disc; ">
778# <li class="listitem">
779# <em class="parameter">
780# <code>device</code>
781# </em>
782# <span class="normative">must</span> be a valid
783# <code class="code">VkDevice</code> handle
784# </li>
785# <li class="listitem">
786# <em class="parameter">
787# <code>commandPool</code>
788# </em>
789# <span class="normative">must</span> be a valid
790# <code class="code">VkCommandPool</code> handle
791# </li>
792# <li class="listitem">
793# <em class="parameter">
794# <code>flags</code>
795# </em>
796# <span class="normative">must</span> be a valid combination of
797# <code class="code">
798# <a class="link" href="#VkCommandPoolResetFlagBits">VkCommandPoolResetFlagBits</a>
799# </code> values
800# </li>
801# <li class="listitem">
802# <em class="parameter">
803# <code>commandPool</code>
804# </em>
805# <span class="normative">must</span> have been created, allocated, or retrieved from
806# <em class="parameter">
807# <code>device</code>
808# </em>
809# </li>
810# <li class="listitem">All
811# <code class="code">VkCommandBuffer</code>
812# objects allocated from
813# <em class="parameter">
814# <code>commandPool</code>
815# </em>
816# <span class="normative">must</span> not currently be pending execution
817# </li>
818# </ul>
819# </div>
820# </div>
821##### Second example dataset
822# <div class="sidebar">
823# <div class="titlepage">
824# <div>
825# <div>
826# <p class="title">
827# <strong>Valid Usage</strong>
828# </p>
829# </div>
830# </div>
831# </div>
832# <div class="itemizedlist">
833# <ul class="itemizedlist" style="list-style-type: disc; ">
834# <li class="listitem">The <em class="parameter"><code>queueFamilyIndex</code></em> member of any given element of <em class="parameter"><code>pQueueCreateInfos</code></em> <span class="normative">must</span> be unique within <em class="parameter"><code>pQueueCreateInfos</code></em>
835# </li>
836# </ul>
837# </div>
838# </div>
839# <div class="sidebar">
840# <div class="titlepage">
841# <div>
842# <div>
843# <p class="title">
844# <strong>Valid Usage (Implicit)</strong>
845# </p>
846# </div>
847# </div>
848# </div>
849# <div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
850#<em class="parameter"><code>sType</code></em> <span class="normative">must</span> be <code class="code">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</code>
851#</li><li class="listitem">
852#<em class="parameter"><code>pNext</code></em> <span class="normative">must</span> be <code class="literal">NULL</code>
853#</li><li class="listitem">
854#<em class="parameter"><code>flags</code></em> <span class="normative">must</span> be <code class="literal">0</code>
855#</li><li class="listitem">
856#<em class="parameter"><code>pQueueCreateInfos</code></em> <span class="normative">must</span> be a pointer to an array of <em class="parameter"><code>queueCreateInfoCount</code></em> valid <code class="code">VkDeviceQueueCreateInfo</code> structures
Mark Lobodzinski267a7cf2017-01-25 09:33:25 -0700857#</li>