Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy non-python launch script in each integration #235

Merged
merged 17 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 0 additions & 58 deletions client/ayon_core/hooks/pre_non_python_host_launch.py

This file was deleted.

8 changes: 7 additions & 1 deletion client/ayon_core/hosts/aftereffects/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from .addon import AfterEffectsAddon
from .addon import (
AFTEREFFECTS_ADDON_ROOT,
AfterEffectsAddon,
get_launch_script_path,
)


__all__ = (
"AFTEREFFECTS_ADDON_ROOT",
"AfterEffectsAddon",
"get_launch_script_path",
)
17 changes: 17 additions & 0 deletions client/ayon_core/hosts/aftereffects/addon.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import os

from ayon_core.addon import AYONAddon, IHostAddon

AFTEREFFECTS_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))


class AfterEffectsAddon(AYONAddon, IHostAddon):
name = "aftereffects"
Expand All @@ -17,3 +21,16 @@ def add_implementation_envs(self, env, _app):

def get_workfile_extensions(self):
return [".aep"]

def get_launch_hook_paths(self, app):
if app.host_name != self.host_name:
return []
return [
os.path.join(AFTEREFFECTS_ADDON_ROOT, "hooks")
]


def get_launch_script_path():
return os.path.join(
AFTEREFFECTS_ADDON_ROOT, "api", "launch_script.py"
)
1 change: 0 additions & 1 deletion client/ayon_core/hosts/aftereffects/api/launch_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import functools
import traceback


