Skip to content
Snippets Groups Projects
Commit d797a28b authored by Simon McVittie's avatar Simon McVittie
Browse files

Merge branch 'wip/smcv/dynamic-sdl' into 'main'

Make it easier for users on non-Debian-derived distros to set SDL_DYNAMIC_API

See merge request !766
parents 9d15225e 66b95a6a
Branches
Tags
1 merge request!766Make it easier for users on non-Debian-derived distros to set SDL_DYNAMIC_API
......@@ -298,6 +298,24 @@ Some environment variables are set by Steam, including:
game's metadata, affecting the functioning of various compatibility
tools:
* `runtime-sdl2`: Use the [`$SDL_DYNAMIC_API` mechanism][SDL_DYNAMIC_API]
to load the version of SDL 2 included in
Steam Linux Runtime 2 (soldier) or later,
in preference to a bundled or statically linked SDL 2 that might be
included in the game itself.
For example, this can be used to ensure that older Source-engine
titles like [Fistful of Frags][]
runs with a version of SDL that supports game controller hotplug
inside a container, even though they include a bundled version
that does not.
Setting `STEAM_COMPAT_RUNTIME_SDL2=1` is equivalent to this,
and is more convenient to use in Launch Options, by setting the
Launch Options to `STEAM_COMPAT_RUNTIME_SDL2=1 %command%`.
* `runtime-sdl3`: Same as `runtime-sdl2`, but for SDL 3 (if available),
using [`$SDL3_DYNAMIC_API`][SDL3_DYNAMIC_API].
Setting `STEAM_COMPAT_RUNTIME_SDL3=1` is equivalent to this.
* `search-cwd`: Instructs the legacy `LD_LIBRARY_PATH` runtime to
append the `${STEAM_COMPAT_INSTALL_PATH}` to the `LD_LIBRARY_PATH`,
for backward compatibility with older games that might rely on this
......@@ -368,6 +386,20 @@ Some environment variables are set by Steam, including:
Colon-delimited list of paths to additional directories that are to be
made available read/write in a pressure-vessel container.
* `STEAM_COMPAT_RUNTIME_SDL2`:
May be set to 1 for the same effect as adding `runtime-sdl2` to
`$STEAM_COMPAT_FLAGS`.
(For example, a game's Launch Options can be set to
`STEAM_COMPAT_RUNTIME_SDL2=1 %command%`)
* `STEAM_COMPAT_RUNTIME_SDL3`:
May be set to 1 for the same effect as adding `runtime-sdl3` to
`$STEAM_COMPAT_FLAGS`.
(For example, a game's Launch Options can be set to
`STEAM_COMPAT_RUNTIME_SDL3=1 %command%`)
* `STEAM_COMPAT_SESSION_ID`:
In the historical session mode (see below), this is used to link together
......@@ -611,6 +643,8 @@ unless this is set to the game's top-level directory.
<!-- References: -->
[SDL_DYNAMIC_API]: https://github.com/libsdl-org/SDL/blob/SDL2/docs/README-dynapi.md
[SDL3_DYNAMIC_API]: https://github.com/libsdl-org/SDL/blob/main/docs/README-dynapi.md
[Steamworks launch options]: https://partner.steamgames.com/doc/sdk/uploading
[VDF]: https://developer.valvesoftware.com/wiki/KeyValues
[app891390-info]: https://steamdb.info/app/891390/info/
......
/*
* Copyright © 2024 Collabora Ltd.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "adverb-sdl.h"
gboolean
pv_adverb_set_up_dynamic_sdl (FlatpakBwrap *wrapped_command,
PvPerArchDirs *lib_temp_dirs,
const char *prefix,
const char *overrides,
const char *dynamic_var,
const char *soname,
GError **error)
{
gboolean have_any = FALSE;
size_t abi_index;
const char *existing_value;
existing_value = g_environ_getenv (wrapped_command->envp, dynamic_var);
if (existing_value != NULL)
{
/* Treat SDL{,3}_DYNAMIC_API from e.g. launch options as being
* more important than STEAM_COMPAT_FLAGS=runtime-sdl{2,3} */
g_message ("Not using %s from runtime because %s is already set to \"%s\"",
soname, dynamic_var, existing_value);
return TRUE;
}
if (lib_temp_dirs == NULL)
return glnx_throw (error,
"Cannot set up dynamic %s without per-architecture directories",
soname);
for (abi_index = 0; abi_index < PV_N_SUPPORTED_ARCHITECTURES; abi_index++)
{
const char *multiarch_tuple = pv_multiarch_tuples[abi_index];
g_autofree gchar *dest = NULL;
g_autofree gchar *from_runtime = NULL;
g_autofree gchar *override = NULL;
const char *target;
/* We assume a Debian multiarch layout here: in practice this
* is true for all Steam Runtime branches. */
from_runtime = g_build_filename (prefix, "lib", multiarch_tuple, soname, NULL);
override = g_build_filename (overrides, "lib", multiarch_tuple, soname, NULL);
if (g_file_test (override, G_FILE_TEST_EXISTS))
{
/* This is quite unexpected - we hope that none of the
* graphics drivers and Vulkan layers will have pulled in
* something as big as SDL, because if they do, that really
* undermines what we're trying to achieve. But if something
* in the graphics stack does depend on SDL, we really have
* no choice but to use that version. */
g_message ("Using %s %s from graphics stack provider instead of runtime",
multiarch_tuple, soname);
target = override;
}
else if (g_file_test (from_runtime, G_FILE_TEST_EXISTS))
{
target = from_runtime;
}
else
{
g_info ("%s doesn't exist in container", from_runtime);
continue;
}
dest = g_build_filename (lib_temp_dirs->abi_paths[abi_index],
soname, NULL);
g_info ("Creating symlink \"%s\" -> \"%s\" in container",
dest, target);
if (symlink (target, dest) != 0)
return glnx_throw_errno_prefix (error,
"While creating symlink \"%s\"",
dest);
have_any = TRUE;
}
if (have_any)
{
g_autofree gchar *value = NULL;
value = g_build_filename (lib_temp_dirs->libdl_token_path, soname, NULL);
g_info ("Setting %s=\"%s\" to use runtime's SDL", dynamic_var, soname);
flatpak_bwrap_set_env (wrapped_command, dynamic_var, value, TRUE);
}
else
{
return glnx_throw (error,
"Unable to set %s: %s wasn't available for any architecture",
dynamic_var, soname);
}
return TRUE;
}
void
pv_adverb_set_up_dynamic_sdls (FlatpakBwrap *wrapped_command,
PvPerArchDirs *lib_temp_dirs,
const char *prefix,
const char *overrides,
SrtSteamCompatFlags compat_flags)
{
static const struct
{
const char *dynamic_var;
const char *soname;
SrtSteamCompatFlags if_flag;
}
sdls[] =
{
{
"SDL_DYNAMIC_API",
"libSDL2-2.0.so.0",
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2
},
{
"SDL3_DYNAMIC_API",
"libSDL3.so.0",
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3
},
};
size_t i;
for (i = 0; i < G_N_ELEMENTS (sdls); i++)
{
if (compat_flags & sdls[i].if_flag)
{
g_autoptr(GError) local_error = NULL;
if (!pv_adverb_set_up_dynamic_sdl (wrapped_command,
lib_temp_dirs,
prefix,
overrides,
sdls[i].dynamic_var,
sdls[i].soname,
&local_error))
g_warning ("%s", local_error->message);
}
}
}
/*
* Copyright © 2024 Collabora Ltd.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "libglnx.h"
#include <glib.h>
#include "steam-runtime-tools/steam-internal.h"
#include "flatpak-bwrap-private.h"
#include "per-arch-dirs.h"
gboolean pv_adverb_set_up_dynamic_sdl (FlatpakBwrap *wrapped_command,
PvPerArchDirs *lib_temp_dirs,
const char *prefix,
const char *overrides,
const char *dynamic_var,
const char *soname,
GError **error);
void pv_adverb_set_up_dynamic_sdls (FlatpakBwrap *wrapped_command,
PvPerArchDirs *lib_temp_dirs,
const char *prefix,
const char *overrides,
SrtSteamCompatFlags compat_flags);
......@@ -46,6 +46,7 @@
#include "libglnx.h"
#include "adverb-preload.h"
#include "adverb-sdl.h"
#include "flatpak-utils-base-private.h"
#include "per-arch-dirs.h"
#include "supported-architectures.h"
......@@ -849,6 +850,7 @@ main (int argc,
glnx_autofd int original_stderr = -1;
g_autoptr(FlatpakBwrap) wrapped_command = NULL;
g_autoptr(PvPerArchDirs) lib_temp_dirs = NULL;
SrtSteamCompatFlags compat_flags;
setlocale (LC_ALL, "");
......@@ -1086,6 +1088,10 @@ main (int argc,
g_clear_error (error);
}
compat_flags = _srt_steam_get_compat_flags (_srt_const_strv (envp));
pv_adverb_set_up_dynamic_sdls (wrapped_command, lib_temp_dirs,
"/usr", opt_overrides, compat_flags);
if (opt_overrides != NULL
&& !pv_adverb_set_up_overrides (wrapped_command,
lib_temp_dirs,
......
......@@ -108,6 +108,8 @@ pressure_vessel_adverb_lib = static_library(
sources : [
'adverb-preload.c',
'adverb-preload.h',
'adverb-sdl.c',
'adverb-sdl.h',
'per-arch-dirs.c',
'per-arch-dirs.h',
'wrap-interactive.c',
......
......@@ -78,6 +78,8 @@ typedef enum
SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD = (1 << 0),
SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD_FIRST = (1 << 1),
SRT_STEAM_COMPAT_FLAGS_SYSTEM_TRACING = (1 << 2),
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2 = (1 << 3),
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3 = (1 << 4),
SRT_STEAM_COMPAT_FLAGS_NONE = 0
} SrtSteamCompatFlags;
......
......@@ -751,21 +751,37 @@ _srt_steam_check (const char * const *envp,
SrtSteamCompatFlags
_srt_steam_get_compat_flags (const char * const *envp)
{
static const struct
{
const char *name;
SrtSteamCompatFlags value;
gboolean def;
}
bool_vars[] =
{
{ "STEAM_COMPAT_TRACING", SRT_STEAM_COMPAT_FLAGS_SYSTEM_TRACING, FALSE },
{ "STEAM_COMPAT_RUNTIME_SDL2", SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2, FALSE },
{ "STEAM_COMPAT_RUNTIME_SDL3", SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3, FALSE },
};
SrtSteamCompatFlags ret = SRT_STEAM_COMPAT_FLAGS_NONE;
const char *value;
gboolean tracing = FALSE;
size_t i;
_srt_environ_get_boolean (envp, "STEAM_COMPAT_TRACING", &tracing, NULL);
for (i = 0; i < G_N_ELEMENTS (bool_vars); i++)
{
gboolean bool_value = bool_vars[i].def;
if (tracing)
ret |= SRT_STEAM_COMPAT_FLAGS_SYSTEM_TRACING;
_srt_environ_get_boolean (envp, bool_vars[i].name, &bool_value, NULL);
if (bool_value)
ret |= bool_vars[i].value;
}
value = _srt_environ_getenv (envp, "STEAM_COMPAT_FLAGS");
if (value != NULL)
{
g_auto(GStrv) tokens = NULL;
size_t i;
tokens = g_strsplit (value, ",", 0);
......@@ -773,6 +789,14 @@ _srt_steam_get_compat_flags (const char * const *envp)
{
switch (tokens[i][0])
{
case 'r':
if (g_str_equal (tokens[i], "runtime-sdl2"))
ret |= SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2;
else if (g_str_equal (tokens[i], "runtime-sdl3"))
ret |= SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3;
break;
case 's':
if (g_str_equal (tokens[i], "search-cwd"))
ret |= SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD;
......
/*
* Copyright © 2023-2024 Collabora Ltd.
* SPDX-License-Identifier: MIT
*/
#include <glib.h>
#include "steam-runtime-tools/glib-backports-internal.h"
#include "steam-runtime-tools/utils-internal.h"
#include "libglnx.h"
#include "adverb-sdl.h"
#include "tests/test-utils.h"
#define SDL_DYNAMIC_API "SDL_DYNAMIC_API"
#define SDL2_SONAME "libSDL2-2.0.so.0"
#define SDL3_DYNAMIC_API "SDL3_DYNAMIC_API"
#define SDL3_SONAME "libSDL3.so.0"
typedef struct
{
TestsOpenFdSet old_fds;
FlatpakBwrap *bwrap;
PvPerArchDirs *lib_temp_dirs;
GError *lib_temp_dirs_error;
GLnxTmpDir mock_prefix;
GLnxTmpDir mock_overrides;
} Fixture;
typedef struct
{
int unused;
} Config;
static void
setup (Fixture *f,
gconstpointer context)
{
G_GNUC_UNUSED const Config *config = context;
g_autoptr(GError) local_error = NULL;
gsize i;
f->old_fds = tests_check_fd_leaks_enter ();
f->bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
f->lib_temp_dirs = pv_per_arch_dirs_new (&f->lib_temp_dirs_error);
if (f->lib_temp_dirs != NULL)
{
g_test_message ("Cross-platform module prefix: %s",
f->lib_temp_dirs->libdl_token_path);
for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++)
g_test_message ("Concrete path for %s architecture: %s",
pv_multiarch_tuples[i],
f->lib_temp_dirs->abi_paths[i]);
}
glnx_mkdtemp ("usr-XXXXXX", 0700, &f->mock_prefix, &local_error);
g_assert_no_error (local_error);
glnx_mkdtemp ("overrides-XXXXXX", 0700, &f->mock_overrides, &local_error);
g_assert_no_error (local_error);
}
static void
teardown (Fixture *f,
gconstpointer context)
{
G_GNUC_UNUSED const Config *config = context;
g_clear_pointer (&f->lib_temp_dirs, pv_per_arch_dirs_free);
g_clear_error (&f->lib_temp_dirs_error);
g_clear_pointer (&f->bwrap, flatpak_bwrap_free);
glnx_tmpdir_cleanup (&f->mock_prefix);
glnx_tmpdir_cleanup (&f->mock_overrides);
tests_check_fd_leaks_leave (f->old_fds);
}
static void touch (gchar **, const char *, ...) G_GNUC_NULL_TERMINATED;
static void
touch (gchar **path_out,
const char *path,
...)
{
g_autoptr(GError) local_error = NULL;
g_autofree gchar *joined = NULL;
g_autofree gchar *parent = NULL;
va_list ap;
va_start (ap, path);
joined = g_build_filename_valist (path, &ap);
va_end (ap);
parent = g_path_get_dirname (joined);
glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, NULL, &local_error);
g_assert_no_error (local_error);
glnx_file_replace_contents_at (AT_FDCWD, joined, (const guint8 *) "", 0,
(GLNX_FILE_REPLACE_NODATASYNC
| GLNX_FILE_REPLACE_INCREASING_MTIME),
NULL, &local_error);
g_assert_no_error (local_error);
if (path_out != NULL)
*path_out = g_steal_pointer (&joined);
}
static void
assert_symlink (const char *arch_dir,
const char *soname,
const char *expect_target,
GIOErrorEnum expect_error)
{
g_autoptr(GError) local_error = NULL;
g_autofree gchar *joined = NULL;
g_autofree gchar *target = NULL;
joined = g_build_filename (arch_dir, soname, NULL);
target = glnx_readlinkat_malloc (AT_FDCWD, joined, NULL, &local_error);
if (target != NULL)
g_test_message ("%s -> %s", joined, target);
else
g_test_message ("%s doesn't exist", joined);
if (expect_target != NULL)
{
g_assert_no_error (local_error);
g_assert_cmpstr (target, ==, expect_target);
}
else
{
g_assert_error (local_error, G_IO_ERROR, expect_error);
g_assert_null (target);
}
}
static void
assert_env (gchar **envp,
const char *var,
const char *expected_dir,
const char *expected_file)
{
g_autofree gchar *expected = NULL;
const char *actual;
if (expected_dir != NULL)
expected = g_build_filename (expected_dir, expected_file, NULL);
actual = g_environ_getenv (envp, var);
if (actual != NULL)
g_test_message ("%s=%s", var, actual);
else
g_test_message ("$%s is unset", var);
g_assert_cmpstr (actual, ==, expected);
}
static void
test_basic (Fixture *f,
gconstpointer context)
{
g_autofree gchar *sdl2_target = NULL;
g_autofree gchar *sdl3_target = NULL;
if (f->lib_temp_dirs == NULL)
{
g_test_skip (f->lib_temp_dirs_error->message);
return;
}
g_test_summary ("Basic setup of " SDL_DYNAMIC_API);
touch (&sdl2_target,
f->mock_prefix.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
touch (&sdl3_target,
f->mock_prefix.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL3_SONAME, NULL);
g_test_message ("With no flags, not setup is done...");
pv_adverb_set_up_dynamic_sdls (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SRT_STEAM_COMPAT_FLAGS_NONE);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL, G_IO_ERROR_NOT_FOUND);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL3_SONAME, NULL, G_IO_ERROR_NOT_FOUND);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API, NULL, NULL);
assert_env (f->bwrap->envp, SDL3_DYNAMIC_API, NULL, NULL);
g_test_message ("SDL2 flag sets up SDL2...");
pv_adverb_set_up_dynamic_sdls (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, sdl2_target, 0);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL3_SONAME, NULL, G_IO_ERROR_NOT_FOUND);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API,
f->lib_temp_dirs->libdl_token_path, SDL2_SONAME);
assert_env (f->bwrap->envp, SDL3_DYNAMIC_API, NULL, NULL);
g_test_message ("SDL3 flag additionally sets up SDL3...");
pv_adverb_set_up_dynamic_sdls (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, sdl2_target, 0);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL3_SONAME, sdl3_target, 0);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API,
f->lib_temp_dirs->libdl_token_path, SDL2_SONAME);
assert_env (f->bwrap->envp, SDL3_DYNAMIC_API,
f->lib_temp_dirs->libdl_token_path, SDL3_SONAME);
}
static void
test_cannot_symlink (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) local_error = NULL;
g_autofree gchar *sdl2_target = NULL;
gboolean res;
if (f->lib_temp_dirs == NULL)
{
g_test_skip (f->lib_temp_dirs_error->message);
return;
}
touch (&sdl2_target,
f->mock_prefix.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
touch (NULL,
f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
g_test_summary ("If we can't create the symlink, setup fails");
res = pv_adverb_set_up_dynamic_sdl (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SDL_DYNAMIC_API,
SDL2_SONAME,
&local_error);
g_assert_nonnull (local_error);
/* pv-adverb would log the GError as a warning */
g_test_message ("%s", local_error->message);
g_assert_false (res);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL, G_IO_ERROR_INVALID_ARGUMENT);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API, NULL, NULL);
}
static void
test_impossible (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) local_error = NULL;
g_autofree gchar *sdl2_target = NULL;
gboolean res;
touch (&sdl2_target,
f->mock_prefix.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
g_test_summary ("If we don't know the $LIB or $PLATFORM, nothing happens, "
"with a warning");
res = pv_adverb_set_up_dynamic_sdl (f->bwrap,
NULL,
f->mock_prefix.path,
f->mock_overrides.path,
SDL_DYNAMIC_API,
SDL2_SONAME,
&local_error);
g_assert_nonnull (local_error);
/* pv-adverb would log the GError as a warning */
g_test_message ("%s", local_error->message);
g_assert_false (res);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL, G_IO_ERROR_NOT_FOUND);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API, NULL, NULL);
}
static void
test_in_gfx_stack (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) local_error = NULL;
g_autofree gchar *sdl2_target = NULL;
g_autofree gchar *gfx_sdl2_target = NULL;
gboolean res;
if (f->lib_temp_dirs == NULL)
{
g_test_skip (f->lib_temp_dirs_error->message);
return;
}
touch (&sdl2_target,
f->mock_prefix.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
touch (&gfx_sdl2_target,
f->mock_overrides.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
g_test_summary ("We prefer SDL from the graphics provider if present");
res = pv_adverb_set_up_dynamic_sdl (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SDL_DYNAMIC_API,
SDL2_SONAME,
&local_error);
g_assert_no_error (local_error);
g_assert_true (res);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, gfx_sdl2_target, 0);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API,
f->lib_temp_dirs->libdl_token_path, SDL2_SONAME);
}
static void
test_in_gfx_stack_only (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) local_error = NULL;
g_autofree gchar *gfx_sdl2_target = NULL;
gboolean res;
if (f->lib_temp_dirs == NULL)
{
g_test_skip (f->lib_temp_dirs_error->message);
return;
}
touch (&gfx_sdl2_target,
f->mock_overrides.path, "lib", pv_multiarch_tuples[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL);
g_test_summary ("We use SDL from the graphics provider if necessary");
res = pv_adverb_set_up_dynamic_sdl (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SDL_DYNAMIC_API,
SDL2_SONAME,
&local_error);
g_assert_no_error (local_error);
g_assert_true (res);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, gfx_sdl2_target, 0);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API,
f->lib_temp_dirs->libdl_token_path, SDL2_SONAME);
}
static void
test_missing (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) local_error = NULL;
gboolean res;
if (f->lib_temp_dirs == NULL)
{
g_test_skip (f->lib_temp_dirs_error->message);
return;
}
g_test_summary ("If SDL is missing, nothing happens, with a warning");
res = pv_adverb_set_up_dynamic_sdl (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SDL_DYNAMIC_API,
SDL2_SONAME,
&local_error);
g_assert_nonnull (local_error);
/* pv-adverb would log the GError as a warning */
g_test_message ("%s", local_error->message);
g_assert_false (res);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL, G_IO_ERROR_NOT_FOUND);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API, NULL, NULL);
}
static void
test_overridden (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) local_error = NULL;
gboolean res;
if (f->lib_temp_dirs == NULL)
{
g_test_skip (f->lib_temp_dirs_error->message);
return;
}
g_test_summary ("Setting " SDL_DYNAMIC_API " takes precedence");
flatpak_bwrap_set_env (f->bwrap, SDL_DYNAMIC_API, "/whatever", TRUE);
res = pv_adverb_set_up_dynamic_sdl (f->bwrap,
f->lib_temp_dirs,
f->mock_prefix.path,
f->mock_overrides.path,
SDL_DYNAMIC_API,
SDL2_SONAME,
&local_error);
g_assert_no_error (local_error);
g_assert_true (res);
assert_symlink (f->lib_temp_dirs->abi_paths[PV_PRIMARY_ARCHITECTURE],
SDL2_SONAME, NULL, G_IO_ERROR_NOT_FOUND);
assert_env (f->bwrap->envp, SDL_DYNAMIC_API, "/whatever", NULL);
}
int
main (int argc,
char **argv)
{
_srt_setenv_disable_gio_modules ();
/* In unit tests it isn't always straightforward to find the real
* ${PLATFORM}, so use a predictable mock implementation that always
* uses PvMultiarchDetails.platforms[0] */
g_setenv ("PRESSURE_VESSEL_TEST_STANDARDIZE_PLATFORM", "1", TRUE);
_srt_tests_init (&argc, &argv, NULL);
g_test_add ("/pv-adverb/sdl/basic", Fixture, NULL,
setup, test_basic, teardown);
g_test_add ("/pv-adverb/sdl/cannot-symlink", Fixture, NULL,
setup, test_cannot_symlink, teardown);
g_test_add ("/pv-adverb/sdl/impossible", Fixture, NULL,
setup, test_impossible, teardown);
g_test_add ("/pv-adverb/sdl/in_gfx_stack", Fixture, NULL,
setup, test_in_gfx_stack, teardown);
g_test_add ("/pv-adverb/sdl/in_gfx_stack_only", Fixture, NULL,
setup, test_in_gfx_stack_only, teardown);
g_test_add ("/pv-adverb/sdl/missing", Fixture, NULL,
setup, test_missing, teardown);
g_test_add ("/pv-adverb/sdl/overridden", Fixture, NULL,
setup, test_overridden, teardown);
return g_test_run ();
}
......@@ -68,6 +68,7 @@ tests = [
compiled_tests = [
{'name': 'adverb-preload', 'adverb': true},
{'name': 'adverb-sdl', 'adverb': true},
{'name': 'bwrap', 'wrap': true},
{'name': 'graphics-provider', 'wrap': true},
{'name': 'wrap-setup', 'wrap': true},
......
......@@ -129,18 +129,53 @@ test_compat_flags (Fixture *f,
{
static const struct
{
const char *env;
/* Length is arbitrary, expand as needed but leave room for NULL
* termination */
const char * const envp[3];
SrtSteamCompatFlags expected;
}
tests[] =
{
{ "search-cwd,search-cwd-first,reticulate-splines,fixme",
(SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD
| SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD_FIRST) },
{ "reticulate-splines,search-cwd", SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD },
{ ",,,,search-cwd-first,,,,", SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD_FIRST },
{ "", SRT_STEAM_COMPAT_FLAGS_NONE },
{ NULL, SRT_STEAM_COMPAT_FLAGS_NONE }
{
{
"STEAM_COMPAT_FLAGS=search-cwd,search-cwd-first,reticulate-splines,fixme",
NULL
},
(SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD
| SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD_FIRST),
},
{
{ "STEAM_COMPAT_FLAGS=reticulate-splines,search-cwd", NULL },
SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD,
},
{
{ "STEAM_COMPAT_FLAGS=,,,,search-cwd-first,,,,", NULL },
SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD_FIRST,
},
{
{ "STEAM_COMPAT_FLAGS=runtime-sdl2", NULL },
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2,
},
{
{ "STEAM_COMPAT_FLAGS=runtime-sdl3", NULL },
SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3,
},
{
{
"STEAM_COMPAT_TRACING=1",
"STEAM_COMPAT_FLAGS=search-cwd",
NULL
},
(SRT_STEAM_COMPAT_FLAGS_SEARCH_CWD
| SRT_STEAM_COMPAT_FLAGS_SYSTEM_TRACING),
},
{ { "STEAM_COMPAT_FLAGS=", NULL }, SRT_STEAM_COMPAT_FLAGS_NONE },
{ { "STEAM_COMPAT_TRACING=1", NULL }, SRT_STEAM_COMPAT_FLAGS_SYSTEM_TRACING },
{ { "STEAM_COMPAT_TRACING=", NULL }, SRT_STEAM_COMPAT_FLAGS_NONE },
{ { "STEAM_COMPAT_TRACING=0", NULL }, SRT_STEAM_COMPAT_FLAGS_NONE },
{ { "STEAM_COMPAT_RUNTIME_SDL2=1", NULL }, SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL2 },
{ { "STEAM_COMPAT_RUNTIME_SDL3=1", NULL }, SRT_STEAM_COMPAT_FLAGS_RUNTIME_SDL3 },
{ { NULL }, SRT_STEAM_COMPAT_FLAGS_NONE }
};
size_t i;
......@@ -149,14 +184,10 @@ test_compat_flags (Fixture *f,
for (i = 0; i < G_N_ELEMENTS (tests); i++)
{
gchar *envp[2] = { NULL, NULL };
if (tests[i].env != NULL)
envp[0] = g_strdup_printf ("STEAM_COMPAT_FLAGS=%s", tests[i].env);
g_assert_cmphex (_srt_steam_get_compat_flags (_srt_const_strv (envp)),
g_assert_cmpuint (g_strv_length ((gchar **) tests[i].envp),
<, G_N_ELEMENTS (tests[i].envp));
g_assert_cmphex (_srt_steam_get_compat_flags (tests[i].envp),
==, tests[i].expected);
g_free (envp[0]);
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment