fix unreal launch on linux

This commit is contained in:
Ondrej Samohel 2021-06-09 18:19:40 +02:00 committed by Ondrej Samohel
parent 31eb53b256
commit 8b0036cf53
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
2 changed files with 139 additions and 83 deletions

View file

@ -1,38 +1,50 @@
# -*- coding: utf-8 -*-
"""Unreal launching and project tools."""
import sys
import os
import platform
import json
from distutils import dir_util
import subprocess
import re
from collections import OrderedDict
from openpype.api import get_project_settings
def get_engine_versions():
"""
def get_engine_versions(env=None):
"""Detect Unreal Engine versions.
This will try to detect location and versions of installed Unreal Engine.
Location can be overridden by `UNREAL_ENGINE_LOCATION` environment
variable.
Returns:
Args:
env (dict, optional): Environment to use.
dict: dictionary with version as a key and dir as value.
Returns:
OrderedDict: dictionary with version as a key and dir as value.
so the highest version is first.
Example:
>>> get_engine_version()
>>> get_engine_versions()
{
"4.23": "C:/Epic Games/UE_4.23",
"4.24": "C:/Epic Games/UE_4.24"
}
"""
try:
engine_locations = {}
root, dirs, files = next(os.walk(os.environ["UNREAL_ENGINE_LOCATION"]))
for dir in dirs:
if dir.startswith("UE_"):
ver = dir.split("_")[1]
engine_locations[ver] = os.path.join(root, dir)
"""
env = env or os.environ
engine_locations = {}
try:
root, dirs, _ = next(os.walk(env["UNREAL_ENGINE_LOCATION"]))
for directory in dirs:
if directory.startswith("UE"):
try:
ver = re.split(r"[-_]", directory)[1]
except IndexError:
continue
engine_locations[ver] = os.path.join(root, directory)
except KeyError:
# environment variable not set
pass
@ -40,32 +52,37 @@ def get_engine_versions():
# specified directory doesn't exists
pass
# if we've got something, terminate autodetection process
# if we've got something, terminate auto-detection process
if engine_locations:
return engine_locations
return OrderedDict(sorted(engine_locations.items()))
# else kick in platform specific detection
if platform.system().lower() == "windows":
return _win_get_engine_versions()
return OrderedDict(sorted(_win_get_engine_versions().items()))
elif platform.system().lower() == "linux":
# on linux, there is no installation and getting Unreal Engine involves
# git clone. So we'll probably depend on `UNREAL_ENGINE_LOCATION`.
pass
elif platform.system().lower() == "darwin":
return _darwin_get_engine_version()
return OrderedDict(sorted(_darwin_get_engine_version(env).items()))
return {}
return OrderedDict()
def _win_get_engine_versions():
"""
"""Get Unreal Engine versions on Windows.
If engines are installed via Epic Games Launcher then there is:
`%PROGRAMDATA%/Epic/UnrealEngineLauncher/LauncherInstalled.dat`
This file is JSON file listing installed stuff, Unreal engines
are marked with `"AppName" = "UE_X.XX"`` like `UE_4.24`
Returns:
dict: version as a key and path as a value.
"""
install_json_path = os.path.join(
os.environ.get("PROGRAMDATA"),
os.getenv("PROGRAMDATA"),
"Epic",
"UnrealEngineLauncher",
"LauncherInstalled.dat",
@ -75,11 +92,19 @@ def _win_get_engine_versions():
def _darwin_get_engine_version() -> dict:
"""
"""Get Unreal Engine versions on MacOS.
It works the same as on Windows, just JSON file location is different.
Returns:
dict: version as a key and path as a value.
See Aslo:
:func:`_win_get_engine_versions`.
"""
install_json_path = os.path.join(
os.environ.get("HOME"),
os.getenv("HOME"),
"Library",
"Application Support",
"Epic",
@ -91,25 +116,26 @@ def _darwin_get_engine_version() -> dict:
def _parse_launcher_locations(install_json_path: str) -> dict:
"""
This will parse locations from json file.
"""This will parse locations from json file.
Args:
install_json_path (str): Path to `LauncherInstalled.dat`.
Returns:
dict: with unreal engine versions as keys and
paths to those engine installations as value.
:param install_json_path: path to `LauncherInstalled.dat`
:type install_json_path: str
:returns: returns dict with unreal engine versions as keys and
paths to those engine installations as value.
:rtype: dict
"""
engine_locations = {}
if os.path.isfile(install_json_path):
with open(install_json_path, "r") as ilf:
try:
install_data = json.load(ilf)
except json.JSONDecodeError:
except json.JSONDecodeError as e:
raise Exception(
"Invalid `LauncherInstalled.dat file. `"
"Cannot determine Unreal Engine location."
)
) from e
for installation in install_data.get("InstallationList", []):
if installation.get("AppName").startswith("UE_"):
@ -123,36 +149,43 @@ def create_unreal_project(project_name: str,
ue_version: str,
pr_dir: str,
engine_path: str,
dev_mode: bool = False) -> None:
"""
This will create `.uproject` file at specified location. As there is no
way I know to create project via command line, this is easiest option.
Unreal project file is basically JSON file. If we find
dev_mode: bool = False,
env: dict = None) -> None:
"""This will create `.uproject` file at specified location.
As there is no way I know to create project via command line, this is
easiest option. Unreal project file is basically JSON file. If we find
`AVALON_UNREAL_PLUGIN` environment variable we assume this is location
of Avalon Integration Plugin and we copy its content to project folder
and enable this plugin.
:param project_name: project name
:type project_name: str
:param ue_version: unreal engine version (like 4.23)
:type ue_version: str
:param pr_dir: path to directory where project will be created
:type pr_dir: str
:param engine_path: Path to Unreal Engine installation
:type engine_path: str
:param dev_mode: Flag to trigger C++ style Unreal project needing
Visual Studio and other tools to compile plugins from
sources. This will trigger automatically if `Binaries`
directory is not found in plugin folders as this indicates
this is only source distribution of the plugin. Dev mode
is also set by preset file `unreal/project_setup.json` in
**OPENPYPE_CONFIG**.
:type dev_mode: bool
:returns: None
"""
preset = get_project_settings(project_name)["unreal"]["project_setup"]
Args:
project_name (str): Name of the project.
ue_version (str): Unreal engine version (like 4.23).
pr_dir (str): Path to directory where project will be created.
engine_path (str): Path to Unreal Engine installation.
dev_mode (bool, optional): Flag to trigger C++ style Unreal project
needing Visual Studio and other tools to compile plugins from
sources. This will trigger automatically if `Binaries`
directory is not found in plugin folders as this indicates
this is only source distribution of the plugin. Dev mode
is also set by preset file `unreal/project_setup.json` in
**OPENPYPE_CONFIG**.
env (dict, optional): Environment to use. If not set, `os.environ`.
if os.path.isdir(os.environ.get("AVALON_UNREAL_PLUGIN", "")):
Throws:
NotImplemented: For unsupported platforms.
Returns:
None
"""
env = env or os.environ
preset = get_project_settings(project_name)["unreal"]["project_setup"]
plugins_path = None
uep_path = None
if os.path.isdir(env.get("AVALON_UNREAL_PLUGIN", "")):
# copy plugin to correct path under project
plugins_path = os.path.join(pr_dir, "Plugins")
avalon_plugin_path = os.path.join(plugins_path, "Avalon")
@ -180,17 +213,17 @@ def create_unreal_project(project_name: str,
}
if preset["install_unreal_python_engine"]:
# If `OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there
# to support offline installation.
# If `PYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there to
# support offline installation.
# Otherwise clone UnrealEnginePython to Plugins directory
# https://github.com/20tab/UnrealEnginePython.git
uep_path = os.path.join(plugins_path, "UnrealEnginePython")
if os.environ.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"):
if env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"):
os.makedirs(uep_path, exist_ok=True)
dir_util._path_created = {}
dir_util.copy_tree(
os.environ.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"),
env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"),
uep_path)
else:
# WARNING: this will trigger dev_mode, because we need to compile
@ -246,25 +279,40 @@ def create_unreal_project(project_name: str,
with open(project_file, mode="w") as pf:
json.dump(data, pf, indent=4)
# UE < 4.26 have Python2 by default, so we need PySide
# but we will not need it in 4.26 and up
if int(ue_version.split(".")[1]) < 26:
# ensure we have PySide installed in engine
# TODO: make it work for other platforms 🍎 🐧
# ensure we have PySide installed in engine
# this won't work probably as pyside is no longer on pypi
# DEPRECATED: support for python 2 in UE4 is dropped.
python_path = None
if int(ue_version.split(".")[0]) == 4 and \
int(ue_version.split(".")[1]) < 25:
if platform.system().lower() == "windows":
python_path = os.path.join(engine_path, "Engine", "Binaries",
"ThirdParty", "Python", "Win64",
"python.exe")
if platform.system().lower() == "linux":
python_path = os.path.join(engine_path, "Engine", "Binaries",
"ThirdParty", "Python", "Linux",
"bin", "python")
if platform.system().lower() == "darwin":
python_path = os.path.join(engine_path, "Engine", "Binaries",
"ThirdParty", "Python", "Mac",
"bin", "python")
if python_path:
subprocess.run([python_path, "-m",
"pip", "install", "pyside"])
else:
raise NotImplemented("Unsupported platform")
if dev_mode or preset["dev_mode"]:
_prepare_cpp_project(project_file, engine_path)
def _prepare_cpp_project(project_file: str, engine_path: str) -> None:
"""
"""Prepare CPP Unreal Project.
This function will add source files needed for project to be
rebuild along with the avalon integration plugin.
@ -273,12 +321,12 @@ def _prepare_cpp_project(project_file: str, engine_path: str) -> None:
by some generator. This needs more research as manually writing
those files is rather hackish. :skull_and_crossbones:
:param project_file: path to .uproject file
:type project_file: str
:param engine_path: path to unreal engine associated with project
:type engine_path: str
"""
Args:
project_file (str): Path to .uproject file.
engine_path (str): Path to unreal engine associated with project.
"""
project_name = os.path.splitext(os.path.basename(project_file))[0]
project_dir = os.path.dirname(project_file)
targets_dir = os.path.join(project_dir, "Source")
@ -388,19 +436,23 @@ class {1}_API A{0}GameModeBase : public AGameModeBase
sources_dir, f"{project_name}GameModeBase.h"), mode="w") as f:
f.write(game_mode_h)
u_build_tool = (f"{engine_path}/Engine/Binaries/DotNET/"
"UnrealBuildTool.exe")
u_header_tool = None
if platform.system().lower() == "windows":
u_build_tool = (f"{engine_path}/Engine/Binaries/DotNET/"
"UnrealBuildTool.exe")
u_header_tool = (f"{engine_path}/Engine/Binaries/Win64/"
f"UnrealHeaderTool.exe")
elif platform.system().lower() == "linux":
# WARNING: there is no UnrealBuildTool on linux?
u_build_tool = ""
u_header_tool = ""
u_header_tool = (f"{engine_path}/Engine/Binaries/Linux/"
f"UnrealHeaderTool")
elif platform.system().lower() == "darwin":
# WARNING: there is no UnrealBuildTool on Mac?
u_build_tool = ""
u_header_tool = ""
# we need to test this out
u_header_tool = (f"{engine_path}/Engine/Binaries/Mac/"
f"UnrealHeaderTool")
if not u_header_tool:
raise NotImplemented("Unsupported platform")
u_build_tool = u_build_tool.replace("\\", "/")
u_header_tool = u_header_tool.replace("\\", "/")

View file

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Hook to launch Unreal and prepare projects."""
import os
from openpype.lib import (
@ -8,19 +10,21 @@ from openpype.hosts.unreal.api import lib as unreal_lib
class UnrealPrelaunchHook(PreLaunchHook):
"""
"""Hook to handle launching Unreal.
This hook will check if current workfile path has Unreal
project inside. IF not, it initialize it and finally it pass
path to the project by environment variable to Unreal launcher
shell script.
"""
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signature = "( {} )".format(self.__class__.__name__)
def execute(self):
"""Hook entry method."""
asset_name = self.data["asset_name"]
task_name = self.data["task_name"]
workdir = self.launch_context.env["AVALON_WORKDIR"]
@ -52,7 +56,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
f"[ {engine_version} ]"
))
detected = unreal_lib.get_engine_versions()
detected = unreal_lib.get_engine_versions(self.launch_context.env)
detected_str = ', '.join(detected.keys()) or 'none'
self.log.info((
f"{self.signature} detected UE4 versions: "