Files
integration/build-everything.py

231 lines
7.5 KiB
Python
Executable File

#!/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')