blob: 326bf371b4f9cc9ae60b14a848a5f7b1d38463ff [file] [log] [blame]
Tobin Ehlis5ade0692016-10-05 17:18:15 -06001#!/usr/bin/python3 -i
2
3import sys
4import xml.etree.ElementTree as etree
5import urllib2
Tobin Ehlis5ade0692016-10-05 17:18:15 -06006
7#############################
8# spec.py script
9#
10# Overview - this script is intended to generate validation error codes and message strings from the xhtml version of
11# the specification. In addition to generating the header file, it provides a number of corrollary services to aid in
12# generating/updating the header.
13#
14# Ideal flow - Not there currently, but the ideal flow for this script would be that you run the script, it pulls the
15# latest spec, compares it to the current set of generated error codes, and makes any updates as needed
16#
17# Current flow - the current flow acheives all of the ideal flow goals, but with more steps than are desired
18# 1. Get the spec - right now spec has to be manually generated or pulled from the web
19# 2. Generate header from spec - This is done in a single command line
20# 3. Generate database file from spec - Can be done along with step #2 above, the database file contains a list of
21# all error enums and message strings, along with some other info on if those errors are implemented/tested
22# 4. Update header using a given database file as the root and a new spec file as goal - This makes sure that existing
23# errors keep the same enum identifier while also making sure that new errors get a unique_id that continues on
24# from the end of the previous highest unique_id.
25#
26# TODO:
27# 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 -060028#
29#############################
30
31
32spec_filename = "vkspec.html" # can override w/ '-spec <filename>' option
33out_filename = "vk_validation_error_messages.h" # can override w/ '-out <filename>' option
34db_filename = "vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option
35gen_db = False # set to True when '-gendb <filename>' option provided
36spec_compare = False # set to True with '-compare <db_filename>' option
37# 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 -060038#old_spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html"
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060039spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060040# After the custom validation error message, this is the prefix for the standard message that includes the
41# spec valid usage language as well as the link to nearest section of spec to that language
42error_msg_prefix = "For more information refer to Vulkan Spec Section "
43ns = {'ns': 'http://www.w3.org/1999/xhtml'}
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060044validation_error_enum_name = "VALIDATION_ERROR_"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060045# Dict of new enum values that should be forced to remap to old handles, explicitly set by -remap option
46remap_dict = {}
47
48def printHelp():
49 print "Usage: python spec.py [-spec <specfile.html>] [-out <headerfile.h>] [-gendb <databasefile.txt>] [-compare <databasefile.txt>] [-update] [-remap <new_id-old_id,count>] [-help]"
50 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"
51 print " Default specfile is from online at %s" % (spec_url)
52 print " Default headerfile is %s" % (out_filename)
53 print " Default databasefile is %s" % (db_filename)
54 print "\nIf '-gendb' option is specified then a database file is generated to default file or <databasefile.txt> if supplied. The database file stores"
55 print " the list of enums and their error messages."
56 print "\nIf '-compare' option is specified then the given database file will be read in as the baseline for generating the new specfile"
57 print "\nIf '-update' option is specified this triggers the master flow to automate updating header and database files using default db file as baseline"
58 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."
59 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"
60 print " option. Starting at newid and remapping to oldid, count ids will be remapped. Default count is '1' and use ':' to specify multiple remappings."
61
62class Specification:
63 def __init__(self):
64 self.tree = None
Tobin Ehlise7560e72016-10-19 15:59:38 -060065 self.val_error_dict = {} # string for enum is key that references 'error_msg' and 'api'
Tobin Ehlis5ade0692016-10-05 17:18:15 -060066 self.error_db_dict = {} # dict of previous error values read in from database file
67 self.delimiter = '~^~' # delimiter for db file
Tobin Ehlis2a176b12017-01-11 16:18:20 -070068 self.implicit_count = 0
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060069 # Global dicts used for tracking spec updates from old to new VUs
70 self.orig_full_msg_dict = {} # Original full error msg to ID mapping
71 self.orig_no_link_msg_dict = {} # Pair of API,Original msg w/o spec link to ID list mapping
72 self.orig_core_msg_dict = {} # Pair of API,Original core msg (no link or section) to ID list mapping
73 self.last_mapped_id = -10 # start as negative so we don't hit an accidental sequence
74 self.orig_test_imp_enums = set() # Track old enums w/ tests and/or implementation to flag any that aren't carried fwd
Tobin Ehlis5ade0692016-10-05 17:18:15 -060075 self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */
76
77/*
78 * Vulkan
79 *
80 * Copyright (c) 2016 Google Inc.
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060081 * Copyright (c) 2016 LunarG, Inc.
Tobin Ehlis5ade0692016-10-05 17:18:15 -060082 *
83 * Licensed under the Apache License, Version 2.0 (the "License");
84 * you may not use this file except in compliance with the License.
85 * You may obtain a copy of the License at
86 *
87 * http://www.apache.org/licenses/LICENSE-2.0
88 *
89 * Unless required by applicable law or agreed to in writing, software
90 * distributed under the License is distributed on an "AS IS" BASIS,
91 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
92 * See the License for the specific language governing permissions and
93 * limitations under the License.
94 *
95 * Author: Tobin Ehlis <tobine@google.com>
96 */"""
97 def _checkInternetSpec(self):
98 """Verify that we can access the spec online"""
99 try:
100 online = urllib2.urlopen(spec_url,timeout=1)
101 return True
102 except urllib2.URLError as err:
103 return False
104 return False
105 def loadFile(self, online=True, spec_file=spec_filename):
106 """Load an API registry XML file into a Registry object and parse it"""
107 # Check if spec URL is available
108 if (online and self._checkInternetSpec()):
109 print "Using spec from online at %s" % (spec_url)
110 self.tree = etree.parse(urllib2.urlopen(spec_url))
111 else:
112 print "Using local spec %s" % (spec_file)
113 self.tree = etree.parse(spec_file)
114 #self.tree.write("tree_output.xhtml")
115 #self.tree = etree.parse("tree_output.xhtml")
116 self.parseTree()
117 def updateDict(self, updated_dict):
118 """Assign internal dict to use updated_dict"""
119 self.val_error_dict = updated_dict
120 def parseTree(self):
121 """Parse the registry Element, once created"""
122 print "Parsing spec file..."
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600123 unique_enum_id = 0
124 self.root = self.tree.getroot()
125 #print "ROOT: %s" % self.root
126 prev_heading = '' # Last seen section heading or sub-heading
127 prev_link = '' # Last seen link id within the spec
Tobin Ehlise7560e72016-10-19 15:59:38 -0600128 api_function = '' # API call that a check appears under
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600129 error_strings = set() # Flag any exact duplicate error strings and skip them
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700130 implicit_count = 0
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600131 for tag in self.root.iter(): # iterate down tree
132 # Grab most recent section heading and link
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800133 if tag.tag in ['h2', 'h3', 'h4']:
134 #if tag.get('class') != 'title':
135 # continue
136 print "Found heading %s" % (tag.tag)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600137 prev_heading = "".join(tag.itertext())
138 # Insert a space between heading number & title
139 sh_list = prev_heading.rsplit('.', 1)
140 prev_heading = '. '.join(sh_list)
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800141 prev_link = tag.get('id')
142 print "Set prev_heading %s to have link of %s" % (prev_heading.encode("ascii", "ignore"), prev_link.encode("ascii", "ignore"))
143 elif tag.tag == 'a': # grab any intermediate links
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600144 if tag.get('id') != None:
145 prev_link = tag.get('id')
Tobin Ehlis16b159c2016-10-25 06:33:27 -0600146 #print "Updated prev link to %s" % (prev_link)
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800147 elif tag.tag == 'div' and tag.get('class') == 'listingblock':
Tobin Ehlise7560e72016-10-19 15:59:38 -0600148 # Check and see if this is API function
149 code_text = "".join(tag.itertext()).replace('\n', '')
150 code_text_list = code_text.split()
151 if len(code_text_list) > 1 and code_text_list[1].startswith('vk'):
152 api_function = code_text_list[1].strip('(')
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800153 print "Found API function: %s" % (api_function)
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600154 prev_link = api_function
155 print "Updated prev link to %s" % (prev_link)
156 elif tag.get('id') != None:
157 prev_link = tag.get('id')
158 print "Updated prev link to %s" % (prev_link)
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800159 #elif tag.tag == '{http://www.w3.org/1999/xhtml}div' and tag.get('class') == 'sidebar':
160 elif tag.tag == 'div' and tag.get('class') == 'content':
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600161 # parse down sidebar to check for valid usage cases
162 valid_usage = False
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700163 implicit = False
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600164 for elem in tag.iter():
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800165 if elem.tag == 'div' and None != elem.text and 'Valid Usage' in elem.text:
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600166 valid_usage = True
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700167 if '(Implicit)' in elem.text:
168 implicit = True
169 else:
170 implicit = False
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800171 elif valid_usage and elem.tag == 'li': # grab actual valid usage requirements
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600172 error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(elem.itertext()).replace('\n', ' ').strip(), spec_url, prev_link)
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600173 # Some txt has multiple spaces so split on whitespace and join w/ single space
174 error_msg_str = " ".join(error_msg_str.split())
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600175 if error_msg_str in error_strings:
176 print "WARNING: SKIPPING adding repeat entry for string. Please review spec and file issue as appropriate. Repeat string is: %s" % (error_msg_str)
177 else:
178 error_strings.add(error_msg_str)
179 enum_str = "%s%05d" % (validation_error_enum_name, unique_enum_id)
180 # TODO : '\' chars in spec error messages are most likely bad spec txt that needs to be updated
Tobin Ehlise7560e72016-10-19 15:59:38 -0600181 self.val_error_dict[enum_str] = {}
182 self.val_error_dict[enum_str]['error_msg'] = error_msg_str.encode("ascii", "ignore").replace("\\", "/")
183 self.val_error_dict[enum_str]['api'] = api_function
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700184 self.val_error_dict[enum_str]['implicit'] = False
185 if implicit:
186 self.val_error_dict[enum_str]['implicit'] = True
187 self.implicit_count = self.implicit_count + 1
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600188 unique_enum_id = unique_enum_id + 1
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600189 #print "Validation Error Dict has a total of %d unique errors and contents are:\n%s" % (unique_enum_id, self.val_error_dict)
190 def genHeader(self, header_file):
191 """Generate a header file based on the contents of a parsed spec"""
192 print "Generating header %s..." % (header_file)
193 file_contents = []
194 file_contents.append(self.copyright)
195 file_contents.append('\n#pragma once')
Mark Lobodzinski267a7cf2017-01-25 09:33:25 -0700196 file_contents.append('\n// Disable auto-formatting for generated file')
197 file_contents.append('// clang-format off')
198 file_contents.append('\n#include <unordered_map>')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600199 file_contents.append('\n// enum values for unique validation error codes')
200 file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below')
201 file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode')
202 file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function')
Tobin Ehlis387fd632016-12-08 13:32:05 -0700203 enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {\n VALIDATION_ERROR_UNDEFINED = -1,']
Tobin Ehlisbf98b692016-10-06 12:58:06 -0600204 error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{']
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800205 enum_value = 0
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600206 for enum in sorted(self.val_error_dict):
207 #print "Header enum is %s" % (enum)
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800208 enum_value = int(enum.split('_')[-1])
209 enum_decl.append(' %s = %d,' % (enum, enum_value))
Tobin Ehlise7560e72016-10-19 15:59:38 -0600210 error_string_map.append(' {%s, "%s"},' % (enum, self.val_error_dict[enum]['error_msg']))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800211 enum_decl.append(' %sMAX_ENUM = %d,' % (validation_error_enum_name, enum_value + 1))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600212 enum_decl.append('};')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600213 error_string_map.append('};\n')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600214 file_contents.extend(enum_decl)
215 file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message')
216 file_contents.append('// The error message should be appended to the end of a custom error message that is passed')
217 file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function')
218 file_contents.extend(error_string_map)
219 #print "File contents: %s" % (file_contents)
220 with open(header_file, "w") as outfile:
221 outfile.write("\n".join(file_contents))
222 def analyze(self):
223 """Print out some stats on the valid usage dict"""
224 # Create dict for # of occurences of identical strings
225 str_count_dict = {}
226 unique_id_count = 0
227 for enum in self.val_error_dict:
Tobin Ehlise7560e72016-10-19 15:59:38 -0600228 err_str = self.val_error_dict[enum]['error_msg']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600229 if err_str in str_count_dict:
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600230 print "Found repeat error string"
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600231 str_count_dict[err_str] = str_count_dict[err_str] + 1
232 else:
233 str_count_dict[err_str] = 1
234 unique_id_count = unique_id_count + 1
235 print "Processed %d unique_ids" % (unique_id_count)
236 repeat_string = 0
237 for es in str_count_dict:
238 if str_count_dict[es] > 1:
239 repeat_string = repeat_string + 1
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600240 print "String '%s' repeated %d times" % (es, repeat_string)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600241 print "Found %d repeat strings" % (repeat_string)
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700242 print "Found %d implicit checks" % (self.implicit_count)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600243 def genDB(self, db_file):
244 """Generate a database of check_enum, check_coded?, testname, error_string"""
245 db_lines = []
246 # Write header for database file
247 db_lines.append("# This is a database file with validation error check information")
248 db_lines.append("# Comments are denoted with '#' char")
249 db_lines.append("# The format of the lines is:")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600250 db_lines.append("# <error_enum>%s<check_implemented>%s<testname>%s<api>%s<errormsg>%s<note>" % (self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter))
Mark Lobodzinski629d47b2016-10-18 13:34:58 -0600251 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 -0700252 db_lines.append("# check_implemented: 'Y' if check has been implemented in layers, or 'N' for not implemented")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600253 db_lines.append("# testname: Name of validation test for this check, 'Unknown' for unknown, or 'None' if not implmented")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600254 db_lines.append("# api: Vulkan API function that this check is related to")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600255 db_lines.append("# errormsg: The unique error message for this check that includes spec language and link")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600256 db_lines.append("# note: Free txt field with any custom notes related to the check in question")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600257 for enum in sorted(self.val_error_dict):
Mike Weiblenfe186122017-02-03 12:44:53 -0700258 # Default check/test implementation status to N/Unknown, then update below if appropriate
259 implemented = 'N'
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600260 testname = 'Unknown'
Tobin Ehlis70980c02016-10-25 14:00:20 -0600261 note = ''
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700262 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600263 # If we have an existing db entry for this enum, use its implemented/testname values
264 if enum in self.error_db_dict:
265 implemented = self.error_db_dict[enum]['check_implemented']
266 testname = self.error_db_dict[enum]['testname']
Tobin Ehlis70980c02016-10-25 14:00:20 -0600267 note = self.error_db_dict[enum]['note']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700268 if implicit and 'implicit' not in note: # add implicit note
269 if '' != note:
270 note = "implicit, %s" % (note)
271 else:
272 note = "implicit"
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600273 #print "delimiter: %s, id: %s, str: %s" % (self.delimiter, enum, self.val_error_dict[enum])
274 # No existing entry so default to N for implemented and None for testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600275 db_lines.append("%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]['error_msg'], self.delimiter, note))
Tobin Ehlisaf75f7c2016-10-31 11:10:38 -0600276 db_lines.append("\n") # newline at end of file
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600277 print "Generating database file %s" % (db_file)
278 with open(db_file, "w") as outfile:
279 outfile.write("\n".join(db_lines))
280 def readDB(self, db_file):
281 """Read a db file into a dict, format of each line is <enum><implemented Y|N?><testname><errormsg>"""
282 db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
283 max_id = 0
284 with open(db_file, "r") as infile:
285 for line in infile:
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600286 line = line.strip()
Tobin Ehlisf4245cb2016-10-31 07:55:19 -0600287 if line.startswith('#') or '' == line:
288 continue
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600289 db_line = line.split(self.delimiter)
Tobin Ehlis70980c02016-10-25 14:00:20 -0600290 if len(db_line) != 6:
291 print "ERROR: Bad database line doesn't have 6 elements: %s" % (line)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600292 error_enum = db_line[0]
293 implemented = db_line[1]
294 testname = db_line[2]
Tobin Ehlis70980c02016-10-25 14:00:20 -0600295 api = db_line[3]
296 error_str = db_line[4]
297 note = db_line[5]
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600298 db_dict[error_enum] = error_str
299 # Also read complete database contents into our class var for later use
300 self.error_db_dict[error_enum] = {}
301 self.error_db_dict[error_enum]['check_implemented'] = implemented
302 self.error_db_dict[error_enum]['testname'] = testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600303 self.error_db_dict[error_enum]['api'] = api
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600304 self.error_db_dict[error_enum]['error_string'] = error_str
Tobin Ehlis70980c02016-10-25 14:00:20 -0600305 self.error_db_dict[error_enum]['note'] = note
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600306 unique_id = int(db_line[0].split('_')[-1])
307 if unique_id > max_id:
308 max_id = unique_id
309 return (db_dict, max_id)
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600310 # This is a helper function to do bookkeeping on data structs when comparing original
311 # error ids to current error ids
312 # It tracks all updated enums in mapped_enums and removes those enums from any lists
313 # in the no_link and core dicts
314 def _updateMappedEnum(self, mapped_enums, enum):
315 mapped_enums.add(enum)
316 # When looking for ID to map, we favor sequences so track last ID mapped
317 self.last_mapped_id = int(enum.split('_')[-1])
318 for msg in self.orig_no_link_msg_dict:
319 if enum in self.orig_no_link_msg_dict[msg]:
320 self.orig_no_link_msg_dict[msg].remove(enum)
321 for msg in self.orig_core_msg_dict:
322 if enum in self.orig_core_msg_dict[msg]:
323 self.orig_core_msg_dict[msg].remove(enum)
324 return mapped_enums
325 # Check all ids in given id list to see if one is in sequence from last mapped id
326 def findSeqID(self, id_list):
327 next_seq_id = self.last_mapped_id + 1
328 for map_id in id_list:
329 id_num = int(map_id.split('_')[-1])
330 if id_num == next_seq_id:
331 return True
332 return False
333 # Use the next ID in sequence. This should only be called if findSeqID() just returned True
334 def useSeqID(self, id_list, mapped_enums):
335 next_seq_id = self.last_mapped_id + 1
336 mapped_id = ''
337 for map_id in id_list:
338 id_num = int(map_id.split('_')[-1])
339 if id_num == next_seq_id:
340 mapped_id = map_id
341 self._updateMappedEnum(mapped_enums, mapped_id)
342 return (mapped_enums, mapped_id)
343 return (mapped_enums, mapped_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600344 # Compare unique ids from original database to data generated from updated spec
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600345 # First, make 3 separate mappings of original error messages:
346 # 1. Map the full error message to its id. There should only be 1 ID per full message (orig_full_msg_dict)
347 # 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)
348 # 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)
349 # Also store a set of all IDs that have been mapped to that will serve 2 purposes:
350 # 1. Pull IDs out of the above dicts as they're remapped since we know they won't be used
351 # 2. Make sure that we don't re-use an ID
352 # The general algorithm for remapping from new IDs to old IDs is:
353 # 1. If there is a user-specified remapping, use that above all else
354 # 2. Elif the new error message hits in orig_full_msg_dict then use that ID
355 # 3. Elif the new error message hits orig_no_link_msg_dict then
356 # a. If only a single ID, use it
357 # b. Elif multiple IDs & one matches last used ID in sequence, use it
358 # c. Else assign a new ID and flag for manual remapping
359 # 4. Elif the new error message hits orig_core_msg_dict then
360 # a. If only a single ID, use it
361 # b. Elif multiple IDs & one matches last used ID in sequence, use it
362 # c. Else assign a new ID and flag for manual remapping
363 # 5. Else - No matches use a new ID
Tobin Ehlise7560e72016-10-19 15:59:38 -0600364 def compareDB(self, orig_error_msg_dict, max_id):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600365 """Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec"""
366 # First create reverse dicts of err_strings to IDs
367 next_id = max_id + 1
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600368 ids_parsed = 0
369 mapped_enums = set() # store all enums that have been mapped to avoid re-use
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600370 # Create an updated dict in-place that will be assigned to self.val_error_dict when done
371 updated_val_error_dict = {}
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600372 # Create a few separate mappings of error msg formats to associated ID(s)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600373 for enum in orig_error_msg_dict:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600374 api = self.error_db_dict[enum]['api']
375 original_full_msg = orig_error_msg_dict[enum]
376 orig_no_link_msg = "%s,%s" % (api, original_full_msg.split('(https', 1)[0])
377 orig_core_msg = "%s,%s" % (api, orig_no_link_msg.split(' which states ', 1)[-1])
378 orig_core_msg_period = "%s.' " % (orig_core_msg[:-2])
379 print "Orig core msg:%s\nOrig cw/o per:%s" % (orig_core_msg, orig_core_msg_period)
380
381 # First store mapping of full error msg to ID, shouldn't have duplicates
382 if original_full_msg in self.orig_full_msg_dict:
383 print "ERROR: Found duplicate full msg in original full error messages: %s" % (original_full_msg)
384 self.orig_full_msg_dict[original_full_msg] = enum
385 # Now map API,no_link_msg to list of IDs
386 if orig_no_link_msg in self.orig_no_link_msg_dict:
387 self.orig_no_link_msg_dict[orig_no_link_msg].append(enum)
388 else:
389 self.orig_no_link_msg_dict[orig_no_link_msg] = [enum]
390 # Finally map API,core_msg to list of IDs
391 if orig_core_msg in self.orig_core_msg_dict:
392 self.orig_core_msg_dict[orig_core_msg].append(enum)
393 else:
394 self.orig_core_msg_dict[orig_core_msg] = [enum]
395 if orig_core_msg_period in self.orig_core_msg_dict:
396 self.orig_core_msg_dict[orig_core_msg_period].append(enum)
397 print "Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum)
398 else:
399 print "Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum)
400 self.orig_core_msg_dict[orig_core_msg_period] = [enum]
401 # Also capture all enums that have a test and/or implementation
402 if self.error_db_dict[enum]['check_implemented'] == 'Y' or self.error_db_dict[enum]['testname'] not in ['None','Unknown']:
403 print "Recording %s with implemented value %s and testname %s" % (enum, self.error_db_dict[enum]['check_implemented'], self.error_db_dict[enum]['testname'])
404 self.orig_test_imp_enums.add(enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600405 # Values to be used for the update dict
406 update_enum = ''
407 update_msg = ''
408 update_api = ''
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600409 # Now parse through new dict and figure out what to do with non-matching things
410 for enum in sorted(self.val_error_dict):
411 ids_parsed = ids_parsed + 1
412 enum_list = enum.split('_') # grab sections of enum for use below
Tobin Ehlise7560e72016-10-19 15:59:38 -0600413 # Default update values to be the same
414 update_enum = enum
415 update_msg = self.val_error_dict[enum]['error_msg']
416 update_api = self.val_error_dict[enum]['api']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700417 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600418 new_full_msg = update_msg
419 new_no_link_msg = "%s,%s" % (update_api, new_full_msg.split('(https', 1)[0])
420 new_core_msg = "%s,%s" % (update_api, new_no_link_msg.split(' which states ', 1)[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600421 # Any user-forced remap takes precendence
422 if enum_list[-1] in remap_dict:
423 enum_list[-1] = remap_dict[enum_list[-1]]
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600424 self.last_mapped_id = int(enum_list[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600425 new_enum = "_".join(enum_list)
426 print "NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600427 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600428 elif new_full_msg in self.orig_full_msg_dict:
429 orig_enum = self.orig_full_msg_dict[new_full_msg]
430 print "Found exact match for full error msg so switching new ID %s to original ID %s" % (enum, orig_enum)
431 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
432 update_enum = orig_enum
433 elif new_no_link_msg in self.orig_no_link_msg_dict:
434 # Try to get single ID to map to from no_link matches
435 if len(self.orig_no_link_msg_dict[new_no_link_msg]) == 1: # Only 1 id, use it!
436 orig_enum = self.orig_no_link_msg_dict[new_no_link_msg][0]
437 print "Found no-link err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum)
438 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
439 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600440 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600441 if self.findSeqID(self.orig_no_link_msg_dict[new_no_link_msg]): # If we have an id in sequence, use it!
442 (mapped_enums, update_enum) = self.useSeqID(self.orig_no_link_msg_dict[new_no_link_msg], mapped_enums)
443 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 -0600444 else:
445 enum_list[-1] = "%05d" % (next_id)
446 new_enum = "_".join(enum_list)
447 next_id = next_id + 1
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600448 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 -0600449 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600450 elif new_core_msg in self.orig_core_msg_dict:
451 # Do similar stuff here
452 if len(self.orig_core_msg_dict[new_core_msg]) == 1:
453 orig_enum = self.orig_core_msg_dict[new_core_msg][0]
454 print "Found core err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum)
455 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
456 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600457 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600458 if self.findSeqID(self.orig_core_msg_dict[new_core_msg]):
459 (mapped_enums, update_enum) = self.useSeqID(self.orig_core_msg_dict[new_core_msg], mapped_enums)
460 print "Found core err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum)
461 else:
462 enum_list[-1] = "%05d" % (next_id)
463 new_enum = "_".join(enum_list)
464 next_id = next_id + 1
465 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)
466 update_enum = new_enum
467 # This seems to be a new error so need to pick it up from end of original unique ids & flag for review
468 else:
469 enum_list[-1] = "%05d" % (next_id)
470 new_enum = "_".join(enum_list)
471 next_id = next_id + 1
472 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)
473 if new_enum in updated_val_error_dict:
474 print "ERROR: About to overwrite entry for %s" % (new_enum)
475 update_enum = new_enum
Tobin Ehlise7560e72016-10-19 15:59:38 -0600476 updated_val_error_dict[update_enum] = {}
477 updated_val_error_dict[update_enum]['error_msg'] = update_msg
478 updated_val_error_dict[update_enum]['api'] = update_api
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700479 updated_val_error_dict[update_enum]['implicit'] = implicit
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600480 # Assign parsed dict to be the updated dict based on db compare
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600481 print "In compareDB parsed %d entries" % (ids_parsed)
482 return updated_val_error_dict
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600483
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600484 def validateUpdateDict(self, update_dict):
485 """Compare original dict vs. update dict and make sure that all of the checks are still there"""
486 # Currently just make sure that the same # of checks as the original checks are there
487 #orig_ids = {}
488 orig_id_count = len(self.val_error_dict)
489 #update_ids = {}
490 update_id_count = len(update_dict)
491 if orig_id_count != update_id_count:
492 print "Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count)
493 return False
494 print "Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count)
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600495 # Now flag any original dict enums that had tests and/or checks that are missing from updated
496 for enum in update_dict:
497 if enum in self.orig_test_imp_enums:
498 self.orig_test_imp_enums.remove(enum)
499 if len(self.orig_test_imp_enums) > 0:
500 print "TODO: Have some enums with tests and/or checks implemented that are missing in update:"
501 for enum in sorted(self.orig_test_imp_enums):
502 print "\t%s" % enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600503 return True
504 # TODO : include some more analysis
505
506# User passes in arg of form <new_id1>-<old_id1>[,count1]:<new_id2>-<old_id2>[,count2]:...
507# new_id# = the new enum id that was assigned to an error
508# old_id# = the previous enum id that was assigned to the same error
509# [,count#] = The number of ids to remap starting at new_id#=old_id# and ending at new_id[#+count#-1]=old_id[#+count#-1]
510# If not supplied, then ,1 is assumed, which will only update a single id
511def updateRemapDict(remap_string):
512 """Set up global remap_dict based on user input"""
513 remap_list = remap_string.split(":")
514 for rmap in remap_list:
515 count = 1 # Default count if none supplied
516 id_count_list = rmap.split(',')
517 if len(id_count_list) > 1:
518 count = int(id_count_list[1])
519 new_old_id_list = id_count_list[0].split('-')
520 for offset in range(count):
521 remap_dict["%05d" % (int(new_old_id_list[0]) + offset)] = "%05d" % (int(new_old_id_list[1]) + offset)
522 for new_id in sorted(remap_dict):
523 print "Set to remap new id %s to old id %s" % (new_id, remap_dict[new_id])
524
525if __name__ == "__main__":
526 i = 1
527 use_online = True # Attempt to grab spec from online by default
528 update_option = False
529 while (i < len(sys.argv)):
530 arg = sys.argv[i]
531 i = i + 1
532 if (arg == '-spec'):
533 spec_filename = sys.argv[i]
534 # If user specifies local specfile, skip online
535 use_online = False
536 i = i + 1
537 elif (arg == '-out'):
538 out_filename = sys.argv[i]
539 i = i + 1
540 elif (arg == '-gendb'):
541 gen_db = True
542 # Set filename if supplied, else use default
543 if i < len(sys.argv) and not sys.argv[i].startswith('-'):
544 db_filename = sys.argv[i]
545 i = i + 1
546 elif (arg == '-compare'):
547 db_filename = sys.argv[i]
548 spec_compare = True
549 i = i + 1
550 elif (arg == '-update'):
551 update_option = True
552 spec_compare = True
553 gen_db = True
554 elif (arg == '-remap'):
555 updateRemapDict(sys.argv[i])
556 i = i + 1
557 elif (arg in ['-help', '-h']):
558 printHelp()
559 sys.exit()
560 if len(remap_dict) > 1 and not update_option:
561 print "ERROR: '-remap' option can only be used along with '-update' option. Exiting."
562 sys.exit()
563 spec = Specification()
564 spec.loadFile(use_online, spec_filename)
565 #spec.parseTree()
566 #spec.genHeader(out_filename)
567 spec.analyze()
568 if (spec_compare):
569 # Read in old spec info from db file
Tobin Ehlise7560e72016-10-19 15:59:38 -0600570 (orig_err_msg_dict, max_id) = spec.readDB(db_filename)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600571 # New spec data should already be read into self.val_error_dict
Tobin Ehlise7560e72016-10-19 15:59:38 -0600572 updated_dict = spec.compareDB(orig_err_msg_dict, max_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600573 update_valid = spec.validateUpdateDict(updated_dict)
574 if update_valid:
575 spec.updateDict(updated_dict)
576 else:
577 sys.exit()
578 if (gen_db):
579 spec.genDB(db_filename)
580 print "Writing out file (-out) to '%s'" % (out_filename)
581 spec.genHeader(out_filename)
582
583##### Example dataset
584# <div class="sidebar">
585# <div class="titlepage">
586# <div>
587# <div>
588# <p class="title">
589# <strong>Valid Usage</strong> # When we get to this guy, we know we're under interesting sidebar
590# </p>
591# </div>
592# </div>
593# </div>
594# <div class="itemizedlist">
595# <ul class="itemizedlist" style="list-style-type: disc; ">
596# <li class="listitem">
597# <em class="parameter">
598# <code>device</code>
599# </em>
600# <span class="normative">must</span> be a valid
601# <code class="code">VkDevice</code> handle
602# </li>
603# <li class="listitem">
604# <em class="parameter">
605# <code>commandPool</code>
606# </em>
607# <span class="normative">must</span> be a valid
608# <code class="code">VkCommandPool</code> handle
609# </li>
610# <li class="listitem">
611# <em class="parameter">
612# <code>flags</code>
613# </em>
614# <span class="normative">must</span> be a valid combination of
615# <code class="code">
616# <a class="link" href="#VkCommandPoolResetFlagBits">VkCommandPoolResetFlagBits</a>
617# </code> values
618# </li>
619# <li class="listitem">
620# <em class="parameter">
621# <code>commandPool</code>
622# </em>
623# <span class="normative">must</span> have been created, allocated, or retrieved from
624# <em class="parameter">
625# <code>device</code>
626# </em>
627# </li>
628# <li class="listitem">All
629# <code class="code">VkCommandBuffer</code>
630# objects allocated from
631# <em class="parameter">
632# <code>commandPool</code>
633# </em>
634# <span class="normative">must</span> not currently be pending execution
635# </li>
636# </ul>
637# </div>
638# </div>
639##### Second example dataset
640# <div class="sidebar">
641# <div class="titlepage">
642# <div>
643# <div>
644# <p class="title">
645# <strong>Valid Usage</strong>
646# </p>
647# </div>
648# </div>
649# </div>
650# <div class="itemizedlist">
651# <ul class="itemizedlist" style="list-style-type: disc; ">
652# <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>
653# </li>
654# </ul>
655# </div>
656# </div>
657# <div class="sidebar">
658# <div class="titlepage">
659# <div>
660# <div>
661# <p class="title">
662# <strong>Valid Usage (Implicit)</strong>
663# </p>
664# </div>
665# </div>
666# </div>
667# <div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
668#<em class="parameter"><code>sType</code></em> <span class="normative">must</span> be <code class="code">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</code>
669#</li><li class="listitem">
670#<em class="parameter"><code>pNext</code></em> <span class="normative">must</span> be <code class="literal">NULL</code>
671#</li><li class="listitem">
672#<em class="parameter"><code>flags</code></em> <span class="normative">must</span> be <code class="literal">0</code>
673#</li><li class="listitem">
674#<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 -0700675#</li>