#!/usr/bin/python3 # # This python script builds integration from scratch. That includes: # # - Generating BuildConfiguration.xml in integration repository # - Generating BuildConfiguration.xml in UnrealEngine repository # - Hardwiring paths into Source/Integration/lpx-paths.hpp # - Generating Integration.uproject # - Generating Integration.code-workspace # - Applies patch to Unreal Engine source. # - Running Setup.sh in the UnrealEngine repository # - Building luprex # - Building ShaderCompileWorker # - Building 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. # # This script is mainly intended for the *initial* build. # If you want to edit the code and recompile, it is okay to use # this script a second time, but it's unnecessarily slow. # It's much faster to edit and recompile using the IDE. # import sys, os, json, shutil, subprocess from pathlib import Path # # These things are operating system specific. # if sys.platform == "windows": OS = "Windows" DLL = "dll" BAT = "bat" DOT_EXE = ".exe" USER = "Unknown" BUILD_BAT = "Build.bat" else: OS = "Linux" DLL = "so" BAT = "sh" DOT_EXE = "" USER = os.environ["USER"] BUILD_BAT = "Linux/Build.sh" # # Some handy utility functions # def readfile(fn): with open(fn) as f: return f.read() def writefile(fn, str): with open(fn, "w") as f: f.write(str) def shell(dir, cmd): print("Running:", cmd) subprocess.run(cmd, shell=True, check=True, cwd=dir) # # This is the code for a simple json macro preprocessor. # It is used to write json files containing macros, which can # then be macroexpanded later in the json file. The json # file should have a 'macros' section at the top level. # def replace_strings_recursively(template, variables): if isinstance(template, str): # Then apply macro-local substitutions for key, value in variables.items(): template = template.replace(key, str(value)) return template elif isinstance(template, list): return [replace_strings_recursively(item, variables) for item in template] elif isinstance(template, dict): return {k: replace_strings_recursively(v, variables) for k, v in template.items()} else: return template def macroexpand_json_recursively(data, macros): if isinstance(data, dict): if "macro" in data and "vars" in data: macro_name = data["macro"] variables = data["vars"] base_macro = macros[macro_name] expanded = replace_strings_recursively(base_macro, variables) return macroexpand_json_recursively(expanded, macros) else: return { key: macroexpand_json_recursively(value, macros) for key, value in data.items() } elif isinstance(data, list): return [macroexpand_json_recursively(item, macros) for item in data] else: return data def macroexpand_json(source_filename, output_filename, globals): """ Load JSON from `source_filename`, perform macro expansion using any macros defined in the "macros" block, and write the expanded JSON to `output_filename`. Global variables are passed as a Python dict. """ # Load the input template with open(source_filename, "r") as f: data = json.load(f) # Extract and remove macros macros = data.pop("macros", {}) # Expand macros expanded = macroexpand_json_recursively(data, macros) # Expand global variables expanded2 = replace_strings_recursively(expanded, globals) # Write output with open(output_filename, "w") as f: json.dump(expanded2, f, indent=4) # # Find the two repositories and verify them. # INTEGRATION=os.path.dirname(os.path.abspath(sys.argv[0])) UNREALENGINE=os.path.join(os.path.dirname(INTEGRATION), "UnrealEngine") 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}") JSONGLOBALS= { "INTEGRATION": INTEGRATION, "UNREALENGINE": UNREALENGINE, "USERNAME": USER, } # # Create the Saved/UnrealBuildTool directories. These will hold # the file BuildConfiguration.xml # # Change directory to one of these in order to force ourselves # to specify all paths explicitly. # Path(f"{INTEGRATION}/Saved/UnrealBuildTool").mkdir(parents=True, exist_ok=True) Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool").mkdir(parents=True, exist_ok=True) os.chdir(f"{INTEGRATION}/Saved/UnrealBuildTool") # # Remove previously-generated files. # Path(f"{INTEGRATION}/Saved/UnrealBuildTool/BuildConfiguration.xml").unlink(missing_ok=True) Path(f"{INTEGRATION}/Integration.uproject").unlink(missing_ok=True) Path(f"{INTEGRATION}/Integration.code-workspace").unlink(missing_ok=True) Path(f"{INTEGRATION}/Makefile").unlink(missing_ok=True) Path(f"{INTEGRATION}/Source/Integration/lpx-paths.hpp").unlink(missing_ok=True) Path(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml").unlink(missing_ok=True) # # Write BuildConfiguration.xml # BUILDCONFIG=readfile(f"{INTEGRATION}/EnginePatches/BuildConfiguration{OS}.xml") writefile(f"{INTEGRATION}/Saved/UnrealBuildTool/BuildConfiguration.xml", BUILDCONFIG) writefile(f"{UNREALENGINE}/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml", BUILDCONFIG) # # Write lpx-paths.hpp. # writefile(f"{INTEGRATION}/Source/Integration/lpx-paths.hpp", f""" #define LUPREX_DLL_PATH "{INTEGRATION}/luprex/build/{OS}/luprexlib.{DLL}" #define LUPREX_ROOT_PATH "{INTEGRATION}/luprex" """) # # Apply patch to the unreal engine source. # Restore any affected sourcefiles before applying patch. # print("Applying patch to Unreal Engine...") PATCHED_FILES = [] for line in readfile(f"{INTEGRATION}/EnginePatches/EnginePatch").splitlines(): if line.startswith("--- a/"): PATCHED_FILES.append(line[6:]) if PATCHED_FILES: shell(UNREALENGINE, "git checkout HEAD -- " + " ".join(PATCHED_FILES)) shell(UNREALENGINE, f"git apply {INTEGRATION}/EnginePatches/EnginePatch") # # Write Integration.uproject. # macroexpand_json(f"{INTEGRATION}/Integration.uproject.tpl.json", f"{INTEGRATION}/Integration.uproject", JSONGLOBALS) # # Run Setup.sh in UNREALENGINE # shell(UNREALENGINE, f"{UNREALENGINE}/Setup.{BAT}") # # Create a trivial makefile that calls into the unreal build system. # writefile(f"{INTEGRATION}/Makefile", f""" # This makefile just invokes the unreal build system, then the luprex build system. all: \t{UNREALENGINE}/Engine/Build/BatchFiles/Linux/Build.sh IntegrationEditor Linux DebugGame {INTEGRATION}/Integration.uproject -waitmutex \t(cd luprex ; make all) clean: \t{UNREALENGINE}/Engine/Build/BatchFiles/Linux/Build.sh IntegrationEditor Linux DebugGame {INTEGRATION}/Integration.uproject -waitmutex -clean \t(cd luprex ; make clean) """) # # 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.workspace-template. # 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.workspace-template. # macroexpand_json(f"{INTEGRATION}/Integration.code-workspace.tpl.json", f"{INTEGRATION}/Integration.code-workspace", JSONGLOBALS) # # Do an initial build of Luprex # print("Building luprex...") shell(f"{INTEGRATION}/luprex", "make") # # Build ShaderCompileWorker # print("Building ShaderCompileWorker...") shell(UNREALENGINE, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} ShaderCompileWorker {OS} Shipping -waitmutex") 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}") # # Build Integration # print("Building integration...") shell(INTEGRATION, f'{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} IntegrationEditor {OS} DebugGame -project="{INTEGRATION}/Integration.uproject" -waitmutex')