from wsrpc_aiohttp import (
WebSocketRoute,
WebSocketAsync
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Script wraps launch mechanism of non python host implementations.
"""Script wraps launch mechanism of AfterEffects implementations.

Arguments passed to the script are passed to launch function in host
implementation. In all cases requires host app executable and may contain
Expand All @@ -8,6 +8,8 @@
import os
import sys

from ayon_core.hosts.aftereffects.api.launch_logic import main

# Get current file to locate start point of sys.argv
CURRENT_FILE = os.path.abspath(__file__)

Expand Down Expand Up @@ -79,23 +81,6 @@ def main(argv):
if after_script_idx is not None:
launch_args = sys_args[after_script_idx:]

host_name = os.environ["AYON_HOST_NAME"].lower()
if host_name == "photoshop":
# TODO refactor launch logic according to AE
from ayon_core.hosts.photoshop.api.lib import main
elif host_name == "aftereffects":
from ayon_core.hosts.aftereffects.api.launch_logic import main
elif host_name == "harmony":
from ayon_core.hosts.harmony.api.lib import main
else:
title = "Unknown host name"
message = (
"BUG: Environment variable AYON_HOST_NAME contains unknown"
" host name \"{}\""
).format(host_name)
show_error_messagebox(title, message)
return

if launch_args:
# Launch host implementation
main(*launch_args)
Expand Down
91 changes: 91 additions & 0 deletions client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import os
import platform
import subprocess

from ayon_core.lib import (
get_ayon_launcher_args,
is_using_ui_executable,
)
from ayon_core.lib.applications import (
PreLaunchHook,
LaunchTypes,
)
from ayon_core.hosts.aftereffects import get_launch_script_path


def get_launch_kwargs(kwargs):
"""Explicit setting of kwargs for Popen for AfterEffects.

Expected behavior
- ayon_console opens window with logs
- ayon has stdout/stderr available for capturing

Args:
kwargs (Union[dict, None]): Current kwargs or None.

"""
if kwargs is None:
kwargs = {}

if platform.system().lower() != "windows":
return kwargs

if is_using_ui_executable():
kwargs.update({
"creationflags": subprocess.CREATE_NO_WINDOW,
"stdout": subprocess.DEVNULL,
"stderr": subprocess.DEVNULL
})
else:
kwargs.update({
"creationflags": subprocess.CREATE_NEW_CONSOLE
})
return kwargs


class AEPrelaunchHook(PreLaunchHook):
"""Launch arguments preparation.

Hook add python executable and script path to AE implementation before
AE executable and add last workfile path to launch arguments.

Existence of last workfile is checked. If workfile does not exists tries
to copy templated workfile from predefined path.
"""
app_groups = {"aftereffects"}

order = 20
launch_types = {LaunchTypes.local}

def execute(self):
# Pop executable
executable_path = self.launch_context.launch_args.pop(0)

# Pop rest of launch arguments - There should not be other arguments!
remainders = []
while self.launch_context.launch_args:
remainders.append(self.launch_context.launch_args.pop(0))

script_path = get_launch_script_path()

new_launch_args = get_ayon_launcher_args(
"run", script_path, executable_path
)
# Add workfile path if exists
workfile_path = self.data["last_workfile_path"]
if (
self.data.get("start_last_workfile")
and workfile_path
and os.path.exists(workfile_path)
):
new_launch_args.append(workfile_path)

# Append as whole list as these arguments should not be separated
self.launch_context.launch_args.append(new_launch_args)

if remainders:
self.launch_context.launch_args.extend(remainders)

self.launch_context.kwargs = get_launch_kwargs(
self.launch_context.kwargs
)
6 changes: 4 additions & 2 deletions client/ayon_core/hosts/harmony/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from .addon import (
HARMONY_HOST_DIR,
HARMONY_ADDON_ROOT,
HarmonyAddon,
get_launch_script_path,
)


__all__ = (
"HARMONY_HOST_DIR",
"HARMONY_ADDON_ROOT",
"HarmonyAddon",
"get_launch_script_path",
)
17 changes: 15 additions & 2 deletions client/ayon_core/hosts/harmony/addon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from ayon_core.addon import AYONAddon, IHostAddon

HARMONY_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
HARMONY_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))


class HarmonyAddon(AYONAddon, IHostAddon):
Expand All @@ -11,10 +11,23 @@ class HarmonyAddon(AYONAddon, IHostAddon):
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
openharmony_path = os.path.join(
HARMONY_HOST_DIR, "vendor", "OpenHarmony"
HARMONY_ADDON_ROOT, "vendor", "OpenHarmony"
)
# TODO check if is already set? What to do if is already set?
env["LIB_OPENHARMONY_PATH"] = openharmony_path

def get_workfile_extensions(self):
return [".zip"]

def get_launch_hook_paths(self, app):
if app.host_name != self.host_name:
return []
return [
os.path.join(HARMONY_ADDON_ROOT, "hooks")
]


def get_launch_script_path():
return os.path.join(
HARMONY_ADDON_ROOT, "api", "launch_script.py"
)
93 changes: 93 additions & 0 deletions client/ayon_core/hosts/harmony/api/launch_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Script wraps launch mechanism of Harmony implementations.

Arguments passed to the script are passed to launch function in host
implementation. In all cases requires host app executable and may contain
workfile or others.
"""

import os
import sys

from ayon_core.hosts.harmony.api.lib import main

# Get current file to locate start point of sys.argv
CURRENT_FILE = os.path.abspath(__file__)


def show_error_messagebox(title, message, detail_message=None):
"""Function will show message and process ends after closing it."""
from qtpy import QtWidgets, QtCore
from ayon_core import style

app = QtWidgets.QApplication([])
app.setStyleSheet(style.load_stylesheet())

msgbox = QtWidgets.QMessageBox()
msgbox.setWindowTitle(title)
msgbox.setText(message)

if detail_message:
msgbox.setDetailedText(detail_message)

msgbox.setWindowModality(QtCore.Qt.ApplicationModal)
msgbox.show()

sys.exit(app.exec_())


def on_invalid_args(script_not_found):
"""Show to user message box saying that something went wrong.

Tell user that arguments to launch implementation are invalid with
arguments details.

Args:
script_not_found (bool): Use different message based on this value.
"""

title = "Invalid arguments"
joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv)
if script_not_found:
submsg = "Where couldn't find script path:\n\"{}\""
else:
submsg = "Expected Host executable after script path:\n\"{}\""

message = "BUG: Got invalid arguments so can't launch Host application."
detail_message = "Process was launched with arguments:\n{}\n\n{}".format(
joined_args,
submsg.format(CURRENT_FILE)
)

show_error_messagebox(title, message, detail_message)


def main(argv):
# Modify current file path to find match in sys.argv which may be different
# on windows (different letter cases and slashes).
modified_current_file = CURRENT_FILE.replace("\\", "/").lower()

# Create a copy of sys argv
sys_args = list(argv)
after_script_idx = None
# Find script path in sys.argv to know index of argv where host
# executable should be.
for idx, item in enumerate(sys_args):
if item.replace("\\", "/").lower() == modified_current_file:
after_script_idx = idx + 1
break

# Validate that there is at least one argument after script path
launch_args = None
if after_script_idx is not None:
launch_args = sys_args[after_script_idx:]

if launch_args:
# Launch host implementation
main(*launch_args)
else:
# Show message box
on_invalid_args(after_script_idx is None)


if __name__ == "__main__":
main(sys.argv)