# -*- coding: utf-8 -*- # Copyright (c) 2022, Compiler Explorer Authors # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import sys from os import listdir from os.path import isfile, join import re COMPILERS_LIST_RE = re.compile(r'compilers=(.*)') ALIAS_LIST_RE = re.compile(r'alias=(.*)') GROUP_NAME_RE = re.compile(r'group\.(.*?)\.') COMPILER_EXE_RE = re.compile(r'compiler\.(.*?)\.exe') FORMATTERS_LIST_RE = re.compile(r'formatters=(.*)') FORMATTER_EXE_RE = re.compile(r'formatter\.(.*?)\.exe') LIBS_LIST_RE = re.compile(r'libs=(.+)') LIB_VERSIONS_LIST_RE = re.compile(r'libs\.(.*?)\.versions=(.*)') LIB_VERSION_RE = re.compile(r'libs\.(.*?)\.versions\.(.*?)\.version') TOOLS_LIST_RE = re.compile(r'tools=(.+)') TOOL_EXE_RE = re.compile(r'tools\.(.*?)\.exe') EMPTY_LIST_RE = re.compile(r'.*(compilers|formatters|versions|tools|alias)=.*::.*') DISABLED_RE = re.compile(r'^# Disabled:\s*(.*)') def match_and_add(line, expr, s): match = expr.match(line) if match: s.add(match.group(1)) return match def match_and_update(line, expr, s, split=':'): match = expr.match(line) if match: s.update(match.group(1).split(split)) return match def process_file(file: str): listed_groups = set() seen_groups = set() listed_compilers = set() seen_compilers = set() listed_formatters = set() seen_formatters = set() listed_tools = set() seen_tools = set() listed_libs_ids = set() seen_libs_ids = set() listed_libs_versions = set() seen_libs_versions = set() empty_separators = set() disabled = set() with open(file) as f: for line in f: match_empty = EMPTY_LIST_RE.match(line) if match_empty: empty_separators.add(f"{line}") match_compilers = COMPILERS_LIST_RE.search(line) if match_compilers: ids = match_compilers.group(1).split(':') for elem_id in ids: if elem_id.startswith('&'): listed_groups.add(elem_id[1:]) elif '@' not in elem_id: listed_compilers.add(elem_id) match_libs_versions = LIB_VERSIONS_LIST_RE.match(line) if match_libs_versions: lib_id = match_libs_versions.group(1) versions = match_libs_versions.group(2).split(':') seen_libs_ids.add(lib_id) listed_libs_versions.update([f"{lib_id} {v}" for v in versions]) match_libs_version = LIB_VERSION_RE.match(line) if match_libs_version: lib_id = match_libs_version.group(1) version = match_libs_version.group(2) seen_libs_versions.add(f"{lib_id} {version}") if( match_and_add(line, GROUP_NAME_RE, seen_groups) or match_and_add(line, COMPILER_EXE_RE, seen_compilers) or match_and_add(line, FORMATTER_EXE_RE, seen_formatters) or match_and_add(line, TOOL_EXE_RE, seen_tools) or match_and_update(line, ALIAS_LIST_RE, seen_compilers) or match_and_update(line, FORMATTERS_LIST_RE, listed_formatters) or match_and_update(line, TOOLS_LIST_RE, listed_tools) or match_and_update(line, LIBS_LIST_RE, listed_libs_ids) or match_and_update(line, DISABLED_RE, disabled, ' ') ): continue bad_compilers = listed_compilers.symmetric_difference(seen_compilers) bad_groups = listed_groups.symmetric_difference(seen_groups) bad_formatters = listed_formatters.symmetric_difference(seen_formatters) bad_libs_ids = listed_libs_ids.symmetric_difference(seen_libs_ids) bad_libs_versions = listed_libs_versions.symmetric_difference(seen_libs_versions) bad_tools = listed_tools.symmetric_difference(seen_tools) return (file, bad_compilers - disabled, bad_groups - disabled, bad_formatters - disabled, bad_libs_ids - disabled, bad_libs_versions - disabled, bad_tools - disabled, empty_separators) def process_folder(folder: str): return [process_file(join(folder, f)) for f in listdir(folder) if isfile(join(folder, f)) and not (f.endswith('.defaults.properties') or f.endswith('.local.properties')) and f.endswith('.properties')] def problems_found(file_result): return any([len(r) for r in file_result[1:]]) def print_issue(name, result): if len(result) > 0: sep = "\n\t" print(f"{name}:\n\t{sep.join(sorted(result))}") def find_orphans(folder: str): result = sorted([r for r in process_folder(folder) if problems_found(r)], key=lambda x: x[0]) if result: print(f"Found {len(result)} property file(s) with mismatching ids:") for r in result: print(r[0]) print_issue("COMPILERS", r[1]) print_issue("GROUPS", r[2]) print_issue("FORMATTERS", r[3]) print_issue("LIB IDS", r[4]) print_issue("LIB VERSIONS", r[5]) print_issue("TOOLS", r[6]) print_issue("EMPTY LISTINGS", r[7]) print("") print("To suppress this warning on IDs that are temporally disabled, " "add one or more comments to each listed file:") print("# Disabled: id1 id2 ...") else: print("No configuration mismatches found") return result if __name__ == '__main__': if find_orphans('./etc/config/'): sys.exit(1)