python: Fix using overrides when not building PyGObject
[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, 'scripts'),
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     overrides_dirs = set()
390     if '--installed' in subprocess.check_output(meson + ['introspect', '-h']).decode():
391         installed_s = subprocess.check_output(meson + ['introspect', options.builddir, '--installed'])
392         for path, installpath in json.loads(installed_s.decode()).items():
393             installpath_parts = pathlib.Path(installpath).parts
394             path_parts = pathlib.Path(path).parts
395
396             # We want to add all python modules to the PYTHONPATH
397             # in a manner consistent with the way they would be imported:
398             # For example if the source path /home/meh/foo/bar.py
399             # is to be installed in /usr/lib/python/site-packages/foo/bar.py,
400             # we want to add /home/meh to the PYTHONPATH.
401             # This will only work for projects where the paths to be installed
402             # mirror the installed directory layout, for example if the path
403             # is /home/meh/baz/bar.py and the install path is
404             # /usr/lib/site-packages/foo/bar.py , we will not add anything
405             # to PYTHONPATH, but the current approach works with pygobject
406             # and gst-python at least.
407             if 'site-packages' in installpath_parts:
408                 install_subpath = os.path.join(*installpath_parts[installpath_parts.index('site-packages') + 1:])
409                 if path.endswith(install_subpath):
410                     if os.path.commonprefix(["gi/overrides", install_subpath]):
411                         overrides_dirs.add(os.path.dirname(path))
412                     else:
413                         python_dirs.add(path[:len (install_subpath) * -1])
414
415             if path.endswith('.prs'):
416                 presets.add(os.path.dirname(path))
417             elif path.endswith('.gep'):
418                 encoding_targets.add(
419                     os.path.abspath(os.path.join(os.path.dirname(path), '..')))
420
421             if path.endswith('gstomx.conf'):
422                 prepend_env_var(env, 'GST_OMX_CONFIG_DIR', os.path.dirname(path),
423                                 options.sysroot)
424
425         for p in sorted(presets):
426             prepend_env_var(env, 'GST_PRESET_PATH', p, options.sysroot)
427
428         for t in sorted(encoding_targets):
429             prepend_env_var(env, 'GST_ENCODING_TARGET_PATH', t, options.sysroot)
430
431     # Check if meson has generated -uninstalled pkgconfig files
432     meson_uninstalled = pathlib.Path(options.builddir) / 'meson-uninstalled'
433     if meson_uninstalled.is_dir():
434         prepend_env_var(env, 'PKG_CONFIG_PATH', str(meson_uninstalled), options.sysroot)
435
436     for python_dir in sorted(python_dirs):
437         prepend_env_var(env, 'PYTHONPATH', python_dir, options.sysroot)
438
439     for python_dir in sorted(overrides_dirs):
440         prepend_env_var(env, '_GI_OVERRIDES_PATH', python_dir, options.sysroot)
441
442     mesonpath = os.path.join(SCRIPTDIR, "meson")
443     if os.path.join(mesonpath):
444         # Add meson/ into PYTHONPATH if we are using a local meson
445         prepend_env_var(env, 'PYTHONPATH', mesonpath, options.sysroot)
446
447     # Ensure that gst-python/gi is used first
448     prepend_env_var(env, "PYTHONPATH", os.path.join(SCRIPTDIR, 'subprojects', 'gst-python'),
449                     options.sysroot)
450
451     # For devhelp books
452     if 'XDG_DATA_DIRS' not in env or not env['XDG_DATA_DIRS']:
453         # Preserve default paths when empty
454         prepend_env_var(env, 'XDG_DATA_DIRS', '/usr/local/share/:/usr/share/', '')
455
456     prepend_env_var (env, 'XDG_DATA_DIRS', os.path.join(options.builddir,
457                                                         'subprojects',
458                                                         'gst-docs',
459                                                         'GStreamer-doc'),
460                      options.sysroot)
461
462     if 'XDG_CONFIG_DIRS' not in env or not env['XDG_CONFIG_DIRS']:
463         # Preserve default paths when empty
464         prepend_env_var(env, 'XDG_CONFIG_DIRS', '/etc/local/xdg:/etc/xdg', '')
465
466     prepend_env_var(env, "XDG_CONFIG_DIRS", os.path.join(PREFIX_DIR, 'etc', 'xdg'),
467                     options.sysroot)
468
469     return env
470
471 def get_windows_shell():
472     command = ['powershell.exe' ,'-noprofile', '-executionpolicy', 'bypass', '-file',
473         os.path.join(SCRIPTDIR, 'data', 'misc', 'cmd_or_ps.ps1')]
474     result = subprocess.check_output(command)
475     return result.decode().strip()
476
477 if __name__ == "__main__":
478     parser = argparse.ArgumentParser(prog="gst-env")
479
480     parser.add_argument("--builddir",
481                         default=DEFAULT_BUILDDIR,
482                         help="The meson build directory")
483     parser.add_argument("--gstbuilddir",
484                         default=None,
485                         help="The meson GStreamer build directory (defaults to builddir)")
486     parser.add_argument("--srcdir",
487                         default=SCRIPTDIR,
488                         help="The top level source directory")
489     parser.add_argument("--sysroot",
490                         default='',
491                         help="The sysroot path used during cross-compilation")
492     parser.add_argument("--wine",
493                         default='',
494                         help="Build a wine env based on specified wine command")
495     parser.add_argument("--winepath",
496                         default='',
497                         help="Extra path to set to WINEPATH.")
498     parser.add_argument("--only-environment",
499                         action='store_true',
500                         default=False,
501                         help="Do not start a shell, only print required environment.")
502     options, args = parser.parse_known_args()
503
504     if not os.path.exists(options.builddir):
505         print("GStreamer not built in %s\n\nBuild it and try again" %
506               options.builddir)
507         exit(1)
508
509     if options.gstbuilddir and not os.path.exists(options.gstbuilddir):
510         print("GStreamer is not built in %s\n\nBuild it and try again" %
511               options.gstbuilddir)
512         exit(1)
513     elif not options.gstbuilddir:
514         options.gstbuilddir = options.builddir
515
516     options.builddir = os.path.abspath(options.builddir)
517     options.gstbuilddir = os.path.abspath(options.gstbuilddir)
518
519     if not os.path.exists(options.srcdir):
520         print("The specified source dir does not exist" %
521               options.srcdir)
522         exit(1)
523
524     # The following incantation will retrieve the current branch name.
525     try:
526       gst_version = git("rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD",
527                         repository_path=options.srcdir).strip('\n')
528     except subprocess.CalledProcessError:
529       gst_version = "unknown"
530
531     if options.wine:
532         gst_version += '-' + os.path.basename(options.wine)
533
534     env = get_subprocess_env(options, gst_version)
535     if not args:
536         if os.name == 'nt':
537             shell = get_windows_shell()
538             if shell == 'powershell.exe':
539                 args = ['powershell.exe']
540                 args += ['-NoLogo', '-NoExit']
541                 prompt = 'function global:prompt {  "[gst-' + gst_version + '"+"] PS " + $PWD + "> "}'
542                 args += ['-Command', prompt]
543             else:
544                 args = [os.environ.get("COMSPEC", r"C:\WINDOWS\system32\cmd.exe")]
545                 args += ['/k', 'prompt [gst-{}] $P$G'.format(gst_version)]
546         else:
547             args = [os.environ.get("SHELL", os.path.realpath("/bin/sh"))]
548         if args[0].endswith('bash') and not strtobool(os.environ.get("GST_BUILD_DISABLE_PS1_OVERRIDE", r"FALSE")):
549             # Let the GC remove the tmp file
550             tmprc = tempfile.NamedTemporaryFile(mode='w')
551             bashrc = os.path.expanduser('~/.bashrc')
552             if os.path.exists(bashrc):
553                 with open(bashrc, 'r') as src:
554                     shutil.copyfileobj(src, tmprc)
555             tmprc.write('\nexport PS1="[gst-%s] $PS1"' % gst_version)
556             tmprc.flush()
557             if is_bash_completion_available(options):
558                 bash_completions_files = []
559                 for p in BASH_COMPLETION_PATHS:
560                     if os.path.exists(p):
561                         bash_completions_files +=  os.listdir(path=p)
562                 bc_rc = BC_RC.format(bash_completions=' '.join(bash_completions_files), bash_completions_paths=' '.join(BASH_COMPLETION_PATHS))
563                 tmprc.write(bc_rc)
564                 tmprc.flush()
565             args.append("--rcfile")
566             args.append(tmprc.name)
567         elif args[0].endswith('fish'):
568             # Ignore SIGINT while using fish as the shell to make it behave
569             # like other shells such as bash and zsh.
570             # See: https://gitlab.freedesktop.org/gstreamer/gst-build/issues/18
571             signal.signal(signal.SIGINT, lambda x, y: True)
572             # Set the prompt
573             args.append('--init-command')
574             prompt_cmd = '''functions --copy fish_prompt original_fish_prompt
575             function fish_prompt
576                 echo -n '[gst-{}] '(original_fish_prompt)
577             end'''.format(gst_version)
578             args.append(prompt_cmd)
579         elif args[0].endswith('zsh'):
580             tmpdir = tempfile.TemporaryDirectory()
581             # Let the GC remove the tmp file
582             tmprc = open(os.path.join(tmpdir.name, '.zshrc'), 'w')
583             zshrc = os.path.expanduser('~/.zshrc')
584             if os.path.exists(zshrc):
585                 with open(zshrc, 'r') as src:
586                     shutil.copyfileobj(src, tmprc)
587             tmprc.write('\nexport PROMPT="[gst-{}] $PROMPT"'.format(gst_version))
588             tmprc.flush()
589             env['ZDOTDIR'] = tmpdir.name
590     try:
591         if options.only_environment:
592             for name, value in env.items():
593                 print('{}={}'.format(name, shlex.quote(value)))
594                 print('export {}'.format(name))
595         else:
596             if os.environ.get("CI_PROJECT_NAME"):
597                 print("Ignoring SIGINT when running on the CI,"
598                       " as we get spurious sigint in there for some reason.")
599                 signal.signal(signal.SIGINT, signal.SIG_IGN)
600             exit(subprocess.call(args, close_fds=False, env=env))
601
602     except subprocess.CalledProcessError as e:
603         exit(e.returncode)