# coding=utf-8 """ OpenSpace Copyright (c) 2014-2024 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This script traverses the file tree of OpenSpace and will check all files' include guards for correctness. At the moment this includes: * Correctness (file has a #ifndef. #define, and #endif lines) * Equality (using the same name for the #ifdef and #define) * Styling * no empty line between #ifndef and #define lines * Empty lines before and after #ifndef #define block * Files end with an empty line * Copyright header is correctly indented * Include guard correctly uses the filename * Include guard is all upper case * Correct usage of the name in the final comment of the file * Correct year of copyright notice * Naming convention * OpenSpace include guards start with OPENSPACE, Ghoul with GHOUL, module includes have the module name in it * The correct submodule is used * Checking for duplicates between all files * Checking that no file includes glm header directly * Checking whether any files starts with the UTF-8 Byte-order mark * Checking whether a file as empty-only lines * Checking whether the default assert macros are used anywhere instead of the ghoul_assert macro * Checking whether there are TABs in the file If this script is executed from the base directory of OpenSpace, no arguments need to be passed, otherwise the first and only argument has to point to the base directory. Thus, the default value of the first argument is '.' """ import fnmatch import glob import os import re import sys current_year = '2024' is_strict_mode = False is_silent_mode = False def get_ifndef_symbol(lines): index = [i for i,s in enumerate(lines) if '#ifndef ' in s] if len(index) == 0: return '', -1 result = re.search('#ifndef (.*)\n', lines[index[0]]) return result.group(1), index[0] def get_define_symbol(lines): index = [i for i,s in enumerate(lines) if '#define ' in s] if len(index) == 0: return '', -1 result = re.search('#define (.*)\n', lines[index[0]]) return result.group(1), index[0] def check_correctness(lines): ifndef_symbol, line_number = get_ifndef_symbol(lines) if line_number == -1: return 'No #ifndef in file' define_symbol, line_number = get_define_symbol(lines) if (line_number == -1): return 'No #define in file' index = [i for i,s in enumerate(lines) if '#endif' in s] if len(index) == 0: return 'No #endif in file' return '' def check_equality(lines): ifndef, _ = get_ifndef_symbol(lines) define, _ = get_define_symbol(lines) if ifndef == define: return '' else: return ifndef + ' ' + define def check_styling(lines): ifndef_symbol, ifndef_line = get_ifndef_symbol(lines) _, define_line = get_define_symbol(lines) if abs(ifndef_line - define_line) != 1: return '#ifndef and #define lines are not subsequent' if lines[ifndef_line - 1].strip() != '': return 'Preceding line is not empty' if lines[define_line + 1].strip() != '': return 'Following line is not empty' if not lines[-1][-1] in ['\n', '\r']: return 'Last line must end with a newline' for l in lines[2:23]: if l[0] != ' ': return 'Copyright header must be indented' if ifndef_symbol != ifndef_symbol.upper(): return 'Include guard is not all upper case' return '' def check_styling_filename(lines, filename): ifndef_symbol, _ = get_ifndef_symbol(lines) file = os.path.splitext(os.path.basename(filename))[0].upper() if not (file in ifndef_symbol or file in ifndef_symbol.replace('_', '')): return 'Malformed include guard: ' + ifndef_symbol + ' || ' + file def check_comment(lines): ifndef_symbol, _ = get_ifndef_symbol(lines) index = [i for i,s in enumerate(lines) if '#endif' in s] endif_line = lines[index[-1]].strip() if endif_line != '#endif // ' + ifndef_symbol: print(ifndef_symbol) print(endif_line) return '#endif line is not correctly formatted' else: return '' def check_copyright(lines): index = [i for i,s in enumerate(lines[0:23]) if 'Copyright' in s] if len(index) == 0: return 'No copyright header found' beginning_string = ' * Copyright (c) 2012-' # * Copyright (c) 2014- year = lines[index[0]][len(beginning_string) : len(beginning_string) + 4] if lines[index[0] + 1][0] != ' ': return 'Copyright header is not correctly indented' if year != current_year: return 'Out of date copyright notice ' + year + ' || ' + current_year return '' def check_byte_order_mark_character(lines): c = lines[0][0] if c == 'ï': return 'File contains UTF-8 byte mark order character' return '' def check_naming_convention_component(lines, component): ifndef_symbol, _ = get_ifndef_symbol(lines) component_part = ifndef_symbol[2:2 + len(component)] if component_part != component.upper(): return '#ifndef naming convention broken: ' + ifndef_symbol + ' || ' + component.upper() else: return '' def check_naming_convention_subcomponent(lines, component, file): ifndef_symbol, _ = get_ifndef_symbol(lines) if component == "ghoul" or component == "openspace_core": return subcomponent_part = ifndef_symbol[2 + len(component) + 1 :] subcomponent_part = subcomponent_part[: subcomponent_part.find('_')] path_part = file.split('/')[1] second_path_part = file.split('/')[2] if (path_part.upper() != subcomponent_part) and (second_path_part.upper() != subcomponent_part): return 'Subcomponent naming convention broken: ' + ifndef_symbol else: return '' def check_duplicates(lines, previousSymbols): ifndef_symbol, _ = get_ifndef_symbol(lines) if ifndef_symbol in previousSymbols: return False, ifndef_symbol else: return True, ifndef_symbol def check_glm_header(lines, file): Allowed_Files = [ 'ghoul/glm.h' ] for f in Allowed_Files: if f in file.replace('\\', '/'): return '' index = [i for i,s in enumerate(lines) if '#include ' in s or '#include "glm/glm.hpp>"' in s] if len(index) > 0: return 'File used wrong glm include. Use "#include " instead' else: return '' def check_core_dependency(lines, component): if component != "openspace_core": return '' index = [i for i,s in enumerate(lines) if 'OPENSPACE_MODULE_' in s] if len(index) > 0: return lines[index[0]][:-1] else: return '' def check_using_namespace(lines): index = [i for i,s in enumerate(lines) if "using namespace" in s.strip()] if len(index) > 0: return lines[index[0]] else: return '' def check_end_of_line(lines): if lines[-1][-1] != '\n': return lines[-1][-1] else: return '' def check_empty_only_line(lines): # Disable this check in non-strict mode if not is_strict_mode: return '' index = [i + 1 for i, s in enumerate(lines) if s.translate({ord(c): None for c in '\n\r'}).isspace()] if len(index) > 0: return index else: return '' def check_assert_usage(lines): # _assert checks for both ghoul_assert and static_assert, which are both reasonable index = [i + 1 for i,s in enumerate(lines) if ('assert(' in s and not '_assert(' in s) and s.strip()[0:2] != '//'] if len(index) > 0: return index else: return ''; def check_line_length(lines): # Disable this check in non-strict mode if not is_strict_mode: return '' index = [i + 1 for i, s in enumerate(lines) if len(s) > (90 + 1)] if len(index) > 0: return index else: return '' def check_empty_character_at_end(lines): # Disable this check in non-strict mode if not is_strict_mode: return '' index = [i + 1 for i, s in enumerate(lines) if len(s) > 1 and s[-2] == ' ' and not s.strip() == ''] if len(index) > 0: return index else: return '' def check_for_tab(lines): index = [i + 1 for i, s in enumerate(lines) if '\t' in s] if len(index) > 0: return index else: return '' previousSymbols = {} def check_header_file(file, component): with open(file, 'r+', encoding="utf8") as f: lines = f.readlines() correctness = check_correctness(lines) if correctness: print(file, '\t', 'Correctness check failed', '\t', correctness) return equality = check_equality(lines) if equality: print(file, '\t', 'Equality check failed', '\t', equality) return styling = check_styling(lines) if styling: print(file, '\t', 'Styling check failed', '\t', styling) return styling_filename = check_styling_filename(lines, file) if styling_filename: print(file, '\t', 'Filename styling check failed', '\t', styling_filename) return comment = check_comment(lines) if comment: print(file, '\t', 'Comment check failed', '\t', comment) return copyright = check_copyright(lines) if copyright: print(file, '\t', 'Copyright check failed', '\t', copyright) return naming_component = check_naming_convention_component(lines, component) if naming_component: print(file, '\t', 'Naming convention broken', '\t', naming_component) return naming_subcomponent = check_naming_convention_subcomponent(lines, component, file) if naming_subcomponent: print(file, '\t', 'Naming convention broken', '\t', naming_subcomponent) return end_of_line = check_end_of_line(lines) if end_of_line: print(file, '\t', 'Last line does not contain a newline character: ', end_of_line) return duplicates, symbol = check_duplicates(lines, previousSymbols) if not duplicates: print(file, '\t', 'Duplicate include guard', symbol, 'first in', previousSymbols[symbol]) return else: previousSymbols[symbol] = file header = check_glm_header(lines, file) if header: print(file, '\t', 'Illegal glm header include', header) return core_dependency = check_core_dependency(lines, component) if core_dependency: print(file, '\t', 'Wrong dependency (core depends on module)', core_dependency) if (not 'ghoul_gl.h' in file): # ghoul_gl.h is allowed to use 'using namespace' to pull the gl namespace in using_namespaces = check_using_namespace(lines) if using_namespaces: print(file, '\t', 'Using namespace found in header file') bom = check_byte_order_mark_character(lines) if bom: print(file, '\t', 'Byte order mark failed:', bom) empty_only_lines = check_empty_only_line(lines) if empty_only_lines: print(file, '\t', 'Empty only line: ', empty_only_lines) line_length = check_line_length(lines) if line_length: print(file, '\t', 'Line length exceeded: ', line_length) empty_character_at_end = check_empty_character_at_end(lines) if empty_character_at_end: print(file, '\t', 'Empty character at end: ', empty_character_at_end) assert_usage = check_assert_usage(lines) if assert_usage: print(file, '\t', 'Wrong assert usage: ', assert_usage) tabs = check_for_tab(lines) if tabs: print(file, '\t', 'TABs found: ', tabs) def check_inline_file(file, component): with open(file, 'r+', encoding="utf8") as f: lines = f.readlines() copyright = check_copyright(lines) if copyright: print(file, '\t', 'Copyright check failed', '\t', copyright) header = check_glm_header(lines, file) if header: print(file, '\t', 'Illegal glm header include', header) core_dependency = check_core_dependency(lines, component) if core_dependency: print(file, '\t', 'Wrong dependency (core depends on module)', core_dependency) end_of_line = check_end_of_line(lines) if end_of_line: print(file, '\t', 'Last line does not contain a newline character: ', end_of_line) return bom = check_byte_order_mark_character(lines) if bom: print(file, '\t', 'Byte order mark failed:', bom) empty_only_lines = check_empty_only_line(lines) if empty_only_lines: print(file, '\t', 'Empty only line: ', empty_only_lines) line_length = check_line_length(lines) if line_length: print(file, '\t', 'Line length exceeded: ', line_length) if (not '_doc.inl' in file and not '_lua.inl'): # The _doc.inl files are allowed to use using namespace as they are inclued # from the cpp files and thus don't leak it using_namespaces = check_using_namespace(lines) if using_namespaces: print(file, '\t', 'Using namespace found in inline file') line_length = check_line_length(lines) if line_length: print(file, '\t', 'Line length exceeded: ', line_length) empty_character_at_end = check_empty_character_at_end(lines) if empty_character_at_end: print(file, '\t', 'Empty character at end: ', empty_character_at_end) assert_usage = check_assert_usage(lines) if assert_usage: print(file, '\t', 'Wrong assert usage: ', assert_usage) tabs = check_for_tab(lines) if tabs: print(file, '\t', 'TABs found: ', tabs) def check_source_file(file, component): with open(file, 'r+', encoding="utf8") as f: lines = f.readlines() header = check_glm_header(lines, file) if header: print(file, '\t', 'Illegal glm header include', header) core_dependency = check_core_dependency(lines, component) if core_dependency: print(file, '\t' 'Wrong core dependency', core_dependency) end_of_line = check_end_of_line(lines) if end_of_line: print(file, '\t', 'Last line does not contain a newline character: ', end_of_line) return copyright = check_copyright(lines) if copyright: print(file, '\t', 'Copyright check failed', '\t', copyright) bom = check_byte_order_mark_character(lines) if bom: print(file, '\t', 'Byte order mark failed:', bom) empty_only_lines = check_empty_only_line(lines) if empty_only_lines: print(file, '\t', 'Empty only line: ', empty_only_lines) line_length = check_line_length(lines) if line_length: print(file, '\t', 'Line length exceeded: ', line_length) empty_character_at_end = check_empty_character_at_end(lines) if empty_character_at_end: print(file, '\t', 'Empty character at end: ', empty_character_at_end) assert_usage = check_assert_usage(lines) if assert_usage: print(file, '\t', 'Wrong assert usage: ', assert_usage) tabs = check_for_tab(lines) if tabs: print(file, '\t', 'TABs found: ', tabs) def check_files(positiveList, negativeList, component, check_function): files = [] for p in positiveList: f = glob.glob(p, recursive=True) f = [fi.replace('\\', '/') for fi in f] files.extend(f) negativeFiles = [] for n in negativeList: f = glob.glob(n, recursive=True) f = [fi.replace('\\', '/') for fi in f] negativeFiles.extend(f) filtered_files = [f for f in files if f not in negativeFiles] for file in filtered_files: check_function(file, component) basePath = './' if len(sys.argv) > 1: if sys.argv[1] != "strict": basePath = sys.argv[1] + '/' for a in sys.argv: if a == "strict": is_strict_mode = True if a == "silent": is_silent_mode = True # Check header files if not is_silent_mode: print("Checking header files") print("=====================") check_files( [basePath + 'include/**/*.h'], [], 'openspace_core', check_header_file ) check_files( [basePath + 'apps/**/*.h'], [basePath + 'apps/**/ext/**/*.h'], 'openspace_app', check_header_file ) check_files( [basePath + 'modules/**/*.h'], [ basePath + 'modules/**/ext/**/*.h', basePath + 'modules/**/node_modules/**/*.h', basePath + 'modules/webbrowser/resource.h' ], 'openspace_module', check_header_file ) check_files( [basePath + 'ext/ghoul/include/**/*.h'], [], 'ghoul', check_header_file ) if not is_silent_mode: print("") print("Checking inline files") print("=====================") check_files( [basePath + 'include/**/*.inl'], [], 'openspace_core', check_inline_file ) check_files( [basePath + 'src/**/*.inl'], [], 'openspace_core', check_inline_file ) check_files( [basePath + 'apps/**/*.inl'], [basePath + 'apps/**/ext/**/*.inl'], 'openspace_app', check_inline_file ) check_files( [basePath + 'modules/**/*.inl'], [basePath + 'modules/**/ext/**/*.h'], 'openspace_module', check_inline_file ) check_files( [basePath + 'ext/ghoul/include/**/*.inl'], [], 'ghoul', check_inline_file ) if not is_silent_mode: print("") print("Checking source files") print("=====================") check_files( [basePath + 'src/**/*.cpp'], [basePath + 'src/**/*_codegen.cpp'], 'openspace_core', check_source_file ) check_files( [basePath + 'apps/**/*.cpp'], [basePath + 'apps/**/ext/**/*.cpp', basePath + 'apps/**/*_codegen.cpp'], 'openspace_app', check_source_file ) check_files( [basePath + 'modules/**/*.cpp'], [ basePath + 'modules/**/ext/**/*.cpp', basePath + 'modules/**/node_modules/**/*.cpp', basePath + 'modules/**/*_codegen.cpp' ], 'openspace_module', check_source_file ) check_files( [basePath + 'ext/ghoul/src/**/*.cpp'], [], 'ghoul', check_source_file )