ext-SDL/docs/README-cmake.md
Ryan C. Gordon 16535b7ee2
examples: Add CMake glue to optionally build examples, as we do for the tests.
In fact, most of the CMake code was stolen from the test directory.
2024-07-23 00:58:57 -04:00

17 KiB

CMake

www.cmake.org

The CMake build system is supported on the following platforms:

  • FreeBSD
  • Linux
  • Microsoft Visual C
  • MinGW and Msys
  • macOS, iOS, tvOS, and visionOS with support for XCode
  • Android
  • Emscripten
  • NetBSD
  • Haiku
  • Nintendo 3DS
  • PlayStation 2
  • PlayStation Portable
  • PlayStation Vita
  • QNX 7.x/8.x
  • RiscOS

Building SDL

Assuming the source tree of SDL is located at ~/sdl, this will configure and build SDL in the ~/build directory:

cmake -S ~/sdl -B ~/build
cmake --build ~/build

Installation can be done using:

cmake --install ~/build --prefix /usr/local        # '--install' requires CMake 3.15, or newer

This will install SDL to /usr/local.

Building SDL tests

You can build the SDL test programs by adding -DSDL_TESTS=ON to the first cmake command above:

cmake -S ~/sdl -B ~/build -DSDL_TEST_LIBRARY=ON -DSDL_TESTS=ON

and then building normally. In this example, the test programs will be built and can be run from ~/build/tests/.

Building SDL examples

You can build the SDL example programs by adding -DSDL_EXAMPLES=ON to the first cmake command above:

cmake -S ~/sdl -B ~/build -DSDL_EXAMPLES=ON

and then building normally. In this example, the example programs will be built and can be run from ~/build/examples/.

Including SDL in your project

SDL can be included in your project in 2 major ways:

  • using a system SDL library, provided by your (*nix) distribution or a package manager
  • using a vendored SDL library: this is SDL copied or symlinked in a subfolder.

The following CMake script supports both, depending on the value of MYGAME_VENDORED.

cmake_minimum_required(VERSION 3.5)
project(mygame)

# Create an option to switch between a system sdl library and a vendored SDL library
option(MYGAME_VENDORED "Use vendored libraries" OFF)

if(MYGAME_VENDORED)
    # This assumes you have added SDL as a submodule in vendored/SDL
    add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL)
else()
    # 1. Look for a SDL3 package,
    # 2. look for the SDL3-shared component, and
    # 3. fail if the shared component cannot be found.
    find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared)
endif()

# Create your game executable target as usual
add_executable(mygame WIN32 mygame.c)

# Link to the actual SDL3 library.
target_link_libraries(mygame PRIVATE SDL3::SDL3)

A system SDL library

For CMake to find SDL, it must be installed in a default location CMake is looking for.

The following components are available, to be used as an argument of find_package.

Component name Description
SDL3-shared The SDL3 shared library, available through the SDL3::SDL3-shared target
SDL3-static The SDL3 static library, available through the SDL3::SDL3-static target
SDL3_test The SDL3_test static library, available through the SDL3::SDL3_test target
SDL3 The SDL3 library, available through the SDL3::SDL3 target. This is an alias of SDL3::SDL3-shared or SDL3::SDL3-static. This component is always available.
Headers The SDL3 headers, available through the SDL3::Headers target. This component is always available.

SDL's CMake support guarantees a SDL3::SDL3 target. Neither SDL3::SDL3-shared nor SDL3::SDL3-static are guaranteed to exist.

Using a vendored SDL

This only requires a copy of SDL in a subdirectory + add_subdirectory. Alternatively, use FetchContent. Depending on the configuration, the same targets as a system SDL package are available.

CMake configuration options

Build optimized library

By default, CMake provides 4 build types: Debug, Release, RelWithDebInfo and MinSizeRel. The main difference(s) between these are the optimization options and the generation of debug info. To configure SDL as an optimized Release library, configure SDL with:

cmake ~/SDL -DCMAKE_BUILD_TYPE=Release

To build it, run:

cmake --build . --config Release

Shared or static

By default, only a dynamic (=shared) SDL library is built and installed. The options -DSDL_SHARED= and -DSDL_STATIC= accept boolean values to change this.

Exceptions exist:

  • some platforms don't support dynamic libraries, so only -DSDL_STATIC=ON makes sense.
  • a static Apple framework is not supported

Pass custom compile options to the compiler

