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