diff --git a/.gitignore b/.gitignore index 23b3f6d..dbb1382 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ /game_compiling_ /setup/__pycache__ /setup/build/setup +/*.mdmp +*.pyc +/vbspautotest/build/setup +/vbspautotest/env diff --git a/LAUNCH hammer.cmd b/LAUNCH hammer.cmd index 0d2e086..f6d8742 100644 --- a/LAUNCH hammer.cmd +++ b/LAUNCH hammer.cmd @@ -3,6 +3,7 @@ @if defined NOHAMMERAUTOUPDATE @GOTO updated + @echo. @echo Modified mapfiles: @git -C "%mapfolder%" status --untracked-files=no -s @@ -10,6 +11,7 @@ @echo # Autoupdating mapfiles... + @git -C "%mapfolder%" pull @if ERRORLEVEL 1 @GOTO updatefail @@ -47,6 +49,12 @@ @rem @set VProject=%VProject_Hammer% @rem @echo Project: %VProject% + +@if not defined NOHAMMERCI ( + @cd vbspautotest + @start /low /min vbspautotest.exe +) + @TITLE "Hammer Repo Waiter" @echo # Waiting for hammer to close... @start /WAIT "Hammer" "%VProject_Hammer%\..\bin\hammer.exe" %HammerParams% %* diff --git a/extras/flashcmd.exe b/extras/flashcmd.exe new file mode 100644 index 0000000..bb632d0 Binary files /dev/null and b/extras/flashcmd.exe differ diff --git a/test.cmd b/test.cmd new file mode 100644 index 0000000..53a41b7 --- /dev/null +++ b/test.cmd @@ -0,0 +1,32 @@ +@set ORIGFOLDER=%CD% +@set CMD_LC_ROOT=%~dp0 + +@call common.cmd +@cd /d "%CMD_LC_ROOT%" + +:docopy +@set targetvmf=%mapfolder%\ci.vmf +@set targetbsp=%mapfolder%\ci.bsp +@set leakfile=%mapfolder%\ci.lin +@del /Q /F "%targetvmf%" 2>nul >nul +@del /Q /F "%targetbsp%" 2>nul >nul +@rem The mappers need this :p +@rem del /Q /F "%leakfile%" + +@COPY "%mapfolder%\%mapfile%.vmf" "%targetvmf%" >nul +@if ERRORLEVEL 1 goto failed + +extras\vmfii "%targetvmf%" "%targetvmf%" --fgd "%FGDS%" > nul +@if ERRORLEVEL 1 goto failed +"%compilers_dir%\vbsp.exe" -allowdynamicpropsasstatic -leaktest -low "%targetvmf%" +@if ERRORLEVEL 1 goto failed +@if NOT exist "%targetbsp%" goto failed +"%compilers_dir%\vvis.exe" -fast -low "%targetvmf%" +@if ERRORLEVEL 1 goto failed +"%compilers_dir%\vrad.exe" -low %VRADLDR% -noskyboxrecurse -bounce 1 -noextra -fastambient -fast -ldr "%targetvmf%" +@if ERRORLEVEL 1 goto failed +@goto ok + +:failed +@exit 1 +:ok diff --git a/vbspautotest/install_icon_155236.ico b/vbspautotest/install_icon_155236.ico new file mode 100644 index 0000000..7f29f62 Binary files /dev/null and b/vbspautotest/install_icon_155236.ico differ diff --git a/vbspautotest/requirements.txt b/vbspautotest/requirements.txt new file mode 100644 index 0000000..c917631 --- /dev/null +++ b/vbspautotest/requirements.txt @@ -0,0 +1,4 @@ +vpk +vdf +pyinstaller +win10toast \ No newline at end of file diff --git a/vbspautotest/utils.py b/vbspautotest/utils.py new file mode 100644 index 0000000..ef0f505 --- /dev/null +++ b/vbspautotest/utils.py @@ -0,0 +1,95 @@ + +import os +import winreg +import subprocess +import vdf +from pathlib import Path +from functools import lru_cache +from pprint import pprint as PrintTable +# DETECT OS VERSION + +def Is64Windows(): + return 'PROGRAMFILES(X86)' in os.environ + +def GetProgramFiles32(): + if Is64Windows(): + return os.environ['PROGRAMFILES(X86)'] + else: + return os.environ['PROGRAMFILES'] + +def GetProgramFiles64(): + if Is64Windows(): + return os.environ['PROGRAMW6432'] + else: + return None + +if Is64Windows() is True: + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Wow6432Node\\Valve\\Steam") +else: + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Valve\\Steam") + +steampath = winreg.QueryValueEx(key, "InstallPath")[0] +@lru_cache(maxsize=32) +def GetSteamPath(): + return steampath + +@lru_cache(maxsize=32) +def GetSteamLibraryPaths(): + with open(GetSteamPath() + "/SteamApps/LibraryFolders.vdf") as lf: + vdffile = vdf.parse(lf) + vdflocations = [val for key,val in vdffile['LibraryFolders'].items() if key.isdigit()]+[steampath] + for path in vdflocations: + print("\tFound Library path: ",path) + return vdflocations + +@lru_cache(maxsize=32) +def GetGamePath(appid): + for acfpath in GetSteamLibraryPaths(): + appmanifestpath = acfpath + "/SteamApps/appmanifest_%d.acf"%(appid) + if os.path.isfile(appmanifestpath): + appmanifest = vdf.parse(open(appmanifestpath)) + return Path(acfpath+"\\SteamApps\\common\\"+appmanifest['AppState']['installdir']+"\\") + +@lru_cache(maxsize=32) +def GetGModPath(): + return GetGamePath(4000) + +@lru_cache(maxsize=32) +def TF2Path(d="."): + return GetGamePath(440) / d + +@lru_cache(maxsize=32) +def CSSPath(d="."): + return GetGamePath(240) / d + +@lru_cache(maxsize=32) +def MapFiles(d="."): + return Path("../../mapfiles").resolve() / d + +@lru_cache(maxsize=32) +def MapAssets(d="."): + return Path("../../mapdata").resolve() / d + +@lru_cache(maxsize=32) +def HammerRoot(): + p = Path("../game_hammer").resolve() + #assert p.exists() + return p + +@lru_cache(maxsize=32) +def CompilerRoot(): + p = Path("../game_compiling").resolve() + #assert p.exists() + return p + +@lru_cache(maxsize=32) +def ToolkitRoot(d="."): + return Path("..").resolve() / d + + + + +def execute_batch(cmd,cwd): + p=subprocess.Popen(cmd,cwd=cwd)#, creationflags=subprocess.CREATE_NEW_CONSOLE) + p.communicate() + return p.returncode \ No newline at end of file diff --git a/vbspautotest/vbspautotest.py b/vbspautotest/vbspautotest.py new file mode 100644 index 0000000..290a23b --- /dev/null +++ b/vbspautotest/vbspautotest.py @@ -0,0 +1,197 @@ + +import ctypes,sys,os +ctypes.windll.kernel32.SetConsoleTitleW("Hammer CI") + +# UGH +def install_and_import(package,package_pip): + import importlib + try: + importlib.import_module(package) + except ImportError: + import subprocess + subprocess.check_call([sys.executable, '-m', 'pip', 'install', package_pip]) + finally: + globals()[package] = importlib.import_module(package) + + +def callback_from_icon(): + pass + +install_and_import("notify","winnotify") +import notify + +ICO='install_icon_155236.ico' +notify.init(ICO, callback_from_icon) + + +from utils import * +from pathlib import Path +import shutil +from shutil import copyfileobj +import traceback +import distutils.dir_util +import time +import win32gui +import subprocess + +import win32event +import win32api +from winerror import ERROR_ALREADY_EXISTS + + + +mutex = win32event.CreateMutex(None, False, 'hammerciautotest') +last_error = win32api.GetLastError() + +if last_error == ERROR_ALREADY_EXISTS: + print("Already running") + sys.exit(43) + + +def my_except_hook(*exc_info): + if exc_info[0] == KeyboardInterrupt: + sys.exit(0) + else: + print("\n\n====== Something unexpected happened that we can't handle ======") + print("".join(traceback.format_exception(*exc_info))) + input("This may be a programming error.\nCopy all of this screen and make an issue at https://github.com/Metastruct/map-compiling-toolkit/issues/new\n\nPress ENTER to abort.") + sys.exit(1) + +sys.excepthook = my_except_hook + +def pathcheck(fatal,printthing,path): + if path.exists(): + print("\t",printthing,path) + else: + print("\t",printthing,path," !!!! MISSING !!!!") + if fatal: + input("\nPress ENTER to abort.") + sys.exit(1) + +print("\nChecking paths:") +pathcheck(True,"GMod\t\t",GetGModPath()) +if not (GetGModPath() / 'bin/win64').exists(): + input("\nERROR: You need to run GMod in x86-64 branch for devenv to work.\n\nPress ENTER to abort.") + sys.exit(1) + +pathcheck(False,"Hammer\t\t",HammerRoot()) +pathcheck(False,"Compiler\t",CompilerRoot()) +pathcheck(False,"TF2\t\t",TF2Path("tf")) +pathcheck(False,"CSS\t\t",CSSPath("cstrike")) +pathcheck(True,"Maps\t\t",MapFiles()) +pathcheck(True,"Map assets\t",MapAssets()) +print("") + +cifile = MapFiles("ci.vmf").resolve() +cilog = MapFiles("ci.log").resolve() + +watchables={vmf:1 or vmf.stat().st_size for vmf in MapFiles().glob("*.vmf") if vmf.resolve()!=cifile and "gm_construct_m" not in str(vmf)} + +def watch(): + found=False + for vmf,size in watchables.items(): + time.sleep(0) + st_size=vmf.stat().st_size + if st_size!=size: + watchables[vmf]=st_size + found=vmf + if found: + time.sleep(1) + for vmf,size in watchables.items(): + time.sleep(0) + st_size=vmf.stat().st_size + if st_size!=size: + watchables[vmf]=st_size + found=vmf + + return found + +def okcb(str): + sys.stdout.write(str) + sys.stdout.flush() + +def failcb(str): + sys.stderr.write(str) + sys.stderr.flush() + +wasfailed=False +firstcompile=True + +def dofail(ret): + + + if cilog.exists(): + print("Found log file") + with cilog.open("rb") as f: + f.seek(-512, os.SEEK_END) + if b"LEAKED" in f.read(): + print("LEAK DETECT") + notify.notify("Map leaked!", + "Hammer CI", + ICO, + False,10,0x03) + ctypes.windll.kernel32.SetConsoleTitleW("Status: MAP LEAKED") + return + notify.notify("Map compiling failed! Look at console window for more info. "+str(ret),"Hammer CI", + + + ICO, + False,10,0x03) + ctypes.windll.kernel32.SetConsoleTitleW("Map compiling failed.") + +startup_time = time.time() + +check_fails=0 +last_ret=None +def checkdead(): + global check_fails + global last_ret + if win32gui.FindWindow("VALVEWORLDCRAFT",None)!=0: + return False + check_fails += 1 + if check_fails < 5: + return + time.sleep(1) + print("Shutting down") + notify.notify("Shutting down. Last compile: "+(last_ret==None and "Unknown" or (last_ret==0 and '[SUCCESS]' or '[FAIL]')),"Hammer CI", + + + ICO, + False,10,last_ret==0 and 0x01 or 0x03) + return True + +while True: + + if checkdead(): + break + + changed = watch() + if changed: + print("\n\nRUNNING CI DUE TO FILE CHANGE: ",changed) + cwd=os.getcwd() + os.chdir(ToolkitRoot()) + + start_time = time.time() + + ret = execute_batch(["test.cmd"],ToolkitRoot()) + + e = int(time.time() - start_time) + print('Compiling ran for {:02d}:{:02d}:{:02d}'.format(e // 3600, (e % 3600 // 60), e % 60)) + os.chdir(cwd) + last_ret = ret + ctypes.windll.kernel32.SetConsoleTitleW((ret==0 and '[SUCCESS]' or '[FAIL]') +'Compiling ran for {:02d}:{:02d}:{:02d}'.format(e // 3600, (e % 3600 // 60), e % 60)) + if ret == 0: + if wasfailed or firstcompile: + notify.notify("Compiling succeeded! :)\nTook {:02d}:{:02d}:{:02d}".format(e // 3600, (e % 3600 // 60), e % 60),"Hammer CI", + + ICO, + False,10,0x01) + firstcompile=False + wasfailed=False + else: + wasfailed=True + print("RETURN",ret) + dofail(ret) + time.sleep(10) + print("Resuming monitoring...") + time.sleep(1)