简体   繁体   中英

How to link and compile a C++ project for Linux using CMake? (Adapting CMakeLists.txt)

Context: I want to make a GameClient, GameServer, and custom library (which both Client and Server use) cross platform for Linux, Windows & MacOS. Server and Client both depend on various libraries I prefer linking statically, and headers. I heard I could do this by wrapping my 3 currently VisualStudio developed projects into a CMake environment, so I started by making CMakeLists.txt's for Server and Shared lib, which are the simplest and have fewer library dependencies.

Question: How can I adapt these CMakeLists.txt's (currently building on and for Windows), so I can choose if I want to compile for Windows or Linux (preferably from Windows)?

Shared Lib CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)

project(MORPH_Server VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

set(SFML_STATIC_LIBRARIES TRUE) #Link to statically
set(SFML_DIR "C:/libraries/SFML/SFMLx64SourceAndCompile")
find_package(SFML 2.5.1 COMPONENTS system network REQUIRED)

add_library(MORPH_Shared_Functions)
target_sources(MORPH_Shared_Functions
               PRIVATE Transform.cpp
               PRIVATE World.cpp
               PRIVATE Player.cpp
               PRIVATE PropsData.cpp
               PRIVATE Prop.cpp
               PRIVATE udp_network_manager.cpp
               PRIVATE utility_functions.cpp
               )

target_link_libraries(MORPH_Shared_Functions 
                      sfml-system #Basically already found sfml modules and their dependencies (find-package)
                      sfml-network
                      )

target_include_directories(MORPH_Shared_Functions
                           PRIVATE C:/libraries/SFML/SFML-2.5.1/include
                           PRIVATE C:/libraries/GLM/glm
                           PRIVATE C:/libraries/CEREAL
                           )

Server CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)

