devenv: Add webrtc testsuite to paths
[platform/upstream/gstreamer.git] / gst-env.py
1 #!/usr/bin/env python3
2
3 import argparse
4 import contextlib
5 import glob
6 import json
7 import os
8 import platform
9 import re
10 import site
11 import shlex
12 import shutil
13 import subprocess
14 import sys
15 import tempfile
16 import pathlib
17 import signal
18 from functools import lru_cache
19 from pathlib import PurePath, Path
20
21 from distutils.sysconfig import get_python_lib
22 from distutils.util import strtobool
23
24 from scripts.common import get_meson
25 from scripts.common import git
26 from scripts.common import win32_get_short_path_name
27 from scripts.common import get_wine_shortpath
28
29 SCRIPTDIR = os.path.dirname(os.path.realpath(__file__))
30 PREFIX_DIR = os.path.join(SCRIPTDIR, 'prefix')
31 # Look for the following build dirs: `build` `_build` `builddir`
32 DEFAULT_BUILDDIR = os.path.join(SCRIPTDIR, 'build')
33 if not os.path.exists(DEFAULT_BUILDDIR):
34     DEFAULT_BUILDDIR = os.path.join(SCRIPTDIR, '_build')
35 if not os.path.exists(DEFAULT_BUILDDIR):
36     DEFAULT_BUILDDIR = os.path.join(SCRIPTDIR, 'builddir')
37
38 TYPELIB_REG = re.compile(r'.*\.typelib$')
39 SHAREDLIB_REG = re.compile(r'\.so|\.dylib|\.dll')
40
41 # libdir is expanded from option of the same name listed in the `meson
42 # introspect --buildoptions` output.
43 GSTPLUGIN_FILEPATH_REG_TEMPLATE = r'.*/{libdir}/gstreamer-1.0/[^/]+$'
44 GSTPLUGIN_FILEPATH_REG = None
45
46 BC_RC =  '''
47 BASH_COMPLETION_SCRIPTS="{bash_completions}"
48 BASH_COMPLETION_PATHS="{bash_completions_paths}"
49 for p in $BASH_COMPLETION_PATHS; do
50 for f in $BASH_COMPLETION_SCRIPTS; do
51   [ -f "$p/$f" ] && . "$p/$f"
52 done
53 done
54 '''
55 BASH_COMPLETION_PATHS = [SCRIPTDIR + '/subprojects/gstreamer/data/bash-completion/completions']
56 BASH_COMPLETION_PATHS += [SCRIPTDIR + '/subprojects/gst-devtools/validate/data/bash-completion/completions']
57
58 def listify(o):
59     if isinstance(o, str):
60         return [o]
61     if isinstance(o, list):
62         return o
63     raise AssertionError('Object {!r} must be a string or a list'.format(o))
64
65 def stringify(o):
66     if isinstance(o, str):
67         return o
68     if isinstance(o, list):
69         if len(o) == 1:
70             return o[0]
71         raise AssertionError('Did not expect object {!r} to have more than one element'.format(o))
72     raise AssertionError('Object {!r} must be a string or a list'.format(o))
73
74 def prepend_env_var(env, var, value, sysroot):
75     if value.startswith(sysroot):
76         value = value[len(sysroot):]
77     # Try not to exceed maximum length limits for env vars on Windows
78     if os.name == 'nt':
79         value = win32_get_short_path_name(value)
80     env_val = env.get(var, '')
81     val = os.pathsep + value + os.pathsep
82     # Don't add the same value twice
83     if val in env_val or env_val.startswith(value + os.pathsep):
84         return
85     env[var] = val + env_val
86     env[var] = env[var].replace(os.pathsep + os.pathsep, os.pathsep).strip(os.pathsep)
87
88 def get_target_install_filename(target, filename):
89     '''
90     Checks whether this file is one of the files installed by the target
91     '''
92     basename = os.path.basename(filename)
93     for install_filename in listify(target['install_filename']):
94         if install_filename.endswith(basename):
95             return install_filename
96     return None
97
98 def get_pkgconfig_variable_from_pcfile(pcfile, varname):
99     variables = {}
100     substre = re.compile('\$\{[^${}]+\}')
101     with pcfile.open('r', encoding='utf-8') as f:
102         for line in f:
103             if '=' not in line:
104                 continue
105             key, value = line[:-1].split('=', 1)
106             subst = {}
107             for each in substre.findall(value):
108                 substkey = each[2:-1]
109                 subst[each] = variables.get(substkey, '')
110             for k, v in subst.items():
111                 value = value.replace(k, v)
112             variables[key] = value
113     return variables.get(varname, '')
114
115 @lru_cache()
116 def get_pkgconfig_variable(builddir, pcname, varname):
117     '''
118     Parsing isn't perfect, but it's good enough.
119     '''
120     pcfile = Path(builddir) / 'meson-private' / (pcname + '.pc')
121     if pcfile.is_file():
122         return get_pkgconfig_variable_from_pcfile(pcfile, varname)
123     return subprocess.check_output(['pkg-config', pcname, '--variable=' + varname],
124                                    universal_newlines=True, encoding='utf-8')
125
126
127 def is_gio_module(target, filename, builddir):
128     if target['type'] != 'shared module':
129         return False
130     install_filename = get_target_install_filename(target, filename)
131     if not install_filename:
132         return False
133     giomoduledir = PurePath(get_pkgconfig_variable(builddir, 'gio-2.0', 'giomoduledir'))
134     fpath = PurePath(install_filename)
135     if fpath.parent != giomoduledir:
136         return False
137     return True
138
139 def is_library_target_and_not_plugin(target, filename):
140     '''
141     Don't add plugins to PATH/LD_LIBRARY_PATH because:
142     1. We don't need to
143     2. It causes us to exceed the PATH length limit on Windows and Wine
144     '''
145     if target['type'] != 'shared library':
146         return False
147     # Check if this output of that target is a shared library
148     if not SHAREDLIB_REG.search(filename):
149         return False
150     # Check if it's installed to the gstreamer plugin location
151     install_filename = get_target_install_filename(target, filename)
152     if not install_filename:
153         return False
154     global GSTPLUGIN_FILEPATH_REG
155     if GSTPLUGIN_FILEPATH_REG is None:
156         GSTPLUGIN_FILEPATH_REG = re.compile(GSTPLUGIN_FILEPATH_REG_TEMPLATE)
157     if GSTPLUGIN_FILEPATH_REG.search(install_filename.replace('\\', '/')):
158         return False
159     return True
160
161 def is_binary_target_and_in_path(target, filename, bindir):
162     if target['type'] != 'executable':
163         return False
164     # Check if this file installed by this target is installed to bindir
165     install_filename = get_target_install_filename(target, filename)
166     if not install_filename:
167         return False
168     fpath = PurePath(install_filename)
169     if fpath.parent != bindir:
170         return False
171     return True
172
173
174 def get_wine_subprocess_env(options, env):
175     with open(os.path.join(options.builddir, 'meson-info', 'intro-buildoptions.json')) as f:
176         buildoptions = json.load(f)
177
178     prefix, = [o for o in buildoptions if o['name'] == 'prefix']
179     path = os.path.normpath(os.path.join(prefix['value'], 'bin'))
180     prepend_env_var(env, "PATH", path, options.sysroot)
181     wine_path = get_wine_shortpath(
182         options.wine.split(' '),
183         [path] + env.get('WINEPATH', '').split(';')
184     )
185     if options.winepath:
186         wine_path += ';' + options.winepath
187     env['WINEPATH'] = wine_path
188     env['WINEDEBUG'] = 'fixme-all'
189
190     return env
191
192 def setup_gdb(options):
193     python_paths = set()
194
195     if not shutil.which('gdb'):
196         return python_paths
197
198     bdir = pathlib.Path(options.builddir).resolve()
199     for libpath, gdb_path in [
200             (os.path.join("subprojects", "gstreamer", "gst"),
201              os.path.join("subprojects", "gstreamer", "libs", "gst", "helpers")),
202             (os.path.join("subprojects", "glib", "gobject"), None),
203             (os.path.join("subprojects", "glib", "glib"), None)]:
204
205         if not gdb_path:
206             gdb_path = libpath
207
208         autoload_path = (pathlib.Path(bdir) / 'gdb-auto-load').joinpath(*bdir.parts[1:]) / libpath
209         autoload_path.mkdir(parents=True, exist_ok=True)
210         for gdb_helper in glob.glob(str(bdir / gdb_path / "*-gdb.py")):
211             python_paths.add(str(bdir / gdb_path))
212             python_paths.add(os.path.join(options.srcdir, gdb_path))
213             try:
214                 if os.name == 'nt':
215                     shutil.copy(gdb_helper, str(autoload_path / os.path.basename(gdb_helper)))
216                 else:
217                     os.symlink(gdb_helper, str(autoload_path / os.path.basename(gdb_helper)))
218             except (FileExistsError, shutil.SameFileError):
219                 pass
220
221     gdbinit_line = 'add-auto-load-scripts-directory {}\n'.format(bdir / 'gdb-auto-load')
222     try:
223         with open(os.path.join(options.srcdir, '.gdbinit'), 'r') as f:
224             if gdbinit_line in f.readlines():
225                 return python_paths
226     except FileNotFoundError:
227         pass
228
229     with open(os.path.join(options.srcdir, '.gdbinit'), 'a') as f:
230         f.write(gdbinit_line)
231
232     return python_paths
233
234 def is_bash_completion_available (options):
235     return  os.path.exists(os.path.join(options.builddir, 'subprojects/gstreamer/data/bash-completion/helpers/gst'))
236
237 def get_subprocess_env(options, gst_version):
238     env = os.environ.copy()
239
240     env["CURRENT_GST"] = os.path.normpath(SCRIPTDIR)
241     env["GST_VERSION"] = gst_version
242     prepend_env_var (env, "GST_VALIDATE_SCENARIOS_PATH", os.path.normpath(
243         "%s/subprojects/gst-devtools/validate/data/scenarios" % SCRIPTDIR),
244         options.sysroot)
245     env["GST_VALIDATE_PLUGIN_PATH"] = os.path.normpath(
246         "%s/subprojects/gst-devtools/validate/plugins" % options.builddir)
247     prepend_env_var (env, "GST_VALIDATE_APPS_DIR", os.path.normpath(
248         "%s/subprojects/gst-editing-services/tests/validate" % SCRIPTDIR),
249         options.sysroot)
250     env["GST_ENV"] = 'gst-' + gst_version
251     env["GST_REGISTRY"] = os.path.normpath(options.builddir + "/registry.dat")
252     prepend_env_var(env, "PATH", os.path.normpath(
253         "%s/subprojects/gst-devtools/validate/tools" % options.builddir),
254         options.sysroot)
255
256     prepend_env_var (env, "GST_VALIDATE_SCENARIOS_PATH", os.path.normpath(
257         "%s/subprojects/gst-examples/webrtc/check/validate/scenarios" %
258         SCRIPTDIR), options.sysroot)
259     prepend_env_var (env, "GST_VALIDATE_APPS_DIR", os.path.normpath(
260         "%s/subprojects/gst-examples/webrtc/check/validate/apps" %
261         SCRIPTDIR), options.sysroot)
262
263     if options.wine:
264         return get_wine_subprocess_env(options, env)
265
266     prepend_env_var(env, "PATH", os.path.join(SCRIPTDIR, 'meson'),
267         options.sysroot)
268
269     env["GST_PLUGIN_SYSTEM_PATH"] = ""
270     env["GST_PLUGIN_SCANNER"] = os.path.normpath(
271         "%s/subprojects/gstreamer/libs/gst/helpers/gst-plugin-scanner" % options.builddir)
272     env["GST_PTP_HELPER"] = os.path.normpath(
273         "%s/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper" % options.builddir)
274
275     if os.name == 'nt':
276         lib_path_envvar = 'PATH'
277     elif platform.system() == 'Darwin':
278         lib_path_envvar = 'DYLD_LIBRARY_PATH'
279     else:
280         lib_path_envvar = 'LD_LIBRARY_PATH'
281
282     prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(SCRIPTDIR, 'subprojects',
283                                                         'gst-python', 'plugin'),
284                     options.sysroot)
285     prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(PREFIX_DIR, 'lib',
286                                                         'gstreamer-1.0'),
287                     options.sysroot)
288     prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(options.builddir, 'subprojects',
289                                                          'libnice', 'gst'),
290                     options.sysroot)
291     prepend_env_var(env, "GST_VALIDATE_SCENARIOS_PATH",
292                     os.path.join(PREFIX_DIR, 'share', 'gstreamer-1.0',
293                                  'validate', 'scenarios'),
294                     options.sysroot)
295     prepend_env_var(env, "GI_TYPELIB_PATH", os.path.join(PREFIX_DIR, 'lib',
296                                                          'lib', 'girepository-1.0'),
297                     options.sysroot)
298     prepend_env_var(env, "PKG_CONFIG_PATH", os.path.join(PREFIX_DIR, 'lib', 'pkgconfig'),
299                     options.sysroot)
300
301     # gst-indent
302     prepend_env_var(env, "PATH", os.path.join(SCRIPTDIR, 'gstreamer', 'tools'),
303                     options.sysroot)
304
305     # tools: gst-launch-1.0, gst-inspect-1.0
306     prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
307                                               'gstreamer', 'tools'),
308                     options.sysroot)
309     prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
310                                               'gst-plugins-base', 'tools'),
311                     options.sysroot)
312
313     # Library and binary search paths
314     prepend_env_var(env, "PATH", os.path.join(PREFIX_DIR, 'bin'),
315                     options.sysroot)
316     if lib_path_envvar != 'PATH':
317         prepend_env_var(env, lib_path_envvar, os.path.join(PREFIX_DIR, 'lib'),
318                         options.sysroot)
319         prepend_env_var(env, lib_path_envvar, os.path.join(PREFIX_DIR, 'lib64'),
320                         options.sysroot)
321     elif 'QMAKE' in os.environ:
322         # There's no RPATH on Windows, so we need to set PATH for the qt5 DLLs
323         prepend_env_var(env, 'PATH', os.path.dirname(os.environ['QMAKE']),
324                         options.sysroot)
325
326     meson = get_meson()
327     targets_s = subprocess.check_output(meson + ['introspect', options.builddir, '--targets'])
328     targets = json.loads(targets_s.decode())
329     paths = set()
330     mono_paths = set()
331     srcdir_path = pathlib.Path(options.srcdir)
332
333     build_options_s = subprocess.check_output(meson + ['introspect', options.builddir, '--buildoptions'])
334     build_options = json.loads(build_options_s.decode())
335     libdir, = [o['value'] for o in build_options if o['name'] == 'libdir']
336     libdir = PurePath(libdir)
337     prefix, = [o['value'] for o in build_options if o['name'] == 'prefix']
338     bindir, = [o['value'] for o in build_options if o['name'] == 'bindir']
339     prefix = PurePath(prefix)
340     bindir = prefix / bindir
341
342     global GSTPLUGIN_FILEPATH_REG_TEMPLATE
343     GSTPLUGIN_FILEPATH_REG_TEMPLATE = GSTPLUGIN_FILEPATH_REG_TEMPLATE.format(libdir=libdir.as_posix())
344
345     for target in targets:
346         filenames = listify(target['filename'])
347         if not target['installed']:
348             continue
349         for filename in filenames:
350             root = os.path.dirname(filename)
351             if srcdir_path / "subprojects/gst-devtools/validate/plugins" in (srcdir_path / root).parents:
352                 continue
353             if filename.endswith('.dll'):
354                 mono_paths.add(os.path.join(options.builddir, root))
355             if TYPELIB_REG.search(filename):
356                 prepend_env_var(env, "GI_TYPELIB_PATH",
357                                 os.path.join(options.builddir, root),
358                                 options.sysroot)
359             elif is_library_target_and_not_plugin(target, filename):
360                 prepend_env_var(env, lib_path_envvar,
361                                 os.path.join(options.builddir, root),
362                                 options.sysroot)
363             elif is_binary_target_and_in_path(target, filename, bindir):
364                 paths.add(os.path.join(options.builddir, root))
365             elif is_gio_module(target, filename, options.builddir):
366                 prepend_env_var(env, 'GIO_EXTRA_MODULES',
367                                 os.path.join(options.builddir, root),
368                                 options.sysroot)
369
370     with open(os.path.join(options.builddir, 'GstPluginsPath.json')) as f:
371         for plugin_path in json.load(f):
372             prepend_env_var(env, 'GST_PLUGIN_PATH', plugin_path,
373                             options.sysroot)
374
375     # Sort to iterate in a consistent order (`set`s and `hash`es are randomized)
376     for p in sorted(paths):
377         prepend_env_var(env, 'PATH', p, options.sysroot)
378
379     if os.name != 'nt':
380         for p in sorted(mono_paths):
381             prepend_env_var(env, "MONO_PATH", p, options.sysroot)
382
383     presets = set()
384     encoding_targets = set()
385     python_dirs = setup_gdb(options)
386     if '--installed' in subprocess.check_output(meson + ['introspect', '-h']).decode():
387         installed_s = subprocess.check_output(meson + ['introspect', options.builddir, '--installed'])
388         for path, installpath in json.loads(installed_s.decode()).items():
389             installpath_parts = pathlib.Path(installpath).parts
390             path_parts = pathlib.Path(path).parts
391
392             # We want to add all python modules to the PYTHONPATH
393             # in a manner consistent with the way they would be imported:
394             # For example if the source path /home/meh/foo/bar.py
395             # is to be installed in /usr/lib/python/site-packages/foo/bar.py,
396             # we want to add /home/meh to the PYTHONPATH.
397             # This will only work for projects where the paths to be installed
398             # mirror the installed directory layout, for example if the path
399             # is /home/meh/baz/bar.py and the install path is
400             # /usr/lib/site-packages/foo/bar.py , we will not add anything
401             # to PYTHONPATH, but the current approach works with pygobject
402             # and gst-python at least.
403             if 'site-packages' in installpath_parts:
404                 install_subpath = os.path.join(*installpath_parts[installpath_parts.index('site-packages') + 1:])
405                 if path.endswith(install_subpath):
406                     python_dirs.add(path[:len (install_subpath) * -1])
407
408             if path.endswith('.prs'):
409                 presets.add(os.path.dirname(path))
410             elif path.endswith('.gep'):
411                 encoding_targets.add(
412                     os.path.abspath(os.path.join(os.path.dirname(path), '..')))
413
414             if path.endswith('gstomx.conf'):
415                 prepend_env_var(env, 'GST_OMX_CONFIG_DIR', os.path.dirname(path),
416                                 options.sysroot)
417
418         for p in sorted(presets):
419             prepend_env_var(env, 'GST_PRESET_PATH', p, options.sysroot)
420
421         for t in sorted(encoding_targets):
422             prepend_env_var(env, 'GST_ENCODING_TARGET_PATH', t, options.sysroot)
423
424     # Check if meson has generated -uninstalled pkgconfig files
425     meson_uninstalled = pathlib.Path(options.builddir) / 'meson-uninstalled'
426     if meson_uninstalled.is_dir():
427         prepend_env_var(env, 'PKG_CONFIG_PATH', str(meson_uninstalled), options.sysroot)
428
429     for python_dir in sorted(python_dirs):
430         prepend_env_var(env, 'PYTHONPATH', python_dir, options.sysroot)
431
432     mesonpath = os.path.join(SCRIPTDIR, "meson")
433     if os.path.join(mesonpath):
434         # Add meson/ into PYTHONPATH if we are using a local meson
435         prepend_env_var(env, 'PYTHONPATH', mesonpath, options.sysroot)
436
437     # For devhelp books
438     if 'XDG_DATA_DIRS' not in env or not env['XDG_DATA_DIRS']:
439         # Preserve default paths when empty
440         prepend_env_var(env, 'XDG_DATA_DIRS', '/usr/local/share/:/usr/share/', '')
441
442     prepend_env_var (env, 'XDG_DATA_DIRS', os.path.join(options.builddir,
443                                                         'subprojects',
444                                                         'gst-docs',
445                                                         'GStreamer-doc'),
446                      options.sysroot)
447
448     if 'XDG_CONFIG_DIRS' not in env or not env['XDG_CONFIG_DIRS']:
449         # Preserve default paths when empty
450         prepend_env_var(env, 'XDG_CONFIG_DIRS', '/etc/local/xdg:/etc/xdg', '')
451
452     prepend_env_var(env, "XDG_CONFIG_DIRS", os.path.join(PREFIX_DIR, 'etc', 'xdg'),
453                     options.sysroot)
454
455     return env
456
457 def get_windows_shell():
458     command = ['powershell.exe' ,'-noprofile', '-executionpolicy', 'bypass', '-file', 'cmd_or_ps.ps1']
459     result = subprocess.check_output(command, cwd=SCRIPTDIR)
460     return result.decode().strip()
461
462 if __name__ == "__main__":
463     parser = argparse.ArgumentParser(prog="gst-env")
464
465     parser.add_argument("--builddir",
466                         default=DEFAULT_BUILDDIR,
467                         help="The meson build directory")
468     parser.add_argument("--srcdir",
469                         default=SCRIPTDIR,
470                         help="The top level source directory")
471     parser.add_argument("--sysroot",
472                         default='',
473                         help="The sysroot path used during cross-compilation")
474     parser.add_argument("--wine",
475                         default='',
476                         help="Build a wine env based on specified wine command")
477     parser.add_argument("--winepath",
478                         default='',
479                         help="Extra path to set to WINEPATH.")
480     parser.add_argument("--only-environment",
481                         action='store_true',
482                         default=False,
483                         help="Do not start a shell, only print required environment.")
484     options, args = parser.parse_known_args()
485
486     if not os.path.exists(options.builddir):
487         print("GStreamer not built in %s\n\nBuild it and try again" %
488               options.builddir)
489         exit(1)
490     options.builddir = os.path.abspath(options.builddir)
491
492     if not os.path.exists(options.srcdir):
493         print("The specified source dir does not exist" %
494               options.srcdir)
495         exit(1)
496
497     # The following incantation will retrieve the current branch name.
498     try:
499       gst_version = git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD",
500                         repository_path=options.srcdir).strip('\n')
501     except subprocess.CalledProcessError:
502       gst_version = "unknown"
503
504     if options.wine:
505         gst_version += '-' + os.path.basename(options.wine)
506
507     env = get_subprocess_env(options, gst_version)
508     if not args:
509         if os.name == 'nt':
510             shell = get_windows_shell()
511             if shell == 'powershell.exe':
512                 args = ['powershell.exe']
513                 args += ['-NoLogo', '-NoExit']
514                 prompt = 'function global:prompt {  "[gst-' + gst_version + '"+"] PS " + $PWD + "> "}'
515                 args += ['-Command', prompt]
516             else:
517                 args = [os.environ.get("COMSPEC", r"C:\WINDOWS\system32\cmd.exe")]
518                 args += ['/k', 'prompt [gst-{}] $P$G'.format(gst_version)]
519         else:
520             args = [os.environ.get("SHELL", os.path.realpath("/bin/sh"))]
521         if args[0].endswith('bash') and not strtobool(os.environ.get("GST_BUILD_DISABLE_PS1_OVERRIDE", r"FALSE")):
522             # Let the GC remove the tmp file
523             tmprc = tempfile.NamedTemporaryFile(mode='w')
524             bashrc = os.path.expanduser('~/.bashrc')
525             if os.path.exists(bashrc):
526                 with open(bashrc, 'r') as src:
527                     shutil.copyfileobj(src, tmprc)
528             tmprc.write('\nexport PS1="[gst-%s] $PS1"' % gst_version)
529             tmprc.flush()
530             if is_bash_completion_available(options):
531                 bash_completions_files = []
532                 for p in BASH_COMPLETION_PATHS:
533                     if os.path.exists(p):
534                         bash_completions_files +=  os.listdir(path=p)
535                 bc_rc = BC_RC.format(bash_completions=' '.join(bash_completions_files), bash_completions_paths=' '.join(BASH_COMPLETION_PATHS))
536                 tmprc.write(bc_rc)
537                 tmprc.flush()
538             args.append("--rcfile")
539             args.append(tmprc.name)
540         elif args[0].endswith('fish'):
541             # Ignore SIGINT while using fish as the shell to make it behave
542             # like other shells such as bash and zsh.
543             # See: https://gitlab.freedesktop.org/gstreamer/gst-build/issues/18
544             signal.signal(signal.SIGINT, lambda x, y: True)
545             # Set the prompt
546             args.append('--init-command')
547             prompt_cmd = '''functions --copy fish_prompt original_fish_prompt
548             function fish_prompt
549                 echo -n '[gst-{}] '(original_fish_prompt)
550             end'''.format(gst_version)
551             args.append(prompt_cmd)
552         elif args[0].endswith('zsh'):
553             tmpdir = tempfile.TemporaryDirectory()
554             # Let the GC remove the tmp file
555             tmprc = open(os.path.join(tmpdir.name, '.zshrc'), 'w')
556             zshrc = os.path.expanduser('~/.zshrc')
557             if os.path.exists(zshrc):
558                 with open(zshrc, 'r') as src:
559                     shutil.copyfileobj(src, tmprc)
560             tmprc.write('\nexport PROMPT="[gst-{}] $PROMPT"'.format(gst_version))
561             tmprc.flush()
562             env['ZDOTDIR'] = tmpdir.name
563     try:
564         if options.only_environment:
565             for name, value in env.items():
566                 print('{}={}'.format(name, shlex.quote(value)))
567                 print('export {}'.format(name))
568         else:
569             exit(subprocess.call(args, close_fds=False, env=env))
570
571     except subprocess.CalledProcessError as e:
572         exit(e.returncode)