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