import os import sys import subprocess import concurrent.futures import tomllib addon_base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) files_to_ignore_lower = [ x.lower() for x in ["initSettings.sqf", "initKeybinds.sqf", "XEH_PREP.sqf"] ] sqfvm_exe = os.path.join(addon_base_path, "sqfvm.exe") virtual_paths = [ # would need to add more even more to /include to use it "P:/a3|/a3", # "{}|/a3".format(os.path.join(addon_base_path, "include", "a3")), "P:/a3|/A3", "P:/x/cba|/x/cba", "{}|/z/ace".format(addon_base_path), ] def get_files_to_process(basePath): arma_files = [] for root, _dirs, files in os.walk(os.path.join(addon_base_path, "addons")): for file in files: if file.endswith(".sqf") or file == "config.cpp": if file.lower() in files_to_ignore_lower: continue skipPreprocessing = False for addonTomlPath in [os.path.join(root, "addon.toml"), os.path.join(os.path.dirname(root), "addon.toml")]: if os.path.isfile(addonTomlPath): with open(addonTomlPath, "rb") as f: tomlFile = tomllib.load(f) try: skipPreprocessing = not tomlFile.get('rapify')['enabled'] except: pass if file == "config.cpp" and skipPreprocessing: continue # ignore configs with __has_include filePath = os.path.join(root, file) arma_files.append(filePath) return arma_files def process_file(filePath, skipA3Warnings=True): with open(filePath, "r", encoding="utf-8", errors="ignore") as file: content = file.read() if content.startswith("//pragma SKIP_COMPILE"): return False cmd = [sqfvm_exe, "--input", filePath, "--parse-only", "--automated"] for v in virtual_paths: cmd.append("-v") cmd.append(v) # cmd.append("-V") proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) try: ret = proc.wait(12) # max wait - seconds except Exception as _e: print("sqfvm timed out: {}".format(filePath)) return True # print("{} = {}".format(filePath, ret)) fileHasError = False keepReadingLines = True while keepReadingLines: line = proc.stdout.readline() if not line: keepReadingLines = False else: line = line.rstrip() if line.startswith("[ERR]"): fileHasError = True if not ( skipA3Warnings and line.startswith("[WRN]") and ("a3/" in line) and (("Unexpected IFDEF" in line) or ("defined twice" in line)) ): print(" {}".format(line)) return fileHasError def main(): if not os.path.isfile(sqfvm_exe): print("Error: sqfvm.exe not found in base folder [{}]".format(sqfvm_exe)) return 1 error_count = 0 arma_files = get_files_to_process(addon_base_path) print("Checking {} files".format(len(arma_files))) with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor: for fileError in executor.map(process_file, arma_files): if fileError: error_count += 1 print("Errors: {}".format(error_count)) return error_count if __name__ == "__main__": sys.exit(main())