From 5a81b2d8ae42ecc8f364d5893dbec8f0b805ee4e Mon Sep 17 00:00:00 2001 From: jyelon Date: Wed, 11 Jun 2025 18:46:10 -0400 Subject: [PATCH] New build.py script, replaces build-everything.py and Makefile --- EnginePatches/EnginePatch | 60 ++--- Integration.code-workspace.tpl.json | 14 +- Source/Integration/LuprexWidgets.cpp | 0 build-everything.py | 230 ------------------ build.py | 334 +++++++++++++++++++++++++++ 5 files changed, 365 insertions(+), 273 deletions(-) delete mode 100644 Source/Integration/LuprexWidgets.cpp delete mode 100755 build-everything.py create mode 100755 build.py diff --git a/EnginePatches/EnginePatch b/EnginePatches/EnginePatch index 8666ec81..22b0a2da 100644 --- a/EnginePatches/EnginePatch +++ b/EnginePatches/EnginePatch @@ -37,41 +37,29 @@ index ca5f4b5fb5ff..a436a624d5b7 100644 // If we're rendering offscreen, use the "dummy" SDL video driver if (FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")) && !getenv("SDL_VIDEODRIVER")) { -diff --git a/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp b/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp -index 1677269adb69..dae6bcde6c25 100644 ---- a/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp -+++ b/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp -@@ -10,6 +10,13 @@ - #include "Stats/Stats.h" - #include "ProfilingDebugging/CsvProfiler.h" +diff --git a/Setup.bat b/Setup.bat +index 34e1cea1e7d2..16bfbe803ac1 100755 +--- a/Setup.bat ++++ b/Setup.bat +@@ -28,7 +28,7 @@ start /wait Engine\Extras\Redist\en-us\UEPrereqSetup_x64.exe /quiet /norestart -+namespace UBreakPoint { -+ volatile int OnLogError_V; -+ FORCENOINLINE static void OnLogError() { -+ OnLogError_V = 0; -+ } -+} -+ - void StaticFailDebugV(const TCHAR* Error, const ANSICHAR* Expression, const ANSICHAR* File, int32 Line, bool bIsEnsure, void* ProgramCounter, const TCHAR* DescriptionFormat, va_list DescriptionArgs); + rem Register the engine installation... + if not exist .\Engine\Binaries\Win64\UnrealVersionSelector-Win64-Shipping.exe goto :no_unreal_version_selector +-.\Engine\Binaries\Win64\UnrealVersionSelector-Win64-Shipping.exe /register ++.\Engine\Binaries\Win64\UnrealVersionSelector-Win64-Shipping.exe /register /unattended + :no_unreal_version_selector + + rem Done! +diff --git a/Setup.sh b/Setup.sh +index f91a96aaac6b..12897001c96b 100755 +--- a/Setup.sh ++++ b/Setup.sh +@@ -80,7 +80,7 @@ else + echo Register the engine installation... + if [ -f Engine/Binaries/Linux/UnrealVersionSelector-Linux-Shipping ]; then + pushd Engine/Binaries/Linux > /dev/null +- ./UnrealVersionSelector-Linux-Shipping -register > /dev/null & ++ ./UnrealVersionSelector-Linux-Shipping -register -unattended > /dev/null & + popd > /dev/null + fi - CSV_DEFINE_CATEGORY(FMsgLogf, true); -@@ -35,6 +42,9 @@ void FMsg::LogfImpl(const ANSICHAR* File, int32 Line, const FLogCategoryName& Ca - } - GROWABLE_LOGF(LogOverride ? LogOverride->Log(Category, Verbosity, Buffer) - : GLog->RedirectLog(Category, Verbosity, Buffer)) -+ if (Verbosity == ELogVerbosity::Error) { -+ UBreakPoint::OnLogError(); -+ } - } - else - { -@@ -79,6 +89,9 @@ void FMsg::LogV(const ANSICHAR* File, int32 Line, const FLogCategoryName& Catego - { - (OutputDevice ? OutputDevice : GLog)->Serialize(Message, Verbosity, Category); - }); -+ if (Verbosity == ELogVerbosity::Error) { -+ UBreakPoint::OnLogError(); -+ } - } - else - { diff --git a/Integration.code-workspace.tpl.json b/Integration.code-workspace.tpl.json index 80a53410..3778ad03 100644 --- a/Integration.code-workspace.tpl.json +++ b/Integration.code-workspace.tpl.json @@ -37,6 +37,7 @@ "*.ipp": "cpp", "*.inc": "cpp" }, + "editor.acceptSuggestionOnEnter": "off", "C_Cpp.intelliSenseEngine": "disabled", "clangd.path": "/usr/bin/clangd-15", "clangd.arguments": [ @@ -60,12 +61,12 @@ "version": "2.0.0", "tasks": [ { - "label": "Make All", + "label": "build.py c++", "group": { "kind": "build", "isDefault": true }, - "command": "make all", + "command": "python3 build.py c++", "presentation": { "clear": true }, @@ -73,9 +74,8 @@ "type": "shell" }, { - "label": "Make Clean", - "group": "build", - "command": "make clean", + "label": "build.py all", + "command": "python3 build.py all", "presentation": { "clear": true }, @@ -83,9 +83,9 @@ "type": "shell" }, { - "label": "Make Intellisense", + "label": "build.py clean", "group": "build", - "command": "make intellisense", + "command": "python3 build.py clean", "presentation": { "clear": true }, diff --git a/Source/Integration/LuprexWidgets.cpp b/Source/Integration/LuprexWidgets.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/build-everything.py b/build-everything.py deleted file mode 100755 index 124f40fd..00000000 --- a/build-everything.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/python3 -# -# This python script builds integration from scratch. That includes -# everything we need: -# -# - Generates BuildConfiguration.xml in integration repository -# - Generates BuildConfiguration.xml in UnrealEngine repository -# - Hardwires paths into Source/Integration/lpx-paths.hpp -# - Generates Integration.uproject -# - Generates Integration.code-workspace -# - Applies patch to Unreal Engine source. -# - Runs Setup.sh in the UnrealEngine repository -# - Builds luprex -# - Builds ShaderCompileWorker -# - Builds Unreal Engine and Unreal Editor -# - Builds integration -# -# Once this is all done, everything is ready to go. It is now possible -# to start up the IDE and run luprex in the debugger. -# -# If you edit the code, you can run this python script again to -# rebuild. It is always safe to use this script to rebuild after -# editing anything. -# -# However, if you only edited C++ code and Unreal Build.cs files, then -# it may be quicker and more convenient to rebuild from inside the -# VSCODE IDE. Bear in mind that doing so only works if you only edited -# the C++ code and the blueprint code. If you edited anything else, you -# should rerun this python script. -# - -import sys, os, json, shutil, subprocess, re, time -from pathlib import Path - -# -# Build the config table: a set of global constants that affect -# just about everything. These values become global variables -# in this script, they are also used as variables when expanding -# template files. -# - -CONFIG = {} - -if sys.platform == "windows": - CONFIG["OS"] = "Windows" - CONFIG["DLL"] = "dll" - CONFIG["BAT"] = "bat" - CONFIG["DOT_EXE"] = ".exe" - CONFIG["USER"] = "Unknown" - CONFIG["BUILD_BAT"] = "Build.bat" -else: - CONFIG["OS"] = "Linux" - CONFIG["DLL"] = "so" - CONFIG["BAT"] = "sh" - CONFIG["DOT_EXE"] = "" - CONFIG["USER"] = os.environ["USER"] - CONFIG["BUILD_BAT"] = "Linux/Build.sh" - -CONFIG["INTEGRATION"] = os.path.dirname(os.path.abspath(sys.argv[0])) -CONFIG["UNREALENGINE"] = os.path.join(os.path.dirname(CONFIG["INTEGRATION"]), "UnrealEngine") -CONFIG["UE_BUILD_BAT"] = CONFIG["UNREALENGINE"] + "/Engine/Build/BatchFiles/" + CONFIG["BUILD_BAT"] + " -waitmutex" - -globals().update(CONFIG) - -# -# Sanity check the INTEGRATION and UNREALENGINE paths. -# - -if not os.path.isdir(f"{INTEGRATION}/Source/Integration"): - sys.exit(f"Integration repository is not valid: {INTEGRATION}") -if not os.path.isdir(f"{UNREALENGINE}/Engine/Source/Editor"): - sys.exit(f"Integration repository is not valid: {UNREALENGINE}") - -# -# This is the code for a simple json preprocessor that can -# expand "for-each" loops and substitute variables. -# - -JSON_VAR_REGEX = re.compile(r'\[([A-Z0-9_]+)\]') - -def expand_json(data, vars): - if isinstance(data, dict): - if "for-each" in data and "body" in data: - body = data["body"] - foreach = data["for-each"] - return [ expand_json(body, vars | lvars) for lvars in foreach ] - else: - return { key: expand_json(value, vars) for key, value in data.items() } - elif isinstance(data, list): - return [ expand_json(item, vars) for item in data ] - elif isinstance(data, str): - return JSON_VAR_REGEX.sub(lambda m: str(vars.get(m.group(1), m.group(0))), data) - else: - return data - -# -# Apply the json expander to a file on disk: read the source file, -# apply the expander, write the result back out to disk. -# - -def expand_json_file(sourcefile, outputfile): - Path(outputfile).unlink(missing_ok=True) - data = json.loads(Path(sourcefile).read_text()) - expanded = expand_json(data, CONFIG) - Path(outputfile).write_text(json.dumps(expanded, indent=4)) - -# -# Because a single string is valid json, we can also use the json -# expander to substitute variables in plain old text files. -# - -def expand_text_file(sourcefile, outputfile): - Path(outputfile).unlink(missing_ok=True) - data = Path(sourcefile).read_text() - expanded = expand_json(data, CONFIG) - Path(outputfile).write_text(expanded) - -# -# A simplified interface to subprocess.run -# - -def shell(dir, cmd): - start = time.time() - subprocess.run(cmd, shell=True, check=True, cwd=dir) - elapsed = time.time() - start - if elapsed > 0.2: - print("") - print("TIMING: ", elapsed, " CMD:", cmd) - print("") - -# -# Change directory to an arbitrary subdirectory. Doing this enforces -# the rule that we specify absolute paths for everything. -# - -os.chdir(f"{INTEGRATION}/EnginePatches") - -# -# Write BuildConfiguration.xml -# - -BUILDCONFIG=Path(f"{INTEGRATION}/EnginePatches/BuildConfiguration{OS}.xml").read_text() - -Path(f"{INTEGRATION}/Saved/UnrealBuildTool").mkdir(parents=True, exist_ok=True) -Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool").mkdir(parents=True, exist_ok=True) - -Path(f"{INTEGRATION}/Saved/UnrealBuildTool/BuildConfiguration.xml").unlink(missing_ok=True) -Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml").unlink(missing_ok=True) - -Path(f"{INTEGRATION}/Saved/UnrealBuildTool/BuildConfiguration.xml").write_text(BUILDCONFIG) -Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml").write_text(BUILDCONFIG) - -# -# Write lpx-paths.hpp. -# - -Path(f"{INTEGRATION}/Source/Integration/lpx-paths.hpp").unlink(missing_ok=True) -Path(f"{INTEGRATION}/Source/Integration/lpx-paths.hpp").write_text(f""" -#define LUPREX_DLL_PATH "{INTEGRATION}/luprex/build/{OS}/luprexlib.{DLL}" -#define LUPREX_ROOT_PATH "{INTEGRATION}/luprex" -""") - -# -# Apply patch to the unreal engine source. Check out HEAD version of -# affected sourcefiles before applying patch. -# - -print("Applying patch to Unreal Engine...") -PATCH_LINES = Path(f"{INTEGRATION}/EnginePatches/EnginePatch").read_text().splitlines() -PATCHED_FILES = [line[6:] for line in PATCH_LINES if line.startswith("--- a/")] -for file in PATCHED_FILES: - shell(UNREALENGINE, f"git show HEAD:{file} > {file}") -shell(UNREALENGINE, f"git apply {INTEGRATION}/EnginePatches/EnginePatch") - -# -# Write Integration.uproject. -# - -expand_json_file(f"{INTEGRATION}/Integration.uproject.tpl.json", - f"{INTEGRATION}/Integration.uproject") - -# -# Run Setup.sh in UNREALENGINE -# - -shell(UNREALENGINE, f"{UNREALENGINE}/Setup.{BAT}") - -# -# Use UnrealBuildTool to generate Integration.code-workspace.ubt -# -# We're not going to use it, but we keep it as a reference that you can -# use when editing Integration.code-workspace.tpl.json. -# - -Path(f"{INTEGRATION}/Integration.code-workspace").unlink(missing_ok=True) -Path(f"{INTEGRATION}/Integration.code-workspace.ubt").unlink(missing_ok=True) -shell(INTEGRATION, f'{UNREALENGINE}/GenerateProjectFiles.{BAT} -projectfiles -project="{INTEGRATION}/Integration.uproject" -game') -Path(f"{INTEGRATION}/Integration.code-workspace").rename(f"{INTEGRATION}/Integration.code-workspace.ubt") - -# -# Build Integration.code-workspace from Integration.code-workspace.tpl.json. -# - -expand_json_file(f"{INTEGRATION}/Integration.code-workspace.tpl.json", - f"{INTEGRATION}/Integration.code-workspace") - -# -# Create Makefile from Makefile.tpl.txt -# - -expand_text_file(f"{INTEGRATION}/Makefile.tpl.txt", - f"{INTEGRATION}/Makefile") - -# -# Build ShaderCompileWorker -# - -print("Building ShaderCompileWorker...") - -shell(UNREALENGINE, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex ShaderCompileWorker {OS} Shipping") - -Path(f"Engine/Binaries/{OS}/ShaderCompileWorker{DOT_EXE}").unlink(missing_ok=True) -shutil.copyfile(f"{UNREALENGINE}/Engine/Binaries/{OS}/ShaderCompileWorker-{OS}-Shipping{DOT_EXE}", f"{UNREALENGINE}/Engine/Binaries/{OS}/ShaderCompileWorker{DOT_EXE}") - -# -# Run Make -# - -shell(INTEGRATION, 'make intellisense') - diff --git a/build.py b/build.py new file mode 100755 index 00000000..dcbd8ab5 --- /dev/null +++ b/build.py @@ -0,0 +1,334 @@ +#!/usr/bin/python3 +# +# This python script builds integration from scratch. That includes +# everything we need: +# +# - Generates BuildConfiguration.xml in integration repository +# - Generates BuildConfiguration.xml in UnrealEngine repository +# - Hardwires paths into Source/Integration/lpx-paths.hpp +# - Generates Integration.uproject +# - Generates Integration.code-workspace +# - Applies patch to Unreal Engine source. +# - Runs Setup.sh in the UnrealEngine repository +# - Builds luprex +# - Builds ShaderCompileWorker +# - Builds Unreal Engine and Unreal Editor +# - Builds integration +# +# Once this is all done, everything is ready to go. It is now possible +# to start up the IDE and run luprex in the debugger. +# +# If you edit the code, you can run this python script again to +# rebuild. It is always safe to use this script to rebuild after +# editing anything. +# +# However, if you only edited C++ code and Unreal Build.cs files, then +# it may be quicker and more convenient to rebuild from inside the +# VSCODE IDE. Bear in mind that doing so only works if you only edited +# the C++ code and the blueprint code. If you edited anything else, you +# should rerun this python script. +# + +import sys, os, json, shutil, subprocess, re, time, tarfile, itertools, hashlib +from pathlib import Path +from types import SimpleNamespace + + +# +# Utility subroutines +# + +def shell(dir, cmd): + "Run a shell command in a directory" + subprocess.run(cmd, shell=True, check=True, cwd=dir) + +def create_tarfile(directory, glob_pattern, outputfile): + "Create a tarfile from a source directory and a glob pattern" + directory = Path(directory) + with tarfile.open(outputfile, mode='w:gz') as tar: + for path in directory.rglob(glob_pattern): + if path.is_file(): + tar.add(path, arcname=path.relative_to(directory)) + +def find_cpp(dir): + "Find all the C++ and C files in a given directory" + list1 = list(Path(dir).rglob("*.[ch]pp")) + list2 = list(Path(dir).rglob("*.[ch]")) + return sorted([str(x) for x in list1 + list2]) + +def hash_json(data): + "Calculate a sha256 hexdigest of any valid json data structure" + serialized = json.dumps(data, sort_keys=True).encode("utf-8") + return hashlib.sha256(serialized).hexdigest() + +def read_if_exists(fn): + "Read a file if it exists, otherwise, return null string" + try: return Path(fn).read_text() + except: return "" + +# +# A JSON preprocessor. +# + +JSON_VAR_REGEX = re.compile(r'\[([A-Z0-9_]+)\]') + +def expand_json(data, vars): + "A simple JSON preprocessor that can expand for-each loops and substitute variables" + if isinstance(data, dict): + if "for-each" in data and "body" in data: + body = data["body"] + foreach = data["for-each"] + return [ expand_json(body, vars | lvars) for lvars in foreach ] + else: + return { key: expand_json(value, vars) for key, value in data.items() } + elif isinstance(data, list): + return [ expand_json(item, vars) for item in data ] + elif isinstance(data, str): + return JSON_VAR_REGEX.sub(lambda m: str(vars.get(m.group(1), m.group(0))), data) + else: + return data + +def expand_json_file(sourcefile, outputfile, config): + "Apply the json preprocessor to a file on disk" + Path(outputfile).unlink(missing_ok=True) + data = json.loads(Path(sourcefile).read_text()) + expanded = expand_json(data, vars(config)) + Path(outputfile).write_text(json.dumps(expanded, indent=4)) + +# +# Determining the build configuration. +# + +def get_build_mode_from_command_line(): + """ + Build.py accepts a single argument, which should + be one of the following modes: all, c++, clean. + If nothing is specified, the mode is all. + We understand cpp and cxx as synonyms for c++. + """ + mode = sys.argv[1].lower() if len(sys.argv) > 1 else 'all' + if mode in ["cpp", "cxx"]: mode = "c++" + if not mode in ["all", "c++", "clean", "experiment"]: + sys.exit(f"Invalid build mode: {mode}") + return mode + + +def autodetect_system_config(): + """ + This autodetects where integration and unrealengine are installed, + and it also autodetects your operating system. Based on these, + it returns a config object containing a variety of useful + configuration settings. + """ + config = SimpleNamespace() + if sys.platform == "windows": + config.OS = "Windows" + config.DLL = "dll" + config.BAT = "bat" + config.DOT_EXE = ".exe" + config.USER = "Unknown" + config.BUILD_BAT = "Build.bat" + else: + config.OS = "Linux" + config.DLL = "so" + config.BAT = "sh" + config.DOT_EXE = "" + config.USER = os.environ["USER"] + config.BUILD_BAT = "Linux/Build.sh" + config.INTEGRATION = os.path.dirname(os.path.abspath(sys.argv[0])) + config.UNREALENGINE = os.path.join(os.path.dirname(config.INTEGRATION), "UnrealEngine") + test1 = Path(f"{config.INTEGRATION}/Source/Integration") + test2 = Path(f"{config.UNREALENGINE}/Engine/Source/Editor") + if not test1.is_dir(): sys.exit(f"Integration repository is not valid: {config.INTEGRATION}") + if not test2.is_dir(): sys.exit(f"UnrealEngine repository is not valid: {config.UNREALENGINE}") + return config + + +def store_system_config_in_globals(config): + """ + Copy all the config data from the config object into global variables. + """ + global OS,DLL,BAT,DOT_EXE,USER,BUILD_BAT,INTEGRATION,UNREALENGINE + OS = config.OS + DLL = config.DLL + BAT = config.BAT + DOT_EXE = config.DOT_EXE + USER = config.USER + BUILD_BAT = config.BUILD_BAT + INTEGRATION = config.INTEGRATION + UNREALENGINE = config.UNREALENGINE + +# +# The actual build steps. +# + +def generate_buildconfiguration_xml(): + """ + Generates BuildConfiguration.xml. We actually have two versions of this + file in git, one for windows, one for linux. This just copies the appropriate + version to the relevant target directories. + """ + dir1 = Path(f"{INTEGRATION}/Saved/UnrealBuildTool") + dir2 = Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool") + target1 = Path(f"{INTEGRATION}/Saved/UnrealBuildTool/BuildConfiguration.xml") + target2 = Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml") + source = Path(f"{INTEGRATION}/EnginePatches/BuildConfiguration{OS}.xml") + template = source.read_text(); + dir1.mkdir(parents=True, exist_ok=True) + dir2.mkdir(parents=True, exist_ok=True) + target1.unlink(missing_ok=True) + target2.unlink(missing_ok=True) + target1.write_text(template) + target2.write_text(template) + + +def generate_lpx_paths(): + """ + Unreal needs to be able to find the Luprex DLL, and it also needs to find the + Lua source code. For now, we just compile some hardwired paths into the + binary. Someday we'll do something more sophisticated. + """ + target = Path(f"{INTEGRATION}/Source/Integration/lpx-paths.hpp") + line1 = f'#define LUPREX_DLL_PATH "{INTEGRATION}/luprex/build/{OS}/luprexlib.{DLL}"' + line2 = f'#define LUPREX_ROOT_PATH "{INTEGRATION}/luprex"' + code = line1 + "\n" + line2 + "\n" + target.unlink(missing_ok=True) + target.write_text(code) + + +def patch_unrealengine_source_code(): + + patch = Path(f"{INTEGRATION}/EnginePatches/EnginePatch") + patch_lines = patch.read_text().splitlines() + patched_files = [line[6:] for line in patch_lines if line.startswith("--- a/")] + for file in patched_files: + shell(UNREALENGINE, f"git show HEAD:{file} > {file}") + shell(UNREALENGINE, f"git apply {INTEGRATION}/EnginePatches/EnginePatch") + + +def generate_integration_uproject(): + """ + Generate integration.uproject + The uproject file is used by UnrealBuildTool to guide the build process. + """ + template = f"{INTEGRATION}/Integration.uproject.tpl.json" + target = f"{INTEGRATION}/Integration.uproject" + expand_json_file(template, target, CONFIG) + + +def run_unrealengine_setup_bat(): + """ + Run Setup.bat in UnrealEngine. + This script downloads assets that aren't stored in git. + """ + shell(UNREALENGINE, f"{UNREALENGINE}/Setup.{BAT}") + + +def generate_integration_code_workspace_ubt(): + """ + Use UnrealBuildTool to generate Integration.code-workspace.ubt + We're not going to use it, but we keep it as a reference that you can + use when editing Integration.code-workspace.tpl.json. + """ + workspace = Path(f"{INTEGRATION}/Integration.code-workspace") + workspace_ubt = Path(f"{INTEGRATION}/Integration.code-workspace.ubt") + workspace.unlink(missing_ok=True) + workspace_ubt.unlink(missing_ok=True) + shell(INTEGRATION, f'{UNREALENGINE}/GenerateProjectFiles.{BAT} -projectfiles -project="{INTEGRATION}/Integration.uproject" -game') + workspace.rename(workspace_ubt) + + +def build_shadercompileworker(): + """ + I have no idea why shadercompileworker isn't built automatically by + unreal's build system, but we have to do this separately. + """ + shell(UNREALENGINE, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex ShaderCompileWorker {OS} Shipping") + Path(f"Engine/Binaries/{OS}/ShaderCompileWorker{DOT_EXE}").unlink(missing_ok=True) + shutil.copyfile(f"{UNREALENGINE}/Engine/Binaries/{OS}/ShaderCompileWorker-{OS}-Shipping{DOT_EXE}", f"{UNREALENGINE}/Engine/Binaries/{OS}/ShaderCompileWorker{DOT_EXE}") + + +def build_luprex_and_integration(): + """ + This builds our code. + """ + shell(f"{INTEGRATION}/luprex", "make all") + shell(INTEGRATION, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex IntegrationEditor {OS} DebugGame {INTEGRATION}/Integration.uproject") + + +def build_intellisense_database_for_clangd(force): + """ + This builds compile_commands.json, which tells the intellisense system + based on clangd how to compile each source file. We only rebuild the + file if a C++ file has been added or removed, or if the force argument + is true. + + Rebuilding the intellisense database touches rsp files, which + unfortunately, causes the entire C++ build to be restarted the next + time you run 'build'. This is terrible behavior from Unreal's build + system. We have a hacky workaround: we save the RSP files before + running the intellisense build, then we restore them afterward. + """ + hash_file = Path(f"{INTEGRATION}/.vscode/cpp_hash") + new_hash = hash_json(find_cpp(f"{INTEGRATION}/Source")) + old_hash = read_if_exists(hash_file).strip() + if (new_hash != old_hash) or force: + hash_file.unlink(missing_ok=True) + create_tarfile(f"{INTEGRATION}/Intermediate", "*.rsp", f"{INTEGRATION}/rsp_files.tgz") + create_tarfile(f"{UNREALENGINE}/Engine", "*.rsp", f"{UNREALENGINE}/rsp_files.tgz") + try: + shell(INTEGRATION, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex IntegrationEditor {OS} DebugGame {INTEGRATION}/Integration.uproject -mode=GenerateClangDatabase -OutputDir={UNREALENGINE}/.vscode") + shell(UNREALENGINE, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex UnrealEditor {OS} DebugGame -mode=GenerateClangDatabase -OutputDir={UNREALENGINE}/.vscode") + shell(INTEGRATION, f"cat {UNREALENGINE}/.vscode/compile_commands.json >> {INTEGRATION}/.vscode/compile_commands.json") + except Exception as e: + error = e + else: + error = None + finally: + tarfile.open(f"{INTEGRATION}/rsp_files.tgz").extractall(path=f"{INTEGRATION}/Intermediate") + tarfile.open(f"{UNREALENGINE}/rsp_files.tgz").extractall(path=f"{UNREALENGINE}/Engine") + Path(f"{INTEGRATION}/rsp_files.tgz").unlink() + Path(f"{UNREALENGINE}/rsp_files.tgz").unlink() + if error: raise error + hash_file.write_text(new_hash) + + +def generate_integration_code_workspace(): + template = f"{INTEGRATION}/Integration.code-workspace.tpl.json" + target = f"{INTEGRATION}/Integration.code-workspace" + expand_json_file(template, target, CONFIG) + + +def build_clean(): + shell(f"{INTEGRATION}/luprex", "make clean") + shell(INTEGRATION, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex IntegrationEditor {OS} DebugGame {INTEGRATION}/Integration.uproject -clean") + Path(f"{INTEGRATION}/.vscode/compile_commands.json").unlink(missing_ok = True) + +# +# MAIN PROGRAM +# + +MODE = get_build_mode_from_command_line() +CONFIG = autodetect_system_config() +store_system_config_in_globals(CONFIG) +os.chdir(f"{INTEGRATION}/EnginePatches") + +if MODE == "all": + generate_buildconfiguration_xml() + generate_lpx_paths() + patch_unrealengine_source_code() + generate_integration_uproject() + run_unrealengine_setup_bat() + build_shadercompileworker() + +if MODE in ["all", "c++"]: + build_luprex_and_integration() + build_intellisense_database_for_clangd(MODE == "all") + +if MODE == "all": + generate_integration_code_workspace_ubt() + generate_integration_code_workspace() + +if MODE == "clean": + build_clean() +