gst-env: only-environment: only dump added and updated vars

By default, `gst-env.py` spawns a new shell with an updated environment
suitable for working with current branch.

The option `only-environment` dumps the resulting environment on `stdout`
without spawing a new shell. This can be used to save it to a file and `source`
it later, possibly from another shell.

However the resulting environment contains all the env vars from the initial
environment, some of which are session, display or DE specific (e.g.
`XAUTHORITY`, `DESKTOP_SESSION`, `WAYLAND_DISPLAY`, ...). This can cause
problems when sourced later.

Reproduction:

1. `gst-env.py --only-environment > env-full`
2. `source env-full`
3. `gst-launch-1.0 videotestsrc ! ximagesink` <= this works
4. Reboot
5. `source env-full`
6. `gst-launch-1.0 videotestsrc ! ximagesink` <= this doesn't work because
   `XAUTHORITY` was overriden with previous value in step 5.

This was initially observed with a Qt application running with the X11 backend.

This commit changes the behaviour of `only-environment` to only dump env vars
that are actually set or modified by `gst-env.py`.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8846>
This commit is contained in:
François Laignel 2025-07-24 10:59:27 +02:00
parent eb4caff16a
commit 6b32228573

View File

@ -52,6 +52,7 @@ done
BASH_COMPLETION_PATHS = [SCRIPTDIR + '/subprojects/gstreamer/data/bash-completion/completions']
BASH_COMPLETION_PATHS += [SCRIPTDIR + '/subprojects/gst-devtools/validate/data/bash-completion/completions']
UPDATED_ENV = dict()
def str_to_bool(value: Any) -> bool:
"""Return whether the provided string (or any value really) represents true. Otherwise false.
@ -80,11 +81,18 @@ def stringify(o):
raise AssertionError('Object {!r} must be a string or a list'.format(o))
def prepend_env_var(env, var, value, sysroot):
def set_env_var(env, var, value, options):
if options.only_environment:
UPDATED_ENV[var] = value
env[var] = value
def prepend_env_var(env, var, value, options):
if var is None:
return
if value.startswith(sysroot):
value = value[len(sysroot):]
if value.startswith(options.sysroot):
value = value[len(options.sysroot):]
# Try not to exceed maximum length limits for env vars on Windows
if os.name == 'nt':
value = win32_get_short_path_name(value)
@ -93,8 +101,8 @@ def prepend_env_var(env, var, value, sysroot):
# Don't add the same value twice
if val in env_val or env_val.startswith(value + os.pathsep):
return
env[var] = val + env_val
env[var] = env[var].replace(os.pathsep + os.pathsep, os.pathsep).strip(os.pathsep)
set_env_var(env, var, val + env_val, options)
set_env_var(env, var, env[var].replace(os.pathsep + os.pathsep, os.pathsep).strip(os.pathsep), options)
def get_target_install_filename(target, filename):
@ -196,15 +204,15 @@ def get_wine_subprocess_env(options, env):
prefix, = [o for o in buildoptions if o['name'] == 'prefix']
path = os.path.normpath(os.path.join(prefix['value'], 'bin'))
prepend_env_var(env, "PATH", path, options.sysroot)
prepend_env_var(env, "PATH", path, options)
wine_path = get_wine_shortpath(
options.wine.split(' '),
[path] + env.get('WINEPATH', '').split(';')
)
if options.winepath:
wine_path += ';' + options.winepath
env['WINEPATH'] = wine_path
env['WINEDEBUG'] = 'fixme-all'
set_env_var(env, 'WINEPATH', wine_path, options)
set_env_var(env, 'WINEDEBUG', 'fixme-all', options)
return env
@ -259,43 +267,46 @@ def is_bash_completion_available(options):
def get_subprocess_env(options, gst_version):
env = os.environ.copy()
env["CURRENT_GST"] = os.path.normpath(SCRIPTDIR)
env["GST_VERSION"] = gst_version
set_env_var(env, "CURRENT_GST", os.path.normpath(SCRIPTDIR), options)
set_env_var(env, "GST_VERSION", gst_version, options)
prepend_env_var(env, "GST_VALIDATE_SCENARIOS_PATH", os.path.normpath(
"%s/subprojects/gst-devtools/validate/data/scenarios" % SCRIPTDIR),
options.sysroot)
env["GST_VALIDATE_PLUGIN_PATH"] = os.path.normpath(
"%s/subprojects/gst-devtools/validate/plugins" % options.builddir)
options)
set_env_var(env, "GST_VALIDATE_PLUGIN_PATH", os.path.normpath(
"%s/subprojects/gst-devtools/validate/plugins" % options.builddir),
options)
prepend_env_var(env, "GST_VALIDATE_APPS_DIR", os.path.normpath(
"%s/subprojects/gst-editing-services/tests/validate" % SCRIPTDIR),
options.sysroot)
env["GST_ENV"] = gst_version
env["GST_REGISTRY"] = os.path.normpath(options.builddir + "/registry.dat")
options)
set_env_var(env, "GST_ENV", gst_version, options)
set_env_var(env, "GST_REGISTRY", os.path.normpath(options.builddir + "/registry.dat"), options)
prepend_env_var(env, "PATH", os.path.normpath(
"%s/subprojects/gst-devtools/validate/tools" % options.builddir),
options.sysroot)
options)
prepend_env_var(env, "GST_VALIDATE_SCENARIOS_PATH", os.path.normpath(
"%s/subprojects/gst-examples/webrtc/check/validate/scenarios" %
SCRIPTDIR), options.sysroot)
SCRIPTDIR), options)
prepend_env_var(env, "GST_VALIDATE_APPS_DIR", os.path.normpath(
"%s/subprojects/gst-examples/webrtc/check/validate/apps" %
SCRIPTDIR), options.sysroot)
env["GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH"] = os.path.normpath(
SCRIPTDIR), options)
set_env_var(env, "GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH", os.path.normpath(
"%s/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py" %
SCRIPTDIR)
SCRIPTDIR), options)
if options.wine:
return get_wine_subprocess_env(options, env)
prepend_env_var(env, "PATH", os.path.join(SCRIPTDIR, 'meson'),
options.sysroot)
options)
env["GST_PLUGIN_SYSTEM_PATH"] = ""
env["GST_PLUGIN_SCANNER"] = os.path.normpath(
"%s/subprojects/gstreamer/libs/gst/helpers/gst-plugin-scanner" % options.builddir)
env["GST_PTP_HELPER"] = os.path.normpath(
"%s/subprojects/gstreamer/libs/gst/helpers/ptp/gst-ptp-helper" % options.builddir)
set_env_var(env, "GST_PLUGIN_SYSTEM_PATH", "", options)
set_env_var(env, "GST_PLUGIN_SCANNER", os.path.normpath(
"%s/subprojects/gstreamer/libs/gst/helpers/gst-plugin-scanner" % options.builddir),
options)
set_env_var(env, "GST_PTP_HELPER", os.path.normpath(
"%s/subprojects/gstreamer/libs/gst/helpers/ptp/gst-ptp-helper" % options.builddir),
options)
if os.name == 'nt':
lib_path_envvar = 'PATH'
@ -311,47 +322,47 @@ def get_subprocess_env(options, gst_version):
prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(SCRIPTDIR, 'subprojects',
'gst-python', 'plugin'),
options.sysroot)
options)
prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(PREFIX_DIR, 'lib',
'gstreamer-1.0'),
options.sysroot)
options)
prepend_env_var(env, "GST_PLUGIN_PATH", os.path.join(options.builddir, 'subprojects',
'libnice', 'gst'),
options.sysroot)
options)
prepend_env_var(env, "GST_VALIDATE_SCENARIOS_PATH",
os.path.join(PREFIX_DIR, 'share', 'gstreamer-1.0',
'validate', 'scenarios'),
options.sysroot)
options)
prepend_env_var(env, "GI_TYPELIB_PATH", os.path.join(PREFIX_DIR, 'lib',
'lib', 'girepository-1.0'),
options.sysroot)
options)
prepend_env_var(env, "PKG_CONFIG_PATH", os.path.join(PREFIX_DIR, 'lib', 'pkgconfig'),
options.sysroot)
options)
# tools: gst-launch-1.0, gst-inspect-1.0
prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
'gstreamer', 'tools'),
options.sysroot)
options)
# plugin scanner and generator
prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
'gstreamer', 'docs'),
options.sysroot)
options)
prepend_env_var(env, "PATH", os.path.join(options.builddir, 'subprojects',
'gst-plugins-base', 'tools'),
options.sysroot)
options)
# Library and binary search paths
prepend_env_var(env, "PATH", os.path.join(PREFIX_DIR, 'bin'),
options.sysroot)
options)
if lib_path_envvar != 'PATH':
prepend_env_var(env, lib_path_envvar, os.path.join(PREFIX_DIR, 'lib'),
options.sysroot)
options)
prepend_env_var(env, lib_path_envvar, os.path.join(PREFIX_DIR, 'lib64'),
options.sysroot)
options)
elif 'QMAKE' in os.environ:
# There's no RPATH on Windows, so we need to set PATH for the qt5 DLLs
prepend_env_var(env, 'PATH', os.path.dirname(os.environ['QMAKE']),
options.sysroot)
options)
meson = get_meson()
targets_s = subprocess.check_output(meson + ['introspect', options.builddir, '--targets'])
@ -385,17 +396,17 @@ def get_subprocess_env(options, gst_version):
if TYPELIB_REG.search(filename):
prepend_env_var(env, "GI_TYPELIB_PATH",
os.path.join(options.builddir, root),
options.sysroot)
options)
elif is_library_target_and_not_plugin(target, filename):
prepend_env_var(env, lib_path_envvar,
os.path.join(options.builddir, root),
options.sysroot)
options)
elif is_binary_target_and_in_path(target, filename, bindir):
paths.add(os.path.join(options.builddir, root))
elif is_gio_module(target, filename, options.builddir):
prepend_env_var(env, 'GIO_EXTRA_MODULES',
os.path.join(options.builddir, root),
options.sysroot)
options)
# Search for the Plugin paths file either in the build directory root
# or check if gstreamer is a subproject of another project
@ -405,16 +416,16 @@ def get_subprocess_env(options, gst_version):
with open(plugin_paths) as f:
for plugin_path in json.load(f):
prepend_env_var(env, 'GST_PLUGIN_PATH', plugin_path,
options.sysroot)
options)
break
# Sort to iterate in a consistent order (`set`s and `hash`es are randomized)
for p in sorted(paths):
prepend_env_var(env, 'PATH', p, options.sysroot)
prepend_env_var(env, 'PATH', p, options)
if os.name != 'nt':
for p in sorted(mono_paths):
prepend_env_var(env, "MONO_PATH", p, options.sysroot)
prepend_env_var(env, "MONO_PATH", p, options)
presets = set()
encoding_targets = set()
@ -456,48 +467,48 @@ def get_subprocess_env(options, gst_version):
os.path.abspath(os.path.join(os.path.dirname(path), '..')))
for p in sorted(presets):
prepend_env_var(env, 'GST_PRESET_PATH', p, options.sysroot)
prepend_env_var(env, 'GST_PRESET_PATH', p, options)
for t in sorted(encoding_targets):
prepend_env_var(env, 'GST_ENCODING_TARGET_PATH', t, options.sysroot)
prepend_env_var(env, 'GST_ENCODING_TARGET_PATH', t, options)
# Check if meson has generated -uninstalled pkgconfig files
meson_uninstalled = pathlib.Path(options.builddir) / 'meson-uninstalled'
if meson_uninstalled.is_dir():
prepend_env_var(env, 'PKG_CONFIG_PATH', str(meson_uninstalled), options.sysroot)
prepend_env_var(env, 'PKG_CONFIG_PATH', str(meson_uninstalled), options)
for python_dir in sorted(python_dirs):
prepend_env_var(env, 'PYTHONPATH', python_dir, options.sysroot)
prepend_env_var(env, 'PYTHONPATH', python_dir, options)
for python_dir in sorted(overrides_dirs):
prepend_env_var(env, '_GI_OVERRIDES_PATH', python_dir, options.sysroot)
prepend_env_var(env, '_GI_OVERRIDES_PATH', python_dir, options)
mesonpath = os.path.join(SCRIPTDIR, "meson")
if os.path.join(mesonpath):
# Add meson/ into PYTHONPATH if we are using a local meson
prepend_env_var(env, 'PYTHONPATH', mesonpath, options.sysroot)
prepend_env_var(env, 'PYTHONPATH', mesonpath, options)
# Ensure that gst-python/gi is used first
prepend_env_var(env, "PYTHONPATH", os.path.join(SCRIPTDIR, 'subprojects', 'gst-python'),
options.sysroot)
options)
# For devhelp books
if 'XDG_DATA_DIRS' not in env or not env['XDG_DATA_DIRS']:
# Preserve default paths when empty
prepend_env_var(env, 'XDG_DATA_DIRS', '/usr/local/share/:/usr/share/', '')
prepend_env_var(env, 'XDG_DATA_DIRS', '/usr/local/share/:/usr/share/', options)
prepend_env_var(env, 'XDG_DATA_DIRS', os.path.join(options.builddir,
'subprojects',
'gst-docs',
'GStreamer-doc'),
options.sysroot)
options)
if 'XDG_CONFIG_DIRS' not in env or not env['XDG_CONFIG_DIRS']:
# Preserve default paths when empty
prepend_env_var(env, 'XDG_CONFIG_DIRS', '/etc/local/xdg:/etc/xdg', '')
prepend_env_var(env, 'XDG_CONFIG_DIRS', '/etc/local/xdg:/etc/xdg', options)
prepend_env_var(env, "XDG_CONFIG_DIRS", os.path.join(PREFIX_DIR, 'etc', 'xdg'),
options.sysroot)
options)
return env
@ -630,10 +641,10 @@ if __name__ == "__main__":
shutil.copyfileobj(src, tmprc)
tmprc.write('\n' + prompt_export)
tmprc.flush()
env['ZDOTDIR'] = tmpdir.name
set_env_var(env, 'ZDOTDIR', tmpdir.name, options)
try:
if options.only_environment:
for name, value in env.items():
for name, value in UPDATED_ENV.items():
print('{}={}'.format(name, shlex.quote(value)))
print('export {}'.format(name))
if prompt_export: