-
Notifications
You must be signed in to change notification settings - Fork 5
/
rez_app_launch.py
executable file
·238 lines (199 loc) · 9.07 KB
/
rez_app_launch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
"""tk-config-default2 hook to run applications, potentially in a Rez context.
Please note that this requires Rez to be installed as a package,
which exposes the Rez Python API. With a proper Rez installation, you can do
this by running ``rez-bind rez``.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
from pprint import pformat
import subprocess
import sys
from tempfile import NamedTemporaryFile, TemporaryFile
import sgtk
HookBaseClass = sgtk.get_hook_baseclass()
class RezAppLaunch(HookBaseClass):
"""Hook to run an application."""
def create_shell_parent_env(
self, context, config, app_path, app_args, version, **kwargs
):
"""Create parent environment variables for rez's sub-shell.
.. versionadded:: 0.3.0
By default, it just returns a copy of the current environment
variables but is designed to be overridden/extended to patch in
any last minute environment modifications.
Args:
context (rez.resolved_context.ResolvedContext):
Resolved context to execute sub-shell with.
config (rez.config.Config):
Current rez configurations *singleton*.
app_path (str):
The path of the application executable
app_args (str):
Any arguments the application may require
version (str):
version of the application being run if set in the "versions"
settings of the Launcher instance, otherwise ``None``.
kwargs (dict):
Additional key word arguments passed into ``self.execute()``.
Returns:
dict[str, str]: Variable names mapped to their values.
"""
return os.environ.copy()
def execute(self, app_path, app_args, version, **kwargs):
"""Start the required application using rez if required.
Notes:
- Define variables used to bootstrap tank from overwrite on
first reference
- Define others within ``tk-multi-launchapp.yml`` file in the
``extra:rez:parent_variables`` list.
Args:
app_path (str):
The path of the application executable
app_args (str):
Any arguments the application may require
version (str):
version of the application being run if set in the "versions"
settings of the Launcher instance, otherwise ``None``
Returns:
dict[str]:
Execute results mapped to 'command' (str) and
'return_code' (int).
"""
multi_launchapp = self.parent
rez_info = multi_launchapp.get_setting("extra", {}).get("rez", {})
cmd, shell_type = self.background_cmd_shell_type(app_path, app_args)
# Execute App in a Rez context
rez_py_path = self.get_rez_path()
if rez_py_path:
if rez_py_path not in sys.path:
self.logger.debug('Appending to sys.path: "%s"', rez_py_path)
sys.path.append(rez_py_path)
# Only import after rez_py_path is inside sys.path
from rez.resolved_context import ResolvedContext
from rez.config import config
rez_parent_variables = rez_info.get("parent_variables", [])
rez_packages = [
request.format(version=version)
for request in rez_info.get("packages", [])
]
self.logger.debug("rez parent variables: %s", rez_parent_variables)
self.logger.debug("rez packages: %s", rez_packages)
config.parent_variables = rez_parent_variables
context = ResolvedContext(rez_packages)
parent_env = self.create_shell_parent_env(
context, config, app_path, app_args, version, **kwargs
)
env_kwargs = {"suffix": "-prev-env.py", "delete": False}
with NamedTemporaryFile(mode="w+", **env_kwargs) as env_file:
env_file.write(pformat(parent_env))
self.logger.debug(
'Copied existing env for rez. See: "%s"', env_file.name
)
with TemporaryFile(mode="w+") as info_buffer:
context.print_info(buf=info_buffer)
info_buffer.seek(0)
self.logger.debug(
"Executing in rez context [%s]: %s\n%s",
shell_type,
cmd,
info_buffer.read(),
)
launcher_process = context.execute_shell(
command=cmd,
parent_environ=parent_env,
shell=shell_type,
stdin=False,
block=False,
)
exit_code = launcher_process.wait()
else:
# run the command to launch the app
exit_code = subprocess.check_call(cmd)
return {"command": cmd, "return_code": exit_code}
def background_cmd_shell_type(self, app_path, app_args):
"""Make command string and shell type name for current environment.
Args:
app_path (str): The path of the application executable.
app_args (str): Any arguments the application may require.
Returns:
str, str: Command to run and (rez) shell type to run in.
"""
system = sys.platform
shell_type = "bash"
if system.startswith("linux"):
# on linux, we just run the executable directly
cmd_template = "{path} {flattened_args} &"
elif self.parent.get_setting("engine") in ["tk-flame", "tk-flare"]:
# flame and flare works in a different way from other DCCs
# on both linux and mac, they run unix-style command line
# and on the mac the more standardized "open" command cannot
# be utilized.
cmd_template = "{path} {flattened_args} &"
elif system == "darwin":
# on the mac, the executable paths are normally pointing
# to the application bundle and not to the binary file
# embedded in the bundle, meaning that we should use the
# built-in mac open command to execute it
cmd_template = 'open -n "{path}"'
if app_args:
app_args = app_args.replace('"', r"\"")
cmd_template += ' --args "{flattened_args}"'
elif system == "win32":
# on windows, we run the start command in order to avoid
# any command shells popping up as part of the application launch.
cmd_template = 'start /B "App" "{path}" {flattened_args}'
else:
error = (
'No cmd (formatting) and shell_type implemented for "%s":\n'
'- app_path:"%s"\n'
'- app_args:"%s"'
)
raise NotImplementedError(error % (system, app_path, app_args))
cmd = cmd_template.format(path=app_path, flattened_args=app_args)
return cmd, shell_type
def get_rez_path(self, strict=True):
"""Get ``rez`` python package path from the current environment.
Args:
strict (bool):
Whether to raise an error if Rez is not available as a package.
This will prevent the app from being launched.
Returns:
str: A path to the Rez package, can be empty if ``strict=False``..
"""
rez_cmd = "rez-python -c 'import rez; print(rez.__path__[0])'"
process = subprocess.Popen(rez_cmd, stdout=subprocess.PIPE, shell=True)
rez_python_path, err = process.communicate()
if err or not rez_python_path:
if strict:
raise ImportError(
"Failed to find Rez as a package in the current "
"environment! Try 'rez-bind rez'!"
)
else:
self.logger.warn(
"Failed to find a Rez package in the current "
"environment. Unable to request Rez packages."
)
rez_python_path = ""
else:
absolute_path = os.path.abspath(rez_python_path.strip())
rez_python_path = os.path.dirname(absolute_path)
self.logger.debug("Found Rez in: %s", rez_python_path)
return rez_python_path
# Copyright 2013-2016 Allan Johns, Shotgun Software Inc.
#
# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.