import argparse
import os
import textwrap
import xml.etree.ElementTree as ET
from copy import deepcopy
from dataclasses import asdict, fields, is_dataclass
from argklass.command import Command, newparser
from uetools.core.conf import engine_folder
valid_file = """
<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
<ParallelExecutor>
<MaxProcessorCount>32</MaxProcessorCount>
</ParallelExecutor>
<BuildConfiguration>
</BuildConfiguration>
<WindowsPlatform>
</WindowsPlatform>
</Configuration>
"""
from .xmldef import Configuration
[docs]
def find_or_insert(root, *paths, namespaces=None):
node = root
for i, p in enumerate(paths):
full_path = "ue:" + "/ue:".join(paths[: i + 1])
val = root.find(full_path, namespaces)
if val is None:
val = ET.Element(p)
node.insert(0, val)
node = val
return val
[docs]
def get_user_ubt_configfile():
from pathlib import Path
home_dir = Path.home()
user_ubt_config = os.path.join(
home_dir,
"AppData",
"Roaming",
"Unreal Engine",
"UnrealBuildTool",
"BuildConfiguration.xml",
)
return user_ubt_config
[docs]
def get_global_ubt_configfile():
engine = engine_folder()
global_ubt_config = os.path.join(
engine, "Saved", "UnrealBuildTool", "BuildConfiguration.xml"
)
return global_ubt_config
[docs]
def get_ubt_configfile():
return get_user_ubt_configfile()
COMMANDS = Configure
[docs]
def list_commands(filter=None):
output = []
print("Keys:")
_list_commands(Configuration(), output, [], 1, filter)
print("\n".join(output))
def _list_commands(config, output, namespaces, depth, filter):
import copy
from argklass.docstring import DocstringIterator
indent = " " * depth
docstr = DocstringIterator(config.__class__)
_ = docstr.get_dataclass_docstring()
for ffield in fields(config):
nm = copy.deepcopy(namespaces) + [ffield.name]
docstring = docstr.find_field(ffield)
value = getattr(config, ffield.name)
if is_dataclass(value):
items = []
_list_commands(value, items, nm, depth + 1, filter)
if items:
output.extend(items)
else:
key = ".".join(nm)
type = ffield.type.__name__
value = getattr(config, ffield.name)
if filter is None or filter in key:
_show_field(output, indent, key, value, type, docstring)
def _show_field(output, indent, key, value, type, docstring, kind=None):
col = 60
if kind is None:
kind = "compact"
msg = f"{key:<{col}}: {type:<5} = {value}"
#
#
#
if kind == "simple":
output.append(f"{indent}{msg}")
#
#
#
if kind == "compact":
output.append(f"{indent}{msg}")
wrap_iter = textwrap.wrap(docstring, width=col, subsequent_indent="")
for i, line in enumerate(wrap_iter):
output.append(f"{indent} # {line}")
output.append("")
#
#
#
if kind == "full":
wrap_iter = textwrap.wrap(docstring, width=40, subsequent_indent=" ")
for i, line in enumerate(wrap_iter):
if i == 0:
output.append(f"{indent}{msg} # {line}")
else:
output.append(f"{indent}{' ':<{len(msg)}} # {line}")
[docs]
def from_xml(filename: str) -> Configuration:
"""Parse UBT XML Configuration file"""
if not os.path.exists(filename):
return Configuration()
namespaces = {"ue": "https://www.unrealengine.com/BuildConfiguration"}
ET.register_namespace("", "https://www.unrealengine.com/BuildConfiguration")
tree = ET.parse(filename)
root = tree.getroot()
config = Configuration()
path = []
_from_xml(config, path, root, namespaces, 0)
return config
def _from_xml(config, path: list, tree, namespaces, depth):
configdict = asdict(config)
for k, v in configdict.items():
fullpath = deepcopy(path) + [k]
ref = getattr(config, k)
if isinstance(v, dict):
_from_xml(ref, fullpath, tree, namespaces, depth + 1)
else:
fullpath = "/".join([f"ue:{p}" for p in fullpath])
node = tree.find(fullpath, namespaces)
if node is not None:
setattr(config, k, node.text)
[docs]
def to_xml(config: Configuration) -> str:
"""Writes a UBT XML configuration file"""
frags = []
dictconf = asdict(config)
frags.append('<?xml version="1.0" encoding="utf-8" ?>\n')
frags.append(
'<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">'
)
_to_xml(dictconf, frags, 1)
frags.append("\n</Configuration>")
return "".join(frags)
def _convert_value(v):
v = v.strip()
if v == "True":
v = 1
if v == "False":
v = 0
return v
def _to_xml(dictionary: dict, output: list, depth: int) -> None:
idt = " " * depth
for k, v in dictionary.items():
if not v:
continue
if isinstance(v, dict):
child = []
_to_xml(v, child, depth + 1)
if child:
output.append(f"\n{idt}<{k}>")
output.extend(child)
output.append(f"\n{idt}</{k}>")
else:
output.append(f"\n{idt}<{k}>")
output.append(f"{_convert_value(v)}")
output.append(f"</{k}>")