scripts:Update unique IDs for spec v1.0.45.1
Another massive update. Totally overhauled the mapping algorithm.
The basic outline of the new algorithm is:
-Try to map complete error message to prev ID
-Then try to map error msg w/o link to prev ID
-Finally try to map just core error string (no section) to prev ID
-Else assign it a new unique ID
See code for complete details.
In anticipation of integrated uniqueIDs I took a little liberty with
this update in that I didn't attempt to remap EVERY new ID to previous
IDs. I did many of them and made sure to account for every previous ID
that was implemented.
IDs will all be undergoing a one-time change with their integration
into the spec anyway so there is little harm in letting non-implemented
IDs drift a bit for now and it helps save my sanity.
I did add a validation step to the spec.py script to make sure and flag
any IDs that previously were implemented and suddenly are no longer in
the re-mapped version of IDs.
I manually presevered IDs 911 & 912 which get masked from the extension
spec.
I also had to do some manual updates on an offline spec to keep the
parser from breaking on the non-xhtml compliant spec.
Command line was:
python spec.py -update -remap 92-72:97-77:112-92:116-96:123-103:124-105:
133-114:148-2349:154-133,2:156-135:157-139:204-156:205-155:206-154:
417-269:1189-769:1417-938:1446-965:1448-967:1449-968:1487-974:
2193-1430,5:683-496:684-494:728-529:729-527:1628-1086:1731-1180:
1736-1183:1796-1234:1815-1251
diff --git a/layers/spec.py b/layers/spec.py
index 51d2b4c..326bf37 100644
--- a/layers/spec.py
+++ b/layers/spec.py
@@ -36,7 +36,7 @@
spec_compare = False # set to True with '-compare <db_filename>' option
# This is the root spec link that is used in error messages to point users to spec sections
#old_spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html"
-spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/xhtml/vkspec.html"
+spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html"
# After the custom validation error message, this is the prefix for the standard message that includes the
# spec valid usage language as well as the link to nearest section of spec to that language
error_msg_prefix = "For more information refer to Vulkan Spec Section "
@@ -66,6 +66,12 @@
self.error_db_dict = {} # dict of previous error values read in from database file
self.delimiter = '~^~' # delimiter for db file
self.implicit_count = 0
+ # Global dicts used for tracking spec updates from old to new VUs
+ self.orig_full_msg_dict = {} # Original full error msg to ID mapping
+ self.orig_no_link_msg_dict = {} # Pair of API,Original msg w/o spec link to ID list mapping
+ self.orig_core_msg_dict = {} # Pair of API,Original core msg (no link or section) to ID list mapping
+ self.last_mapped_id = -10 # start as negative so we don't hit an accidental sequence
+ self.orig_test_imp_enums = set() # Track old enums w/ tests and/or implementation to flag any that aren't carried fwd
self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */
/*
@@ -145,6 +151,11 @@
if len(code_text_list) > 1 and code_text_list[1].startswith('vk'):
api_function = code_text_list[1].strip('(')
print "Found API function: %s" % (api_function)
+ prev_link = api_function
+ print "Updated prev link to %s" % (prev_link)
+ elif tag.get('id') != None:
+ prev_link = tag.get('id')
+ print "Updated prev link to %s" % (prev_link)
#elif tag.tag == '{http://www.w3.org/1999/xhtml}div' and tag.get('class') == 'sidebar':
elif tag.tag == 'div' and tag.get('class') == 'content':
# parse down sidebar to check for valid usage cases
@@ -158,7 +169,7 @@
else:
implicit = False
elif valid_usage and elem.tag == 'li': # grab actual valid usage requirements
- error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(elem.itertext()).replace('\n', ''), spec_url, prev_link)
+ 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)
# Some txt has multiple spaces so split on whitespace and join w/ single space
error_msg_str = " ".join(error_msg_str.split())
if error_msg_str in error_strings:
@@ -296,26 +307,101 @@
if unique_id > max_id:
max_id = unique_id
return (db_dict, max_id)
+ # This is a helper function to do bookkeeping on data structs when comparing original
+ # error ids to current error ids
+ # It tracks all updated enums in mapped_enums and removes those enums from any lists
+ # in the no_link and core dicts
+ def _updateMappedEnum(self, mapped_enums, enum):
+ mapped_enums.add(enum)
+ # When looking for ID to map, we favor sequences so track last ID mapped
+ self.last_mapped_id = int(enum.split('_')[-1])
+ for msg in self.orig_no_link_msg_dict:
+ if enum in self.orig_no_link_msg_dict[msg]:
+ self.orig_no_link_msg_dict[msg].remove(enum)
+ for msg in self.orig_core_msg_dict:
+ if enum in self.orig_core_msg_dict[msg]:
+ self.orig_core_msg_dict[msg].remove(enum)
+ return mapped_enums
+ # Check all ids in given id list to see if one is in sequence from last mapped id
+ def findSeqID(self, id_list):
+ next_seq_id = self.last_mapped_id + 1
+ for map_id in id_list:
+ id_num = int(map_id.split('_')[-1])
+ if id_num == next_seq_id:
+ return True
+ return False
+ # Use the next ID in sequence. This should only be called if findSeqID() just returned True
+ def useSeqID(self, id_list, mapped_enums):
+ next_seq_id = self.last_mapped_id + 1
+ mapped_id = ''
+ for map_id in id_list:
+ id_num = int(map_id.split('_')[-1])
+ if id_num == next_seq_id:
+ mapped_id = map_id
+ self._updateMappedEnum(mapped_enums, mapped_id)
+ return (mapped_enums, mapped_id)
+ return (mapped_enums, mapped_id)
# Compare unique ids from original database to data generated from updated spec
- # 1. If a new id and error code exactly match original, great
- # 2. If new id is not in original, but exact error code is, need to use original error code
- # 3. If new id and new error are not in original, make sure new id picks up from end of original list
- # 4. If new id in original, but error strings don't match then:
- # 4a. If error string has exact match in original, update new to use original
- # 4b. If error string not in original, may be updated error message, manually address
+ # First, make 3 separate mappings of original error messages:
+ # 1. Map the full error message to its id. There should only be 1 ID per full message (orig_full_msg_dict)
+ # 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)
+ # 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)
+ # Also store a set of all IDs that have been mapped to that will serve 2 purposes:
+ # 1. Pull IDs out of the above dicts as they're remapped since we know they won't be used
+ # 2. Make sure that we don't re-use an ID
+ # The general algorithm for remapping from new IDs to old IDs is:
+ # 1. If there is a user-specified remapping, use that above all else
+ # 2. Elif the new error message hits in orig_full_msg_dict then use that ID
+ # 3. Elif the new error message hits orig_no_link_msg_dict then
+ # a. If only a single ID, use it
+ # b. Elif multiple IDs & one matches last used ID in sequence, use it
+ # c. Else assign a new ID and flag for manual remapping
+ # 4. Elif the new error message hits orig_core_msg_dict then
+ # a. If only a single ID, use it
+ # b. Elif multiple IDs & one matches last used ID in sequence, use it
+ # c. Else assign a new ID and flag for manual remapping
+ # 5. Else - No matches use a new ID
def compareDB(self, orig_error_msg_dict, max_id):
"""Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec"""
# First create reverse dicts of err_strings to IDs
next_id = max_id + 1
- orig_err_to_id_dict = {}
+ ids_parsed = 0
+ mapped_enums = set() # store all enums that have been mapped to avoid re-use
# Create an updated dict in-place that will be assigned to self.val_error_dict when done
updated_val_error_dict = {}
+ # Create a few separate mappings of error msg formats to associated ID(s)
for enum in orig_error_msg_dict:
- orig_err_to_id_dict[orig_error_msg_dict[enum]] = enum
- new_err_to_id_dict = {}
- for enum in self.val_error_dict:
- new_err_to_id_dict[self.val_error_dict[enum]['error_msg']] = enum
- ids_parsed = 0
+ api = self.error_db_dict[enum]['api']
+ original_full_msg = orig_error_msg_dict[enum]
+ orig_no_link_msg = "%s,%s" % (api, original_full_msg.split('(https', 1)[0])
+ orig_core_msg = "%s,%s" % (api, orig_no_link_msg.split(' which states ', 1)[-1])
+ orig_core_msg_period = "%s.' " % (orig_core_msg[:-2])
+ print "Orig core msg:%s\nOrig cw/o per:%s" % (orig_core_msg, orig_core_msg_period)
+
+ # First store mapping of full error msg to ID, shouldn't have duplicates
+ if original_full_msg in self.orig_full_msg_dict:
+ print "ERROR: Found duplicate full msg in original full error messages: %s" % (original_full_msg)
+ self.orig_full_msg_dict[original_full_msg] = enum
+ # Now map API,no_link_msg to list of IDs
+ if orig_no_link_msg in self.orig_no_link_msg_dict:
+ self.orig_no_link_msg_dict[orig_no_link_msg].append(enum)
+ else:
+ self.orig_no_link_msg_dict[orig_no_link_msg] = [enum]
+ # Finally map API,core_msg to list of IDs
+ if orig_core_msg in self.orig_core_msg_dict:
+ self.orig_core_msg_dict[orig_core_msg].append(enum)
+ else:
+ self.orig_core_msg_dict[orig_core_msg] = [enum]
+ if orig_core_msg_period in self.orig_core_msg_dict:
+ self.orig_core_msg_dict[orig_core_msg_period].append(enum)
+ print "Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum)
+ else:
+ print "Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum)
+ self.orig_core_msg_dict[orig_core_msg_period] = [enum]
+ # Also capture all enums that have a test and/or implementation
+ if self.error_db_dict[enum]['check_implemented'] == 'Y' or self.error_db_dict[enum]['testname'] not in ['None','Unknown']:
+ print "Recording %s with implemented value %s and testname %s" % (enum, self.error_db_dict[enum]['check_implemented'], self.error_db_dict[enum]['testname'])
+ self.orig_test_imp_enums.add(enum)
# Values to be used for the update dict
update_enum = ''
update_msg = ''
@@ -329,68 +415,72 @@
update_msg = self.val_error_dict[enum]['error_msg']
update_api = self.val_error_dict[enum]['api']
implicit = self.val_error_dict[enum]['implicit']
+ new_full_msg = update_msg
+ new_no_link_msg = "%s,%s" % (update_api, new_full_msg.split('(https', 1)[0])
+ new_core_msg = "%s,%s" % (update_api, new_no_link_msg.split(' which states ', 1)[-1])
# Any user-forced remap takes precendence
if enum_list[-1] in remap_dict:
enum_list[-1] = remap_dict[enum_list[-1]]
+ self.last_mapped_id = int(enum_list[-1])
new_enum = "_".join(enum_list)
print "NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum)
update_enum = new_enum
- elif enum in orig_error_msg_dict:
- if self.val_error_dict[enum]['error_msg'] == orig_error_msg_dict[enum]:
- print "Exact match for enum %s" % (enum)
- # Nothing to see here
- if enum in updated_val_error_dict:
- print "ERROR: About to overwrite entry for %s" % (enum)
- elif self.val_error_dict[enum]['error_msg'] in orig_err_to_id_dict:
- # Same value w/ different error id, need to anchor to original id
- print "Need to switch new id %s to original id %s" % (enum, orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']])
- # Update id at end of new enum to be same id from original enum
- enum_list[-1] = orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']].split('_')[-1]
- new_enum = "_".join(enum_list)
- if new_enum in updated_val_error_dict:
- print "ERROR: About to overwrite entry for %s" % (new_enum)
- update_enum = new_enum
+ elif new_full_msg in self.orig_full_msg_dict:
+ orig_enum = self.orig_full_msg_dict[new_full_msg]
+ print "Found exact match for full error msg so switching new ID %s to original ID %s" % (enum, orig_enum)
+ mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
+ update_enum = orig_enum
+ elif new_no_link_msg in self.orig_no_link_msg_dict:
+ # Try to get single ID to map to from no_link matches
+ if len(self.orig_no_link_msg_dict[new_no_link_msg]) == 1: # Only 1 id, use it!
+ orig_enum = self.orig_no_link_msg_dict[new_no_link_msg][0]
+ print "Found no-link err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum)
+ mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
+ update_enum = orig_enum
else:
- # No error match:
- # First check if only link has changed, in which case keep ID but update message
- orig_msg_list = orig_error_msg_dict[enum].split('(', 1)
- new_msg_list = self.val_error_dict[enum]['error_msg'].split('(', 1)
- if orig_msg_list[0] == new_msg_list[0]: # Msg is same bug link has changed, keep enum & update msg
- print "NOTE: Found that only spec link changed for %s so keeping same id w/ new link" % (enum)
- # This seems to be a new error so need to pick it up from end of original unique ids & flag for review
+ if self.findSeqID(self.orig_no_link_msg_dict[new_no_link_msg]): # If we have an id in sequence, use it!
+ (mapped_enums, update_enum) = self.useSeqID(self.orig_no_link_msg_dict[new_no_link_msg], mapped_enums)
+ print "Found no-link err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum)
else:
enum_list[-1] = "%05d" % (next_id)
new_enum = "_".join(enum_list)
next_id = next_id + 1
- 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)
- print " New error string: %s" % (self.val_error_dict[enum]['error_msg'])
- if new_enum in updated_val_error_dict:
- print "ERROR: About to overwrite entry for %s" % (new_enum)
+ 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)
update_enum = new_enum
- else: # new enum is not in orig db
- if self.val_error_dict[enum]['error_msg'] in orig_err_to_id_dict:
- 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']])
- # Update new unique_id to use original
- enum_list[-1] = orig_err_to_id_dict[self.val_error_dict[enum]['error_msg']].split('_')[-1]
- new_enum = "_".join(enum_list)
- if new_enum in updated_val_error_dict:
- print "ERROR: About to overwrite entry for %s" % (new_enum)
- update_enum = new_enum
+ elif new_core_msg in self.orig_core_msg_dict:
+ # Do similar stuff here
+ if len(self.orig_core_msg_dict[new_core_msg]) == 1:
+ orig_enum = self.orig_core_msg_dict[new_core_msg][0]
+ print "Found core err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum)
+ mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
+ update_enum = orig_enum
else:
- enum_list[-1] = "%05d" % (next_id)
- new_enum = "_".join(enum_list)
- next_id = next_id + 1
- print "Completely new id and error code, update new id from %s to unique %s" % (enum, new_enum)
- if new_enum in updated_val_error_dict:
- print "ERROR: About to overwrite entry for %s" % (new_enum)
- update_enum = new_enum
+ if self.findSeqID(self.orig_core_msg_dict[new_core_msg]):
+ (mapped_enums, update_enum) = self.useSeqID(self.orig_core_msg_dict[new_core_msg], mapped_enums)
+ print "Found core err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum)
+ else:
+ enum_list[-1] = "%05d" % (next_id)
+ new_enum = "_".join(enum_list)
+ next_id = next_id + 1
+ 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)
+ update_enum = new_enum
+ # This seems to be a new error so need to pick it up from end of original unique ids & flag for review
+ else:
+ enum_list[-1] = "%05d" % (next_id)
+ new_enum = "_".join(enum_list)
+ next_id = next_id + 1
+ 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)
+ if new_enum in updated_val_error_dict:
+ print "ERROR: About to overwrite entry for %s" % (new_enum)
+ update_enum = new_enum
updated_val_error_dict[update_enum] = {}
updated_val_error_dict[update_enum]['error_msg'] = update_msg
updated_val_error_dict[update_enum]['api'] = update_api
updated_val_error_dict[update_enum]['implicit'] = implicit
- # Assign parsed dict to be the udpated dict based on db compare
+ # Assign parsed dict to be the updated dict based on db compare
print "In compareDB parsed %d entries" % (ids_parsed)
return updated_val_error_dict
+
def validateUpdateDict(self, update_dict):
"""Compare original dict vs. update dict and make sure that all of the checks are still there"""
# Currently just make sure that the same # of checks as the original checks are there
@@ -402,6 +492,14 @@
print "Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count)
return False
print "Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count)
+ # Now flag any original dict enums that had tests and/or checks that are missing from updated
+ for enum in update_dict:
+ if enum in self.orig_test_imp_enums:
+ self.orig_test_imp_enums.remove(enum)
+ if len(self.orig_test_imp_enums) > 0:
+ print "TODO: Have some enums with tests and/or checks implemented that are missing in update:"
+ for enum in sorted(self.orig_test_imp_enums):
+ print "\t%s" % enum
return True
# TODO : include some more analysis