blob: 155d68cd510c6c5fc5d2b6cf3eead4636ccd167f [file] [log] [blame]
Tobin Ehlis3198ba32017-04-19 17:30:52 -06001#!/usr/bin/python -i
Tobin Ehlis5ade0692016-10-05 17:18:15 -06002
3import sys
4import xml.etree.ElementTree as etree
5import urllib2
Tobin Ehlis3198ba32017-04-19 17:30:52 -06006from bs4 import BeautifulSoup
Tobin Ehlis5ade0692016-10-05 17:18:15 -06007
8#############################
9# spec.py script
10#
11# Overview - this script is intended to generate validation error codes and message strings from the xhtml version of
12# the specification. In addition to generating the header file, it provides a number of corrollary services to aid in
13# generating/updating the header.
14#
15# Ideal flow - Not there currently, but the ideal flow for this script would be that you run the script, it pulls the
16# latest spec, compares it to the current set of generated error codes, and makes any updates as needed
17#
18# Current flow - the current flow acheives all of the ideal flow goals, but with more steps than are desired
19# 1. Get the spec - right now spec has to be manually generated or pulled from the web
20# 2. Generate header from spec - This is done in a single command line
21# 3. Generate database file from spec - Can be done along with step #2 above, the database file contains a list of
22# all error enums and message strings, along with some other info on if those errors are implemented/tested
23# 4. Update header using a given database file as the root and a new spec file as goal - This makes sure that existing
24# errors keep the same enum identifier while also making sure that new errors get a unique_id that continues on
25# from the end of the previous highest unique_id.
26#
27# TODO:
28# 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 -060029#
30#############################
31
32
33spec_filename = "vkspec.html" # can override w/ '-spec <filename>' option
34out_filename = "vk_validation_error_messages.h" # can override w/ '-out <filename>' option
35db_filename = "vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option
36gen_db = False # set to True when '-gendb <filename>' option provided
37spec_compare = False # set to True with '-compare <db_filename>' option
38# 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 -060039#old_spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html"
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060040spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060041# After the custom validation error message, this is the prefix for the standard message that includes the
42# spec valid usage language as well as the link to nearest section of spec to that language
43error_msg_prefix = "For more information refer to Vulkan Spec Section "
44ns = {'ns': 'http://www.w3.org/1999/xhtml'}
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060045validation_error_enum_name = "VALIDATION_ERROR_"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060046# Dict of new enum values that should be forced to remap to old handles, explicitly set by -remap option
47remap_dict = {}
48
49def printHelp():
Tobin Ehlis3198ba32017-04-19 17:30:52 -060050 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]")
51 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")
52 print (" Default specfile is from online at %s" % (spec_url))
53 print (" Default headerfile is %s" % (out_filename))
54 print (" Default databasefile is %s" % (db_filename))
55 print ("\nIf '-gendb' option is specified then a database file is generated to default file or <databasefile.txt> if supplied. The database file stores")
56 print (" the list of enums and their error messages.")
57 print ("\nIf '-compare' option is specified then the given database file will be read in as the baseline for generating the new specfile")
58 print ("\nIf '-update' option is specified this triggers the master flow to automate updating header and database files using default db file as baseline")
59 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.")
60 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")
61 print (" option. Starting at newid and remapping to oldid, count ids will be remapped. Default count is '1' and use ':' to specify multiple remappings.")
Tobin Ehlis5ade0692016-10-05 17:18:15 -060062
63class Specification:
64 def __init__(self):
65 self.tree = None
Tobin Ehlise7560e72016-10-19 15:59:38 -060066 self.val_error_dict = {} # string for enum is key that references 'error_msg' and 'api'
Tobin Ehlis5ade0692016-10-05 17:18:15 -060067 self.error_db_dict = {} # dict of previous error values read in from database file
68 self.delimiter = '~^~' # delimiter for db file
Tobin Ehlis2a176b12017-01-11 16:18:20 -070069 self.implicit_count = 0
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060070 # Global dicts used for tracking spec updates from old to new VUs
71 self.orig_full_msg_dict = {} # Original full error msg to ID mapping
72 self.orig_no_link_msg_dict = {} # Pair of API,Original msg w/o spec link to ID list mapping
73 self.orig_core_msg_dict = {} # Pair of API,Original core msg (no link or section) to ID list mapping
74 self.last_mapped_id = -10 # start as negative so we don't hit an accidental sequence
75 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 -060076 self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */
77
78/*
79 * Vulkan
80 *
81 * Copyright (c) 2016 Google Inc.
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060082 * Copyright (c) 2016 LunarG, Inc.
Tobin Ehlis5ade0692016-10-05 17:18:15 -060083 *
84 * Licensed under the Apache License, Version 2.0 (the "License");
85 * you may not use this file except in compliance with the License.
86 * You may obtain a copy of the License at
87 *
88 * http://www.apache.org/licenses/LICENSE-2.0
89 *
90 * Unless required by applicable law or agreed to in writing, software
91 * distributed under the License is distributed on an "AS IS" BASIS,
92 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
93 * See the License for the specific language governing permissions and
94 * limitations under the License.
95 *
96 * Author: Tobin Ehlis <tobine@google.com>
97 */"""
98 def _checkInternetSpec(self):
99 """Verify that we can access the spec online"""
100 try:
101 online = urllib2.urlopen(spec_url,timeout=1)
102 return True
103 except urllib2.URLError as err:
104 return False
105 return False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600106 def soupLoadFile(self, online=True, spec_file=spec_filename):
107 """Load a spec file into BeutifulSoup"""
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600108 if (online and self._checkInternetSpec()):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600109 print ("Making soup from spec online at %s, this will take a minute" % (spec_url))
110 self.soup = BeautifulSoup(urllib2.urlopen(spec_url), 'html.parser')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600111 else:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600112 print ("Making soup from local spec %s, this will take a minute" % (spec_file))
113 self.soup = BeautifulSoup(spec_file, 'html.parser')
114 self.parseSoup()
115 #print(self.soup.prettify())
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600116 def updateDict(self, updated_dict):
117 """Assign internal dict to use updated_dict"""
118 self.val_error_dict = updated_dict
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600119 def parseSoup(self):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600120 """Parse the registry Element, once created"""
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600121 print ("Parsing spec file...")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600122 unique_enum_id = 0
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600123 #self.root = self.tree.getroot()
124 #print ("ROOT: %s") % self.root
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600125 prev_heading = '' # Last seen section heading or sub-heading
126 prev_link = '' # Last seen link id within the spec
Tobin Ehlise7560e72016-10-19 15:59:38 -0600127 api_function = '' # API call that a check appears under
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600128 error_strings = set() # Flag any exact duplicate error strings and skip them
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600129 for tag in self.soup.find_all(True):#self.root.iter(): # iterate down tree
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600130 # Grab most recent section heading and link
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600131 #print ("tag.name is %s and class is %s" % (tag.name, tag.get('class')))
132 if tag.name in ['h2', 'h3', 'h4']:
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800133 #if tag.get('class') != 'title':
134 # continue
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600135 #print ("Found heading %s w/ string %s" % (tag.name, tag.string))
136 if None == tag.string:
137 prev_heading = ""
138 else:
139 prev_heading = "".join(tag.string)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600140 # Insert a space between heading number & title
141 sh_list = prev_heading.rsplit('.', 1)
142 prev_heading = '. '.join(sh_list)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600143 prev_link = tag['id']
144 #print ("Set prev_heading %s to have link of %s" % (prev_heading.encode("ascii", "ignore"), prev_link.encode("ascii", "ignore")))
145 elif tag.name == 'a': # grab any intermediate links
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600146 if tag.get('id') != None:
147 prev_link = tag.get('id')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600148 #print ("Updated prev link to %s" % (prev_link))
149 elif tag.name == 'div' and tag.get('class') is not None and tag['class'][0] == 'listingblock':
Tobin Ehlise7560e72016-10-19 15:59:38 -0600150 # Check and see if this is API function
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600151 code_text = "".join(tag.strings).replace('\n', '')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600152 code_text_list = code_text.split()
153 if len(code_text_list) > 1 and code_text_list[1].startswith('vk'):
154 api_function = code_text_list[1].strip('(')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600155 #print ("Found API function: %s" % (api_function))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600156 prev_link = api_function
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600157 #print ("Updated prev link to %s" % (prev_link))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600158 elif tag.get('id') != None:
159 prev_link = tag.get('id')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600160 #print ("Updated prev link to %s" % (prev_link))
161 #elif tag.name == '{http://www.w3.org/1999/xhtml}div' and tag.get('class') == 'sidebar':
162 elif tag.name == 'div' and tag.get('class') is not None and tag['class'][0] == 'content':
163 #print("Parsing down a div content tag")
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600164 # parse down sidebar to check for valid usage cases
165 valid_usage = False
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700166 implicit = False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600167 for elem in tag.find_all(True):
168 #print(" elem is %s w/ string %s" % (elem.name, elem.string))
169 if elem.name == 'div' and None != elem.string and 'Valid Usage' in elem.string:
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600170 valid_usage = True
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600171 if '(Implicit)' in elem.string:
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700172 implicit = True
173 else:
174 implicit = False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600175 elif valid_usage and elem.name == 'li': # grab actual valid usage requirements
176 #print("I think this is a VU w/ elem.strings is %s" % (elem.strings))
177 error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(elem.strings).replace('\n', ' ').strip(), spec_url, prev_link)
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600178 # Some txt has multiple spaces so split on whitespace and join w/ single space
179 error_msg_str = " ".join(error_msg_str.split())
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600180 if error_msg_str in error_strings:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600181 print ("WARNING: SKIPPING adding repeat entry for string. Please review spec and file issue as appropriate. Repeat string is: %s" % (error_msg_str))
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600182 else:
183 error_strings.add(error_msg_str)
184 enum_str = "%s%05d" % (validation_error_enum_name, unique_enum_id)
185 # TODO : '\' chars in spec error messages are most likely bad spec txt that needs to be updated
Tobin Ehlise7560e72016-10-19 15:59:38 -0600186 self.val_error_dict[enum_str] = {}
187 self.val_error_dict[enum_str]['error_msg'] = error_msg_str.encode("ascii", "ignore").replace("\\", "/")
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600188 self.val_error_dict[enum_str]['api'] = api_function.encode("ascii", "ignore")
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700189 self.val_error_dict[enum_str]['implicit'] = False
190 if implicit:
191 self.val_error_dict[enum_str]['implicit'] = True
192 self.implicit_count = self.implicit_count + 1
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600193 unique_enum_id = unique_enum_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600194 #print ("Validation Error Dict has a total of %d unique errors and contents are:\n%s" % (unique_enum_id, self.val_error_dict))
195 print ("Validation Error Dict has a total of %d unique errors" % (unique_enum_id))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600196 def genHeader(self, header_file):
197 """Generate a header file based on the contents of a parsed spec"""
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600198 print ("Generating header %s..." % (header_file))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600199 file_contents = []
200 file_contents.append(self.copyright)
201 file_contents.append('\n#pragma once')
Mark Lobodzinski267a7cf2017-01-25 09:33:25 -0700202 file_contents.append('\n// Disable auto-formatting for generated file')
203 file_contents.append('// clang-format off')
204 file_contents.append('\n#include <unordered_map>')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600205 file_contents.append('\n// enum values for unique validation error codes')
206 file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below')
207 file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode')
208 file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function')
Tobin Ehlis387fd632016-12-08 13:32:05 -0700209 enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {\n VALIDATION_ERROR_UNDEFINED = -1,']
Tobin Ehlisbf98b692016-10-06 12:58:06 -0600210 error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{']
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800211 enum_value = 0
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600212 for enum in sorted(self.val_error_dict):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600213 #print ("Header enum is %s" % (enum))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800214 enum_value = int(enum.split('_')[-1])
215 enum_decl.append(' %s = %d,' % (enum, enum_value))
Tobin Ehlise7560e72016-10-19 15:59:38 -0600216 error_string_map.append(' {%s, "%s"},' % (enum, self.val_error_dict[enum]['error_msg']))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800217 enum_decl.append(' %sMAX_ENUM = %d,' % (validation_error_enum_name, enum_value + 1))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600218 enum_decl.append('};')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600219 error_string_map.append('};\n')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600220 file_contents.extend(enum_decl)
221 file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message')
222 file_contents.append('// The error message should be appended to the end of a custom error message that is passed')
223 file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function')
224 file_contents.extend(error_string_map)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600225 #print ("File contents: %s" % (file_contents))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600226 with open(header_file, "w") as outfile:
227 outfile.write("\n".join(file_contents))
228 def analyze(self):
229 """Print out some stats on the valid usage dict"""
230 # Create dict for # of occurences of identical strings
231 str_count_dict = {}
232 unique_id_count = 0
233 for enum in self.val_error_dict:
Tobin Ehlise7560e72016-10-19 15:59:38 -0600234 err_str = self.val_error_dict[enum]['error_msg']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600235 if err_str in str_count_dict:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600236 print ("Found repeat error string")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600237 str_count_dict[err_str] = str_count_dict[err_str] + 1
238 else:
239 str_count_dict[err_str] = 1
240 unique_id_count = unique_id_count + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600241 print ("Processed %d unique_ids" % (unique_id_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600242 repeat_string = 0
243 for es in str_count_dict:
244 if str_count_dict[es] > 1:
245 repeat_string = repeat_string + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600246 print ("String '%s' repeated %d times" % (es, repeat_string))
247 print ("Found %d repeat strings" % (repeat_string))
248 print ("Found %d implicit checks" % (self.implicit_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600249 def genDB(self, db_file):
250 """Generate a database of check_enum, check_coded?, testname, error_string"""
251 db_lines = []
252 # Write header for database file
253 db_lines.append("# This is a database file with validation error check information")
254 db_lines.append("# Comments are denoted with '#' char")
255 db_lines.append("# The format of the lines is:")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600256 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 -0600257 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 -0700258 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 -0600259 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 -0600260 db_lines.append("# api: Vulkan API function that this check is related to")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600261 db_lines.append("# errormsg: The unique error message for this check that includes spec language and link")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600262 db_lines.append("# note: Free txt field with any custom notes related to the check in question")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600263 for enum in sorted(self.val_error_dict):
Mike Weiblenfe186122017-02-03 12:44:53 -0700264 # Default check/test implementation status to N/Unknown, then update below if appropriate
265 implemented = 'N'
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600266 testname = 'Unknown'
Tobin Ehlis70980c02016-10-25 14:00:20 -0600267 note = ''
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700268 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600269 # If we have an existing db entry for this enum, use its implemented/testname values
270 if enum in self.error_db_dict:
271 implemented = self.error_db_dict[enum]['check_implemented']
272 testname = self.error_db_dict[enum]['testname']
Tobin Ehlis70980c02016-10-25 14:00:20 -0600273 note = self.error_db_dict[enum]['note']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700274 if implicit and 'implicit' not in note: # add implicit note
275 if '' != note:
276 note = "implicit, %s" % (note)
277 else:
278 note = "implicit"
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600279 #print ("delimiter: %s, id: %s, str: %s" % (self.delimiter, enum, self.val_error_dict[enum])
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600280 # No existing entry so default to N for implemented and None for testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600281 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 -0600282 db_lines.append("\n") # newline at end of file
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600283 print ("Generating database file %s" % (db_file))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600284 with open(db_file, "w") as outfile:
285 outfile.write("\n".join(db_lines))
286 def readDB(self, db_file):
287 """Read a db file into a dict, format of each line is <enum><implemented Y|N?><testname><errormsg>"""
288 db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
289 max_id = 0
290 with open(db_file, "r") as infile:
291 for line in infile:
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600292 line = line.strip()
Tobin Ehlisf4245cb2016-10-31 07:55:19 -0600293 if line.startswith('#') or '' == line:
294 continue
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600295 db_line = line.split(self.delimiter)
Tobin Ehlis70980c02016-10-25 14:00:20 -0600296 if len(db_line) != 6:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600297 print ("ERROR: Bad database line doesn't have 6 elements: %s" % (line))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600298 error_enum = db_line[0]
299 implemented = db_line[1]
300 testname = db_line[2]
Tobin Ehlis70980c02016-10-25 14:00:20 -0600301 api = db_line[3]
302 error_str = db_line[4]
303 note = db_line[5]
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600304 db_dict[error_enum] = error_str
305 # Also read complete database contents into our class var for later use
306 self.error_db_dict[error_enum] = {}
307 self.error_db_dict[error_enum]['check_implemented'] = implemented
308 self.error_db_dict[error_enum]['testname'] = testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600309 self.error_db_dict[error_enum]['api'] = api
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600310 self.error_db_dict[error_enum]['error_string'] = error_str
Tobin Ehlis70980c02016-10-25 14:00:20 -0600311 self.error_db_dict[error_enum]['note'] = note
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600312 unique_id = int(db_line[0].split('_')[-1])
313 if unique_id > max_id:
314 max_id = unique_id
315 return (db_dict, max_id)
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600316 # This is a helper function to do bookkeeping on data structs when comparing original
317 # error ids to current error ids
318 # It tracks all updated enums in mapped_enums and removes those enums from any lists
319 # in the no_link and core dicts
320 def _updateMappedEnum(self, mapped_enums, enum):
321 mapped_enums.add(enum)
322 # When looking for ID to map, we favor sequences so track last ID mapped
323 self.last_mapped_id = int(enum.split('_')[-1])
324 for msg in self.orig_no_link_msg_dict:
325 if enum in self.orig_no_link_msg_dict[msg]:
326 self.orig_no_link_msg_dict[msg].remove(enum)
327 for msg in self.orig_core_msg_dict:
328 if enum in self.orig_core_msg_dict[msg]:
329 self.orig_core_msg_dict[msg].remove(enum)
330 return mapped_enums
331 # Check all ids in given id list to see if one is in sequence from last mapped id
332 def findSeqID(self, id_list):
333 next_seq_id = self.last_mapped_id + 1
334 for map_id in id_list:
335 id_num = int(map_id.split('_')[-1])
336 if id_num == next_seq_id:
337 return True
338 return False
339 # Use the next ID in sequence. This should only be called if findSeqID() just returned True
340 def useSeqID(self, id_list, mapped_enums):
341 next_seq_id = self.last_mapped_id + 1
342 mapped_id = ''
343 for map_id in id_list:
344 id_num = int(map_id.split('_')[-1])
345 if id_num == next_seq_id:
346 mapped_id = map_id
347 self._updateMappedEnum(mapped_enums, mapped_id)
348 return (mapped_enums, mapped_id)
349 return (mapped_enums, mapped_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600350 # Compare unique ids from original database to data generated from updated spec
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600351 # First, make 3 separate mappings of original error messages:
352 # 1. Map the full error message to its id. There should only be 1 ID per full message (orig_full_msg_dict)
353 # 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)
354 # 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)
355 # Also store a set of all IDs that have been mapped to that will serve 2 purposes:
356 # 1. Pull IDs out of the above dicts as they're remapped since we know they won't be used
357 # 2. Make sure that we don't re-use an ID
358 # The general algorithm for remapping from new IDs to old IDs is:
359 # 1. If there is a user-specified remapping, use that above all else
360 # 2. Elif the new error message hits in orig_full_msg_dict then use that ID
361 # 3. Elif the new error message hits orig_no_link_msg_dict then
362 # a. If only a single ID, use it
363 # b. Elif multiple IDs & one matches last used ID in sequence, use it
364 # c. Else assign a new ID and flag for manual remapping
365 # 4. Elif the new error message hits orig_core_msg_dict then
366 # a. If only a single ID, use it
367 # b. Elif multiple IDs & one matches last used ID in sequence, use it
368 # c. Else assign a new ID and flag for manual remapping
369 # 5. Else - No matches use a new ID
Tobin Ehlise7560e72016-10-19 15:59:38 -0600370 def compareDB(self, orig_error_msg_dict, max_id):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600371 """Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec"""
372 # First create reverse dicts of err_strings to IDs
373 next_id = max_id + 1
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600374 ids_parsed = 0
375 mapped_enums = set() # store all enums that have been mapped to avoid re-use
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600376 # Create an updated dict in-place that will be assigned to self.val_error_dict when done
377 updated_val_error_dict = {}
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600378 # Create a few separate mappings of error msg formats to associated ID(s)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600379 for enum in orig_error_msg_dict:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600380 api = self.error_db_dict[enum]['api']
381 original_full_msg = orig_error_msg_dict[enum]
382 orig_no_link_msg = "%s,%s" % (api, original_full_msg.split('(https', 1)[0])
383 orig_core_msg = "%s,%s" % (api, orig_no_link_msg.split(' which states ', 1)[-1])
384 orig_core_msg_period = "%s.' " % (orig_core_msg[:-2])
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600385 print ("Orig core msg:%s\nOrig cw/o per:%s" % (orig_core_msg, orig_core_msg_period))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600386
387 # First store mapping of full error msg to ID, shouldn't have duplicates
388 if original_full_msg in self.orig_full_msg_dict:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600389 print ("ERROR: Found duplicate full msg in original full error messages: %s" % (original_full_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600390 self.orig_full_msg_dict[original_full_msg] = enum
391 # Now map API,no_link_msg to list of IDs
392 if orig_no_link_msg in self.orig_no_link_msg_dict:
393 self.orig_no_link_msg_dict[orig_no_link_msg].append(enum)
394 else:
395 self.orig_no_link_msg_dict[orig_no_link_msg] = [enum]
396 # Finally map API,core_msg to list of IDs
397 if orig_core_msg in self.orig_core_msg_dict:
398 self.orig_core_msg_dict[orig_core_msg].append(enum)
399 else:
400 self.orig_core_msg_dict[orig_core_msg] = [enum]
401 if orig_core_msg_period in self.orig_core_msg_dict:
402 self.orig_core_msg_dict[orig_core_msg_period].append(enum)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600403 print ("Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600404 else:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600405 print ("Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600406 self.orig_core_msg_dict[orig_core_msg_period] = [enum]
407 # Also capture all enums that have a test and/or implementation
408 if self.error_db_dict[enum]['check_implemented'] == 'Y' or self.error_db_dict[enum]['testname'] not in ['None','Unknown']:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600409 print ("Recording %s with implemented value %s and testname %s" % (enum, self.error_db_dict[enum]['check_implemented'], self.error_db_dict[enum]['testname']))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600410 self.orig_test_imp_enums.add(enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600411 # Values to be used for the update dict
412 update_enum = ''
413 update_msg = ''
414 update_api = ''
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600415 # Now parse through new dict and figure out what to do with non-matching things
416 for enum in sorted(self.val_error_dict):
417 ids_parsed = ids_parsed + 1
418 enum_list = enum.split('_') # grab sections of enum for use below
Tobin Ehlise7560e72016-10-19 15:59:38 -0600419 # Default update values to be the same
420 update_enum = enum
421 update_msg = self.val_error_dict[enum]['error_msg']
422 update_api = self.val_error_dict[enum]['api']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700423 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600424 new_full_msg = update_msg
425 new_no_link_msg = "%s,%s" % (update_api, new_full_msg.split('(https', 1)[0])
426 new_core_msg = "%s,%s" % (update_api, new_no_link_msg.split(' which states ', 1)[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600427 # Any user-forced remap takes precendence
428 if enum_list[-1] in remap_dict:
429 enum_list[-1] = remap_dict[enum_list[-1]]
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600430 self.last_mapped_id = int(enum_list[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600431 new_enum = "_".join(enum_list)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600432 print ("NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum))
433 mapped_enums = self._updateMappedEnum(mapped_enums, new_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600434 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600435 elif new_full_msg in self.orig_full_msg_dict:
436 orig_enum = self.orig_full_msg_dict[new_full_msg]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600437 print ("Found exact match for full error msg so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600438 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
439 update_enum = orig_enum
440 elif new_no_link_msg in self.orig_no_link_msg_dict:
441 # Try to get single ID to map to from no_link matches
442 if len(self.orig_no_link_msg_dict[new_no_link_msg]) == 1: # Only 1 id, use it!
443 orig_enum = self.orig_no_link_msg_dict[new_no_link_msg][0]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600444 print ("Found no-link err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600445 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
446 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600447 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600448 if self.findSeqID(self.orig_no_link_msg_dict[new_no_link_msg]): # If we have an id in sequence, use it!
449 (mapped_enums, update_enum) = self.useSeqID(self.orig_no_link_msg_dict[new_no_link_msg], mapped_enums)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600450 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 -0600451 else:
452 enum_list[-1] = "%05d" % (next_id)
453 new_enum = "_".join(enum_list)
454 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600455 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 -0600456 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600457 elif new_core_msg in self.orig_core_msg_dict:
458 # Do similar stuff here
459 if len(self.orig_core_msg_dict[new_core_msg]) == 1:
460 orig_enum = self.orig_core_msg_dict[new_core_msg][0]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600461 print ("Found core err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600462 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
463 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600464 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600465 if self.findSeqID(self.orig_core_msg_dict[new_core_msg]):
466 (mapped_enums, update_enum) = self.useSeqID(self.orig_core_msg_dict[new_core_msg], mapped_enums)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600467 print ("Found core err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600468 else:
469 enum_list[-1] = "%05d" % (next_id)
470 new_enum = "_".join(enum_list)
471 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600472 print ("Found core msg match but have multiple matched IDs w/o a sequence ID, updating ID %s to unique ID %s for msg %s" % (enum, new_enum, new_no_link_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600473 update_enum = new_enum
474 # This seems to be a new error so need to pick it up from end of original unique ids & flag for review
475 else:
476 enum_list[-1] = "%05d" % (next_id)
477 new_enum = "_".join(enum_list)
478 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600479 print ("Completely new id and error code, update new id from %s to unique %s for core message:%s" % (enum, new_enum, new_core_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600480 update_enum = new_enum
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600481 if update_enum in updated_val_error_dict:
482 print ("ERROR: About to OVERWRITE entry for %s" % update_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600483 updated_val_error_dict[update_enum] = {}
484 updated_val_error_dict[update_enum]['error_msg'] = update_msg
485 updated_val_error_dict[update_enum]['api'] = update_api
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700486 updated_val_error_dict[update_enum]['implicit'] = implicit
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600487 # Assign parsed dict to be the updated dict based on db compare
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600488 print ("In compareDB parsed %d entries" % (ids_parsed))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600489 return updated_val_error_dict
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600490
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600491 def validateUpdateDict(self, update_dict):
492 """Compare original dict vs. update dict and make sure that all of the checks are still there"""
493 # Currently just make sure that the same # of checks as the original checks are there
494 #orig_ids = {}
495 orig_id_count = len(self.val_error_dict)
496 #update_ids = {}
497 update_id_count = len(update_dict)
498 if orig_id_count != update_id_count:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600499 print ("Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600500 return False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600501 print ("Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600502 # Now flag any original dict enums that had tests and/or checks that are missing from updated
503 for enum in update_dict:
504 if enum in self.orig_test_imp_enums:
505 self.orig_test_imp_enums.remove(enum)
506 if len(self.orig_test_imp_enums) > 0:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600507 print ("TODO: Have some enums with tests and/or checks implemented that are missing in update:")
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600508 for enum in sorted(self.orig_test_imp_enums):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600509 print ("\t%s") % enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600510 return True
511 # TODO : include some more analysis
512
513# User passes in arg of form <new_id1>-<old_id1>[,count1]:<new_id2>-<old_id2>[,count2]:...
514# new_id# = the new enum id that was assigned to an error
515# old_id# = the previous enum id that was assigned to the same error
516# [,count#] = The number of ids to remap starting at new_id#=old_id# and ending at new_id[#+count#-1]=old_id[#+count#-1]
517# If not supplied, then ,1 is assumed, which will only update a single id
518def updateRemapDict(remap_string):
519 """Set up global remap_dict based on user input"""
520 remap_list = remap_string.split(":")
521 for rmap in remap_list:
522 count = 1 # Default count if none supplied
523 id_count_list = rmap.split(',')
524 if len(id_count_list) > 1:
525 count = int(id_count_list[1])
526 new_old_id_list = id_count_list[0].split('-')
527 for offset in range(count):
528 remap_dict["%05d" % (int(new_old_id_list[0]) + offset)] = "%05d" % (int(new_old_id_list[1]) + offset)
529 for new_id in sorted(remap_dict):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600530 print ("Set to remap new id %s to old id %s" % (new_id, remap_dict[new_id]))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600531
532if __name__ == "__main__":
533 i = 1
534 use_online = True # Attempt to grab spec from online by default
535 update_option = False
536 while (i < len(sys.argv)):
537 arg = sys.argv[i]
538 i = i + 1
539 if (arg == '-spec'):
540 spec_filename = sys.argv[i]
541 # If user specifies local specfile, skip online
542 use_online = False
543 i = i + 1
544 elif (arg == '-out'):
545 out_filename = sys.argv[i]
546 i = i + 1
547 elif (arg == '-gendb'):
548 gen_db = True
549 # Set filename if supplied, else use default
550 if i < len(sys.argv) and not sys.argv[i].startswith('-'):
551 db_filename = sys.argv[i]
552 i = i + 1
553 elif (arg == '-compare'):
554 db_filename = sys.argv[i]
555 spec_compare = True
556 i = i + 1
557 elif (arg == '-update'):
558 update_option = True
559 spec_compare = True
560 gen_db = True
561 elif (arg == '-remap'):
562 updateRemapDict(sys.argv[i])
563 i = i + 1
564 elif (arg in ['-help', '-h']):
565 printHelp()
566 sys.exit()
567 if len(remap_dict) > 1 and not update_option:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600568 print ("ERROR: '-remap' option can only be used along with '-update' option. Exiting.")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600569 sys.exit()
570 spec = Specification()
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600571 spec.soupLoadFile(use_online, spec_filename)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600572 spec.analyze()
573 if (spec_compare):
574 # Read in old spec info from db file
Tobin Ehlise7560e72016-10-19 15:59:38 -0600575 (orig_err_msg_dict, max_id) = spec.readDB(db_filename)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600576 # New spec data should already be read into self.val_error_dict
Tobin Ehlise7560e72016-10-19 15:59:38 -0600577 updated_dict = spec.compareDB(orig_err_msg_dict, max_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600578 update_valid = spec.validateUpdateDict(updated_dict)
579 if update_valid:
580 spec.updateDict(updated_dict)
581 else:
582 sys.exit()
583 if (gen_db):
584 spec.genDB(db_filename)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600585 print ("Writing out file (-out) to '%s'" % (out_filename))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600586 spec.genHeader(out_filename)
587
588##### Example dataset
589# <div class="sidebar">
590# <div class="titlepage">
591# <div>
592# <div>
593# <p class="title">
594# <strong>Valid Usage</strong> # When we get to this guy, we know we're under interesting sidebar
595# </p>
596# </div>
597# </div>
598# </div>
599# <div class="itemizedlist">
600# <ul class="itemizedlist" style="list-style-type: disc; ">
601# <li class="listitem">
602# <em class="parameter">
603# <code>device</code>
604# </em>
605# <span class="normative">must</span> be a valid
606# <code class="code">VkDevice</code> handle
607# </li>
608# <li class="listitem">
609# <em class="parameter">
610# <code>commandPool</code>
611# </em>
612# <span class="normative">must</span> be a valid
613# <code class="code">VkCommandPool</code> handle
614# </li>
615# <li class="listitem">
616# <em class="parameter">
617# <code>flags</code>
618# </em>
619# <span class="normative">must</span> be a valid combination of
620# <code class="code">
621# <a class="link" href="#VkCommandPoolResetFlagBits">VkCommandPoolResetFlagBits</a>
622# </code> values
623# </li>
624# <li class="listitem">
625# <em class="parameter">
626# <code>commandPool</code>
627# </em>
628# <span class="normative">must</span> have been created, allocated, or retrieved from
629# <em class="parameter">
630# <code>device</code>
631# </em>
632# </li>
633# <li class="listitem">All
634# <code class="code">VkCommandBuffer</code>
635# objects allocated from
636# <em class="parameter">
637# <code>commandPool</code>
638# </em>
639# <span class="normative">must</span> not currently be pending execution
640# </li>
641# </ul>
642# </div>
643# </div>
644##### Second example dataset
645# <div class="sidebar">
646# <div class="titlepage">
647# <div>
648# <div>
649# <p class="title">
650# <strong>Valid Usage</strong>
651# </p>
652# </div>
653# </div>
654# </div>
655# <div class="itemizedlist">
656# <ul class="itemizedlist" style="list-style-type: disc; ">
657# <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>
658# </li>
659# </ul>
660# </div>
661# </div>
662# <div class="sidebar">
663# <div class="titlepage">
664# <div>
665# <div>
666# <p class="title">
667# <strong>Valid Usage (Implicit)</strong>
668# </p>
669# </div>
670# </div>
671# </div>
672# <div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
673#<em class="parameter"><code>sType</code></em> <span class="normative">must</span> be <code class="code">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</code>
674#</li><li class="listitem">
675#<em class="parameter"><code>pNext</code></em> <span class="normative">must</span> be <code class="literal">NULL</code>
676#</li><li class="listitem">
677#<em class="parameter"><code>flags</code></em> <span class="normative">must</span> be <code class="literal">0</code>
678#</li><li class="listitem">
679#<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 -0700680#</li>