Examples

  • build a SDL library optimized for (more) modern x64 microprocessor architectures.

    With gcc or clang:

    cmake ~/sdl -DCMAKE_C_FLAGS="-march=x86-64-v3" -DCMAKE_CXX_FLAGS="-march=x86-64-v3"
    

    With Visual C:

    cmake .. -DCMAKE_C_FLAGS="/ARCH:AVX2" -DCMAKE_CXX_FLAGS="/ARCH:AVX2"
    

Apple

CMake documentation for cross building for Apple: link

iOS/tvOS/visionOS

CMake 3.14+ natively includes support for iOS, tvOS and watchOS. visionOS requires CMake 3.28+. SDL binaries may be built using Xcode or Make, possibly among other build-systems.

When using a compatible version of CMake, it should be possible to:

  • build SDL dylibs, both static and dynamic dylibs
  • build SDL frameworks, only shared
  • build SDL test apps

Frameworks

Configure with -DSDL_FRAMEWORK=ON to build a SDL framework instead of a dylib shared library. Only shared frameworks are supported, no static ones.

Platforms

Use -DCMAKE_PLATFORM_NAME=<value> to configure the platform. CMake can target only one platform at a time.

Apple platform CMAKE_SYSTEM_NAME value
macOS (MacOS X) Darwin
iOS iOS
tvOS tvOS
visionOS visionOS
watchOS watchOS

Universal binaries

A universal binaries, can be built by configuring CMake with -DCMAKE_OSX_ARCHITECTURES=<semicolon-separated list of CPU architectures>.

For example -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" will build binaries that run on both Intel cpus and Apple silicon.

SDL supports following Apple architectures:

Platform CMAKE_OSX_ARCHITECTURES value
64-bit ARM (Apple Silicon) arm64
x86_64 x86_64
32-bit ARM armv7s

CMake documentation: link

Simulators and/or non-default maxOS platform SDK

Use -DCMAKE_OSX_SYSROOT=<value> to configure a different platform SDK. The value can be either the name of the SDK, or a full path to the sdk (e.g. /full/path/to/iPhoneOS.sdk).

SDK CMAKE_OSX_SYSROOT value
iphone iphoneos
iphonesimulator iphonesimulator
appleTV appletvos
appleTV simulator appletvsimulator
visionOS xr
visionOS simulator xrsimulator
watchOS watchos
watchOS simulator watchsimulator

Append with a version number to target a specific SDK revision: e.g. iphoneos12.4, appletvos12.4.

CMake documentation: link