project(MORPH_Server VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

configure_file(CmakeConfig.h.in CmakeConfig.h)

set(SFML_STATIC_LIBRARIES TRUE) #Link to statically
set(SFML_DIR "C:/libraries/SFML/SFMLx64SourceAndCompile")
find_package(SFML 2.5.1 COMPONENTS system network REQUIRED)

add_executable(MORPH_Server)
target_sources(MORPH_Server
               PRIVATE Main.cpp 
               PRIVATE Game.cpp
               PRIVATE CommandManager.cpp
               PRIVATE ServerPlayer.cpp
               PRIVATE ServerProp.cpp
               PRIVATE ServerWorldState.cpp
               PRIVATE UDP_Server_CS.cpp
               )
add_library(MORPH_Shared_Functions STATIC IMPORTED)

set_target_properties(MORPH_Shared_Functions PROPERTIES IMPORTED_LOCATION C:/Users/dylan/Desktop/MORPH/MORPH_Shared_Functions/Build/Debug/MORPH_Shared_Functions.lib)

target_link_libraries(MORPH_Server
                      MORPH_Shared_Functions 
                      sfml-system
                      sfml-network
                      )

target_include_directories(MORPH_Server 
                           PRIVATE C:/Users/dylan/Desktop/MORPH/MORPH_Shared_Functions
                           PRIVATE C:/libraries/SFML/SFML-2.5.1/include
                           PRIVATE C:/libraries/GLM/glm
                           PRIVATE C:/libraries/CEREAL
                           )

You are lucky as both the three libraries you use export their packages correctly.

On linux, I will assume that the libraries are installed in ~/workspace/libraries .

The trick will be to use find_package for all your dependencies, and you must drop every custom Findxyz.cmake files.

Then, you will have to drop the ALL_CAPS for the directory name. This is important for CMake to find the packages correctly. On windows it may not matter as the filesystem is not case sensitive, but on linux it is.

So first, to make your CMakeLists completely agnostic on where to find the packages, we will create a new file called linux-dependencies.cmake with this content:

list(APPEND CMAKE_PREFIX_PATH "$ENV{HOME}/workspace/libraries")
list(APPEND CMAKE_PREFIX_PATH "path/to/morph/build") # can be relative

And windows-dependencies.cmake :

list(APPEND CMAKE_PREFIX_PATH "C:/libraries/")
list(APPEND CMAKE_PREFIX_PATH "$ENV{HOMEPATH}/Desktop/MORPH/build")

Of course, if you set the library installation directory relative to the project or on both platform relative to the home directory, you can have only one dependencies.cmake file.

Then, on your next cmake invocation, simply tell cmake to include that file:

cmake .. -DCMAKE_PROJECT_INCLUDE=linux-dependencies.cmake

Note that CMAKE_PROJECT_INCLUDE is only available since CMake 3.15. If you don't want to upgrade CMake or simply to provide a default, you can always include the file manually.

You CMakeLists file should look like this:

cmake_minimum_required(VERSION 3.15)

project(MORPH_Server VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

configure_file(CmakeConfig.h.in CmakeConfig.h)

find_package(SFML 2.5.1 COMPONENTS system network REQUIRED)
find_package(glm 0.9.9 REQUIRED)
find_package(cereal REQUIRED) # cereal don't export version info as far as I know

# You will have to export your target or write a MORPH-config.cmake file
find_package(MORPH REQUIRED)

add_executable(MORPH_Server)
target_sources(MORPH_Server
               PRIVATE Main.cpp 
               PRIVATE Game.cpp
               PRIVATE CommandManager.cpp
               PRIVATE ServerPlayer.cpp
               PRIVATE ServerProp.cpp
               PRIVATE ServerWorldState.cpp
               PRIVATE UDP_Server_CS.cpp
               )

# this take care of both linking and include dirs
target_link_libraries(MORPH_Server
                      MORPH_Shared_Functions 
                      sfml-system
                      sfml-network
                      glm
                      cereal
                      )

Another thing that can help: set the same CMAKE_PREFIX_PATH as CMAKE_INSTALL_PREFIX or CMAKE_INSTALL_PREFIX/libname when installing libraries.


If you want to compile the linux executable on windows, I would advise to simply use WSL or to run the compiler inside Docker. VSCode actually has a nice extension to use a docker as a build environment, and as far as I know, can also use WSL as a build and run environment.

my 2 cents,

1

According to the find_package() search procedure documentation:

In all cases the <name> is treated as case-insensitive and corresponds to any of the names specified ( <PackageName> or names given by NAMES ).

ref: https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure

note: personally I prefer to use PackageNameConfig.cmake than package-name-config.cmake ...

2

Why not simply add the CMAKE_PREFIX_PATH on the configure command line than passing by your dependencies.cmake files ?

3

For static linking, I would use a FetchContent() approach to control the "static-ness" since most of Linux distro will tend to build libraries as shared then put them in directories following the FHS .

3.1

You can also use flag to allow user to use system-wide libraries if they want (thus distro maintainers are happy as well as you can create a standalone artifact easily).
warning: Most libraries are not intended to be used inside a super build so you may need to patch them (ie PATCH_COMMAND git apply ... )

# enable CMP0077 so superbuild can set it.
option(BUILD_THIRDPARTY "Build the third party" ON)
...
# to allow recursive superbuild (add_subdirectory(morph_server) etc...) 
# first check if third party target is not already present
# see: https://gitlab.kitware.com/cmake/cmake/-/issues/17735
if(NOT TARGET ThirdParty::ThirdParty)
  if(BUILD_THIRDPARTY)
   FetchContent_Declare(...)
   set(BUILD_SHARED_LIBS OFF)
   set(BUILD_EXAMPLES OFF) ...
   FetchContent_MakeAvailable(third_party)
   set(BUILD_SHARED_LIBS ${BUILD_SHARED_BCKP})
  else()
    find_package(ThirdParty REQUIRED)
  endif()
endif()

4

You may also provide a default FindThirdParty.cmake .

if(UNIX)
  list(APPEND CMAKE_PREFIX_PATH "$ENV{HOME}/workspace/libraries")
  list(APPEND CMAKE_PREFIX_PATH "path/to/morph/build") # can be relative
else()
  list(APPEND CMAKE_PREFIX_PATH "C:/libraries/")
  list(APPEND CMAKE_PREFIX_PATH "$ENV{HOMEPATH}/Desktop/MORPH/build")
endif()

find_package(ThirdParty CONFIG REQUIRED)

So you don't have to play with CMAKE_PREFIX_PATH or dependencies.cmake etc. and users can still provide their own FindThirdParty.cmake and prepend the CMAKE_PREFIX_PATH if they want to change...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM