Linux Game Development Part 2
libraries library needed linux custom game version find
Before we get started, there are some loose ends from the previous article that I want to tie up, based on feedback from folks who e-mailed me or discussed the article in the forums. These folks pointed out some items that I forgot to address.
- Command-line. Most of the time, the average user wonÆt need to know how to use the Linux Command Line Interface (CLI). Like Windows, you can usually do what you need to do through the GUI. However, there are times when it is necessary and even more efficient to use the CLI. This is especially true for developers; in fact, you will need to use the CLI later in this article. You can find a good tutorial for getting started with the CLI at http://linuxcommand.org.
- Other distributions. In my previous article, I recommended a few Linux distributions I have used that I felt would be good for those who are new to Linux. These certainly arenÆt the only distributions available. You can find a list of alternatives at http://distrowatch.com if youÆre not satisfied with the ones I recommended. Diversity is one of the strengths of Linux; just keep looking and youÆre bound to find a distribution that suits your personal taste.
- Development environments. In this series, IÆm not recommending any one particular development environment, nor will I be teaching you how to compile, link, and debug a program on Linux. Linux isnÆt like Windows, where one IDE (namely Visual Studio) dominates the landscape and a tutorial on how to use it would satisfy the needs of 90% of developers. With Linux, you have a lot more freedom of choice û that can be good (because youÆre not locked into any one tool or way to do things) and that can be bad (because itÆs hard for me to write something comprehensive for everyone). How you compile, link, and debug will largely depend on what development environment you choose.
The best I can do is point you to some tools and let you try them. One of the common complaints I often hear from developers new to Linux is that they arenÆt even sure where to start looking. At least Apple provides a developer site where you can go and find everything you need to learn about how to develop for Mac OS. But there is no equivalent, definitive site for Linux development that IÆve ever been able to find. ThatÆs why I wrote the first article in this series: to point you to the information you need about some of the Linux development tools that are available so that you can get started on your own.
ItÆs up to you to find a development environment or set of tools that you are comfortable with, and then to learn how to use it. When you find a tool you like, you can find further documentation and help at the toolÆs web site. Many tools have excellent documentation, and there are plenty of tutorials available for particular tools û if you canÆt find a good tutorial on a particular toolÆs web site, then you just have to do a little Googling. In addition, lots of the tool web sites have forums where you can go to ask questions and get help from people knowledgeable with that tool.
So if youÆre stuck on this point, I recommend going back to the first article in this series, look over the listed tools (both the ones I recommended and the ones that the folks in the discussion forums recommended), pick a tool that you think you would feel comfortable with, and start learning how to use the tool.
The rest of the articles in this series assume you have some working knowledge of Linux. IÆm assuming that youÆve found a Linux distribution youÆre comfortable with, and that youÆve learned how to set it up, install the applications and packages that you need, and start using it. IÆm also assuming that youÆve selected a development environment that you like and youÆve got your game to compile, link, and run on your Linux computer. If thatÆs not true, you should go back to the first article and start there; otherwise, youÆre now ready to have some other people test your Linux game.
So you zip up the executable and game assets, e-mail it to your buddy so he can test it for you, and you eagerly await his feedback. A short while later, he e-mails you back these three devastating words: ôIt wonÆt run.ö
After a few brief questions about his setup, you discover he is running on a different Linux distribution than you are. Your heart sinks. Now what?
Creating an executable that works on almost all Linux distributions is a challenge. There are a number of factors that contribute to the problem:
- Different distributions use different library versions. Dynamic libraries, which are called shared objects in Linux and have a .so extension, are not like DLLs in Windows. Newer versions of a library might use all new symbol versions, which would be unresolved in an older library version.
- Different distributions may build libraries differently. Many open source libraries, particularly those built from source using ôconfigureö, provide a wide array of build options. You cannot make any assumptions about how a library was built on any particular distribution.
- Some distributions may not have all the libraries installed that you need. The standard C/C++ libraries, namely glibc and libstdc++, are probably installed, but other game libraries like SDL, Ogg/Vorbis, and OpenAL may not be.
- Many libraries are dependent on the presence of other libraries. For example, if you link your executable against the OpenAL library, you may discover that the OpenAL library (depending on how it was built) requires other libraries like ALSA, arts or esd (even though you did not explicitly link against these libraries).
There are basically three options (and a few hybrids that I wonÆt cover here):
Option #1: open your source code. This is the easiest solution for the developer, and it allows the end user to build a version of the executable that is tailor-made for their computer. Of course, not all users can or want to build a program from source. Most of them just want to install and run your program with a minimum of effort. Also, opening the source code is not always an option for a commercial company. So letÆs move on.
Option #2: build distribution-specific versions of your game. Essentially, you would provide a distribution-dependent package like rpm or deb to deliver your game, and allow the distributionÆs package manager to resolve the library dependencies for you. While this sounds good in theory, in practice this can be a nightmare for both the developer and the end user. From the end userÆs standpoint, it can be a very frustrating experience trying to find and install all of the dependent packages that the package manager says it needs, especially when there are conflicts û this is not the first impression you want a potential customer to have with your game. From the developer standpoint, you would have to provide not only a package for every distribution that you want to support but also a package for every version of that distribution you want to support. New versions can be released several times a year. Plus there are so many distributions (and every one is different) that you canÆt possibly support them all in this manner. It would be a full-time job. And if you choose to only support a handful of the more popular distributions, you will effectively shut out many potential customers who use other distributions. So this isnÆt an ideal commercial solution either.
Option #3: bundle the specific libraries you need along with your application. The basic idea is this: since you cannot rely on the presence of a particular versioned library on an end userÆs system, just include it with your application. That way, your application will always have the libraries it needs in order to run. Linux purists may cringe at this option, but in my opinion, it really is the best way for a commercial company to deliver a binary application that will run on almost any distribution. Gerry Jo Jellestad, a very knowledgeable member of the Linux gaming community who has been credited on games developed by Caravel Games and Grubby Games, recommended this solution to me and provided me with the information I needed to implement it. Many other commercial companies have also chosen this solution, so you can do worse than follow their lead.
For the rest of this article, I will cover what you need to know to build and distribute your application using Option #3.
Most of this information came directly from Gerry Jo Jellestad, so I would like to take this opportunity to publicly thank him for it. Without his assistance, I could never have finished the Linux version of Dirk Dashing.
ThereÆs a lot of information to cover here, so letÆs get started.
Step 1: Statically Link Everything Possible
If you statically link a library with your application, then you donÆt have to worry about any dependency issues with it. Dirk Dashing, for example, uses Ogg/Vorbis for its music, and the license for the Ogg/Vorbis libraries allows for static linking in commercial applications. Unfortunately, the licensing for other libraries like SDL and OpenAL require you to open your source code if you statically link them into your program. If youÆre not willing to do that, then you must link these libraries dynamically. So proceed to step 2.
Step 2: Identify Your Remaining Dependencies
Before we can go any further, the first thing you need to do is to find out all the dynamic libraries that your application is dependent on. IÆm sure youÆre thinking to yourself, ôThatÆs easy - just look at the link line.ö But hold on a second, the game libraries themselves might be dependent on other libraries that you donÆt necessarily know about (because youÆre not linking against them directly). You will need to resolve all of these dependencies yourself, if youÆre going to build an executable that will run on any distribution.
Here is a handy-dandy little tool that is invaluable for this task: objdump. ItÆs probably already installed on your Linux system. If you type in
objdump ûx your-exe
it will show you a lot of useful information about your program. For example,
objdump ûx dirkdashing | grep NEEDED
will show the libraries *directly* required by my Dirk Dashing executable. Here was the output when I ran it on v1.02 of the executable:
NEEDED libSDL-1.2.so.0 NEEDED libGLU.so.1 NEEDED libGL.so.1 NEEDED libalut.so.0 NEEDED libopenal.so.0 NEEDED libboost_filesystem-gcc.so NEEDED libpthread.so.0 NEEDED libstdc++.so.6 NEEDED libgcc_s.so.1 NEEDED libc.so.6 NEEDED libm.so.6
Some of the libraries listed here are low-level or system-specific, and including these libraries with your application will often lead to problems. So let me point them out to you.
libc and libm are part of glibc, the C library. They are not on my link line, and probably wonÆt be on yours either; the linker automatically pulls these in. libc consists of general C library functions, and libm contains math-specific C functions. Both of these are pretty much guaranteed to be present on any Linux system, and their version has been fixed for a very long time, so you can count on them being compatible.
libpthread is also part of glibc and provides threading support. Technically, it is an optional library, but every distribution includes it because if they didnÆt there would be problems. This library, too, has been fixed at its current version for a very long time, so you can rely on compatibility with it as well.
libgcc is part of the GNU Compiler Collection (GCC), and it contains a number of internal subroutines that GCC uses to overcome any shortcomings on particular machines. This library isnÆt on my link line either. libgcc generally provides for backward compatibility, so you can rely on it too.
libGL and libGLU are the OpenGL libraries. They are highly system-specific, so you definitely should not include them with your application.
libstdc++ is not system-specific, but it is up to you whether or not you want to include it with your application. It will probably be installed on your end userÆs system, but it might be an older version (although, it has been awhile since it was last updated, so you could choose to ignore it). To be on the safe side, I chose to include it with Dirk Dashing, and IÆve noticed that other commercial games (like DROD from Caravel Games) do too.
libdl is another common library that isnÆt on the dependency list for my particular executable, but since we see it later in this article I want to cover it here. libdl is used for dynamic linking of libraries at runtime, and it is another low-level library that is always available and compatible. It shouldnÆt be included with your application either.
To summarize, don't worry about these low-level libraries. They're pretty much always available and compatible, as long as you make sure to not use any newer symbol versions than the oldest glibc you want to support. One other thing I should point out: don't statically link these libraries into your program.
The rest of the libraries listed by objdump are probably ones you recognize as standard development libraries. The list will probably be different for your game, depending on which libraries you use. Regardless, these are the libraries you need to look at in more detail. These libraries may or may not exist on a given Linux system, and even if they do exist, they may not be the version your game needs.
You can use the same objdump command above to look at the dependencies for each of the dynamic libraries. For example, when I run it on the OpenAL library provided in SUSE 10 (which is the distribution I originally used to develop Dirk Dashing), I get this output:
NEEDED libdl.so.2 NEEDED libm.so.6 NEEDED libpthread.so.0 NEEDED libasound.so.2 NEEDED libartsc.so.0 NEEDED libgmodule-2.0.so.0 NEEDED libgthread-2.0.so.0 NEEDED libglib-2.0.so.0 NEEDED libesd.so.0 NEEDED libaudiofile.so.0 NEEDED libSDL-1.2.so.0 NEEDED libvorbis.so.0 NEEDED libvorbisfile.so.3 NEEDED libc.so.6
This library has a lot of problems. First, look at the long list of dependent libraries. If I ran objdump on every one of these libraries to find all of their dependencies, and then ran it on those libraries, and so on and so forth, I would end up with a huge list of dynamic libraries that I would have to bundle with my application. The size of the download file for my game would be pretty big. Second, the library depends on a lot of glib stuff - not glibc, but glib, which is the low-level library that forms the basis of GTK+ and GNOME. Not all distributions use GNOME, but for those that do, I could cause problems by including the SUSE 10 version of this low-level library with my game. Third, the library depends on ALSA, arts, and ESD, which are low-level audio libraries and could also be problematic to bundle with my game.
Now, I can almost see the look of despair on your face as youÆre starting to understand the seemingly daunting task before us. But donÆt worry, because the solution is very simple. HereÆs the bottom line: you should never bundle a pre-built library with your application, because you donÆt know how it was built or why it was built the way it was. Instead, you should build your own custom version of the dynamic libraries that you use. The goal is to strip off all the dependencies that you donÆt need, and to deliver a small, streamlined version of each library with your game.
Step 3: Build custom libraries
Before you can build custom libraries, you need to download the source code. You probably know the web sites for SDL, OpenAL, Ogg/Vorbis, and any other libraries you use, so I wonÆt cover that here. If you donÆt know, just plug it into Google and it will take you there.
The source code is usually packaged as a tarball, which is a compressed archive with a .tar.gz or .tgz extension. YouÆll usually find it on the download page on a particular libraryÆs web site. After you have downloaded the source tarball for each library, extract it into a temporary directory where you can build it.
Now we need to use the command line. Open an Xterm or Terminal window, and cd to the temporary directory that contains the source code. You need to be in the top-level directory of the source code tree you just extracted.
To build a custom version of each library, you should follow the directions that come with the library. Usually there is a README file that explains the steps you need to take. Basically, each library follows these three easy steps (you type these in on the command line):
./configure <your list of options> make make install
Most open source libraries come with a utility called ôconfigureö that allows it to figure out where the development tools and libraries are installed on your system. The configure tool also provides a plethora of options you can use to control how the library is built and tailor it to your needs. To find all the options, type:
There are too many options to cover for each library, so instead, IÆve decided to share with you the options I used and why I used them. Hopefully, that will provide some criteria that you can use to tailor your own libraries.
For SDL, these are the options I supplied to ôconfigureö:
ô--prefix=/home/troy/Projects/SDLö - specifies where my custom version of SDL will be installed at the end. All of my Makefiles (or my IDE project) will point here to pick up include files and libraries.
ô--enable-X11-sharedö - forces SDL to use the X11 libraries that are on the end userÆs system. The low-level X11 libraries are pretty much guaranteed to be on the end userÆs system and compatible, so you donÆt need to worry about distributing them with your game.
ô--disable-rpathö - basically, you want to avoid using RPATH. RPATH restricts libraries to only run in a specific location, so distributing RPATH-enabled libraries makes them pretty much useless. The SDL library is the only one IÆm using that has RPATH enabled by default, but youÆll want to watch for this on other libraries you might be using.
ô--disable-ipodö - this is obvious, since my game doesnÆt need this.
ô--disable-video-directfbö - disables the use of the DirectFB video driver, which as I understand it is built into the Linux kernel. It doesnÆt support many video cards, and it is not optimized. This option removes support for DirectFB from my custom SDL library, thereby reducing the library size and ensuring that SDL uses the hardware-accelerated video driver for OpenGL (if present).
ô--disable-audioö û when Dirk Dashing used OpenAL for its audio, I used this option. It removes the audio subsystem from my custom SDL library, which my game didnÆt use, thereby reducing the library file size.
ô--enable-sdl-dlopenö û enables dynamic runtime linking, so that SDL does not require all of the different low-level audio libraries for ALSA, arts, and ESD in order to run, but it will use them if they are present. I didnÆt need to use this option when I disabled the audio subsystem, but IÆm using SDL_mixer now, so I needed it. This option will reduce a significant number of dependencies on your custom version of SDL.
As of v1.04, Dirk Dashing uses SDL_mixer for its audio. For SDL_mixer, these are the options I supplied to ôconfigureö:
ô--disable-music-modö û disables support for MOD, which my game doesnÆt use, thereby reducing the library file size.
ô--disable-music-midiö û disables support for MIDI, which my game doesnÆt use, thereby reducing the library file size.
ô--disable-music-mp3ö û disables support for MP3, which my game doesnÆt use, thereby reducing the library file size.
ô--disable-music-ogg-sharedö û disables dynamic linking of the Ogg/Vorbis libraries, so I can statically link them into my executable.
ô--with-sdl-prefix=/home/troy/Projects/SDLö û tells SDL_mixer to use my custom version of SDL. This is important, because I donÆt want SDL_mixer to compile or link against the prebuilt version that came with my Linux distribution.
For libogg, these are the options I supplied to ôconfigureö:
ô--prefix=/home/troy/Projects/oggö - specifies where my custom version of the library will be installed at the end.
For libvorbis, these are the options I supplied to ôconfigureö:
ô--prefix=/home/troy/Projects/vorbisö - specifies where my custom version of the library will be installed at the end.
ô--disable-oggtestö - speeds up the build time.
ô--with-ogg=/home/troy/Projects/oggö - tells libvorbis to use my custom version of libogg. This is important, because I donÆt want libvorbis to compile or link against the prebuilt version that came with my Linux distribution.
Dirk Dashing v1.03 and earlier versions used OpenAL for the audio. For OpenAL, these are the options I specified to ôconfigureö:
ô--prefix=/home/troy/Projects/openalö - specifies where my custom version of the library will be installed at the end.
ô--disable-sdlö - removes SDL support from OpenAL, since I donÆt use it.
ô--disable-vorbisö - removes the AL_EXT_vorbis extension, which Dirk Dashing doesnÆt use.
ô--disable-mp3ö - removes the AL_EXT_mp3 extension, which Dirk Dashing doesnÆt use.
For freealut, these are the options I specified to ôconfigureö:
ô--prefix=/home/troy/Projects/freealutö - specifies where my custom version of the library will be installed at the end.
ôCPPFLAGS=-I/home/troy/Projects/openal/includeö - tells freealut where to find the header files for my custom version of OpenAL
Building a custom library is a fairly easy process. The configure utility makes it very easy, once you identify the options you need to use.
Sometimes the configure script will fail because it cannot find a particular tool or library it needs. When that happens, use the package manager for your distribution to install the missing tool or library. Most good distributions provide a package manager that allows you to browse the set of available packages and search for a particular package you need. You just need to find the missing package, install it, and then run the configure tool again.
Just for fun, letÆs run objdump on my custom OpenAL library and compare it to the dependency list we had before:
NEEDED libm.so.6 NEEDED libdl.so.2 NEEDED libpthread.so.0 NEEDED libc.so.6
Wow, weÆve gone from fourteen dependencies to only four! And they are all low-level libraries that I donÆt need to worry about.
I found this to be true with all of the libraries that my Dirk Dashing executable directly depended on û when I built custom versions of those libraries to minimize the dependencies, I didnÆt have to build anything that those libraries in turn depended on. I was able to minimize the dependencies of each custom library to the point that it only needed low-level libraries.
Step 4: Use Your Custom Libraries
Finally, edit the include and library paths in your Makefiles or your IDE projects to point to your custom libraries, and rebuild your application. Congratulations! You now have an application and a set of custom libraries that you can distribute that will run on almost any Linux distribution.
To run your program, it will need to be able to find your custom libraries. Shared objects are not like Windows DLLs - you canÆt just co-locate your libraries in the same directory as your executable and expect the executable to find them. The runtime linker in Linux only looks for libraries based on directories specified in the LD_LIBRARY_PATH environment variable.
I recommend writing a short script to run your game. The script should set the LD_LIBRARY_PATH to point to your custom libraries and any system libraries you need, and then launch your executable. You can also have it cd to your installation directory first, so that the working directory is the directory where the executable resides - that way, your program can easily find all the game assets. Your desktop and menu shortcuts would then invoke your script rather than your executable.
Here is an example startup script that I used during development of Dirk Dashing:
# Set the library path to point to my custom libs and my game libs # Separate multiple paths with a colon export LD_LIBRARY_PATH=/home/troy/Projects/DirkDashing/Lib:. # cd to the working directory, so it can find game assets cd /home/troy/Projects/DirkDashing # Start the game ./dirkdashing
The # sign at the start of a line indicates a comment. Notice that you can set multiple directories in LD_LIBRARY_PATH by separating them with a colon. I added the ô.ö at the end of the LD_LIBRARY_PATH to find game libraries that are located in the same directory as the executable.
This article was rather lengthy, but hereÆs the whole thing in a nutshell: to build a binary executable that will run on almost any Linux distribution, you need to statically link as many libraries as possible, and build custom versions of the rest. Then you provide your application and custom libraries together in your installer.
But weÆll talk about installers next time. Until then!