Files
integration/build-everything.py

271 lines
8.5 KiB
Python
Executable File

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