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
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 inC: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 thePATH
.Use
ntldd -R <filename.exe>
to get a list of dlls. The ones in MinGW’sbin/
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:
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.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.)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 ofpatchelf
.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:
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
Then, when you call at the command line to build your project, do:
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.
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.
With this you will have it statically and works cross-platform.
Include directories will work as usual.
remember to have your main in this format