Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MicrosoftDocs/cpp-docs/llms.txt

Use this file to discover all available pages before exploring further.

CMake is the de facto standard build system for cross-platform C++ development, and Visual Studio has first-class CMake support built in. When you open any folder containing a CMakeLists.txt, Visual Studio automatically configures IntelliSense, parses build targets, and offers a rich editing and debugging experience — with no .sln or .vcxproj files required. The modern CMakePresets.json workflow lets you define named configurations for every platform and share them with your team through source control.

The CMakePresets.json Workflow

CMakePresets.json (introduced in CMake 3.19) defines named configure, build, and test presets that work identically on the command line, in Visual Studio, and in CI pipelines. Place the file at the root of your repository.
{
  "version": 6,
  "configurePresets": [
    {
      "name": "windows-base",
      "hidden": true,
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/out/build/${presetName}",
      "installDir": "${sourceDir}/out/install/${presetName}",
      "cacheVariables": {
        "CMAKE_C_COMPILER":   "cl.exe",
        "CMAKE_CXX_COMPILER": "cl.exe"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Windows"
      }
    },
    {
      "name": "windows-debug",
      "displayName": "Windows x64 Debug (MSVC)",
      "inherits": "windows-base",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug"
      }
    },
    {
      "name": "windows-release",
      "displayName": "Windows x64 Release (MSVC)",
      "inherits": "windows-base",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release"
      }
    },
    {
      "name": "linux-debug",
      "displayName": "Linux x64 Debug (GCC)",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/out/build/${presetName}",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE":   "Debug",
        "CMAKE_C_COMPILER":   "gcc",
        "CMAKE_CXX_COMPILER": "g++"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Linux"
      }
    },
    {
      "name": "linux-clang-debug",
      "displayName": "Linux x64 Debug (Clang)",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/out/build/${presetName}",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE":   "Debug",
        "CMAKE_C_COMPILER":   "clang",
        "CMAKE_CXX_COMPILER": "clang++"
      }
    },
    {
      "name": "arm-embedded",
      "displayName": "ARM Cortex-M4 (arm-none-eabi)",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/out/build/${presetName}",
      "toolchainFile": "${sourceDir}/cmake/arm-none-eabi.cmake",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "MinSizeRel"
      }
    }
  ],
  "buildPresets": [
    { "name": "windows-debug",   "configurePreset": "windows-debug"   },
    { "name": "windows-release", "configurePreset": "windows-release" },
    { "name": "linux-debug",     "configurePreset": "linux-debug"     },
    { "name": "arm-embedded",    "configurePreset": "arm-embedded"    }
  ],
  "testPresets": [
    {
      "name": "windows-debug",
      "configurePreset": "windows-debug",
      "output": { "outputOnFailure": true }
    }
  ]
}
In Visual Studio, these presets appear in the Configuration dropdown in the toolbar. Switching presets reconfigures the CMake cache, updates IntelliSense, and retargets the build — all without leaving the IDE.

Conditional Platform Logic in CMakeLists.txt

A well-structured CMakeLists.txt uses CMAKE_SYSTEM_NAME and compiler-detection variables to apply platform-specific settings while keeping the common build logic in one place:
cmake_minimum_required(VERSION 3.21)
project(CrossPlatformLib VERSION 2.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)   # Enforce standard C++, no GNU extensions

# ── Source files ────────────────────────────────────────────────────────────
set(SOURCES
    src/core.cpp
    src/parser.cpp
    src/network.cpp
)

