mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Applications: Use prelaunch hooks to extract environments (#5387)
* ApplicationManager can have more granular way how applications are launched * executable is optional to be able create ApplicationLaunchContext * launch context can run prelaunch hooks without launching application * 'get_app_environments_for_context' is using launch context to prepare environments * added 'launch_type' as one of filtering options for LaunchHook * added 'local' launch type filter to existing launch hooks * define 'automated' launch type in remote publish function * modified publish and extract environments cli commands * launch types are only for local by default * fix import * fix launch types of global host data * change order or kwargs * change unreal filter attribute
This commit is contained in:
parent
55aead8470
commit
7e9f42b447
35 changed files with 266 additions and 152 deletions
|
|
@ -12,10 +12,6 @@ from abc import ABCMeta, abstractmethod
|
|||
import six
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED, PACKAGE_DIR
|
||||
from openpype.client import (
|
||||
get_project,
|
||||
get_asset_by_name,
|
||||
)
|
||||
from openpype.settings import (
|
||||
get_system_settings,
|
||||
get_project_settings,
|
||||
|
|
@ -47,6 +43,25 @@ CUSTOM_LAUNCH_APP_GROUPS = {
|
|||
}
|
||||
|
||||
|
||||
class LaunchTypes:
|
||||
"""Launch types are filters for pre/post-launch hooks.
|
||||
|
||||
Please use these variables in case they'll change values.
|
||||
"""
|
||||
|
||||
# Local launch - application is launched on local machine
|
||||
local = "local"
|
||||
# Farm render job - application is on farm
|
||||
farm_render = "farm-render"
|
||||
# Farm publish job - integration post-render job
|
||||
farm_publish = "farm-publish"
|
||||
# Remote launch - application is launched on remote machine from which
|
||||
# can be started publishing
|
||||
remote = "remote"
|
||||
# Automated launch - application is launched with automated publishing
|
||||
automated = "automated"
|
||||
|
||||
|
||||
def parse_environments(env_data, env_group=None, platform_name=None):
|
||||
"""Parse environment values from settings byt group and platform.
|
||||
|
||||
|
|
@ -483,6 +498,42 @@ class ApplicationManager:
|
|||
break
|
||||
return output
|
||||
|
||||
def create_launch_context(self, app_name, **data):
|
||||
"""Prepare launch context for application.
|
||||
|
||||
Args:
|
||||
app_name (str): Name of application that should be launched.
|
||||
**data (Any): Any additional data. Data may be used during
|
||||
|
||||
Returns:
|
||||
ApplicationLaunchContext: Launch context for application.
|
||||
|
||||
Raises:
|
||||
ApplicationNotFound: Application was not found by entered name.
|
||||
"""
|
||||
|
||||
app = self.applications.get(app_name)
|
||||
if not app:
|
||||
raise ApplicationNotFound(app_name)
|
||||
|
||||
executable = app.find_executable()
|
||||
|
||||
return ApplicationLaunchContext(
|
||||
app, executable, **data
|
||||
)
|
||||
|
||||
def launch_with_context(self, launch_context):
|
||||
"""Launch application using existing launch context.
|
||||
|
||||
Args:
|
||||
launch_context (ApplicationLaunchContext): Prepared launch
|
||||
context.
|
||||
"""
|
||||
|
||||
if not launch_context.executable:
|
||||
raise ApplictionExecutableNotFound(launch_context.application)
|
||||
return launch_context.launch()
|
||||
|
||||
def launch(self, app_name, **data):
|
||||
"""Launch procedure.
|
||||
|
||||
|
|
@ -503,18 +554,10 @@ class ApplicationManager:
|
|||
failed. Exception should contain explanation message,
|
||||
traceback should not be needed.
|
||||
"""
|
||||
app = self.applications.get(app_name)
|
||||
if not app:
|
||||
raise ApplicationNotFound(app_name)
|
||||
|
||||
executable = app.find_executable()
|
||||
if not executable:
|
||||
raise ApplictionExecutableNotFound(app)
|
||||
context = self.create_launch_context(app_name, **data)
|
||||
return self.launch_with_context(context)
|
||||
|
||||
context = ApplicationLaunchContext(
|
||||
app, executable, **data
|
||||
)
|
||||
return context.launch()
|
||||
|
||||
|
||||
class EnvironmentToolGroup:
|
||||
|
|
@ -736,13 +779,17 @@ class LaunchHook:
|
|||
# Order of prelaunch hook, will be executed as last if set to None.
|
||||
order = None
|
||||
# List of host implementations, skipped if empty.
|
||||
hosts = []
|
||||
# List of application groups
|
||||
app_groups = []
|
||||
# List of specific application names
|
||||
app_names = []
|
||||
# List of platform availability, skipped if empty.
|
||||
platforms = []
|
||||
hosts = set()
|
||||
# Set of application groups
|
||||
app_groups = set()
|
||||
# Set of specific application names
|
||||
app_names = set()
|
||||
# Set of platform availability
|
||||
platforms = set()
|
||||
# Set of launch types for which is available
|
||||
# - if empty then is available for all launch types
|
||||
# - by default has 'local' which is most common reason for launc hooks
|
||||
launch_types = {LaunchTypes.local}
|
||||
|
||||
def __init__(self, launch_context):
|
||||
"""Constructor of launch hook.
|
||||
|
|
@ -790,6 +837,10 @@ class LaunchHook:
|
|||
if launch_context.app_name not in cls.app_names:
|
||||
return False
|
||||
|
||||
if cls.launch_types:
|
||||
if launch_context.launch_type not in cls.launch_types:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
|
|
@ -859,9 +910,9 @@ class PostLaunchHook(LaunchHook):
|
|||
class ApplicationLaunchContext:
|
||||
"""Context of launching application.
|
||||
|
||||
Main purpose of context is to prepare launch arguments and keyword arguments
|
||||
for new process. Most important part of keyword arguments preparations
|
||||
are environment variables.
|
||||
Main purpose of context is to prepare launch arguments and keyword
|
||||
arguments for new process. Most important part of keyword arguments
|
||||
preparations are environment variables.
|
||||
|
||||
During the whole process is possible to use `data` attribute to store
|
||||
object usable in multiple places.
|
||||
|
|
@ -874,14 +925,30 @@ class ApplicationLaunchContext:
|
|||
insert argument between `nuke.exe` and `--NukeX`. To keep them together
|
||||
it is better to wrap them in another list: `[["nuke.exe", "--NukeX"]]`.
|
||||
|
||||
Notes:
|
||||
It is possible to use launch context only to prepare environment
|
||||
variables. In that case `executable` may be None and can be used
|
||||
'run_prelaunch_hooks' method to run prelaunch hooks which prepare
|
||||
them.
|
||||
|
||||
Args:
|
||||
application (Application): Application definition.
|
||||
executable (ApplicationExecutable): Object with path to executable.
|
||||
env_group (Optional[str]): Environment variable group. If not set
|
||||
'DEFAULT_ENV_SUBGROUP' is used.
|
||||
launch_type (Optional[str]): Launch type. If not set 'local' is used.
|
||||
**data (dict): Any additional data. Data may be used during
|
||||
preparation to store objects usable in multiple places.
|
||||
"""
|
||||
|
||||
def __init__(self, application, executable, env_group=None, **data):
|
||||
def __init__(
|
||||
self,
|
||||
application,
|
||||
executable,
|
||||
env_group=None,
|
||||
launch_type=None,
|
||||
**data
|
||||
):
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
# Application object
|
||||
|
|
@ -896,6 +963,10 @@ class ApplicationLaunchContext:
|
|||
|
||||
self.executable = executable
|
||||
|
||||
if launch_type is None:
|
||||
launch_type = LaunchTypes.local
|
||||
self.launch_type = launch_type
|
||||
|
||||
if env_group is None:
|
||||
env_group = DEFAULT_ENV_SUBGROUP
|
||||
|
||||
|
|
@ -903,8 +974,11 @@ class ApplicationLaunchContext:
|
|||
|
||||
self.data = dict(data)
|
||||
|
||||
launch_args = []
|
||||
if executable is not None:
|
||||
launch_args = executable.as_args()
|
||||
# subprocess.Popen launch arguments (first argument in constructor)
|
||||
self.launch_args = executable.as_args()
|
||||
self.launch_args = launch_args
|
||||
self.launch_args.extend(application.arguments)
|
||||
if self.data.get("app_args"):
|
||||
self.launch_args.extend(self.data.pop("app_args"))
|
||||
|
|
@ -946,6 +1020,7 @@ class ApplicationLaunchContext:
|
|||
self.postlaunch_hooks = None
|
||||
|
||||
self.process = None
|
||||
self._prelaunch_hooks_executed = False
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
|
|
@ -1215,6 +1290,27 @@ class ApplicationLaunchContext:
|
|||
# Return process which is already terminated
|
||||
return process
|
||||
|
||||
def run_prelaunch_hooks(self):
|
||||
"""Run prelaunch hooks.
|
||||
|
||||
This method will be executed only once, any future calls will skip
|
||||
the processing.
|
||||
"""
|
||||
|
||||
if self._prelaunch_hooks_executed:
|
||||
self.log.warning("Prelaunch hooks were already executed.")
|
||||
return
|
||||
# Discover launch hooks
|
||||
self.discover_launch_hooks()
|
||||
|
||||
# Execute prelaunch hooks
|
||||
for prelaunch_hook in self.prelaunch_hooks:
|
||||
self.log.debug("Executing prelaunch hook: {}".format(
|
||||
str(prelaunch_hook.__class__.__name__)
|
||||
))
|
||||
prelaunch_hook.execute()
|
||||
self._prelaunch_hooks_executed = True
|
||||
|
||||
def launch(self):
|
||||
"""Collect data for new process and then create it.
|
||||
|
||||
|
|
@ -1227,15 +1323,8 @@ class ApplicationLaunchContext:
|
|||
self.log.warning("Application was already launched.")
|
||||
return
|
||||
|
||||
# Discover launch hooks
|
||||
self.discover_launch_hooks()
|
||||
|
||||
# Execute prelaunch hooks
|
||||
for prelaunch_hook in self.prelaunch_hooks:
|
||||
self.log.debug("Executing prelaunch hook: {}".format(
|
||||
str(prelaunch_hook.__class__.__name__)
|
||||
))
|
||||
prelaunch_hook.execute()
|
||||
if not self._prelaunch_hooks_executed:
|
||||
self.run_prelaunch_hooks()
|
||||
|
||||
self.log.debug("All prelaunch hook executed. Starting new process.")
|
||||
|
||||
|
|
@ -1353,6 +1442,7 @@ def get_app_environments_for_context(
|
|||
task_name,
|
||||
app_name,
|
||||
env_group=None,
|
||||
launch_type=None,
|
||||
env=None,
|
||||
modules_manager=None
|
||||
):
|
||||
|
|
@ -1363,54 +1453,33 @@ def get_app_environments_for_context(
|
|||
task_name (str): Name of task.
|
||||
app_name (str): Name of application that is launched and can be found
|
||||
by ApplicationManager.
|
||||
env (dict): Initial environment variables. `os.environ` is used when
|
||||
not passed.
|
||||
modules_manager (ModulesManager): Initialized modules manager.
|
||||
env_group (Optional[str]): Name of environment group. If not passed
|
||||
default group is used.
|
||||
launch_type (Optional[str]): Type for which prelaunch hooks are
|
||||
executed.
|
||||
env (Optional[dict[str, str]]): Initial environment variables.
|
||||
`os.environ` is used when not passed.
|
||||
modules_manager (Optional[ModulesManager]): Initialized modules
|
||||
manager.
|
||||
|
||||
Returns:
|
||||
dict: Environments for passed context and application.
|
||||
"""
|
||||
|
||||
from openpype.modules import ModulesManager
|
||||
from openpype.pipeline import Anatomy
|
||||
from openpype.lib.openpype_version import is_running_staging
|
||||
|
||||
# Project document
|
||||
project_doc = get_project(project_name)
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
||||
if modules_manager is None:
|
||||
modules_manager = ModulesManager()
|
||||
|
||||
# Prepare app object which can be obtained only from ApplciationManager
|
||||
# Prepare app object which can be obtained only from ApplicationManager
|
||||
app_manager = ApplicationManager()
|
||||
app = app_manager.applications[app_name]
|
||||
|
||||
# Project's anatomy
|
||||
anatomy = Anatomy(project_name)
|
||||
|
||||
data = EnvironmentPrepData({
|
||||
"project_name": project_name,
|
||||
"asset_name": asset_name,
|
||||
"task_name": task_name,
|
||||
|
||||
"app": app,
|
||||
|
||||
"project_doc": project_doc,
|
||||
"asset_doc": asset_doc,
|
||||
|
||||
"anatomy": anatomy,
|
||||
|
||||
"env": env
|
||||
})
|
||||
data["env"].update(anatomy.root_environments())
|
||||
if is_running_staging():
|
||||
data["env"]["OPENPYPE_IS_STAGING"] = "1"
|
||||
|
||||
prepare_app_environments(data, env_group, modules_manager)
|
||||
prepare_context_environments(data, env_group, modules_manager)
|
||||
|
||||
return data["env"]
|
||||
context = app_manager.create_launch_context(
|
||||
app_name,
|
||||
project_name=project_name,
|
||||
asset_name=asset_name,
|
||||
task_name=task_name,
|
||||
env_group=env_group,
|
||||
launch_type=launch_type,
|
||||
env=env,
|
||||
modules_manager=modules_manager,
|
||||
)
|
||||
context.run_prelaunch_hooks()
|
||||
return context.env
|
||||
|
||||
|
||||
def _merge_env(env, current_env):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue