skip to Main Content

My goal is to build a C++ app with SDL2 that can run on both Linux and Windows which may not have SDL2 installed. (I know that there are lot of posts about it, I’ll come later).

So I’m going through my little project structure and I remember using CMake to make building more efficient. So I document myself a lot and conclude to this structure:

- app (contains the main function)
  |- main.cpp
  |- CMakeLists.txt
- build
- libs (contains externals libraries such as: "spdlog", "googletest")
- src
  |- all the source files
  |- CMakeLists.txt
- tests
  |- tests files
  |- CMakeLists.txt
CMakeLists.txt

Structure is not the point of this discussion.

Now that my project structure is done, I focus on my build files.

Thanks to a lot of posts, I understand that I should go through static linking. (I know the cons such as recompiling statics libraries, not having last updates automatically, larger files, …). I want to distribute my program as a "folder" so I excluded the solution of (package) installers. My program must be able to run with his own resources (folders). Also, remember that SDL2 came with the "zlib license" allowing static link.

So I write my src/CMakeLists.txt:

set(PROJ_LIB_NAME "projectlib")

configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/config/cmake_config.hpp.in 
    ${CMAKE_CURRENT_SOURCE_DIR}/config/cmake_config.hpp
)

set(PROJ_LIB_SOURCES
    core.cpp
    utils/logs_utils.cpp
    gui/gui.cpp
)

