Introduction
This is my first CMake project. I had a bit of help with the initial setup but I have since made a lot of changes myself. It's working well but I think I am doing things in a slightly unconventional way.
In particular, I am unsure about:
1) Dependencies
Instead of using find_package (which I am still struggling to wrap my head around), I assume that all dependencies are placed, pre-built, into a libs folder.
I define a custom function for each one that can be used to add it to a project (e.g. CMake/AddFreetype.cmake, below). I also create user-facing properties so that the version number of each library can be specified.
One thing I like about this approach is that dependencies can be trivially shared between multiple projects. Adding a dependency requires only 1 line.
2) Auto-Generated Source Lists
For one of my projects, I have a script that outputs a CMake file containing all of its source and header files as CMake variables (${OPEN_RIVAL_SOURCES} and ${OPEN_RIVAL_HEADERS}). This has 2 advantages:
I can programmatically regenerate the list of source/header files without needing to manually update CMake whenever I create or delete files.
I can include the same set of files in my test project, without needing to duplicate the list of files.
To me these benefits are so obvious that I can't believe there isn't a more standardized way of achieving the same thing.
3) Precompiled Headers
I have included a handful of precompiled headers, but I am still not completely sure if this is beneficial or not. These headers are widely used throughout the project, but do the precompiled headers not get included in all header files, even those that don't need them? Could this actually be detrimental to the compilation process in some way?
Files
/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Open-Rival CXX)
include(CMake/AddFreetype.cmake)
include(CMake/AddGLEW.cmake)
include(CMake/AddGLM.cmake)
include(CMake/AddJson.cmake)
include(CMake/AddOpenAL.cmake)
include(CMake/AddRtMidi.cmake)
include(CMake/AddSDL2.cmake)
include(CMake/AddSDL2_Image.cmake)
include(CMake/AddSpdlog.cmake)
include(CMake/CompilerWarnings.cmake)
include(CMake/StandardProjectSettings.cmake)
set(CMAKE_CXX_STANDARD 20)
# Library directory
set(LIBS_DIR ${CMAKE_SOURCE_DIR}/libs CACHE STRING "Library directory")
# Dependency versions
set(FREETYPE_VERSION "2.13.1" CACHE STRING "FreeType version number")
set(GLEW_VERSION "2.1.0" CACHE STRING "glew version number")
set(GLM_VERSION "0.9.9.8" CACHE STRING "GLM version number")
set(JSON_VERSION "3.11.2" CACHE STRING "JSON version number")
set(OPENAL_VERSION "1.23.1" CACHE STRING "OpenAL version number")
set(RTMIDI_VERSION "5.0.0" CACHE STRING "RtMidi version number")
set(SDL2_VERSION "2.28.0" CACHE STRING "SDL2 version number")
set(SDL2_IMAGE_VERSION "2.6.3" CACHE STRING "SDL2_Image version number")
set(SPDLOG_VERSION "1.12.0" CACHE STRING "spdlog version number")
# This is seemingly an empty interface which all projects depend on... remove?
add_library(project_options INTERFACE)
# Option specifications
option(ENABLE_TESTING "Enable Test Builds" OFF)
# Projects can link with this "library" to use the warnings
# specified in CompilerWarnings.cmake
add_library(project_warnings INTERFACE)
set_project_warnings(project_warnings)
# Add projects
add_subdirectory(projects/Open-Rival)
add_subdirectory(projects/Open-Rival-test)
add_subdirectory(projects/setup)
add_subdirectory(projects/audio-extractor)
add_subdirectory(projects/campaign-extractor)
add_subdirectory(projects/image-extractor)
add_subdirectory(projects/interface-extractor)
add_subdirectory(projects/texture-builder)
# Set start-up project for Visual Studio
set(VS_STARTUP_PROJECT, Open-Rival)
CMake/AddFreetype.cmake
#
# Adds a dependency on Freetype to a project.
#
function(add_freetype project_name)
# Detect 32- vs 64-bit systems
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_freetype_lib_suffix "objs/x64")
else()
set(_freetype_lib_suffix "objs/Win32")
endif()
# Include directory
target_include_directories(${project_name} PRIVATE
"${LIBS_DIR}/freetype-${FREETYPE_VERSION}/include"
)
# Library directory
if(WIN32)
target_link_directories(${project_name} PUBLIC
"$<$<CONFIG:Debug>:${LIBS_DIR}/freetype-${FREETYPE_VERSION}/${_freetype_lib_suffix}/Debug>"
"$<$<CONFIG:Release>:${LIBS_DIR}/freetype-${FREETYPE_VERSION}/${_freetype_lib_suffix}/Release>"
)
else()
message(STATUS "Operating system not (yet) supported!")
endif()
# Library filename
target_link_libraries(${project_name} PRIVATE
freetype
)
endfunction()
projects/Open-Rival/CMakeLists.txt
cmake_minimum_required (VERSION 3.16)
# Pull in auto-generated list of source/header files
set(OPEN_RIVAL_DIR ${CMAKE_CURRENT_LIST_DIR})
include(${CMAKE_SOURCE_DIR}/CMake/OpenRivalFiles.cmake)
# Not required for compilation, but helpful to have at hand
set(OPEN_RIVAL_DOCS
${CMAKE_SOURCE_DIR}/dist/docs/changelog.txt
${CMAKE_SOURCE_DIR}/docs/TODO.md
)
# Not required for compilation, but helpful to have at hand
set(OPEN_RIVAL_EXTRA_FILES
${CMAKE_SOURCE_DIR}/projects/.clang-format
)
set(OPEN_RIVAL_INCLUDE_DIRECTORIES
${CMAKE_CURRENT_LIST_DIR}/
${CMAKE_CURRENT_LIST_DIR}/include
)
set(OPEN_RIVAL_RESOURCES
${CMAKE_CURRENT_LIST_DIR}/Open-Rival.rc
${CMAKE_CURRENT_LIST_DIR}/resource.h
)
# Organise files into folders (filters)
source_group(TREE ${CMAKE_CURRENT_LIST_DIR}/src PREFIX "Source Files" FILES ${OPEN_RIVAL_SOURCES})
source_group(TREE ${CMAKE_CURRENT_LIST_DIR}/include PREFIX "Source Files" FILES ${OPEN_RIVAL_HEADERS})
source_group(TREE ${CMAKE_CURRENT_LIST_DIR} PREFIX "Resources" FILES ${OPEN_RIVAL_RESOURCES})
source_group("Docs" FILES ${OPEN_RIVAL_DOCS})
# Creates the executable including all the sources and resources
add_executable(Open-Rival
${OPEN_RIVAL_SOURCES}
${OPEN_RIVAL_HEADERS}
${OPEN_RIVAL_RESOURCES}
${OPEN_RIVAL_DOCS}
${OPEN_RIVAL_EXTRA_FILES}
)
# Add the directories and headers of the project
target_include_directories(Open-Rival PRIVATE
${OPEN_RIVAL_INCLUDE_DIRECTORIES}
)
# Dependencies
add_freetype(Open-Rival)
add_glew(Open-Rival)
add_glm(Open-Rival)
add_json(Open-Rival)
add_openal(Open-Rival)
add_rtmidi(Open-Rival)
add_sdl2(Open-Rival)
add_sdl2_image(Open-Rival)
add_spdlog(Open-Rival)
# Adds the libraries it must link against to find the symbols
target_link_libraries(Open-Rival PRIVATE
winmm
Ws2_32
project_options
project_warnings
)
# Pre-compiled headers
target_precompile_headers(Open-Rival PRIVATE
<cstdint>
<memory>
<stdexcept>
<string>
<vector>
)
(Other files are not particularly important but can be found in the project repo.)