Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 1 | #!/usr/bin/python3 -i |
| 2 | |
| 3 | import sys |
| 4 | import xml.etree.ElementTree as etree |
| 5 | import urllib2 |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 6 | |
| 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 Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 28 | # |
| 29 | ############################# |
| 30 | |
| 31 | |
| 32 | spec_filename = "vkspec.html" # can override w/ '-spec <filename>' option |
| 33 | out_filename = "vk_validation_error_messages.h" # can override w/ '-out <filename>' option |
| 34 | db_filename = "vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option |
| 35 | gen_db = False # set to True when '-gendb <filename>' option provided |
| 36 | spec_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 Ehlis | bd0a9c6 | 2016-10-14 18:06:16 -0600 | [diff] [blame] | 38 | #old_spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html" |
| 39 | spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/xhtml/vkspec.html" |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 40 | # 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 |
| 42 | error_msg_prefix = "For more information refer to Vulkan Spec Section " |
| 43 | ns = {'ns': 'http://www.w3.org/1999/xhtml'} |
Mark Lobodzinski | 629d47b | 2016-10-18 13:34:58 -0600 | [diff] [blame] | 44 | validation_error_enum_name = "VALIDATION_ERROR_" |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 45 | # Dict of new enum values that should be forced to remap to old handles, explicitly set by -remap option |
| 46 | remap_dict = {} |
| 47 | |
| 48 | def 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 | |
| 62 | class Specification: |
| 63 | def __init__(self): |
| 64 | self.tree = None |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 65 | self.val_error_dict = {} # string for enum is key that references 'error_msg' and 'api' |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 66 | self.error_db_dict = {} # dict of previous error values read in from database file |
| 67 | self.delimiter = '~^~' # delimiter for db file |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 68 | self.implicit_count = 0 |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 69 | self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */ |
| 70 | |
| 71 | /* |
| 72 | * Vulkan |
| 73 | * |
| 74 | * Copyright (c) 2016 Google Inc. |
Mark Lobodzinski | 629d47b | 2016-10-18 13:34:58 -0600 | [diff] [blame] | 75 | * Copyright (c) 2016 LunarG, Inc. |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 76 | * |
| 77 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 78 | * you may not use this file except in compliance with the License. |
| 79 | * You may obtain a copy of the License at |
| 80 | * |
| 81 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 82 | * |
| 83 | * Unless required by applicable law or agreed to in writing, software |
| 84 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 85 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 86 | * See the License for the specific language governing permissions and |
| 87 | * limitations under the License. |
| 88 | * |
| 89 | * Author: Tobin Ehlis <tobine@google.com> |
| 90 | */""" |
| 91 | def _checkInternetSpec(self): |
| 92 | """Verify that we can access the spec online""" |
| 93 | try: |
| 94 | online = urllib2.urlopen(spec_url,timeout=1) |
| 95 | return True |
| 96 | except urllib2.URLError as err: |
| 97 | return False |
| 98 | return False |
| 99 | def loadFile(self, online=True, spec_file=spec_filename): |
| 100 | """Load an API registry XML file into a Registry object and parse it""" |
| 101 | # Check if spec URL is available |
| 102 | if (online and self._checkInternetSpec()): |
| 103 | print "Using spec from online at %s" % (spec_url) |
| 104 | self.tree = etree.parse(urllib2.urlopen(spec_url)) |
| 105 | else: |
| 106 | print "Using local spec %s" % (spec_file) |
| 107 | self.tree = etree.parse(spec_file) |
| 108 | #self.tree.write("tree_output.xhtml") |
| 109 | #self.tree = etree.parse("tree_output.xhtml") |
| 110 | self.parseTree() |
| 111 | def updateDict(self, updated_dict): |
| 112 | """Assign internal dict to use updated_dict""" |
| 113 | self.val_error_dict = updated_dict |
| 114 | def parseTree(self): |
| 115 | """Parse the registry Element, once created""" |
| 116 | print "Parsing spec file..." |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 117 | unique_enum_id = 0 |
| 118 | self.root = self.tree.getroot() |
| 119 | #print "ROOT: %s" % self.root |
| 120 | prev_heading = '' # Last seen section heading or sub-heading |
| 121 | prev_link = '' # Last seen link id within the spec |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 122 | api_function = '' # API call that a check appears under |
Tobin Ehlis | 85008cd | 2016-10-19 15:32:35 -0600 | [diff] [blame] | 123 | error_strings = set() # Flag any exact duplicate error strings and skip them |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 124 | implicit_count = 0 |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 125 | for tag in self.root.iter(): # iterate down tree |
| 126 | # Grab most recent section heading and link |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 127 | if tag.tag in ['h2', 'h3', 'h4']: |
| 128 | #if tag.get('class') != 'title': |
| 129 | # continue |
| 130 | print "Found heading %s" % (tag.tag) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 131 | prev_heading = "".join(tag.itertext()) |
| 132 | # Insert a space between heading number & title |
| 133 | sh_list = prev_heading.rsplit('.', 1) |
| 134 | prev_heading = '. '.join(sh_list) |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 135 | prev_link = tag.get('id') |
| 136 | print "Set prev_heading %s to have link of %s" % (prev_heading.encode("ascii", "ignore"), prev_link.encode("ascii", "ignore")) |
| 137 | elif tag.tag == 'a': # grab any intermediate links |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 138 | if tag.get('id') != None: |
| 139 | prev_link = tag.get('id') |
Tobin Ehlis | 16b159c | 2016-10-25 06:33:27 -0600 | [diff] [blame] | 140 | #print "Updated prev link to %s" % (prev_link) |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 141 | elif tag.tag == 'div' and tag.get('class') == 'listingblock': |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 142 | # Check and see if this is API function |
| 143 | code_text = "".join(tag.itertext()).replace('\n', '') |
| 144 | code_text_list = code_text.split() |
| 145 | if len(code_text_list) > 1 and code_text_list[1].startswith('vk'): |
| 146 | api_function = code_text_list[1].strip('(') |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 147 | print "Found API function: %s" % (api_function) |
| 148 | #elif tag.tag == '{http://www.w3.org/1999/xhtml}div' and tag.get('class') == 'sidebar': |
| 149 | elif tag.tag == 'div' and tag.get('class') == 'content': |
Tobin Ehlis | 69ebddf | 2016-10-18 15:55:07 -0600 | [diff] [blame] | 150 | # parse down sidebar to check for valid usage cases |
| 151 | valid_usage = False |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 152 | implicit = False |
Tobin Ehlis | 69ebddf | 2016-10-18 15:55:07 -0600 | [diff] [blame] | 153 | for elem in tag.iter(): |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 154 | if elem.tag == 'div' and None != elem.text and 'Valid Usage' in elem.text: |
Tobin Ehlis | 69ebddf | 2016-10-18 15:55:07 -0600 | [diff] [blame] | 155 | valid_usage = True |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 156 | if '(Implicit)' in elem.text: |
| 157 | implicit = True |
| 158 | else: |
| 159 | implicit = False |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 160 | elif valid_usage and elem.tag == 'li': # grab actual valid usage requirements |
Tobin Ehlis | 69ebddf | 2016-10-18 15:55:07 -0600 | [diff] [blame] | 161 | error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(elem.itertext()).replace('\n', ''), spec_url, prev_link) |
| 162 | # Some txt has multiple spaces so split on whitespace and join w/ single space |
| 163 | error_msg_str = " ".join(error_msg_str.split()) |
Tobin Ehlis | 85008cd | 2016-10-19 15:32:35 -0600 | [diff] [blame] | 164 | if error_msg_str in error_strings: |
| 165 | print "WARNING: SKIPPING adding repeat entry for string. Please review spec and file issue as appropriate. Repeat string is: %s" % (error_msg_str) |
| 166 | else: |
| 167 | error_strings.add(error_msg_str) |
| 168 | enum_str = "%s%05d" % (validation_error_enum_name, unique_enum_id) |
| 169 | # TODO : '\' chars in spec error messages are most likely bad spec txt that needs to be updated |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 170 | self.val_error_dict[enum_str] = {} |
| 171 | self.val_error_dict[enum_str]['error_msg'] = error_msg_str.encode("ascii", "ignore").replace("\\", "/") |
| 172 | self.val_error_dict[enum_str]['api'] = api_function |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 173 | self.val_error_dict[enum_str]['implicit'] = False |
| 174 | if implicit: |
| 175 | self.val_error_dict[enum_str]['implicit'] = True |
| 176 | self.implicit_count = self.implicit_count + 1 |
Tobin Ehlis | 85008cd | 2016-10-19 15:32:35 -0600 | [diff] [blame] | 177 | unique_enum_id = unique_enum_id + 1 |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 178 | #print "Validation Error Dict has a total of %d unique errors and contents are:\n%s" % (unique_enum_id, self.val_error_dict) |
| 179 | def genHeader(self, header_file): |
| 180 | """Generate a header file based on the contents of a parsed spec""" |
| 181 | print "Generating header %s..." % (header_file) |
| 182 | file_contents = [] |
| 183 | file_contents.append(self.copyright) |
| 184 | file_contents.append('\n#pragma once') |
Mark Lobodzinski | 267a7cf | 2017-01-25 09:33:25 -0700 | [diff] [blame^] | 185 | file_contents.append('\n// Disable auto-formatting for generated file') |
| 186 | file_contents.append('// clang-format off') |
| 187 | file_contents.append('\n#include <unordered_map>') |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 188 | file_contents.append('\n// enum values for unique validation error codes') |
| 189 | file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below') |
| 190 | file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode') |
| 191 | file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function') |
Tobin Ehlis | 387fd63 | 2016-12-08 13:32:05 -0700 | [diff] [blame] | 192 | enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {\n VALIDATION_ERROR_UNDEFINED = -1,'] |
Tobin Ehlis | bf98b69 | 2016-10-06 12:58:06 -0600 | [diff] [blame] | 193 | error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{'] |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 194 | enum_value = 0 |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 195 | for enum in sorted(self.val_error_dict): |
| 196 | #print "Header enum is %s" % (enum) |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 197 | enum_value = int(enum.split('_')[-1]) |
| 198 | enum_decl.append(' %s = %d,' % (enum, enum_value)) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 199 | error_string_map.append(' {%s, "%s"},' % (enum, self.val_error_dict[enum]['error_msg'])) |
Tobin Ehlis | ce1e56f | 2017-01-25 12:42:55 -0800 | [diff] [blame] | 200 | enum_decl.append(' %sMAX_ENUM = %d,' % (validation_error_enum_name, enum_value + 1)) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 201 | enum_decl.append('};') |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 202 | error_string_map.append('};\n') |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 203 | file_contents.extend(enum_decl) |
| 204 | file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message') |
| 205 | file_contents.append('// The error message should be appended to the end of a custom error message that is passed') |
| 206 | file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function') |
| 207 | file_contents.extend(error_string_map) |
| 208 | #print "File contents: %s" % (file_contents) |
| 209 | with open(header_file, "w") as outfile: |
| 210 | outfile.write("\n".join(file_contents)) |
| 211 | def analyze(self): |
| 212 | """Print out some stats on the valid usage dict""" |
| 213 | # Create dict for # of occurences of identical strings |
| 214 | str_count_dict = {} |
| 215 | unique_id_count = 0 |
| 216 | for enum in self.val_error_dict: |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 217 | err_str = self.val_error_dict[enum]['error_msg'] |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 218 | if err_str in str_count_dict: |
Tobin Ehlis | 69ebddf | 2016-10-18 15:55:07 -0600 | [diff] [blame] | 219 | print "Found repeat error string" |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 220 | str_count_dict[err_str] = str_count_dict[err_str] + 1 |
| 221 | else: |
| 222 | str_count_dict[err_str] = 1 |
| 223 | unique_id_count = unique_id_count + 1 |
| 224 | print "Processed %d unique_ids" % (unique_id_count) |
| 225 | repeat_string = 0 |
| 226 | for es in str_count_dict: |
| 227 | if str_count_dict[es] > 1: |
| 228 | repeat_string = repeat_string + 1 |
Tobin Ehlis | 69ebddf | 2016-10-18 15:55:07 -0600 | [diff] [blame] | 229 | print "String '%s' repeated %d times" % (es, repeat_string) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 230 | print "Found %d repeat strings" % (repeat_string) |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 231 | print "Found %d implicit checks" % (self.implicit_count) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 232 | def genDB(self, db_file): |
| 233 | """Generate a database of check_enum, check_coded?, testname, error_string""" |
| 234 | db_lines = [] |
| 235 | # Write header for database file |
| 236 | db_lines.append("# This is a database file with validation error check information") |
| 237 | db_lines.append("# Comments are denoted with '#' char") |
| 238 | db_lines.append("# The format of the lines is:") |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 239 | 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 Lobodzinski | 629d47b | 2016-10-18 13:34:58 -0600 | [diff] [blame] | 240 | db_lines.append("# error_enum: Unique error enum for this check of format %s<uniqueid>" % validation_error_enum_name) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 241 | db_lines.append("# check_implemented: 'Y' if check has been implemented in layers, 'U' for unknown, or 'N' for not implemented") |
| 242 | db_lines.append("# testname: Name of validation test for this check, 'Unknown' for unknown, or 'None' if not implmented") |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 243 | db_lines.append("# api: Vulkan API function that this check is related to") |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 244 | db_lines.append("# errormsg: The unique error message for this check that includes spec language and link") |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 245 | db_lines.append("# note: Free txt field with any custom notes related to the check in question") |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 246 | for enum in sorted(self.val_error_dict): |
| 247 | # Default to unknown if check or test are implemented, then update below if appropriate |
| 248 | implemented = 'U' |
| 249 | testname = 'Unknown' |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 250 | note = '' |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 251 | implicit = self.val_error_dict[enum]['implicit'] |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 252 | # If we have an existing db entry for this enum, use its implemented/testname values |
| 253 | if enum in self.error_db_dict: |
| 254 | implemented = self.error_db_dict[enum]['check_implemented'] |
| 255 | testname = self.error_db_dict[enum]['testname'] |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 256 | note = self.error_db_dict[enum]['note'] |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 257 | if implicit and 'implicit' not in note: # add implicit note |
| 258 | if '' != note: |
| 259 | note = "implicit, %s" % (note) |
| 260 | else: |
| 261 | note = "implicit" |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 262 | #print "delimiter: %s, id: %s, str: %s" % (self.delimiter, enum, self.val_error_dict[enum]) |
| 263 | # No existing entry so default to N for implemented and None for testname |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 264 | 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 Ehlis | af75f7c | 2016-10-31 11:10:38 -0600 | [diff] [blame] | 265 | db_lines.append("\n") # newline at end of file |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 266 | print "Generating database file %s" % (db_file) |
| 267 | with open(db_file, "w") as outfile: |
| 268 | outfile.write("\n".join(db_lines)) |
| 269 | def readDB(self, db_file): |
| 270 | """Read a db file into a dict, format of each line is <enum><implemented Y|N?><testname><errormsg>""" |
| 271 | db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec |
| 272 | max_id = 0 |
| 273 | with open(db_file, "r") as infile: |
| 274 | for line in infile: |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 275 | line = line.strip() |
Tobin Ehlis | f4245cb | 2016-10-31 07:55:19 -0600 | [diff] [blame] | 276 | if line.startswith('#') or '' == line: |
| 277 | continue |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 278 | db_line = line.split(self.delimiter) |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 279 | if len(db_line) != 6: |
| 280 | print "ERROR: Bad database line doesn't have 6 elements: %s" % (line) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 281 | error_enum = db_line[0] |
| 282 | implemented = db_line[1] |
| 283 | testname = db_line[2] |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 284 | api = db_line[3] |
| 285 | error_str = db_line[4] |
| 286 | note = db_line[5] |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 287 | db_dict[error_enum] = error_str |
| 288 | # Also read complete database contents into our class var for later use |
| 289 | self.error_db_dict[error_enum] = {} |
| 290 | self.error_db_dict[error_enum]['check_implemented'] = implemented |
| 291 | self.error_db_dict[error_enum]['testname'] = testname |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 292 | self.error_db_dict[error_enum]['api'] = api |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 293 | self.error_db_dict[error_enum]['error_string'] = error_str |
Tobin Ehlis | 70980c0 | 2016-10-25 14:00:20 -0600 | [diff] [blame] | 294 | self.error_db_dict[error_enum]['note'] = note |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 295 | unique_id = int(db_line[0].split('_')[-1]) |
| 296 | if unique_id > max_id: |
| 297 | max_id = unique_id |
| 298 | return (db_dict, max_id) |
| 299 | # Compare unique ids from original database to data generated from updated spec |
| 300 | # 1. If a new id and error code exactly match original, great |
| 301 | # 2. If new id is not in original, but exact error code is, need to use original error code |
| 302 | # 3. If new id and new error are not in original, make sure new id picks up from end of original list |
| 303 | # 4. If new id in original, but error strings don't match then: |
| 304 | # 4a. If error string has exact match in original, update new to use original |
| 305 | # 4b. If error string not in original, may be updated error message, manually address |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 306 | def compareDB(self, orig_error_msg_dict, max_id): |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 307 | """Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec""" |
| 308 | # First create reverse dicts of err_strings to IDs |
| 309 | next_id = max_id + 1 |
| 310 | orig_err_to_id_dict = {} |
| 311 | # Create an updated dict in-place that will be assigned to self.val_error_dict when done |
| 312 | updated_val_error_dict = {} |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 313 | for enum in orig_error_msg_dict: |
| 314 | orig_err_to_id_dict[orig_error_msg_dict[enum]] = enum |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 315 | new_err_to_id_dict = {} |
| 316 | for enum in self.val_error_dict: |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 317 | new_err_to_id_dict[self.val_error_dict[enum]['error_msg']] = enum |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 318 | ids_parsed = 0 |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 319 | # Values to be used for the update dict |
| 320 | update_enum = '' |
| 321 | update_msg = '' |
| 322 | update_api = '' |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 323 | # Now parse through new dict and figure out what to do with non-matching things |
| 324 | for enum in sorted(self.val_error_dict): |
| 325 | ids_parsed = ids_parsed + 1 |
| 326 | enum_list = enum.split('_') # grab sections of enum for use below |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 327 | # Default update values to be the same |
| 328 | update_enum = enum |
| 329 | update_msg = self.val_error_dict[enum]['error_msg'] |
| 330 | update_api = self.val_error_dict[enum]['api'] |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 331 | implicit = self.val_error_dict[enum]['implicit'] |
Tobin Ehlis | bd0a9c6 | 2016-10-14 18:06:16 -0600 | [diff] [blame] | 332 | # Any user-forced remap takes precendence |
| 333 | if enum_list[-1] in remap_dict: |
| 334 | enum_list[-1] = remap_dict[enum_list[-1]] |
| 335 | new_enum = "_".join(enum_list) |
| 336 | print "NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 337 | update_enum = new_enum |
| 338 | elif enum in orig_error_msg_dict: |
| 339 | if self.val_error_dict[enum]['error_msg'] == orig_error_msg_dict[enum]: |
Tobin Ehlis | bd0a9c6 | 2016-10-14 18:06:16 -0600 | [diff] [blame] | 340 | print "Exact match for enum %s" % (enum) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 341 | # Nothing to see here |
| 342 | if enum in updated_val_error_dict: |
| 343 | print "ERROR: About to overwrite entry for %s" % (enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 344 | elif self.val_error_dict[enum]['error_msg'] in orig_err_to_id_dict: |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 345 | # Same value w/ different error id, need to anchor to original id |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 346 | print "Need to switch new id %s to original id %s" % (enum, orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']]) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 347 | # Update id at end of new enum to be same id from original enum |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 348 | enum_list[-1] = orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']].split('_')[-1] |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 349 | new_enum = "_".join(enum_list) |
| 350 | if new_enum in updated_val_error_dict: |
| 351 | print "ERROR: About to overwrite entry for %s" % (new_enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 352 | update_enum = new_enum |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 353 | else: |
| 354 | # No error match: |
| 355 | # First check if only link has changed, in which case keep ID but update message |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 356 | orig_msg_list = orig_error_msg_dict[enum].split('(', 1) |
| 357 | new_msg_list = self.val_error_dict[enum]['error_msg'].split('(', 1) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 358 | if orig_msg_list[0] == new_msg_list[0]: # Msg is same bug link has changed, keep enum & update msg |
| 359 | print "NOTE: Found that only spec link changed for %s so keeping same id w/ new link" % (enum) |
Tobin Ehlis | bd0a9c6 | 2016-10-14 18:06:16 -0600 | [diff] [blame] | 360 | # This seems to be a new error so need to pick it up from end of original unique ids & flag for review |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 361 | else: |
| 362 | enum_list[-1] = "%05d" % (next_id) |
| 363 | new_enum = "_".join(enum_list) |
| 364 | next_id = next_id + 1 |
| 365 | print "MANUALLY VERIFY: Updated new enum %s to be unique %s. Make sure new error msg is actually unique and not just changed" % (enum, new_enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 366 | print " New error string: %s" % (self.val_error_dict[enum]['error_msg']) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 367 | if new_enum in updated_val_error_dict: |
| 368 | print "ERROR: About to overwrite entry for %s" % (new_enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 369 | update_enum = new_enum |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 370 | else: # new enum is not in orig db |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 371 | if self.val_error_dict[enum]['error_msg'] in orig_err_to_id_dict: |
| 372 | print "New enum %s not in orig dict, but exact error message matches original unique id %s" % (enum, orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']]) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 373 | # Update new unique_id to use original |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 374 | enum_list[-1] = orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']].split('_')[-1] |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 375 | new_enum = "_".join(enum_list) |
| 376 | if new_enum in updated_val_error_dict: |
| 377 | print "ERROR: About to overwrite entry for %s" % (new_enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 378 | update_enum = new_enum |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 379 | else: |
| 380 | enum_list[-1] = "%05d" % (next_id) |
| 381 | new_enum = "_".join(enum_list) |
| 382 | next_id = next_id + 1 |
| 383 | print "Completely new id and error code, update new id from %s to unique %s" % (enum, new_enum) |
| 384 | if new_enum in updated_val_error_dict: |
| 385 | print "ERROR: About to overwrite entry for %s" % (new_enum) |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 386 | update_enum = new_enum |
| 387 | updated_val_error_dict[update_enum] = {} |
| 388 | updated_val_error_dict[update_enum]['error_msg'] = update_msg |
| 389 | updated_val_error_dict[update_enum]['api'] = update_api |
Tobin Ehlis | 2a176b1 | 2017-01-11 16:18:20 -0700 | [diff] [blame] | 390 | updated_val_error_dict[update_enum]['implicit'] = implicit |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 391 | # Assign parsed dict to be the udpated dict based on db compare |
| 392 | print "In compareDB parsed %d entries" % (ids_parsed) |
| 393 | return updated_val_error_dict |
| 394 | def validateUpdateDict(self, update_dict): |
| 395 | """Compare original dict vs. update dict and make sure that all of the checks are still there""" |
| 396 | # Currently just make sure that the same # of checks as the original checks are there |
| 397 | #orig_ids = {} |
| 398 | orig_id_count = len(self.val_error_dict) |
| 399 | #update_ids = {} |
| 400 | update_id_count = len(update_dict) |
| 401 | if orig_id_count != update_id_count: |
| 402 | print "Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count) |
| 403 | return False |
| 404 | print "Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count) |
| 405 | return True |
| 406 | # TODO : include some more analysis |
| 407 | |
| 408 | # User passes in arg of form <new_id1>-<old_id1>[,count1]:<new_id2>-<old_id2>[,count2]:... |
| 409 | # new_id# = the new enum id that was assigned to an error |
| 410 | # old_id# = the previous enum id that was assigned to the same error |
| 411 | # [,count#] = The number of ids to remap starting at new_id#=old_id# and ending at new_id[#+count#-1]=old_id[#+count#-1] |
| 412 | # If not supplied, then ,1 is assumed, which will only update a single id |
| 413 | def updateRemapDict(remap_string): |
| 414 | """Set up global remap_dict based on user input""" |
| 415 | remap_list = remap_string.split(":") |
| 416 | for rmap in remap_list: |
| 417 | count = 1 # Default count if none supplied |
| 418 | id_count_list = rmap.split(',') |
| 419 | if len(id_count_list) > 1: |
| 420 | count = int(id_count_list[1]) |
| 421 | new_old_id_list = id_count_list[0].split('-') |
| 422 | for offset in range(count): |
| 423 | remap_dict["%05d" % (int(new_old_id_list[0]) + offset)] = "%05d" % (int(new_old_id_list[1]) + offset) |
| 424 | for new_id in sorted(remap_dict): |
| 425 | print "Set to remap new id %s to old id %s" % (new_id, remap_dict[new_id]) |
| 426 | |
| 427 | if __name__ == "__main__": |
| 428 | i = 1 |
| 429 | use_online = True # Attempt to grab spec from online by default |
| 430 | update_option = False |
| 431 | while (i < len(sys.argv)): |
| 432 | arg = sys.argv[i] |
| 433 | i = i + 1 |
| 434 | if (arg == '-spec'): |
| 435 | spec_filename = sys.argv[i] |
| 436 | # If user specifies local specfile, skip online |
| 437 | use_online = False |
| 438 | i = i + 1 |
| 439 | elif (arg == '-out'): |
| 440 | out_filename = sys.argv[i] |
| 441 | i = i + 1 |
| 442 | elif (arg == '-gendb'): |
| 443 | gen_db = True |
| 444 | # Set filename if supplied, else use default |
| 445 | if i < len(sys.argv) and not sys.argv[i].startswith('-'): |
| 446 | db_filename = sys.argv[i] |
| 447 | i = i + 1 |
| 448 | elif (arg == '-compare'): |
| 449 | db_filename = sys.argv[i] |
| 450 | spec_compare = True |
| 451 | i = i + 1 |
| 452 | elif (arg == '-update'): |
| 453 | update_option = True |
| 454 | spec_compare = True |
| 455 | gen_db = True |
| 456 | elif (arg == '-remap'): |
| 457 | updateRemapDict(sys.argv[i]) |
| 458 | i = i + 1 |
| 459 | elif (arg in ['-help', '-h']): |
| 460 | printHelp() |
| 461 | sys.exit() |
| 462 | if len(remap_dict) > 1 and not update_option: |
| 463 | print "ERROR: '-remap' option can only be used along with '-update' option. Exiting." |
| 464 | sys.exit() |
| 465 | spec = Specification() |
| 466 | spec.loadFile(use_online, spec_filename) |
| 467 | #spec.parseTree() |
| 468 | #spec.genHeader(out_filename) |
| 469 | spec.analyze() |
| 470 | if (spec_compare): |
| 471 | # Read in old spec info from db file |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 472 | (orig_err_msg_dict, max_id) = spec.readDB(db_filename) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 473 | # New spec data should already be read into self.val_error_dict |
Tobin Ehlis | e7560e7 | 2016-10-19 15:59:38 -0600 | [diff] [blame] | 474 | updated_dict = spec.compareDB(orig_err_msg_dict, max_id) |
Tobin Ehlis | 5ade069 | 2016-10-05 17:18:15 -0600 | [diff] [blame] | 475 | update_valid = spec.validateUpdateDict(updated_dict) |
| 476 | if update_valid: |
| 477 | spec.updateDict(updated_dict) |
| 478 | else: |
| 479 | sys.exit() |
| 480 | if (gen_db): |
| 481 | spec.genDB(db_filename) |
| 482 | print "Writing out file (-out) to '%s'" % (out_filename) |
| 483 | spec.genHeader(out_filename) |
| 484 | |
| 485 | ##### Example dataset |
| 486 | # <div class="sidebar"> |
| 487 | # <div class="titlepage"> |
| 488 | # <div> |
| 489 | # <div> |
| 490 | # <p class="title"> |
| 491 | # <strong>Valid Usage</strong> # When we get to this guy, we know we're under interesting sidebar |
| 492 | # </p> |
| 493 | # </div> |
| 494 | # </div> |
| 495 | # </div> |
| 496 | # <div class="itemizedlist"> |
| 497 | # <ul class="itemizedlist" style="list-style-type: disc; "> |
| 498 | # <li class="listitem"> |
| 499 | # <em class="parameter"> |
| 500 | # <code>device</code> |
| 501 | # </em> |
| 502 | # <span class="normative">must</span> be a valid |
| 503 | # <code class="code">VkDevice</code> handle |
| 504 | # </li> |
| 505 | # <li class="listitem"> |
| 506 | # <em class="parameter"> |
| 507 | # <code>commandPool</code> |
| 508 | # </em> |
| 509 | # <span class="normative">must</span> be a valid |
| 510 | # <code class="code">VkCommandPool</code> handle |
| 511 | # </li> |
| 512 | # <li class="listitem"> |
| 513 | # <em class="parameter"> |
| 514 | # <code>flags</code> |
| 515 | # </em> |
| 516 | # <span class="normative">must</span> be a valid combination of |
| 517 | # <code class="code"> |
| 518 | # <a class="link" href="#VkCommandPoolResetFlagBits">VkCommandPoolResetFlagBits</a> |
| 519 | # </code> values |
| 520 | # </li> |
| 521 | # <li class="listitem"> |
| 522 | # <em class="parameter"> |
| 523 | # <code>commandPool</code> |
| 524 | # </em> |
| 525 | # <span class="normative">must</span> have been created, allocated, or retrieved from |
| 526 | # <em class="parameter"> |
| 527 | # <code>device</code> |
| 528 | # </em> |
| 529 | # </li> |
| 530 | # <li class="listitem">All |
| 531 | # <code class="code">VkCommandBuffer</code> |
| 532 | # objects allocated from |
| 533 | # <em class="parameter"> |
| 534 | # <code>commandPool</code> |
| 535 | # </em> |
| 536 | # <span class="normative">must</span> not currently be pending execution |
| 537 | # </li> |
| 538 | # </ul> |
| 539 | # </div> |
| 540 | # </div> |
| 541 | ##### Second example dataset |
| 542 | # <div class="sidebar"> |
| 543 | # <div class="titlepage"> |
| 544 | # <div> |
| 545 | # <div> |
| 546 | # <p class="title"> |
| 547 | # <strong>Valid Usage</strong> |
| 548 | # </p> |
| 549 | # </div> |
| 550 | # </div> |
| 551 | # </div> |
| 552 | # <div class="itemizedlist"> |
| 553 | # <ul class="itemizedlist" style="list-style-type: disc; "> |
| 554 | # <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> |
| 555 | # </li> |
| 556 | # </ul> |
| 557 | # </div> |
| 558 | # </div> |
| 559 | # <div class="sidebar"> |
| 560 | # <div class="titlepage"> |
| 561 | # <div> |
| 562 | # <div> |
| 563 | # <p class="title"> |
| 564 | # <strong>Valid Usage (Implicit)</strong> |
| 565 | # </p> |
| 566 | # </div> |
| 567 | # </div> |
| 568 | # </div> |
| 569 | # <div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"> |
| 570 | #<em class="parameter"><code>sType</code></em> <span class="normative">must</span> be <code class="code">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</code> |
| 571 | #</li><li class="listitem"> |
| 572 | #<em class="parameter"><code>pNext</code></em> <span class="normative">must</span> be <code class="literal">NULL</code> |
| 573 | #</li><li class="listitem"> |
| 574 | #<em class="parameter"><code>flags</code></em> <span class="normative">must</span> be <code class="literal">0</code> |
| 575 | #</li><li class="listitem"> |
| 576 | #<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 Lobodzinski | 267a7cf | 2017-01-25 09:33:25 -0700 | [diff] [blame^] | 577 | #</li> |