add_library(${PROJ_LIB_NAME} STATIC ${PROJ_LIB_SOURCES})
target_include_directories(${PROJ_LIB_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

### ====================================
### LIBRARIES
### ====================================

### spdlog
### ====================================
if(NOT TARGET spdlog)
    # Stand-alone build
    find_package(spdlog REQUIRED)
endif()
target_link_libraries(${PROJ_LIB_NAME} PRIVATE spdlog::spdlog)


### SDL2
### ====================================
find_package(SDL2 REQUIRED)
target_include_directories(${PROJ_LIB_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(${PROJ_LIB_NAME} PRIVATE -static ${SDL2_LIBRARIES})

Please note the -static at the last line

And my CMakeLists.txt (root):

cmake_minimum_required(VERSION 3.16.3 FATAL_ERROR)
enable_testing()

set(PROJ_NAME "progr")
set(PROJ_VERSION "1.0.0")

# set the project name
project(${PROJ_NAME} VERSION ${PROJ_VERSION})

# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# specify compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -Wpedantic")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")

add_subdirectory(${CMAKE_SOURCE_DIR}/libs/googletest-1.11.0)
add_subdirectory(${CMAKE_SOURCE_DIR}/libs/spdlog-1.8.5)
add_subdirectory(${CMAKE_SOURCE_DIR}/src)
# add_subdirectory(${CMAKE_SOURCE_DIR}/tests)
# add_subdirectory(${CMAKE_SOURCE_DIR}/app)

Finally, the app/CMakeLists.txt:

set(PROJ_EXE_NAME "main")

add_executable(${PROJ_EXE_NAME} main.cpp)
target_link_libraries(${PROJ_EXE_NAME} PUBLIC projectlib)

Seems good but doesn’t work. If I uncomment add_subdirectory(${CMAKE_SOURCE_DIR}/app) to build my app, I get a bunch of undefined references coming from "/usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a", " ../libs/spdlog-1.8.5/libspdlogd.a", "/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/libSDL2.a"…


What I’ve tried so far to make my app standalone portable:

  • downloading the SDL2 source code and put it in libs/, telling CMake to treat it like a subdirectory -> that was dumb because the source code is aiming to install SDL on your computer, not to be used as it by your project… (following SDL installation guide, you can)
  • looking for a findSDL2.cmake file -> do I really need of those incomprehensible file? Also, it was good for SDL1 but not anymore for SDL2…
  • playing with so much differents parameters in CMake since 2 days…
  • many more that I can’t remember for now…

There is not a single solution that everyone applauds.

I don’t understand why there are no tutorials on how to achieve the goal of making a C++ SDL2 portable app even on machines where SDL2 is not installed… Everyone seems to agree that apps like Steam will definitely be installed and that it is up to them to manage SDL. What about games that don’t go through Steam then? But then again I wonder if this is a good idea. As a JS developer, when I build an app with NPM dependencies, I block my packages versions so that all developers are working with the same tools and my production environment is stable. I don’t understand this dynamic library logic so I’ll be very happy to have explanations 🙂


EDIT: Forgot to mention that my project library (src/CMakeLists.txt) is building well and all goes wrong when I try to link it with my main fucntion (app/CMakeLists.txt)


EDIT 2: Have a working static link but with some others dependencies that must be downloaded… What I’ve done so far:

  • downloaded the SDL source code and put it in my /libs folder (https://www.libsdl.org/download-2.0.php) (No need to create a build directory inside and run ‘make’ or ‘configure’ commands. CMakeLists will handle this for you. Also, do not ‘make install’)
  • modify my root CMakeLists.txt to add add_subdirectory(${CMAKE_SOURCE_DIR}/libs/SDL2-2.0.14) before building my sources
  • modify my src/CMakeLists.txt to comment all the SDL find_package things (etc…) and add only the libraries linking. See:
### SDL2
### ====================================
## Comments
#set(SDL2_DIR ${CMAKE_SOURCE_DIR}/libs/SDL2-2.0.14)
#find_package(SDL2 REQUIRED)
#target_include_directories(${PROJ_LIB_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
#target_link_libraries(${PROJ_LIB_NAME} PRIVATE -static ${SDL2_LIBRARIES})
## end Comments

## the only needed link to SDL2
target_link_libraries(${PROJ_LIB_NAME} PRIVATE SDL2main SDL2-static)
  • after that, running my app give me some errors as "No available video device" at the initialization of SDL but my app was building!
  • turns out that I need some more dependencies which are installed (I guess) when you do sudo apt install libsdl2-dev (what we want to avoid). You can found all the dependencies here: https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md#build-dependencies
  • I’ve installed the dependencies and… Everything works fine!

I guess my new problem now is "What if I try to run my app on a computer that does not have these dependencies?". Asking that because I manage to compile and run my app before installing them. I conclude that my executable has the SDL code in it but not the dependencies code.

I still wonder why the static link is always so demonizing when for me it allows to have control over the versions of the dependencies. I agree with the "bug fixes" argument, but these new versions may be incompatible with your app. In this case, you should explain to your users that you are working very hard to release a fix when your code is not causing the regression…

3

Answers


  1. Answering the question in the title, ignoring the error in the question. I’m going to suggest a different solution.


    On Windows (assuming MinGW) this is easy. Static linking doesn’t really matter: if you don’t do it, you just ship all necessary dlls with your executable.

    The list of required dlls is determined as follows:

    • Make sure the dlls shipped with your MinGW and libraries don’t overlap with the ones in C:Windows (including subdirectories). If you see an overlap, delete the matching dlls in C:Windows. Some crappy installers like to put custom dlls in there, which tend to cause problems.

    • Open a shell, and in it prepend MinGW’s bin/ directory to the PATH.

    • Use ntldd -R <filename.exe> to get a list of dlls. The ones in MinGW’s bin/ you have to ship.


    On Linux, there are several prominent solutions: Appimage, Flatpak, Snap.

    I don’t have too much experience with this, but here’s what I’ve been doing:

    • Make sure you’re running the oldest Linux version you wish to support (I used Ubuntu 18.04). You can run it under Docker.
    • Build SDL2 from sources, don’t get it from a package manager. At least on Ubuntu, I heard that the packaged version doesn’t dynamically look up available dependencies at runtime, decreasing portability.
    • Use ldd to determine the list of dependencies. Which ones need to be shipped has to be determined experimentally. Start by copying all of them to the current directory.
    • Use patchelf --set-rpath '$ORIGIN' <filename> on all those libraries and on your executable. (The 's are important, you don’t want $ORIGIN to be expanded as a shell variable.)
    • Copy the whole directory to a different system (or a bunch of them), and see if it runs. You’ll have to remove some of the shared libraries you copied, until it starts working. I ended up with just libstdc++.so.6, libgcc_s.so.1, and the libraries I used.

    Alternatively, you can use a launcher script that sets LD_LIBRARY_PATH instead of patchelf.

    Login or Signup to reply.
  2. All of this assumes a recent version of CMake – you may need to install a newer version if this does not work for you.

    So, first, you want to compile SDL2. On Linux, for the latest version, this would mean involve downloading and building the source code like this:

    wget https://www.libsdl.org/release/SDL2-2.0.14.tar.gz
    tar -xvf SDL2-2.0.14.tar.gz
    cd SDL2-2.0.14
    mkdir build
    cd build
    cmake .. -DCMAKE_INSTALL_PREFIX=~/SDL2
    make -j8
    make install
    

    This means you now have a built copy of SDL2 on your machine in the ~/SDL2 folder (you probably want to change this location). CMake has a built in FindSDL module which is quite old fashioned, but to use it add the following to your CMakeLists.txt

    find_package(SDL 2 REQUIRED)
    # When building an application:
    include_directories(${SDL_INCLUDE})
    target_link_libraries(myapplication SDL::SDL)
    

    Then, when you call at the command line to build your project, do:

    cmake .. -DSDL_LIBRARY=~/SDL2/lib -DSDL_INCLUDE_DIR=~/SDL2/include
    

    For Windows, it’s much the same procedure with CMake, except that if you are using VS2019, you use CMake through the GUI and so set parameters within that rather than on the command line. You can do it on the command line if you open a developer command prompt from the start menu though (this is needed so that the compiler variables are set for CMake to find).

    In terms of other libraries – on Linux you will always need to depend on the C standard library (libc) supplied by the system (unless you use musl). Generally, this is forwards compatible but not backwards compatible, so many developers compile their code on a very old OS in order to ensure it runs ‘everywhere’. You’ll also need to statically link in libstdc++. On Windows, redistributable packages exist (for e.g. "Visual C++ Redistributable for Visual Studio 2019") which are generally distributed with your application or assumed to be installed.

    Login or Signup to reply.
  3. You can use the method that I used for my project, I use SDL2 and SLD_Image and get them via FetchContent to a particular TAG on their repo.

    cmake_minimum_required(VERSION 3.24)
    project(sdl_test)
    
    include(FetchContent)
    Set(FETCHCONTENT_QUIET FALSE)
    
    FetchContent_Declare(
            SDL2
            GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
            GIT_TAG release-2.26.3
            GIT_SHALLOW TRUE
            GIT_PROGRESS TRUE
    )
    FetchContent_MakeAvailable(SDL2)
    
    FetchContent_Declare(
            SDL2_image
            GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git
            GIT_TAG release-2.6.3
            GIT_SHALLOW TRUE
            GIT_PROGRESS TRUE
    )
    
    set(SDL2IMAGE_INSTALL OFF)
    set(BUILD_SHARED_LIBS FALSE)
    
    FetchContent_MakeAvailable(SDL2_image)
    
    add_executable(sdl_test main.cpp)
    target_link_libraries(sdl_test SDL2::SDL2main SDL2::SDL2-static SDL2_image::SDL2_image-static)
    

    With this you will have it statically and works cross-platform.

    Include directories will work as usual.

    #include <SDL.h>
    

    remember to have your main in this format

    int main(int, char *[]) {
    ....
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search