diff options
Diffstat (limited to 'codegen/vulkan/scripts/spec_tools/main.py')
-rw-r--r-- | codegen/vulkan/scripts/spec_tools/main.py | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/spec_tools/main.py b/codegen/vulkan/scripts/spec_tools/main.py new file mode 100644 index 00000000..2cd4f69c --- /dev/null +++ b/codegen/vulkan/scripts/spec_tools/main.py @@ -0,0 +1,244 @@ +"""Provides a re-usable command-line interface to a MacroChecker.""" + +# Copyright (c) 2018-2019 Collabora, Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com> + + +import argparse +import logging +import re +from pathlib import Path + +from .shared import MessageId + + +def checkerMain(default_enabled_messages, make_macro_checker, + all_docs, available_messages=None): + """Perform the bulk of the work for a command-line interface to a MacroChecker. + + Arguments: + default_enabled_messages -- The MessageId values that should be enabled by default. + make_macro_checker -- A function that can be called with a set of enabled MessageId to create a + properly-configured MacroChecker. + all_docs -- A list of all spec documentation files. + available_messages -- a list of all MessageId values that can be generated for this project. + Defaults to every value. (e.g. some projects don't have MessageId.LEGACY) + """ + enabled_messages = set(default_enabled_messages) + if not available_messages: + available_messages = list(MessageId) + + disable_args = [] + enable_args = [] + + parser = argparse.ArgumentParser() + parser.add_argument( + "--scriptlocation", + help="Append the script location generated a message to the output.", + action="store_true") + parser.add_argument( + "--verbose", + "-v", + help="Output 'info'-level development logging messages.", + action="store_true") + parser.add_argument( + "--debug", + "-d", + help="Output 'debug'-level development logging messages (more verbose than -v).", + action="store_true") + parser.add_argument( + "-Werror", + "--warning_error", + help="Make warnings act as errors, exiting with non-zero error code", + action="store_true") + parser.add_argument( + "--include_warn", + help="List all expected but unseen include files, not just those that are referenced.", + action='store_true') + parser.add_argument( + "-Wmissing_refpages", + help="List all entities with expected but unseen ref page blocks. NOT included in -Wall!", + action='store_true') + parser.add_argument( + "--include_error", + help="Make expected but unseen include files cause exiting with non-zero error code", + action='store_true') + parser.add_argument( + "--broken_error", + help="Make missing include/anchor for linked-to entities cause exiting with non-zero error code. Weaker version of --include_error.", + action='store_true') + parser.add_argument( + "--dump_entities", + help="Just dump the parsed entity data to entities.json and exit.", + action='store_true') + parser.add_argument( + "--html", + help="Output messages to the named HTML file instead of stdout.") + parser.add_argument( + "file", + help="Only check the indicated file(s). By default, all chapters and extensions are checked.", + nargs="*") + parser.add_argument( + "--ignore_count", + type=int, + help="Ignore up to the given number of errors without exiting with a non-zero error code.") + parser.add_argument("-Wall", + help="Enable all warning categories.", + action='store_true') + + for message_id in MessageId: + enable_arg = message_id.enable_arg() + enable_args.append((message_id, enable_arg)) + + disable_arg = message_id.disable_arg() + disable_args.append((message_id, disable_arg)) + if message_id in enabled_messages: + parser.add_argument('-' + disable_arg, action="store_true", + help="Disable message category {}: {}".format(str(message_id), message_id.desc())) + # Don't show the enable flag in help since it's enabled by default + parser.add_argument('-' + enable_arg, action="store_true", + help=argparse.SUPPRESS) + else: + parser.add_argument('-' + enable_arg, action="store_true", + help="Enable message category {}: {}".format(str(message_id), message_id.desc())) + # Don't show the disable flag in help since it's disabled by + # default + parser.add_argument('-' + disable_arg, action="store_true", + help=argparse.SUPPRESS) + + args = parser.parse_args() + + arg_dict = vars(args) + for message_id, arg in enable_args: + if args.Wall or (arg in arg_dict and arg_dict[arg]): + enabled_messages.add(message_id) + + for message_id, arg in disable_args: + if arg in arg_dict and arg_dict[arg]: + enabled_messages.discard(message_id) + + if args.verbose: + logging.basicConfig(level='INFO') + + if args.debug: + logging.basicConfig(level='DEBUG') + + checker = make_macro_checker(enabled_messages) + + if args.dump_entities: + with open('entities.json', 'w', encoding='utf-8') as f: + f.write(checker.getEntityJson()) + exit(0) + + if args.file: + files = (str(Path(f).resolve()) for f in args.file) + else: + files = all_docs + + for fn in files: + checker.processFile(fn) + + if args.html: + from .html_printer import HTMLPrinter + printer = HTMLPrinter(args.html) + else: + from .console_printer import ConsolePrinter + printer = ConsolePrinter() + + if args.scriptlocation: + printer.show_script_location = True + + if args.file: + printer.output("Only checked specified files.") + for f in args.file: + printer.output(f) + else: + printer.output("Checked all chapters and extensions.") + + if args.warning_error: + numErrors = checker.numDiagnostics() + else: + numErrors = checker.numErrors() + + check_includes = args.include_warn + check_broken = not args.file + + if args.file and check_includes: + print('Note: forcing --include_warn off because only checking supplied files.') + check_includes = False + + printer.outputResults(checker, broken_links=(not args.file), + missing_includes=check_includes) + + if check_broken: + numErrors += len(checker.getBrokenLinks()) + + if args.file and args.include_error: + print('Note: forcing --include_error off because only checking supplied files.') + args.include_error = False + if args.include_error: + numErrors += len(checker.getMissingUnreferencedApiIncludes()) + + check_missing_refpages = args.Wmissing_refpages + if args.file and check_missing_refpages: + print('Note: forcing -Wmissing_refpages off because only checking supplied files.') + check_missing_refpages = False + + if check_missing_refpages: + missing = checker.getMissingRefPages() + if missing: + printer.output("Expected, but did not find, ref page blocks for the following {} entities: {}".format( + len(missing), + ', '.join(missing) + )) + if args.warning_error: + numErrors += len(missing) + + printer.close() + + if args.broken_error and not args.file: + numErrors += len(checker.getBrokenLinks()) + + if checker.hasFixes(): + fixFn = 'applyfixes.sh' + print('Saving shell script to apply fixes as {}'.format(fixFn)) + with open(fixFn, 'w', encoding='utf-8') as f: + f.write('#!/bin/sh -e\n') + for fileChecker in checker.files: + wroteComment = False + for msg in fileChecker.messages: + if msg.fix is not None: + if not wroteComment: + f.write('\n# {}\n'.format(fileChecker.filename)) + wroteComment = True + search, replace = msg.fix + f.write( + r"sed -i -r 's~\b{}\b~{}~g' {}".format( + re.escape(search), + replace, + fileChecker.filename)) + f.write('\n') + + print('Total number of errors with this run: {}'.format(numErrors)) + + if args.ignore_count: + if numErrors > args.ignore_count: + # Exit with non-zero error code so that we "fail" CI, etc. + print('Exceeded specified limit of {}, so exiting with error'.format( + args.ignore_count)) + exit(1) + else: + print('At or below specified limit of {}, so exiting with success'.format( + args.ignore_count)) + exit(0) + + if numErrors: + # Exit with non-zero error code so that we "fail" CI, etc. + print('Exiting with error') + exit(1) + else: + print('Exiting with success') + exit(0) |