# Platform-specific sources
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    list(APPEND SOURCES src/platform/windows_io.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    list(APPEND SOURCES src/platform/linux_io.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    list(APPEND SOURCES src/platform/macos_io.cpp)
endif()

add_library(crossplatformlib STATIC ${SOURCES})
target_include_directories(crossplatformlib PUBLIC include)

# ── Compiler-specific flags ──────────────────────────────────────────────────
if(MSVC)
    target_compile_options(crossplatformlib PRIVATE
        /W4             # High warning level
        /WX             # Warnings as errors
        /GS             # Buffer security check
        /guard:cf       # Control Flow Guard
        /sdl            # SDL security checks
        /permissive-    # Strict standards conformance
    )
    target_link_options(crossplatformlib INTERFACE
        /DYNAMICBASE /NXCOMPAT /guard:cf
    )
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
    target_compile_options(crossplatformlib PRIVATE
        -Wall -Wextra -Wpedantic
        -Werror
        -fstack-protector-strong
        $<$<CONFIG:Debug>:-g -O0>
        $<$<CONFIG:Release>:-O2 -DNDEBUG>
    )
endif()

# ── Platform-specific libraries ──────────────────────────────────────────────
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    target_link_libraries(crossplatformlib PRIVATE ws2_32 advapi32)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    target_link_libraries(crossplatformlib PRIVATE pthread dl)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    find_library(CORE_FOUNDATION CoreFoundation REQUIRED)
    target_link_libraries(crossplatformlib PRIVATE ${CORE_FOUNDATION})
endif()

# ── Feature detection ────────────────────────────────────────────────────────
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(std::filesystem::exists "filesystem" HAS_STD_FILESYSTEM)
if(HAS_STD_FILESYSTEM)
    target_compile_definitions(crossplatformlib PRIVATE HAS_FILESYSTEM=1)
endif()

Toolchain Files for Cross-Compilation

When building for a different CPU architecture — such as ARM for embedded systems or an Android target — CMake uses a toolchain file (-DCMAKE_TOOLCHAIN_FILE=...) to set the cross-compiler, sysroot, and system name. Visual Studio passes the toolchain file through the preset’s toolchainFile field.

Example: ARM Cortex-M4 Toolchain File

# cmake/arm-none-eabi.cmake

set(CMAKE_SYSTEM_NAME  Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

# Specify the cross-compiler
set(CMAKE_C_COMPILER   arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY      arm-none-eabi-objcopy)
set(CMAKE_SIZE         arm-none-eabi-size)

# Don't try to link executables when probing the compiler
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

# Target CPU flags for Cortex-M4 with FPU
set(CPU_FLAGS "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")

set(CMAKE_C_FLAGS_INIT   "${CPU_FLAGS}")
set(CMAKE_CXX_FLAGS_INIT "${CPU_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS_INIT "${CPU_FLAGS} -specs=nosys.specs")

# Find programs in the host system, not the target sysroot
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Reference this toolchain from your preset:
{
  "name": "arm-cortex-m4",
  "generator": "Ninja",
  "binaryDir": "${sourceDir}/out/build/arm-cortex-m4",
  "toolchainFile": "${sourceDir}/cmake/arm-none-eabi.cmake",
  "cacheVariables": {
    "CMAKE_BUILD_TYPE": "MinSizeRel"
  }
}

Switching Between MSVC, Clang, and GCC in Visual Studio

MSVC is selected when CMAKE_CXX_COMPILER is cl.exe. Use it for Windows targets with full Visual Studio debugger integration:
"cacheVariables": {
  "CMAKE_C_COMPILER":   "cl.exe",
  "CMAKE_CXX_COMPILER": "cl.exe"
}

Using Generator Expressions for Per-Config Settings

CMake generator expressions ($<...>) evaluate at build time, allowing per-configuration logic without if-statements:
target_compile_options(myapp PRIVATE
    # Debug builds: no optimization, full debug info
    $<$<CONFIG:Debug>:-O0 -g>

    # Release builds: full optimization, strip asserts
    $<$<CONFIG:Release>:-O3 -DNDEBUG>

    # MSVC only: enable whole program optimization in release
    $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<CONFIG:Release>>:/GL>

    # Clang/GCC only: enable LTO in release
    $<$<AND:$<NOT:$<CXX_COMPILER_ID:MSVC>>,$<CONFIG:Release>>:-flto>
)

Running the Same Build from the Command Line

One of the key advantages of CMakePresets.json is that your CI pipeline uses the exact same preset names as your IDE:
# Configure
cmake --preset windows-release

# Build
cmake --build --preset windows-release

# Test
ctest --preset windows-release --output-on-failure

# Install
cmake --install out/build/windows-release --prefix /opt/myapp
Commit both CMakePresets.json and CMakeUserPresets.json conventions to your repo. The CMakePresets.json file holds shared configurations checked into source control. Individual developers add machine-specific overrides in CMakeUserPresets.json (which should be .gitignored).

vcpkg Integration for Cross-Platform Dependencies

vcpkg integrates natively with CMake presets to manage C++ library dependencies across platforms. Add the vcpkg toolchain file to your preset:
{
  "name": "windows-debug",
  "inherits": "windows-base",
  "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
  "cacheVariables": {
    "CMAKE_BUILD_TYPE": "Debug",
    "VCPKG_TARGET_TRIPLET": "x64-windows"
  }
}
Create a vcpkg.json manifest at the repo root to declare dependencies:
{
  "name": "my-cross-platform-app",
  "version": "1.0.0",
  "dependencies": [
    "boost-filesystem",
    "nlohmann-json",
    "zlib"
  ]
}
vcpkg automatically builds dependencies for each target triplet (x64-windows, x64-linux, arm64-linux, etc.) so the same CMakeLists.txt finds libraries on every platform.

Build docs developers (and LLMs) love