Allow running gst-uninstalled when gst-build is a subproject
[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.gstbuilddir, '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("--gstbuilddir",
469                         default=None,
470                         help="The meson gst-build build directory (defaults to builddir)")
471     parser.add_argument("--srcdir",
472                         default=SCRIPTDIR,
473                         help="The top level source directory")
474     parser.add_argument("--sysroot",
475                         default='',
476                         help="The sysroot path used during cross-compilation")
477     parser.add_argument("--wine",
478                         default='',
479                         help="Build a wine env based on specified wine command")
480     parser.add_argument("--winepath",
481                         default='',
482                         help="Extra path to set to WINEPATH.")
483     parser.add_argument("--only-environment",
484                         action='store_true',
485                         default=False,
486                         help="Do not start a shell, only print required environment.")
487     options, args = parser.parse_known_args()
488
489     if not os.path.exists(options.builddir):
490         print("GStreamer not built in %s\n\nBuild it and try again" %
491               options.builddir)
492         exit(1)
493
494     if options.gstbuilddir and not os.path.exists(options.gstbuilddir):
495         print("gst-build is not built in %s\n\nBuild it and try again" %
496               options.gstbuilddir)
497         exit(1)
498     elif not options.gstbuilddir:
499         options.gstbuilddir = options.builddir
500
501     options.builddir = os.path.abspath(options.builddir)
502     options.gstbuilddir = os.path.abspath(options.gstbuilddir)
503
504     if not os.path.exists(options.srcdir):
505         print("The specified source dir does not exist" %
506               options.srcdir)
507         exit(1)
508
509     # The following incantation will retrieve the current branch name.
510     try:
511       gst_version = git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD",
512                         repository_path=options.srcdir).strip('\n')
513     except subprocess.CalledProcessError:
514       gst_version = "unknown"
515
516     if options.wine:
517         gst_version += '-' + os.path.basename(options.wine)
518
519     env = get_subprocess_env(options, gst_version)
520     if not args:
521         if os.name == 'nt':
522             shell = get_windows_shell()
523             if shell == 'powershell.exe':
524                 args = ['powershell.exe']
525                 args += ['-NoLogo', '-NoExit']
526                 prompt = 'function global:prompt {  "[gst-' + gst_version + '"+"] PS " + $PWD + "> "}'
527                 args += ['-Command', prompt]
528             else:
529                 args = [os.environ.get("COMSPEC", r"C:\WINDOWS\system32\cmd.exe")]
530                 args += ['/k', 'prompt [gst-{}] $P$G'.format(gst_version)]
531         else:
532             args = [os.environ.get("SHELL", os.path.realpath("/bin/sh"))]
533         if args[0].endswith('bash') and not strtobool(os.environ.get("GST_BUILD_DISABLE_PS1_OVERRIDE", r"FALSE")):
534             # Let the GC remove the tmp file
535             tmprc = tempfile.NamedTemporaryFile(mode='w')
536             bashrc = os.path.expanduser('~/.bashrc')
537             if os.path.exists(bashrc):
538                 with open(bashrc, 'r') as src:
539                     shutil.copyfileobj(src, tmprc)
540             tmprc.write('\nexport PS1="[gst-%s] $PS1"' % gst_version)
541             tmprc.flush()
542             if is_bash_completion_available(options):
543                 bash_completions_files = []
544                 for p in BASH_COMPLETION_PATHS:
545                     if os.path.exists(p):
546                         bash_completions_files +=  os.listdir(path=p)
547                 bc_rc = BC_RC.format(bash_completions=' '.join(bash_completions_files), bash_completions_paths=' '.join(BASH_COMPLETION_PATHS))
548                 tmprc.write(bc_rc)
549                 tmprc.flush()
550             args.append("--rcfile")
551             args.append(tmprc.name)
552         elif args[0].endswith('fish'):
553             # Ignore SIGINT while using fish as the shell to make it behave
554             # like other shells such as bash and zsh.
555             # See: https://gitlab.freedesktop.org/gstreamer/gst-build/issues/18
556             signal.signal(signal.SIGINT, lambda x, y: True)
557             # Set the prompt
558             args.append('--init-command')
559             prompt_cmd = '''functions --copy fish_prompt original_fish_prompt
560             function fish_prompt
561                 echo -n '[gst-{}] '(original_fish_prompt)
562             end'''.format(gst_version)
563             args.append(prompt_cmd)
564         elif args[0].endswith('zsh'):
565             tmpdir = tempfile.TemporaryDirectory()
566             # Let the GC remove the tmp file
567             tmprc = open(os.path.join(tmpdir.name, '.zshrc'), 'w')
568             zshrc = os.path.expanduser('~/.zshrc')
569             if os.path.exists(zshrc):
570                 with open(zshrc, 'r') as src:
571                     shutil.copyfileobj(src, tmprc)
572             tmprc.write('\nexport PROMPT="[gst-{}] $PROMPT"'.format(gst_version))
573             tmprc.flush()
574             env['ZDOTDIR'] = tmpdir.name
575     try:
576         if options.only_environment:
577             for name, value in env.items():
578                 print('{}={}'.format(name, shlex.quote(value)))
579                 print('export {}'.format(name))
580         else:
581             exit(subprocess.call(args, close_fds=False, env=env))
582
583     except subprocess.CalledProcessError as e:
584         exit(e.returncode)