diff options
| author | Mark Lobodzinski <mark@lunarg.com> | 2017-06-21 13:41:00 -0600 |
|---|---|---|
| committer | Mark Lobodzinski <mark@lunarg.com> | 2017-06-23 10:27:55 -0600 |
| commit | 102588ce85c480264467ccc509fa793ed7e2f230 (patch) | |
| tree | 811999b1f3a5d112aa0824d890778719c1577444 /scripts/spec.py | |
| parent | d1aae7bee69df07cb1d22e425e40c8171cfe0285 (diff) | |
| download | usermoji-102588ce85c480264467ccc509fa793ed7e2f230.tar.xz | |
scripts: Move layer scripts into scripts directory
Simplifies sharing code among python modules, keeps everything together.
- Moved python scripts out of layers dir into scripts dir
- Updated relative paths for input/output files in layers dir
- Updated doc validation tests to use correct paths
Change-Id: I57e6ad6f551d4fb652583315fe3dccb23b187db1
Diffstat (limited to 'scripts/spec.py')
| -rw-r--r-- | scripts/spec.py | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/scripts/spec.py b/scripts/spec.py new file mode 100644 index 00000000..de346320 --- /dev/null +++ b/scripts/spec.py @@ -0,0 +1,357 @@ +#!/usr/bin/python -i + +import sys +try: + import urllib.request as urllib2 +except ImportError: + import urllib2 +from bs4 import BeautifulSoup +import json +import vuid_mapping + +############################# +# spec.py script +# +# Overview - this script is intended to generate validation error codes and message strings from the json spec file +# that contains all of the valid usage statements. In addition to generating the header file, it provides a number of +# corrollary services to aid in generating/updating the header. +# +# Ideal flow - Pull the valid usage text and IDs from the spec json, pull the IDs from the validation error database, +# then update the database with any new IDs from the json file and generate new database and header file. +# +# TODO: +# 1. When VUs go away (in error DB, but not in json) need to report them and remove from DB as deleted +# +############################# + + +out_filename = "../layers/vk_validation_error_messages.h" # can override w/ '-out <filename>' option +db_filename = "../layers/vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option +json_filename = None # con pass in w/ '-json <filename> option +gen_db = False # set to True when '-gendb <filename>' option provided +json_compare = False # compare existing DB to json file input +json_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/validation/validusage.json" +read_json = False +# 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/html/vkspec.html" +core_url = "https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html" +ext_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 = "The spec valid usage text states " +validation_error_enum_name = "VALIDATION_ERROR_" + +def printHelp(): + print ("Usage: python spec.py [-out <headerfile.h>] [-gendb <databasefile.txt>] [-update] [-json <json_file>] [-help]") + 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") + print (" Default specfile is from online at %s" % (spec_url)) + print (" Default headerfile is %s" % (out_filename)) + print (" Default databasefile is %s" % (db_filename)) + print ("\nIf '-gendb' option is specified then a database file is generated to default file or <databasefile.txt> if supplied. The database file stores") + print (" the list of enums and their error messages.") + print ("\nIf '-update' option is specified this triggers the master flow to automate updating header and database files using default db file as baseline") + 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.") + print ("\nIf '-json' option is used trigger the script to load in data from a json file.") + print ("\nIf '-json-file' option is it will point to a local json file, else '%s' is used from the web." % (json_url)) + +def get8digithex(dec_num): + """Convert a decimal # into an 8-digit hex""" + if dec_num > 4294967295: + print ("ERROR: Decimal # %d can't be represented in 8 hex digits" % (dec_num)) + sys.exit() + hex_num = hex(dec_num) + return hex_num[2:].zfill(8) + +class Specification: + def __init__(self): + self.tree = None + self.error_db_dict = {} # dict of previous error values read in from database file + self.delimiter = '~^~' # delimiter for db file + # Global dicts used for tracking spec updates from old to new VUs + 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 + # Dict of data from json DB + # Key is API,<short_msg> which leads to dict w/ following values + # 'ext' -> <core|<ext_name>> + # 'string_vuid' -> <string_vuid> + # 'number_vuid' -> <numerical_vuid> + self.json_db = {} + self.json_missing = 0 + self.struct_to_func_map = {} # Map structs to the API func that they fall under in the spec + self.duplicate_json_key_count = 0 + self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */ + +/* + * Vulkan + * + * Copyright (c) 2016 Google Inc. + * Copyright (c) 2016 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Tobin Ehlis <tobine@google.com> + */""" + + def readJSON(self): + """Read in JSON file""" + if json_filename is not None: + with open(json_filename) as jsf: + self.json_data = json.load(jsf, encoding='utf-8') + else: + response = urllib2.urlopen(json_url).read().decode('utf-8') + self.json_data = json.loads(response) + + def parseJSON(self): + """Parse JSON VUIDs into data struct""" + # Format of JSON file is: + # "API": { "core|EXT": [ {"vuid": "<id>", "text": "<VU txt>"}]}, + # "VK_KHX_external_memory" & "VK_KHX_device_group" - extension case (vs. "core") + for top_level in sorted(self.json_data): + if "validation" == top_level: + for api in sorted(self.json_data[top_level]): + for ext in sorted(self.json_data[top_level][api]): + for vu_txt_dict in self.json_data[top_level][api][ext]: + print ("Looking at dict for api:ext entry %s:%s" % (api, ext)) + vuid = vu_txt_dict['vuid'] + vutxt = vu_txt_dict['text'] + #print ("%s:%s:%s:%s" % (api, ext, vuid, vutxt)) + #print ("VUTXT orig:%s" % (vutxt)) + just_txt = BeautifulSoup(vutxt, 'html.parser') + #print ("VUTXT only:%s" % (just_txt.get_text())) + num_vuid = vuid_mapping.convertVUID(vuid) + self.json_db[vuid] = {} + self.json_db[vuid]['ext'] = ext + self.json_db[vuid]['number_vuid'] = num_vuid + self.json_db[vuid]['struct_func'] = api + just_txt = just_txt.get_text().strip() + unicode_map = { + u"\u2019" : "'", + u"\u2192" : "->", + } + for um in unicode_map: + just_txt = just_txt.replace(um, unicode_map[um]) + self.json_db[vuid]['vu_txt'] = just_txt.replace("\\", "") + print ("Spec vu txt:%s" % (self.json_db[vuid]['vu_txt'])) + #sys.exit() + + def compareJSON(self): + """Compare parsed json file with existing data read in from DB file""" + json_db_set = set() + for vuid in self.json_db: # pull entries out and see which fields we're missing from error_db + json_db_set.add(vuid) + for enum in self.error_db_dict: + vuid_string = self.error_db_dict[enum]['vuid_string'] + if vuid_string not in self.json_db: + #print ("Full string for %s is:%s" % (enum, full_error_string)) + print ("WARN: Couldn't find vuid_string in json db:%s" % (vuid_string)) + self.json_missing = self.json_missing + 1 + self.error_db_dict[enum]['ext'] = 'core' + # TODO: Currently GL843 tracks 2 VUs that are missing from json incorrectly + # Fix will land in 1.0.51 spec. After that we should take some alternative + # action here to indicate that VUs have gone away. + # Can have a removed_enums set that we add to and report to user + #sys.exit() + else: + json_db_set.remove(vuid_string) + self.error_db_dict[enum]['ext'] = self.json_db[vuid_string]['ext'] + if 'core' == self.json_db[vuid_string]['ext'] or '!' in self.json_db[vuid_string]['ext']: + spec_link = "%s#%s" % (core_url, vuid_string) + else: + spec_link = "%s#%s" % (ext_url, vuid_string) + self.error_db_dict[enum]['error_msg'] = "%s'%s' (%s)" % (error_msg_prefix, self.json_db[vuid_string]['vu_txt'], spec_link) + print ("Updated error_db error_msg:%s" % (self.error_db_dict[enum]['error_msg'])) + #sys.exit() + print ("These json DB entries are not in error DB:") + for extra_vuid in json_db_set: + print ("\t%s" % (extra_vuid)) + # Add these missing entries into the error_db + # Create link into core or ext spec as needed + if 'core' == self.json_db[extra_vuid]['ext'] or '!' in self.json_db[extra_vuid]['ext']: + spec_link = "%s#%s" % (core_url, extra_vuid) + else: + spec_link = "%s#%s" % (ext_url, extra_vuid) + error_enum = "%s%s" % (validation_error_enum_name, get8digithex(self.json_db[extra_vuid]['number_vuid'])) + self.error_db_dict[error_enum] = {} + self.error_db_dict[error_enum]['check_implemented'] = 'N' + self.error_db_dict[error_enum]['testname'] = 'None' + self.error_db_dict[error_enum]['api'] = self.json_db[extra_vuid]['struct_func'] + self.error_db_dict[error_enum]['vuid_string'] = extra_vuid + self.error_db_dict[error_enum]['error_msg'] = "%s'%s' (%s)" % (error_msg_prefix, self.json_db[extra_vuid]['vu_txt'], spec_link) + self.error_db_dict[error_enum]['note'] = '' + self.error_db_dict[error_enum]['ext'] = self.json_db[extra_vuid]['ext'] + implicit = False + last_segment = extra_vuid.split("-")[-1] + if last_segment in vuid_mapping.implicit_type_map: + implicit = True + elif not last_segment.isdigit(): # Explicit ids should only have digits in last segment + print ("ERROR: Found last segment of val error ID that isn't in implicit map and doesn't have numbers in last segment: %s" % (last_segment)) + sys.exit() + self.error_db_dict[error_enum]['implicit'] = implicit + + def genHeader(self, header_file): + """Generate a header file based on the contents of a parsed spec""" + print ("Generating header %s..." % (header_file)) + file_contents = [] + file_contents.append(self.copyright) + file_contents.append('\n#pragma once') + file_contents.append('\n// Disable auto-formatting for generated file') + file_contents.append('// clang-format off') + file_contents.append('\n#include <unordered_map>') + file_contents.append('\n// enum values for unique validation error codes') + file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below') + file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode') + file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function') + enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {\n VALIDATION_ERROR_UNDEFINED = -1,'] + error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{'] + enum_value = 0 + max_enum_val = 0 + for enum in sorted(self.error_db_dict): + #print ("Header enum is %s" % (enum)) + # TMP: Use updated value + vuid_str = self.error_db_dict[enum]['vuid_string'] + if vuid_str in self.json_db: + enum_value = self.json_db[vuid_str]['number_vuid'] + else: + enum_value = vuid_mapping.convertVUID(vuid_str) + new_enum = "%s%s" % (validation_error_enum_name, get8digithex(enum_value)) + enum_decl.append(' %s = 0x%s,' % (new_enum, get8digithex(enum_value))) + error_string_map.append(' {%s, "%s"},' % (new_enum, self.error_db_dict[enum]['error_msg'])) + max_enum_val = max(max_enum_val, enum_value) + enum_decl.append(' %sMAX_ENUM = %d,' % (validation_error_enum_name, max_enum_val + 1)) + enum_decl.append('};') + error_string_map.append('};\n') + file_contents.extend(enum_decl) + file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message') + file_contents.append('// The error message should be appended to the end of a custom error message that is passed') + file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function') + file_contents.extend(error_string_map) + #print ("File contents: %s" % (file_contents)) + with open(header_file, "w") as outfile: + outfile.write("\n".join(file_contents)) + def genDB(self, db_file): + """Generate a database of check_enum, check_coded?, testname, API, VUID_string, core|ext, error_string, notes""" + db_lines = [] + # Write header for database file + db_lines.append("# This is a database file with validation error check information") + db_lines.append("# Comments are denoted with '#' char") + db_lines.append("# The format of the lines is:") + db_lines.append("# <error_enum>%s<check_implemented>%s<testname>%s<api>%s<vuid_string>%s<core|ext>%s<errormsg>%s<note>" % (self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter, self.delimiter)) + db_lines.append("# error_enum: Unique error enum for this check of format %s<uniqueid>" % validation_error_enum_name) + db_lines.append("# check_implemented: 'Y' if check has been implemented in layers, or 'N' for not implemented") + db_lines.append("# testname: Name of validation test for this check, 'Unknown' for unknown, 'None' if not implemented, or 'NotTestable' if cannot be implemented") + db_lines.append("# api: Vulkan API function that this check is related to") + db_lines.append("# vuid_string: Unique string to identify this check") + db_lines.append("# core|ext: Either 'core' for core spec or some extension string that indicates the extension required for this VU to be relevant") + db_lines.append("# errormsg: The unique error message for this check that includes spec language and link") + db_lines.append("# note: Free txt field with any custom notes related to the check in question") + for enum in sorted(self.error_db_dict): + print ("Gen DB for enum %s" % (enum)) + implicit = self.error_db_dict[enum]['implicit'] + implemented = self.error_db_dict[enum]['check_implemented'] + testname = self.error_db_dict[enum]['testname'] + note = self.error_db_dict[enum]['note'] + core_ext = self.error_db_dict[enum]['ext'] + self.error_db_dict[enum]['vuid_string'] = self.error_db_dict[enum]['vuid_string'] + if implicit and 'implicit' not in note: # add implicit note + if '' != note: + note = "implicit, %s" % (note) + else: + note = "implicit" + db_lines.append("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % (enum, self.delimiter, implemented, self.delimiter, testname, self.delimiter, self.error_db_dict[enum]['api'], self.delimiter, self.error_db_dict[enum]['vuid_string'], self.delimiter, core_ext, self.delimiter, self.error_db_dict[enum]['error_msg'], self.delimiter, note)) + db_lines.append("\n") # newline at end of file + print ("Generating database file %s" % (db_file)) + with open(db_file, "w") as outfile: + outfile.write("\n".join(db_lines)) + def readDB(self, db_file): + """Read a db file into a dict, refer to genDB function above for format of each line""" + with open(db_file, "r") as infile: + for line in infile: + line = line.strip() + if line.startswith('#') or '' == line: + continue + db_line = line.split(self.delimiter) + if len(db_line) != 8: + print ("ERROR: Bad database line doesn't have 8 elements: %s" % (line)) + error_enum = db_line[0] + implemented = db_line[1] + testname = db_line[2] + api = db_line[3] + vuid_str = db_line[4] + core_ext = db_line[5] + error_str = db_line[6] + note = db_line[7] + # Also read complete database contents into our class var for later use + self.error_db_dict[error_enum] = {} + self.error_db_dict[error_enum]['check_implemented'] = implemented + self.error_db_dict[error_enum]['testname'] = testname + self.error_db_dict[error_enum]['api'] = api + self.error_db_dict[error_enum]['vuid_string'] = vuid_str + self.error_db_dict[error_enum]['ext'] = core_ext + self.error_db_dict[error_enum]['error_msg'] = error_str + self.error_db_dict[error_enum]['note'] = note + implicit = False + last_segment = vuid_str.split("-")[-1] + if last_segment in vuid_mapping.implicit_type_map: + implicit = True + elif not last_segment.isdigit(): # Explicit ids should only have digits in last segment + print ("ERROR: Found last segment of val error ID that isn't in implicit map and doesn't have numbers in last segment: %s" % (last_segment)) + sys.exit() + self.error_db_dict[error_enum]['implicit'] = implicit +if __name__ == "__main__": + i = 1 + use_online = True # Attempt to grab spec from online by default + while (i < len(sys.argv)): + arg = sys.argv[i] + i = i + 1 + if (arg == '-json-file'): + json_filename = sys.argv[i] + i = i + 1 + elif (arg == '-json'): + read_json = True + elif (arg == '-json-compare'): + json_compare = True + elif (arg == '-out'): + out_filename = sys.argv[i] + i = i + 1 + elif (arg == '-gendb'): + gen_db = True + # Set filename if supplied, else use default + if i < len(sys.argv) and not sys.argv[i].startswith('-'): + db_filename = sys.argv[i] + i = i + 1 + elif (arg == '-update'): + read_json = True + json_compare = True + gen_db = True + elif (arg in ['-help', '-h']): + printHelp() + sys.exit() + spec = Specification() + if read_json: + spec.readJSON() + spec.parseJSON() + #sys.exit() + if (json_compare): + # Read in current spec info from db file + (orig_err_msg_dict) = spec.readDB(db_filename) + spec.compareJSON() + print ("Found %d missing db entries in json db" % (spec.json_missing)) + print ("Found %d duplicate json entries" % (spec.duplicate_json_key_count)) + spec.genDB(db_filename) + if (gen_db): + spec.genDB(db_filename) + print ("Writing out file (-out) to '%s'" % (out_filename)) + spec.genHeader(out_filename) |
