我的目标是用SDL2构建一个C++应用程序,该应用程序可以在Linux和Windows上运行,而可能没有安装SDL2(我知道有很多关于它的帖子,我稍后会来)。
所以我正在浏览我的小项目结构,我记得我使用CMake来提高构建效率。因此,我对自己进行了大量记录,并得出了以下结构的结论:
- 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
结构不是本次讨论的重点
现在我的项目结构已经完成,我将重点放在构建文件上。
多亏了很多帖子,我明白我应该通过静态链接。(我知道诸如重新编译静态库、不自动进行最后更新、更大的文件等缺点);文件夹";所以我排除了(包)安装程序的解决方案我的程序必须能够使用他自己的资源(文件夹)运行此外,请记住SDL2附带了";zlib许可证";允许静态链接。
所以我写了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})
请注意最后一行的-static
和我的CMakeLists.txt(根):
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)
最后,app/CMakeLists.txt:
set(PROJ_EXE_NAME "main")
add_executable(${PROJ_EXE_NAME} main.cpp)
target_link_libraries(${PROJ_EXE_NAME} PUBLIC projectlib)
看起来不错,但不起作用。如果我取消注释add_subdirectory(${CMAKE_SOURCE_DIR}/app)
来构建我的应用程序,我会得到一堆来自"/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";。。。
到目前为止,我尝试让我的应用程序独立可移植:
- 下载SDL2源代码并将其放入libs/中,告诉CMake将其视为子目录->
- 查找findSDL2.cmake文件->我真的需要那些无法理解的文件吗?此外,这对SDL1有好处,但对SDL2没有好处
- 自从2天以来,在CMake中玩了这么多不同的参数
- 还有很多我现在记不起来了
没有一个单一的解决方案能让每个人都称赞
我不明白为什么即使在没有安装SDL2的机器上,也没有关于如何实现C++SDL2便携式应用程序目标的教程。。。每个人似乎都同意,像Steam这样的应用程序肯定会被安装,由他们来管理SDL。那么那些没有通过Steam的游戏呢?但话说回来,我想知道这是否是个好主意。作为一名JS开发人员,当我构建一个具有NPM依赖性的应用程序时,我会阻止我的包版本,以便所有开发人员都使用相同的工具,并且我的生产环境是稳定的。我不理解这个动态库逻辑,所以我很高兴有解释:)
编辑:忘了提我的项目库(src/CMakeLists.txt)构建得很好,当我试图将它与我的主要功能(app/CMakeLists.txt
编辑2:有一个可工作的静态链接,但有一些其他必须下载的依赖项。。。到目前为止我所做的:
- 下载SDL源代码并将其放在my/libs文件夹中(https://www.libsdl.org/download-2.0.php)(无需在内部创建构建目录并运行"make"或"configure"命令。CMakeLists将为您处理此问题。此外,不要"make-install")
- 在构建源代码之前,修改我的根CMakeLists.txt以添加
add_subdirectory(${CMAKE_SOURCE_DIR}/libs/SDL2-2.0.14)
- 修改我的src/CMakeLists.txt以注释所有SDL find_package内容(等等),并只添加链接的库。请参阅:
### 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)
- 之后,运行我的应用程序会给我一些错误,比如";没有可用的视频设备";在SDL初始化时,但我的应用程序正在构建中
- 事实证明,我需要更多的依赖项,这些依赖项在您执行
sudo apt install libsdl2-dev
时安装(我想)(我们希望避免的)。您可以在此处找到所有依赖项:https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md#build-依赖项 - 我已经安装了依赖项一切都很好
我想我的新问题现在是">如果我试图在没有这些依赖关系的计算机上运行我的应用程序,该怎么办";。之所以这么问,是因为我设法在安装它们之前编译并运行了我的应用程序。我得出的结论是,我的可执行文件中有SDL代码,但没有依赖关系代码。
我仍然想知道为什么静态链接总是如此妖魔化,而对我来说,它允许控制依赖关系的版本。我同意";错误修复";参数,但这些新版本可能与您的应用程序不兼容。在这种情况下,您应该向用户解释,当您的代码没有导致回归时,您正在非常努力地发布修复程序。。。
回答标题中的问题,忽略问题中的错误。我将提出一个不同的解决方案。
在Windows上(假设为MinGW),这很容易。静态链接其实并不重要:如果你不这样做,你只需要将所有必要的dll与你的可执行文件一起发送。
所需dll的列表确定如下:
-
确保MinGW和库附带的dll与
C:Windows
(包括子目录)中的dll不重叠。如果看到重叠,请删除C:Windows
中的匹配dll。一些糟糕的安装程序喜欢在其中放入自定义dll,这往往会导致问题。 -
打开一个shell,并在其中将MinGW的
bin/
目录预置到PATH
。 -
使用
ntldd -R <filename.exe>
获取dll列表。MinGW的bin/
中的那些你必须发货。
在Linux上,有几个突出的解决方案:Appimage、Flatpak、Snap。
我对此没有太多经验,但以下是我一直在做的事情:
- 确保你运行的是你想要支持的最旧的Linux版本(我用的是Ubuntu 18.04)。你可以在Docker下运行它
- 从源代码构建SDL2,而不是从包管理器获得它。至少在Ubuntu上,我听说打包版本在运行时不会动态查找可用的依赖项,从而降低了可移植性
- 使用
ldd
确定依赖项列表。哪些需要运输必须通过实验来确定。首先将它们全部复制到当前目录 - 在所有这些库和可执行文件上使用
patchelf --set-rpath '$ORIGIN' <filename>
。('
很重要,您不希望将$ORIGIN
扩展为shell变量。) - 将整个目录复制到另一个系统(或一堆系统),看看它是否运行。您必须删除您复制的一些共享库,直到它开始工作。我最终只得到了
libstdc++.so.6
、libgcc_s.so.1
和我使用的库
或者,您可以使用设置LD_LIBRARY_PATH
而不是patchelf
的启动程序脚本。
所有这些都假设CMake是最新版本——如果不适用,您可能需要安装新版本。
因此,首先,您需要编译SDL2。在Linux上,对于最新版本,这意味着需要下载和构建这样的源代码:
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
这意味着您现在在机器上的~/SDL2文件夹中有一个SDL2的构建副本(您可能想更改此位置)。CMake有一个内置的FindSDL模块,这是非常老式的,但要使用它,请在您的CMakeLists.txt 中添加以下内容
find_package(SDL 2 REQUIRED)
# When building an application:
include_directories(${SDL_INCLUDE})
target_link_libraries(myapplication SDL::SDL)
然后,当你在命令行调用来构建你的项目时,执行:
cmake .. -DSDL_LIBRARY=~/SDL2/lib -DSDL_INCLUDE_DIR=~/SDL2/include
对于Windows,这与CMake的过程基本相同,只是如果您使用VS2019,则通过GUI使用CMake,因此在GUI中而不是在命令行上设置参数。如果从开始菜单打开开发人员命令提示符,则可以在命令行上执行此操作(这是必需的,以便为CMake设置编译器变量以查找)。
就其他库而言,在Linux上,您将始终需要依赖系统提供的C标准库(libc)(除非您使用musl)。一般来说,这是向前兼容的,但不是向后兼容的,因此许多开发人员在非常旧的操作系统上编译代码,以确保它"无处不在"。您还需要在libstdc++中进行静态链接。在Windows上,存在可重新分发的包(例如"Visual C++redistributable for Visual Studio 2019"),这些包通常与您的应用程序一起分发或假定已安装。
您可以使用我在项目中使用的方法,我使用SDL2和SLD_Image,并通过FetchContent将它们获取到其repo上的特定TAG。
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)
有了这个,你将拥有它的静态和跨平台工作。
Include目录将照常工作。
#include <SDL.h>
记住要有这种格式的主
int main(int, char *[]) {
....
}