bash-completion: add completion for gstreamer tools
[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     env["GST_VALIDATE_SCENARIOS_PATH"] = os.path.normpath(
243         "%s/subprojects/gst-devtools/validate/data/scenarios" % SCRIPTDIR)
244     env["GST_VALIDATE_PLUGIN_PATH"] = os.path.normpath(
245         "%s/subprojects/gst-devtools/validate/plugins" % options.builddir)
246     env["GST_VALIDATE_APPS_DIR"] = os.path.normpath(
247         "%s/subprojects/gst-editing-services/tests/validate" % SCRIPTDIR)
248     env["GST_ENV"] = 'gst-' + gst_version
249     env["GST_REGISTRY"] = os.path.normpath(options.builddir + "/registry.dat")
250     prepend_env_var(env, "PATH", os.path.normpath(
251         "%s/subprojects/gst-devtools/validate/tools" % options.builddir),
252         options.sysroot)
253
254     if options.wine:
255         return get_wine_subprocess_env(options, env)
256
257     prepend_env_var(env, "PATH", os.path.join(SCRIPTDIR, 'meson'),
258         options.sysroot)
259
260     env["GST_PLUGIN_SYSTEM_PATH"] = ""
261     env["GST_PLUGIN_SCANNER"] = os.path.normpath(
262         "%s/subprojects/gstreamer/libs/gst/helpers/gst-plugin-scanner" % options.builddir)
263     env["GST_PTP_HELPER"] = os.path.normpath(
264         "%s/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper" % options.builddir)
265
266     if os.name == 'nt':
267         lib_path_envvar = 'PATH'
268     elif platform.system() == 'Darwin':
269         lib_path_envvar = 'DYLD_LIBRARY_PATH'
270     else:
271         lib_path_envvar = 'LD_LIBRARY_PATH'
272
273     prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(SCRIPTDIR, 'subprojects',
274                                                         'gst-python', 'plugin'),
275                     options.sysroot)
276     prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(PREFIX_DIR, 'lib',
277                                                         'gstreamer-1.0'),
278                     options.sysroot)
279     prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(options.builddir, 'subprojects',
280                                                          'libnice', 'gst'),
281                     options.sysroot)
282     prepend_env_var(env, "GST_VALIDATE_SCENARIOS_PATH",
283                     os.path.join(PREFIX_DIR, 'share', 'gstreamer-1.0',
284                                  'validate', 'scenarios'),
285                     options.sysroot)
286     prepend_env_var(env, "GI_TYPELIB_PATH", os.path.join(PREFIX_DIR, 'lib',
287                                                          'lib', 'girepository-1.0'),
288                     options.sysroot)
289     prepend_env_var(env, "PKG_CONFIG_PATH", os.path.join(PREFIX_DIR, 'lib', 'pkgconfig'),
290                     options.sysroot)
291
292     # gst-indent
293     prepend_env_var(env, "PATH", os.path.join(SCRIPTDIR, 'gstreamer', 'tools'),
294                     options.sysroot)
295
296     # tools: gst-launch-1.0, gst-inspect-1.0
297     prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
298                                               'gstreamer', 'tools'),
299                     options.sysroot)
300     prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
301                                               'gst-plugins-base', 'tools'),
302                     options.sysroot)
303
304     # Library and binary search paths
305     prepend_env_var(env, "PATH", os.path.join(PREFIX_DIR, 'bin'),
306                     options.sysroot)
307     if lib_path_envvar != 'PATH':
308         prepend_env_var(env, lib_path_envvar, os.path.join(PREFIX_DIR, 'lib'),
309                         options.sysroot)
310         prepend_env_var(env, lib_path_envvar, os.path.join(PREFIX_DIR, 'lib64'),
311                         options.sysroot)
312     elif 'QMAKE' in os.environ:
313         # There's no RPATH on Windows, so we need to set PATH for the qt5 DLLs
314         prepend_env_var(env, 'PATH', os.path.dirname(os.environ['QMAKE']),
315                         options.sysroot)
316
317     meson = get_meson()
318     targets_s = subprocess.check_output(meson + ['introspect', options.builddir, '--targets'])
319     targets = json.loads(targets_s.decode())
320     paths = set()
321     mono_paths = set()
322     srcdir_path = pathlib.Path(options.srcdir)
323
324     build_options_s = subprocess.check_output(meson + ['introspect', options.builddir, '--buildoptions'])
325     build_options = json.loads(build_options_s.decode())
326     libdir, = [o['value'] for o in build_options if o['name'] == 'libdir']
327     libdir = PurePath(libdir)
328     prefix, = [o['value'] for o in build_options if o['name'] == 'prefix']
329     bindir, = [o['value'] for o in build_options if o['name'] == 'bindir']
330     prefix = PurePath(prefix)
331     bindir = prefix / bindir
332
333     global GSTPLUGIN_FILEPATH_REG_TEMPLATE
334     GSTPLUGIN_FILEPATH_REG_TEMPLATE = GSTPLUGIN_FILEPATH_REG_TEMPLATE.format(libdir=libdir.as_posix())
335
336     for target in targets:
337         filenames = listify(target['filename'])
338         if not target['installed']:
339             continue
340         for filename in filenames:
341             root = os.path.dirname(filename)
342             if srcdir_path / "subprojects/gst-devtools/validate/plugins" in (srcdir_path / root).parents:
343                 continue
344             if filename.endswith('.dll'):
345                 mono_paths.add(os.path.join(options.builddir, root))
346             if TYPELIB_REG.search(filename):
347                 prepend_env_var(env, "GI_TYPELIB_PATH",
348                                 os.path.join(options.builddir, root),
349                                 options.sysroot)
350             elif is_library_target_and_not_plugin(target, filename):
351                 prepend_env_var(env, lib_path_envvar,
352                                 os.path.join(options.builddir, root),
353                                 options.sysroot)
354             elif is_binary_target_and_in_path(target, filename, bindir):
355                 paths.add(os.path.join(options.builddir, root))
356             elif is_gio_module(target, filename, options.builddir):
357                 prepend_env_var(env, 'GIO_EXTRA_MODULES',
358                                 os.path.join(options.builddir, root),
359                                 options.sysroot)
360
361     with open(os.path.join(options.builddir, 'GstPluginsPath.json')) as f:
362         for plugin_path in json.load(f):
363             prepend_env_var(env, 'GST_PLUGIN_PATH', plugin_path,
364                             options.sysroot)
365
366     # Sort to iterate in a consistent order (`set`s and `hash`es are randomized)
367     for p in sorted(paths):
368         prepend_env_var(env, 'PATH', p, options.sysroot)
369
370     if os.name != 'nt':
371         for p in sorted(mono_paths):
372             prepend_env_var(env, "MONO_PATH", p, options.sysroot)
373
374     presets = set()
375     encoding_targets = set()
376     python_dirs = setup_gdb(options)
377     if '--installed' in subprocess.check_output(meson + ['introspect', '-h']).decode():
378         installed_s = subprocess.check_output(meson + ['introspect', options.builddir, '--installed'])
379         for path, installpath in json.loads(installed_s.decode()).items():
380             installpath_parts = pathlib.Path(installpath).parts
381             path_parts = pathlib.Path(path).parts
382
383             # We want to add all python modules to the PYTHONPATH
384             # in a manner consistent with the way they would be imported:
385             # For example if the source path /home/meh/foo/bar.py
386             # is to be installed in /usr/lib/python/site-packages/foo/bar.py,
387             # we want to add /home/meh to the PYTHONPATH.
388             # This will only work for projects where the paths to be installed
389             # mirror the installed directory layout, for example if the path
390             # is /home/meh/baz/bar.py and the install path is
391             # /usr/lib/site-packages/foo/bar.py , we will not add anything
392             # to PYTHONPATH, but the current approach works with pygobject
393             # and gst-python at least.
394             if 'site-packages' in installpath_parts:
395                 install_subpath = os.path.join(*installpath_parts[installpath_parts.index('site-packages') + 1:])
396                 if path.endswith(install_subpath):
397                     python_dirs.add(path[:len (install_subpath) * -1])
398
399             if path.endswith('.prs'):
400                 presets.add(os.path.dirname(path))
401             elif path.endswith('.gep'):
402                 encoding_targets.add(
403                     os.path.abspath(os.path.join(os.path.dirname(path), '..')))
404
405             if path.endswith('gstomx.conf'):
406                 prepend_env_var(env, 'GST_OMX_CONFIG_DIR', os.path.dirname(path),
407                                 options.sysroot)
408
409         for p in sorted(presets):
410             prepend_env_var(env, 'GST_PRESET_PATH', p, options.sysroot)
411
412         for t in sorted(encoding_targets):
413             prepend_env_var(env, 'GST_ENCODING_TARGET_PATH', t, options.sysroot)
414
415     # Check if meson has generated -uninstalled pkgconfig files
416     meson_uninstalled = pathlib.Path(options.builddir) / 'meson-uninstalled'
417     if meson_uninstalled.is_dir():
418         prepend_env_var(env, 'PKG_CONFIG_PATH', str(meson_uninstalled), options.sysroot)
419
420     for python_dir in sorted(python_dirs):
421         prepend_env_var(env, 'PYTHONPATH', python_dir, options.sysroot)
422
423     mesonpath = os.path.join(SCRIPTDIR, "meson")
424     if os.path.join(mesonpath):
425         # Add meson/ into PYTHONPATH if we are using a local meson
426         prepend_env_var(env, 'PYTHONPATH', mesonpath, options.sysroot)
427
428     # For devhelp books
429     if 'XDG_DATA_DIRS' not in env or not env['XDG_DATA_DIRS']:
430         # Preserve default paths when empty
431         prepend_env_var(env, 'XDG_DATA_DIRS', '/usr/local/share/:/usr/share/', '')
432
433     prepend_env_var (env, 'XDG_DATA_DIRS', os.path.join(options.builddir,
434                                                         'subprojects',
435                                                         'gst-docs',
436                                                         'GStreamer-doc'),
437                      options.sysroot)
438
439     if 'XDG_CONFIG_DIRS' not in env or not env['XDG_CONFIG_DIRS']:
440         # Preserve default paths when empty
441         prepend_env_var(env, 'XDG_CONFIG_DIRS', '/etc/local/xdg:/etc/xdg', '')
442
443     prepend_env_var(env, "XDG_CONFIG_DIRS", os.path.join(PREFIX_DIR, 'etc', 'xdg'),
444                     options.sysroot)
445
446     return env
447
448 def get_windows_shell():
449     command = ['powershell.exe' ,'-noprofile', '-executionpolicy', 'bypass', '-file', 'cmd_or_ps.ps1']
450     result = subprocess.check_output(command, cwd=SCRIPTDIR)
451     return result.decode().strip()
452
453 if __name__ == "__main__":
454     parser = argparse.ArgumentParser(prog="gst-env")
455
456     parser.add_argument("--builddir",
457                         default=DEFAULT_BUILDDIR,
458                         help="The meson build directory")
459     parser.add_argument("--srcdir",
460                         default=SCRIPTDIR,
461                         help="The top level source directory")
462     parser.add_argument("--sysroot",
463                         default='',
464                         help="The sysroot path used during cross-compilation")
465     parser.add_argument("--wine",
466                         default='',
467                         help="Build a wine env based on specified wine command")
468     parser.add_argument("--winepath",
469                         default='',
470                         help="Extra path to set to WINEPATH.")
471     parser.add_argument("--only-environment",
472                         action='store_true',
473                         default=False,
474                         help="Do not start a shell, only print required environment.")
475     options, args = parser.parse_known_args()
476
477     if not os.path.exists(options.builddir):
478         print("GStreamer not built in %s\n\nBuild it and try again" %
479               options.builddir)
480         exit(1)
481     options.builddir = os.path.abspath(options.builddir)
482
483     if not os.path.exists(options.srcdir):
484         print("The specified source dir does not exist" %
485               options.srcdir)
486         exit(1)
487
488     # The following incantation will retrieve the current branch name.
489     try:
490       gst_version = git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD",
491                         repository_path=options.srcdir).strip('\n')
492     except subprocess.CalledProcessError:
493       gst_version = "unknown"
494
495     if options.wine:
496         gst_version += '-' + os.path.basename(options.wine)
497
498     env = get_subprocess_env(options, gst_version)
499     if not args:
500         if os.name == 'nt':
501             shell = get_windows_shell()
502             if shell == 'powershell.exe':
503                 args = ['powershell.exe']
504                 args += ['-NoLogo', '-NoExit']
505                 prompt = 'function global:prompt {  "[gst-' + gst_version + '"+"] PS " + $PWD + "> "}'
506                 args += ['-Command', prompt]
507             else:
508                 args = [os.environ.get("COMSPEC", r"C:\WINDOWS\system32\cmd.exe")]
509                 args += ['/k', 'prompt [gst-{}] $P$G'.format(gst_version)]
510         else:
511             args = [os.environ.get("SHELL", os.path.realpath("/bin/sh"))]
512         if args[0].endswith('bash') and not strtobool(os.environ.get("GST_BUILD_DISABLE_PS1_OVERRIDE", r"FALSE")):
513             # Let the GC remove the tmp file
514             tmprc = tempfile.NamedTemporaryFile(mode='w')
515             bashrc = os.path.expanduser('~/.bashrc')
516             if os.path.exists(bashrc):
517                 with open(bashrc, 'r') as src:
518                     shutil.copyfileobj(src, tmprc)
519             tmprc.write('\nexport PS1="[gst-%s] $PS1"' % gst_version)
520             tmprc.flush()
521             if is_bash_completion_available(options):
522                 bash_completions_files = []
523                 for p in BASH_COMPLETION_PATHS:
524                     if os.path.exists(p):
525                         bash_completions_files +=  os.listdir(path=p)
526                 bc_rc = BC_RC.format(bash_completions=' '.join(bash_completions_files), bash_completions_paths=' '.join(BASH_COMPLETION_PATHS))
527                 tmprc.write(bc_rc)
528                 tmprc.flush()
529             args.append("--rcfile")
530             args.append(tmprc.name)
531         elif args[0].endswith('fish'):
532             # Ignore SIGINT while using fish as the shell to make it behave
533             # like other shells such as bash and zsh.
534             # See: https://gitlab.freedesktop.org/gstreamer/gst-build/issues/18
535             signal.signal(signal.SIGINT, lambda x, y: True)
536             # Set the prompt
537             args.append('--init-command')
538             prompt_cmd = '''functions --copy fish_prompt original_fish_prompt
539             function fish_prompt
540                 echo -n '[gst-{}] '(original_fish_prompt)
541             end'''.format(gst_version)
542             args.append(prompt_cmd)
543         elif args[0].endswith('zsh'):
544             tmpdir = tempfile.TemporaryDirectory()
545             # Let the GC remove the tmp file
546             tmprc = open(os.path.join(tmpdir.name, '.zshrc'), 'w')
547             zshrc = os.path.expanduser('~/.zshrc')
548             if os.path.exists(zshrc):
549                 with open(zshrc, 'r') as src:
550                     shutil.copyfileobj(src, tmprc)
551             tmprc.write('\nexport PROMPT="[gst-{}] $PROMPT"'.format(gst_version))
552             tmprc.flush()
553             env['ZDOTDIR'] = tmpdir.name
554     try:
555         if options.only_environment:
556             for name, value in env.items():
557                 print('{}={}'.format(name, shlex.quote(value)))
558                 print('export {}'.format(name))
559         else:
560             exit(subprocess.call(args, close_fds=False, env=env))
561
562     except subprocess.CalledProcessError as e:
563         exit(e.returncode)