Examples

  • for macOS, building a dylib and/or static library for x86_64 and arm64:

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
    
    
  • for macOS, building an universal framework for x86_64 and arm64:

    cmake ~/sdl -DSDL_FRAMEWORK=ON -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
    
    
  • for iOS-Simulator, using the latest, installed SDK:

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64
    
  • for iOS-Device, using the latest, installed SDK, 64-bit only

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES=arm64
    
  • for iOS-Device, using the latest, installed SDK, mixed 32/64 bit

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES="arm64;armv7s"
    
  • for iOS-Device, using a specific SDK revision (iOS 12.4, in this example):

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos12.4 -DCMAKE_OSX_ARCHITECTURES=arm64
    
  • for iOS-Simulator, using the latest, installed SDK, and building SDL test apps (as .app bundles):

    cmake ~/sdl -DSDL_TESTS=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64
    
  • for tvOS-Simulator, using the latest, installed SDK:

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvsimulator -DCMAKE_OSX_ARCHITECTURES=x86_64
    
  • for tvOS-Device, using the latest, installed SDK:

    cmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64`
    
  • for QNX/aarch64, using the latest, installed SDK:

    cmake ~/sdl -DCMAKE_TOOLCHAIN_FILE=~/sdl/build-scripts/cmake-toolchain-qnx-aarch64le.cmake -DSDL_X11=0
    

SDL-specific CMake options

SDL can be customized through (platform-specific) CMake options. The following table shows generic options that are available for most platforms. At the end of SDL CMake configuration, a table shows all CMake options along with its detected value.

CMake option Valid values Description
-DSDL_SHARED= ON/OFF Build SDL shared library (not all platforms support this) (libSDL3.so/libSDL3.dylib/SDL3.dll)
-DSDL_STATIC= ON/OFF Build SDL static library (libSDL3.a/SDL3-static.lib)
-DSDL_TEST_LIBRARY= ON/OFF Build SDL test library (libSDL3_test.a/SDL3_test.lib)
-DSDL_TESTS= ON/OFF Build SDL test programs (requires -DSDL_TEST_LIBRARY=ON)
-DSDL_DISABLE_INSTALL= ON/OFF Don't create a SDL install target
-DSDL_DISABLE_INSTALL_DOCS= ON/OFF Don't install the SDL documentation
-DSDL_INSTALL_TESTS= ON/OFF Install the SDL test programs

CMake FAQ

How do I copy a SDL3 dynamic library to another location?

Use CMake generator expressions. Generator expressions support multiple configurations, and are evaluated during build system generation time.

On Windows, the following example this copies SDL3.dll to the directory where mygame.exe is built. On Unix systems, $<TARGET_FILE:...> will refer to the dynamic library (or framework).

if(WIN32)
    add_custom_command(
        TARGET mygame POST_BUILD
        COMMAND "${CMAKE_COMMAND}" -E copy $<TARGET_FILE:SDL3::SDL3-shared> $<TARGET_FILE_DIR:mygame>
        VERBATIM
    )
endif()

Linking against a static SDL library fails due to relocation errors

On unix platforms, all code that ends up in shared libraries needs to be built as relocatable (=position independent) code. However, by default CMake builds static libraries as non-relocatable. Configuring SDL with -DCMAKE_POSITION_INDEPENDENT_CODE=ON will result in a static libSDL3.a library which you can link against to create a shared library.

Help, it doesn't work!

Below, a SDL3 CMake project can be found that builds 99.9% of time (assuming you have internet connectivity). When you have a problem with building or using SDL, please modify it until it reproduces your issue.

cmake_minimum_required(VERSION 3.16)
project(sdl_issue)

# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!                                                                            !!!!!!
# !!!!!!     This CMake script is not using "CMake best practices".                 !!!!!!
# !!!!!!                 Don't use it in your project.                              !!!!!!
# !!!!!!                                                                            !!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

# 1. Try system SDL3 package first
find_package(SDL3 QUIET)
if(SDL3_FOUND)
    message(STATUS "Using SDL3 via find_package")
endif()

# 2. Try using a vendored SDL library
if(NOT SDL3_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL/CMakeLists.txt")
    add_subdirectory(SDL EXCLUDE_FROM_ALL)
    message(STATUS "Using SDL3 via add_subdirectory")
    set(SDL3_FOUND TRUE)
endif()

# 3. Download SDL, and use that.
if(NOT SDL3_FOUND)
    include(FetchContent)
    set(SDL_SHARED TRUE CACHE BOOL "Build a SDL shared library (if available)")
    set(SDL_STATIC TRUE CACHE BOOL "Build a SDL static library (if available)")
    FetchContent_Declare(
        SDL
        GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
        GIT_TAG main  # Replace this with a particular git tag or git hash
        GIT_SHALLOW TRUE
        GIT_PROGRESS TRUE
    )
    message(STATUS "Using SDL3 via FetchContent")
    FetchContent_MakeAvailable(SDL)
    set_property(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_deps/sdl-src" PROPERTY EXCLUDE_FROM_ALL TRUE)
endif()

file(WRITE main.c [===========================================[
/**
 * Modify this source such that it reproduces your problem.
 */

/* START of source modifications */

#include <SDL3/SDL.h>
/*
 * SDL3/SDL_main.h is explicitly not included such that a terminal window would appear on Windows.
 */

int main(int argc, char *argv[]) {
    (void)argc;
    (void)argv;

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        SDL_Log("SDL_Init failed (%s)", SDL_GetError());
        return 1;
    }

    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;

    if (SDL_CreateWindowAndRenderer("SDL issue", 640, 480, 0, &window, &renderer) < 0) {
        SDL_Log("SDL_CreateWindowAndRenderer failed (%s)", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    while (1) {
        int finished = 0;
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_EVENT_QUIT) {
                finished = 1;
                break;
            }
        }
        if (finished) {
            break;
        }

        SDL_SetRenderDrawColor(renderer, 80, 80, 80, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();
    return 0;
}

/* END of source modifications */

]===========================================])

add_executable(sdl_issue main.c)

target_link_libraries(sdl_issue PRIVATE SDL3::SDL3)
# target_link_libraries(sdl_issue PRIVATE SDL3::SDL3-shared)
# target_link_libraries(sdl_issue PRIVATE SDL3::SDL3-static)