Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions client/ayon_photoshop/api/launch_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
import platform
import subprocess
import sys

from wsrpc_aiohttp import (
WebSocketRoute,
Expand All @@ -26,6 +27,7 @@
from ayon_core.pipeline.template_data import get_template_data_with_names
from ayon_core.tools.utils import host_tools

from .launch_utils import get_macos_launch_args
from .webserver import WebServerTool
from .ws_stub import PhotoshopServerStub

Expand Down Expand Up @@ -314,8 +316,19 @@ def _start_process(self):
try:
args = list(self._subprocess_args)
if platform.system().lower() == "darwin":
args.insert(0, "arch")
args.insert(1, "-x86_64")
executable_arches = self._macos_get_arches(args[0])
process_arches = set(
self._macos_get_arches(sys.executable)
)
args, arch = get_macos_launch_args(
args,
executable_arches,
process_arches,
)
if arch:
self.log.info(
f"Using arch '{arch}' to launch host process"
)

self._process = subprocess.Popen(
args,
Expand All @@ -326,6 +339,21 @@ def _start_process(self):
self.log.info("exce", exc_info=True)
self.exit()

def _macos_get_arches(self, executable_path: str) -> list[str]:
try:
output = subprocess.check_output(
["lipo", "-archs", executable_path],
text=True
).strip()
except Exception:
self.log.warning(
"Failed to get architectures of an executable:"
f" {executable_path}",
exc_info=True
)
return []
return output.split()


def show_script_editor():
from ayon_core.tools.console_interpreter import InterpreterController
Expand Down
19 changes: 19 additions & 0 deletions client/ayon_photoshop/api/launch_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Optional


def get_macos_launch_args(
subprocess_args: list[str],
executable_arches: list[str],
process_arches: set[str],
) -> tuple[list[str], Optional[str]]:
"""Prepare launch arguments for a macOS executable."""
args = list(subprocess_args)
if (
executable_arches
and process_arches
and not process_arches.intersection(executable_arches)
):
arch = executable_arches[0]
args[:0] = ["arch", f"-{arch}"]
return args, arch
return args, None
77 changes: 77 additions & 0 deletions tests/test_launch_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import importlib.util
from pathlib import Path
import unittest


MODULE_PATH = (
Path(__file__).parents[1]
/ "client"
/ "ayon_photoshop"
/ "api"
/ "launch_utils.py"
)
SPEC = importlib.util.spec_from_file_location("launch_utils", MODULE_PATH)
launch_utils = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(launch_utils)


class GetMacosLaunchArgsTest(unittest.TestCase):
def test_keeps_native_arguments_for_universal_photoshop(self):
args, arch = launch_utils.get_macos_launch_args(
["/Applications/Adobe Photoshop/Photoshop"],
["x86_64", "arm64"],
{"arm64"},
)

self.assertEqual(
args,
["/Applications/Adobe Photoshop/Photoshop"],
)
self.assertIsNone(arch)

def test_uses_rosetta_for_x86_only_photoshop(self):
args, arch = launch_utils.get_macos_launch_args(
["/Applications/Adobe Photoshop/Photoshop", "--flag"],
["x86_64"],
{"arm64"},
)

self.assertEqual(
args,
[
"arch",
"-x86_64",
"/Applications/Adobe Photoshop/Photoshop",
"--flag",
],
)
self.assertEqual(arch, "x86_64")

def test_keeps_arguments_when_architecture_detection_fails(self):
original_args = ["/Applications/Adobe Photoshop/Photoshop"]

args, arch = launch_utils.get_macos_launch_args(
original_args,
[],
{"arm64"},
)

self.assertEqual(args, original_args)
self.assertIsNone(arch)
self.assertIsNot(args, original_args)

def test_keeps_arguments_when_process_architecture_is_unknown(self):
original_args = ["/Applications/Adobe Photoshop/Photoshop"]

args, arch = launch_utils.get_macos_launch_args(
original_args,
["x86_64", "arm64"],
set(),
)

self.assertEqual(args, original_args)
self.assertIsNone(arch)


if __name__ == "__main__":
unittest.main()