mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #2408 from pypeclub/feature/OP-2088_Run-applications-as-separate-processes-under-Linux
General: Run applications as separate processes under linux
This commit is contained in:
commit
1e7378c80f
5 changed files with 159 additions and 6 deletions
49
app_launcher.py
Normal file
49
app_launcher.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""Launch process that is not child process of python or OpenPype.
|
||||
|
||||
This is written for linux distributions where process tree may affect what
|
||||
is when closed or blocked to be closed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
|
||||
def main(input_json_path):
|
||||
"""Read launch arguments from json file and launch the process.
|
||||
|
||||
Expected that json contains "args" key with string or list of strings.
|
||||
|
||||
Arguments are converted to string using `list2cmdline`. At the end is added
|
||||
`&` which will cause that launched process is detached and running as
|
||||
"background" process.
|
||||
|
||||
## Notes
|
||||
@iLLiCiT: This should be possible to do with 'disown' or double forking but
|
||||
I didn't find a way how to do it properly. Disown didn't work as
|
||||
expected for me and double forking killed parent process which is
|
||||
unexpected too.
|
||||
"""
|
||||
with open(input_json_path, "r") as stream:
|
||||
data = json.load(stream)
|
||||
|
||||
# Change environment variables
|
||||
env = data.get("env") or {}
|
||||
for key, value in env.items():
|
||||
os.environ[key] = value
|
||||
|
||||
# Prepare launch arguments
|
||||
args = data["args"]
|
||||
if isinstance(args, list):
|
||||
args = subprocess.list2cmdline(args)
|
||||
|
||||
# Run the command as background process
|
||||
shell_cmd = args + " &"
|
||||
os.system(shell_cmd)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Expect that last argument is path to a json with launch args information
|
||||
main(sys.argv[-1])
|
||||
|
|
@ -25,6 +25,7 @@ from .env_tools import (
|
|||
from .terminal import Terminal
|
||||
from .execute import (
|
||||
get_pype_execute_args,
|
||||
get_linux_launcher_args,
|
||||
execute,
|
||||
run_subprocess,
|
||||
path_to_subprocess_arg,
|
||||
|
|
@ -173,6 +174,7 @@ terminal = Terminal
|
|||
|
||||
__all__ = [
|
||||
"get_pype_execute_args",
|
||||
"get_linux_launcher_args",
|
||||
"execute",
|
||||
"run_subprocess",
|
||||
"path_to_subprocess_arg",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import copy
|
||||
import json
|
||||
import tempfile
|
||||
import platform
|
||||
import collections
|
||||
import inspect
|
||||
|
|
@ -37,6 +37,7 @@ from .python_module_tools import (
|
|||
modules_from_path,
|
||||
classes_from_module
|
||||
)
|
||||
from .execute import get_linux_launcher_args
|
||||
|
||||
|
||||
_logger = None
|
||||
|
|
@ -1022,6 +1023,48 @@ class ApplicationLaunchContext:
|
|||
def manager(self):
|
||||
return self.application.manager
|
||||
|
||||
def _run_process(self):
|
||||
# Windows and MacOS have easier process start
|
||||
low_platform = platform.system().lower()
|
||||
if low_platform in ("windows", "darwin"):
|
||||
return subprocess.Popen(self.launch_args, **self.kwargs)
|
||||
|
||||
# Linux uses mid process
|
||||
# - it is possible that the mid process executable is not
|
||||
# available for this version of OpenPype in that case use standard
|
||||
# launch
|
||||
launch_args = get_linux_launcher_args()
|
||||
if launch_args is None:
|
||||
return subprocess.Popen(self.launch_args, **self.kwargs)
|
||||
|
||||
# Prepare data that will be passed to midprocess
|
||||
# - store arguments to a json and pass path to json as last argument
|
||||
# - pass environments to set
|
||||
json_data = {
|
||||
"args": self.launch_args,
|
||||
"env": self.kwargs.pop("env", {})
|
||||
}
|
||||
# Create temp file
|
||||
json_temp = tempfile.NamedTemporaryFile(
|
||||
mode="w", prefix="op_app_args", suffix=".json", delete=False
|
||||
)
|
||||
json_temp.close()
|
||||
json_temp_filpath = json_temp.name
|
||||
with open(json_temp_filpath, "w") as stream:
|
||||
json.dump(json_data, stream)
|
||||
|
||||
launch_args.append(json_temp_filpath)
|
||||
|
||||
# Create mid-process which will launch application
|
||||
process = subprocess.Popen(launch_args, **self.kwargs)
|
||||
# Wait until the process finishes
|
||||
# - This is important! The process would stay in "open" state.
|
||||
process.wait()
|
||||
# Remove the temp file
|
||||
os.remove(json_temp_filpath)
|
||||
# Return process which is already terminated
|
||||
return process
|
||||
|
||||
def launch(self):
|
||||
"""Collect data for new process and then create it.
|
||||
|
||||
|
|
@ -1058,8 +1101,10 @@ class ApplicationLaunchContext:
|
|||
self.app_name, args_len_str, args
|
||||
)
|
||||
)
|
||||
self.launch_args = args
|
||||
|
||||
# Run process
|
||||
self.process = subprocess.Popen(args, **self.kwargs)
|
||||
self.process = self._run_process()
|
||||
|
||||
# Process post launch hooks
|
||||
for postlaunch_hook in self.postlaunch_hooks:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import platform
|
||||
import distutils.spawn
|
||||
|
||||
from .log import PypeLogger as Logger
|
||||
|
||||
|
|
@ -175,3 +174,46 @@ def get_pype_execute_args(*args):
|
|||
pype_args.extend(args)
|
||||
|
||||
return pype_args
|
||||
|
||||
|
||||
def get_linux_launcher_args(*args):
|
||||
"""Path to application mid process executable.
|
||||
|
||||
This function should be able as arguments are different when used
|
||||
from code and build.
|
||||
|
||||
It is possible that this function is used in OpenPype build which does
|
||||
not have yet the new executable. In that case 'None' is returned.
|
||||
|
||||
Args:
|
||||
args (iterable): List of additional arguments added after executable
|
||||
argument.
|
||||
|
||||
Returns:
|
||||
list: Executables with possible positional argument to script when
|
||||
called from code.
|
||||
"""
|
||||
filename = "app_launcher"
|
||||
openpype_executable = os.environ["OPENPYPE_EXECUTABLE"]
|
||||
|
||||
executable_filename = os.path.basename(openpype_executable)
|
||||
if "python" in executable_filename.lower():
|
||||
script_path = os.path.join(
|
||||
os.environ["OPENPYPE_ROOT"],
|
||||
"{}.py".format(filename)
|
||||
)
|
||||
launch_args = [openpype_executable, script_path]
|
||||
else:
|
||||
new_executable = os.path.join(
|
||||
os.path.dirname(openpype_executable),
|
||||
filename
|
||||
)
|
||||
executable_path = distutils.spawn.find_executable(new_executable)
|
||||
if executable_path is None:
|
||||
return None
|
||||
launch_args = [executable_path]
|
||||
|
||||
if args:
|
||||
launch_args.extend(args)
|
||||
|
||||
return launch_args
|
||||
|
|
|
|||
19
setup.py
19
setup.py
|
|
@ -3,6 +3,7 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
from cx_Freeze import setup, Executable
|
||||
|
|
@ -18,8 +19,13 @@ with open(openpype_root / "openpype" / "version.py") as fp:
|
|||
version_match = re.search(r"(\d+\.\d+.\d+).*", version["__version__"])
|
||||
__version__ = version_match.group(1)
|
||||
|
||||
low_platform_name = platform.system().lower()
|
||||
IS_WINDOWS = low_platform_name == "windows"
|
||||
IS_LINUX = low_platform_name == "linux"
|
||||
IS_MACOS = low_platform_name == "darwin"
|
||||
|
||||
base = None
|
||||
if sys.platform == "win32":
|
||||
if IS_WINDOWS:
|
||||
base = "Win32GUI"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
|
@ -72,7 +78,7 @@ include_files = [
|
|||
"README.md"
|
||||
]
|
||||
|
||||
if sys.platform == "win32":
|
||||
if IS_WINDOWS:
|
||||
install_requires.extend([
|
||||
# `pywin32` packages
|
||||
"win32ctypes",
|
||||
|
|
@ -104,6 +110,15 @@ executables = [
|
|||
Executable("start.py", base=None,
|
||||
target_name="openpype_console", icon=icon_path.as_posix())
|
||||
]
|
||||
if IS_LINUX:
|
||||
executables.append(
|
||||
Executable(
|
||||
"app_launcher.py",
|
||||
base=None,
|
||||
target_name="app_launcher",
|
||||
icon=icon_path.as_posix()
|
||||
)
|
||||
)
|
||||
|
||||
setup(
|
||||
name="OpenPype",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue