I am creating a game using C and some libraries. I wish to not require my users to download anything but the game as most games are self contained. What is the usual process for compiling and installing dependencies such as glfw or SDL2 locally on Linux?
3 Answers
Standalone game
For Linux, SDL suggests to download the source code, compile it, and include the relevant files as part of your game.
The main information can be found on the official site SDL2 - Installation, but here's the most relevant information:
- For the most part, you only need to deliver a
.so
file for each library you are using (for Linux). So you'd needlibSDL2.so
to be part of the project you deliver. - SDL offers some pre-compiled packages, but I find often that doesn't include Linux, they also suggest you can compile the project from scratch, so you can enable/disable functionality that you want included.
- You can compile the library statically (so it's included in your executable and you won't need a
.so
file), but the makers of SDL advice against that. The main reason is, if you publish a game with a version of SDL, it would be possible for a user to update SDL without touching your game, and everything should just work.
To compile SDL2 (based on the link above, with minor variations) you can use the following commands:
git clone https://github.com/libsdl-org/SDL.git -b SDL2
cd SDL
mkdir build
cd build
../configure --prefix=my_sdl_directory
make
make install
This will compile the project and install it inside build/my_sdl_directory/
. Inside that directory, you should be able to locate the .so
file and also other files useful for development (like the header files). You can the make a project, compile against that library, and ship the .so
file with it.
You can then allow users to download your project, and run it straight away without them needing to install SDL2 themselves.
Linux Distros
It's worth mentioning if you plan to publish your game on official Linux Distros, you can pretty much declare SDL2 as a dependency, and the package manager of each distro will be able to deal with it.
The instructions are different for each distro, and more likely out of scope for this answer.
-
2\$\begingroup\$ This makes things look way easier than they are. You have to install dependencies for SDL2 before compiling it. You often can't reuse SDL2 from the system package managers disable some features, like dynamic .so discovery on Ubuntu. You have to build on the oldest linux you want to support, because there are some system libraries you can't bring with you, and you don't want to depend on new versions of them. You have to ship other dependencies in addition to SDL2. \$\endgroup\$ Commented Jul 6, 2023 at 6:19
AppImage
A possible cross-distro solution is to use AppImage format, which allows to easily bundle all your resources and dependencies into a single directly executable file needing no installation.
While it's a good idea to consult the official guide for packaging native binaries, the overall process can be summarized as follows:
- Install dependencies using package manager of distro you're using as a build machine (e.g. apt), including header files.
- Build your game as you normally would, taking care to specify
/usr
as installation prefix. In with CMake it might be (inside build directory)
cmake PATH_TO_SOURCE_DIR -GNinja -DCMAKE_INSTALL_PREFIX=/usr
You'll also want to make use of standard path specifications, i.e. installing binaries in bin/
, data in share/
, etc.. Something else you'll probably want to do is to install desktop file together with an icon for your app to ensure that AppImage desktop integration works properly.
- Compile and install your application into a temporary directory by setting DESTDIR variable. Continuing the example above:
DESTDIR=/tmp/myappdir ninja -j4 install
- Call
linuxdeploy
tool to produce AppImage in your current directory:
linuxdeploy --appdir /tmp/myappdir --output appimage
This will automatically include in all shared libraries that your game depends on into resulting appimage.
Steam
If you plan to use Steam for distribution of native build of your game on Linux you should look into documentation for steam runtime, which already includes many libraries commonly used by games (which, relevantly for your question, includes SDL2 but not GLFW). I'm not very familiar with it, but the linked repository should get you started. Another official guide specifically for game developers is also available.
As for libraries that do not happen to be provided by steam runtime, GLFW3 in your case, you'll have to ship those with your game, e.g. in lib
subfolder and point LD_LIBRARY_PATH
at it. The latter is often done with a simple script like the following, which you configure as game's main executable:
#!/bin/sh
export LD_LIBRARY_PATH="${PWD}/lib"
exec bin/mygame "$@"
There's a way, but it's complicated.
Install the oldest Linux you want to support.
You want to build on an old Linux. Install it in Docker.
We're going to ship libraries we depend on with your application. But there are some system libraries we can't ship, like
libc
(see list below). Since we're going to depend on whatever version is installed in the system, it's better to build on a system that uses an old version, so our users have the one at least as new.Compile SDL2 yourself.
SDL2 has a mechanism where it dynamically determines the available libraries at runtime, and uses what it can.
But, interestingly, the maintainers of at least Ubuntu's SDL2 package disable this logic. To have it, you need to compile SDL2 yourself from scratch.
Install dependencies.
Before following the usual
./configure && make
procedure, you have to install the libraries SDL2 depends on. Read the manual. If you don't do it, you'll end up with non-functional build of SDL2.
Copy the right dependencies.
You need to ship the
.so
s of the correct dependencies alongside your executable.Run
ldd my_app
on your application to get a list of the shared libraries you depend on. From those, copy following (to two SEPARATE directories next to your executable, will explain below):libgcc*
,libstdc++*
,libc++*
(whichever are listed inldd
output)This list was figured out experimentally; copying some other libraries causes issues. Try for yourself to be sure.
Those are probably only needed if you installed a newer compiler than what's present by default on your old build system.
Any other
.so
s you built yourself, including the one for SDL2.
Note that
ldd
might suffix the printed libraries with a version, e.g.libstdc++.so.6
. If so, you use the exact filename as printed, not stripping the version suffix. The target file can be a symlink, make sure you copy its contents rather than the link itself (but using the name of the symlink as the resulting filename).Create a runner script.
By default, Linux apps don't search for shared libraries next to the executable. And in any case, we need libgcc/libstdc++/libc++ in a separate directory, for reasons that will be explained later.
The solution is to place a small script next to the executable, that will set the
LD_LIBRARY_PATH
environment variable before running the executable:#!/usr/bin/env bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) LD_LIBRARY_PATH='$ORIGIN/foo:$ORIGIN/bar' "$SCRIPT_DIR/my_app"
Here
$ORIGIN
will be replaced with the name of the directory where your executable is located. Substitutefoo
andbar
for the names of subdirectories with your libraries.Improve the runner script to automatically determine the newer library version.
I don't have this figured out yet. The above should be enough most of the time, but if the system you're running on ships newer libstdc++ (or perhaps libgcc/libc++) than what you shipped, you can run into issues: some libraries SDL2 will load dynamically from the system might want a newer libstdc++ (the one in the system), and will break if given the old one you shipped.
The solution is to dynamically decide (in the runner script) which libraries are newer, and use them.
I'm not sure what is the best way yet. I'd probably recommend shipping a tiny executable that uses something from C++ standard library (to depend on
lib[std]c++
), try running it first. If it runs successfully, you remove the directory with libgcc/libstdc++/libc++ fromLD_LIBRARY_PATH
. If it fails, you keep that directory in the path.
Alternatives:
Appimage
It does almost exactly the same thing we do (you need to install an old Linux to build, etc), except they package everything into one nice executable.
But they don't do the dynamic library selection, and just tell you to use an old libstdc++. See this issue. There's also this plugin which seems to do the dynamic selection thing.
Flatpak
Looks promising, because all libraries your application will see will come from the flatpak runtime, which should prevent the version conflicts explained above.
Snap
Similar to Flatpak, but would avoid for ideological reasons (server is closed-source, Canonical pushes it aggressively, etc).