531 lines
27 KiB
CMake
531 lines
27 KiB
CMake
# =============================================================================
|
|
# breeze/cmake/Breeze.cmake
|
|
#
|
|
# The build system itself: what the template contributes to a project, from
|
|
# the C++ standard and the output directories to module discovery and the
|
|
# install rules. Included by the project's CMakeLists.txt right after
|
|
# project(); the project's own settings are in project/Configuration.cmake (read
|
|
# here, through CMakeLists.txt having included it). Replacing breeze/ updates
|
|
# the template without touching either.
|
|
# =============================================================================
|
|
|
|
cmake_minimum_required(VERSION 3.25)
|
|
|
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
|
|
# This project's settings (PROJECT_CODE_NAME, DEFAULT_BUILD_TYPE, the output paths
|
|
# and the bin/lib subdirectory names) come from project/Configuration.cmake --
|
|
# already included by CMakeLists.txt, before project(); this is a no-op unless
|
|
# something includes this file on its own. The target selection
|
|
# (DIST_TARGET/PLATFORM/ARCHITECTURE/COMPILER_ID) is TargetSelection.cmake: it
|
|
# needs real compiler detection, which is why it comes after project(), and
|
|
# everything below depends on it. Edit project/Configuration.cmake, or pass
|
|
# -D<NAME>=... at configure time, rather than hardcoding a value it already owns
|
|
# anywhere else in this project, scripts included.
|
|
# Where the template is, and where the project that uses it is -- found from this
|
|
# file's own location rather than from CMAKE_SOURCE_DIR, which is the outermost
|
|
# project's when this one is embedded with add_subdirectory().
|
|
set(PMN_BREEZE_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
|
get_filename_component(PMN_PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
|
|
include("${PMN_PROJECT_ROOT}/project/Configuration.cmake")
|
|
|
|
# The contract with project/Configuration.cmake: these are the settings the template
|
|
# reads. A project file from an older template version may lack one that a newer
|
|
# version added -- say which, instead of failing somewhere downstream on an empty value.
|
|
foreach(_pmn_setting
|
|
PROJECT_CODE_NAME DEFAULT_BUILD_TYPE
|
|
OUT_DIR BUILD_OUTPUT_DIR INSTALL_OUTPUT_DIR DOCS_OUTPUT_DIR DIST_OUTPUT_DIR
|
|
TOOLCACHE_DIR COMPILE_COMMANDS_DESTINATION
|
|
RUNTIME_OUTPUT_SUBDIR LIBRARY_OUTPUT_SUBDIR ARCHIVE_OUTPUT_SUBDIR)
|
|
if(NOT DEFINED ${_pmn_setting})
|
|
message(FATAL_ERROR
|
|
"project/Configuration.cmake does not define ${_pmn_setting}, which this version "
|
|
"of breeze/ needs. Add it: a fresh copy of the template's project/Configuration.cmake "
|
|
"shows the default.")
|
|
endif()
|
|
endforeach()
|
|
include("${CMAKE_CURRENT_LIST_DIR}/TargetSelection.cmake")
|
|
include("${CMAKE_CURRENT_LIST_DIR}/GitignoreSync.cmake")
|
|
|
|
# The language standard is the project's choice (project/Configuration.cmake);
|
|
# requiring it and turning compiler extensions off is the template's policy.
|
|
if(NOT DEFINED PROJECT_CXX_STANDARD)
|
|
set(PROJECT_CXX_STANDARD 20)
|
|
endif()
|
|
set(CMAKE_CXX_STANDARD ${PROJECT_CXX_STANDARD})
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
|
|
|
|
# This project uses C++20 but not C++20 modules. CMake >= 3.28 nevertheless
|
|
# assumes C++20 sources might `import` modules and scans every one of them
|
|
# before compiling (policy CMP0155) -- in any directory whose own
|
|
# cmake_minimum_required is >= 3.28, which includes fetched dependencies
|
|
# like fmt even though this project's own minimum is lower. The scan needs
|
|
# a scanner tool (clang-scan-deps for Clang, which e.g. Ubuntu's plain
|
|
# `clang` package doesn't ship, so the build dies with
|
|
# "CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND") and adds a step to every
|
|
# compile. This variable is CMake's documented opt-out; it is inherited by
|
|
# every target created below, third-party ones included. If you start using
|
|
# modules, remove it (or set the CXX_SCAN_FOR_MODULES property on just the
|
|
# targets that need it).
|
|
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
|
|
|
# Every static library in the project gets built with -fPIC by default --
|
|
# including vendored/fetched ones, whose own CMakeLists.txt we don't
|
|
# control and can't rely on to opt in themselves. Without this, a static
|
|
# dependency (ours or third-party) fails to link the moment it ends up
|
|
# pulled into a SHARED module: "recompile with -fPIC". Cheap on every
|
|
# platform that matters here; add_lib_module's STATIC branch also sets
|
|
# this per-target as a belt-and-braces fallback.
|
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
|
|
# -------------------------------------------------------
|
|
# Generator guard -- require Ninja (single or multi-config)
|
|
# -------------------------------------------------------
|
|
if(NOT CMAKE_GENERATOR MATCHES "Ninja")
|
|
message(WARNING
|
|
"This project targets Ninja. "
|
|
"Current generator: '${CMAKE_GENERATOR}'. "
|
|
"Reconfigure with: cmake -G Ninja or cmake -G \"Ninja Multi-Config\"")
|
|
endif()
|
|
|
|
# Single-config Ninja needs CMAKE_BUILD_TYPE at configure time.
|
|
# Ninja Multi-Config uses CMAKE_CONFIGURATION_TYPES instead (no BUILD_TYPE needed).
|
|
if(CMAKE_GENERATOR STREQUAL "Ninja")
|
|
if(NOT CMAKE_BUILD_TYPE)
|
|
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING
|
|
"Build type (Debug|Release|RelWithDebInfo|MinSizeRel)" FORCE)
|
|
message(STATUS "CMAKE_BUILD_TYPE not set -- defaulting to ${DEFAULT_BUILD_TYPE}")
|
|
endif()
|
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
|
Debug Release RelWithDebInfo MinSizeRel)
|
|
endif()
|
|
|
|
# -------------------------------------------------------
|
|
# Flat output layout
|
|
# Multi-config generators (Ninja Multi-Config) append /<Config> by default.
|
|
# Override all four configs so both Ninja variants produce the same layout.
|
|
# Subdirectory names come from project/Configuration.cmake (RUNTIME_OUTPUT_SUBDIR
|
|
# etc.); the base directory is always CMAKE_BINARY_DIR, wherever -B points
|
|
# (breeze/scripts/build.sh uses out/build/<preset> -- see project/Configuration.cmake).
|
|
# -------------------------------------------------------
|
|
foreach(_cfg Debug Release RelWithDebInfo MinSizeRel)
|
|
string(TOUPPER "${_cfg}" _CFG)
|
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${_CFG} "${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}")
|
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${_CFG} "${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}")
|
|
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${_CFG} "${CMAKE_BINARY_DIR}/${ARCHIVE_OUTPUT_SUBDIR}")
|
|
endforeach()
|
|
|
|
# -------------------------------------------------------
|
|
# RPATH strategy
|
|
# By default CMake builds with a temporary RPATH and relinks at install time
|
|
# to embed the final one. Ninja on Linux with Clang does not support that
|
|
# relink step and will error out. Setting this ON tells CMake to embed the
|
|
# install RPATH at build time so no relink is ever needed.
|
|
# This is safe here because all our RPATHs are relative to the binary that
|
|
# carries them ($ORIGIN on Linux, @loader_path on macOS -- see
|
|
# _apply_rpath in breeze/cmake/BuildHelpers.cmake), so the same value is correct
|
|
# for both the build tree and the installed layout. Guarded to non-Windows
|
|
# since RPATH is an ELF/Mach-O concept only. (On macOS the same setting also
|
|
# spares CMake from rewriting load commands with install_name_tool at
|
|
# install time.)
|
|
# -------------------------------------------------------
|
|
if(NOT WIN32)
|
|
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
|
|
endif()
|
|
|
|
# -------------------------------------------------------
|
|
# options
|
|
# -------------------------------------------------------
|
|
option(BUILD_TESTS "Build test binaries" OFF)
|
|
option(BUILD_EXAMPLES "Build example binaries" OFF)
|
|
option(INSTALL_TESTS "Install test binaries" OFF)
|
|
option(INSTALL_EXAMPLES "Install example binaries" OFF)
|
|
|
|
# Project-wide default for add_lib_module() when a module doesn't pass
|
|
# TYPE explicitly. This is CMake's own BUILD_SHARED_LIBS variable, so it
|
|
# also affects any third-party add_library() calls (FetchContent, etc.)
|
|
# that respect it. Override per-module with add_lib_module(... TYPE
|
|
# STATIC|SHARED ...), or globally with -DBUILD_SHARED_LIBS=ON.
|
|
option(BUILD_SHARED_LIBS "Build libraries as SHARED by default" OFF)
|
|
|
|
# INSTALL_* implies BUILD_* so targets exist when cmake --install runs
|
|
if(INSTALL_TESTS)
|
|
set(BUILD_TESTS ON CACHE BOOL "" FORCE)
|
|
endif()
|
|
if(INSTALL_EXAMPLES)
|
|
set(BUILD_EXAMPLES ON CACHE BOOL "" FORCE)
|
|
endif()
|
|
|
|
message(STATUS "BUILD_TESTS = ${BUILD_TESTS}")
|
|
message(STATUS "BUILD_EXAMPLES = ${BUILD_EXAMPLES}")
|
|
message(STATUS "BUILD_SHARED_LIBS (default lib TYPE) = ${BUILD_SHARED_LIBS}")
|
|
|
|
# -------------------------------------------------------
|
|
# Global compiler settings
|
|
#
|
|
# MSVC is TRUE for both cl.exe and clang-cl (windows-x64-msvc and
|
|
# windows-x64-clang-cl -- Clang targeting the MSVC ABI), so the outer
|
|
# block covers both with the flags they share. Two flags are cl.exe-only,
|
|
# though: clang-cl doesn't implement them and reports "argument unused
|
|
# during compilation" for every translation unit, so they're gated on the
|
|
# compiler ID (MSVC == cl.exe; clang-cl reports Clang) rather than on
|
|
# the MSVC variable.
|
|
#
|
|
# Shared by cl.exe and clang-cl:
|
|
# /EHsc -- standard C++ exception handling
|
|
# /utf-8 -- UTF-8 source + execution charset
|
|
# cl.exe only:
|
|
# /MP -- parallel file compilation within one compiler
|
|
# invocation (Ninja also parallelizes across
|
|
# invocations; clang-cl ignores this)
|
|
# /Zc:preprocessor -- conformant preprocessor (fixes __VA_ARGS__ etc.);
|
|
# clang's preprocessor is always conformant
|
|
# -------------------------------------------------------
|
|
if(MSVC)
|
|
add_compile_options(/EHsc /utf-8)
|
|
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
|
add_compile_options(/MP /Zc:preprocessor)
|
|
endif()
|
|
endif()
|
|
|
|
# -------------------------------------------------------
|
|
# MinGW runtime linking (windows-x86-mingw32 / windows-x64-mingw64)
|
|
#
|
|
# A MinGW build dynamically links libgcc/libstdc++/(on some toolchains)
|
|
# libwinpthread by default, which then have to ship alongside every
|
|
# binary that needs them -- easy to forget, and the exact DLL names
|
|
# differ between mingw32 and mingw64 and between exception models (SEH
|
|
# on x86_64; DWARF-2 or SJLJ on i686). PMN_MINGW_RUNTIME_DLLS below
|
|
# resolves the actual files by asking the compiler rather than
|
|
# hardcoding names (-print-file-name=NAME echoes NAME back unchanged if
|
|
# it can't find it, which is how "not applicable to this toolchain" is
|
|
# detected), and BuildHelpers.cmake's _copy_mingw_runtime_dlls() copies
|
|
# them next to every EXECUTABLE/SHARED/test/example target that needs
|
|
# them, in the build tree and at install time (the "install" section
|
|
# below) -- so nothing here needs a manual step. MINGW is CMake's
|
|
# built-in equivalent of this project's own COMPILER_ID being "mingw32"
|
|
# or "mingw64".
|
|
#
|
|
# PMN_MINGW_STATIC_RUNTIME (default OFF) is what this project used to do
|
|
# unconditionally: link every EXECUTABLE against a private, static copy
|
|
# of libgcc/libstdc++/winpthread instead (-static-libgcc -static-libstdc++
|
|
# -static -lwinpthread -- EXECUTABLE targets only, via a generator
|
|
# expression, since applying it to a SHARED target collides with
|
|
# WINDOWS_EXPORT_ALL_SYMBOLS: a statically-linked unwinder re-exported
|
|
# from the DLL's own import library and a second static copy in the EXE
|
|
# both trying to define `_Unwind_Resume` is a link error). It trades
|
|
# shipping the runtime DLLs for a standalone .exe -- which is safe only
|
|
# as long as no EXECUTABLE built this way ALSO links a MinGW SHARED
|
|
# module (this project always has one -- core_lib/demo_core.dll) THAT
|
|
# PASSES AN STL TYPE ACROSS THAT BOUNDARY BY VALUE. core_lib's
|
|
# std::string makeGreeting(...) does exactly that: an executable with
|
|
# its own private, statically-linked libstdc++ has its own private
|
|
# allocator, separate from the one the dynamically-linked DLL uses
|
|
# (libstdc++-6.dll's); a std::string built on the DLL side -- heap
|
|
# allocated there once its contents outgrow small-string optimization,
|
|
# which "Hello, World! (from demo_core)" does -- and then destroyed on
|
|
# the executable's side is freed through a DIFFERENT allocator instance
|
|
# than the one that allocated it. That is undefined behavior across any
|
|
# Windows DLL boundary (well documented -- e.g.
|
|
# github.com/msys2/MINGW-packages/issues/5763, a DLL/EXE pair crashing
|
|
# the moment an exception -- which unwinds through the same kind of
|
|
# per-runtime-instance bookkeeping a heap allocator uses -- crosses a
|
|
# static/dynamic runtime mismatch like this one), and on real Windows
|
|
# (confirmed directly: some emulators are considerably more lenient
|
|
# about a cross-heap free() than real Windows is, so a mismatch like
|
|
# this can look fine there and still be live) it reliably crashes
|
|
# shortly after the mismatched value is used -- which is exactly what
|
|
# this project's own demos did while PMN_MINGW_STATIC_RUNTIME was the
|
|
# only option: hello_exe/farewell_exe printed their (still-valid, not
|
|
# yet corrupted) greeting and then crashed destroying the very
|
|
# std::string they had just printed. Turning this option ON is safe
|
|
# again only for a MinGW build with no SHARED module at all, or where
|
|
# every SHARED module's boundary is a plain C API (no STL type crosses
|
|
# it by value) -- e.g. also statically-linking that module and giving
|
|
# it a C entry point avoids the whole class of problem, since it no
|
|
# longer depends on the dynamic runtime at all.
|
|
# -------------------------------------------------------
|
|
option(PMN_MINGW_STATIC_RUNTIME
|
|
"MinGW: statically link libgcc/libstdc++/winpthread into every executable instead of shipping the runtime DLLs next to it. UNSAFE for an executable that also links a MinGW SHARED module (like this project's core_lib) which passes an STL type across that boundary by value -- see the comment above. Default OFF: dynamic linking everywhere, with the runtime DLLs copied automatically."
|
|
OFF)
|
|
|
|
if(MINGW)
|
|
if(PMN_MINGW_STATIC_RUNTIME)
|
|
add_link_options(
|
|
"$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:-static-libgcc;-static-libstdc++;-static;-lwinpthread>"
|
|
)
|
|
endif()
|
|
|
|
set(PMN_MINGW_RUNTIME_DLLS "")
|
|
# -print-file-name=<name>.dll only searches "lib"-shaped paths, where
|
|
# the .dll.a import library lives -- not "bin", where the runtime
|
|
# .dll itself does on every package manager here except apt: asked
|
|
# for the bare .dll on those, it just echoes the name back
|
|
# unresolved, silently leaving it out of this list further down.
|
|
# Confirmed directly against each package's own file listing:
|
|
# pacman -- /usr/<triplet>/bin/<dll> (flat)
|
|
# dnf -- /usr/<triplet>/sys-root/mingw/bin/<dll> (nested)
|
|
# -- and against discussion.fedoraproject.org's "Location of
|
|
# mingw64/mingw32 system libraries like libgcc_s_seh-1.dll and
|
|
# libwinpthread-1.dll" (Oct 2024) for why dnf's -print-file-name
|
|
# specifically comes up empty. On apt, -print-file-name already
|
|
# resolves the .dll directly (confirmed independently in that same
|
|
# thread) -- both fallbacks below only run where that lookup came
|
|
# back empty, so apt is unaffected either way.
|
|
get_filename_component(_pmn_mingw_cxx_name "${CMAKE_CXX_COMPILER}" NAME)
|
|
string(REGEX REPLACE "-g\\+\\+(\\.exe)?$" "" _pmn_mingw_triplet "${_pmn_mingw_cxx_name}")
|
|
execute_process(
|
|
COMMAND "${CMAKE_CXX_COMPILER}" -print-sysroot
|
|
OUTPUT_VARIABLE _pmn_mingw_sysroot
|
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
ERROR_QUIET
|
|
)
|
|
foreach(_pmn_dll_candidate
|
|
libgcc_s_seh-1.dll
|
|
libgcc_s_dw2-1.dll
|
|
libgcc_s_sjlj-1.dll
|
|
libstdc++-6.dll
|
|
libwinpthread-1.dll
|
|
)
|
|
execute_process(
|
|
COMMAND "${CMAKE_CXX_COMPILER}" -print-file-name=${_pmn_dll_candidate}
|
|
OUTPUT_VARIABLE _pmn_resolved_dll
|
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
)
|
|
if(NOT EXISTS "${_pmn_resolved_dll}")
|
|
if(_pmn_mingw_triplet AND EXISTS "/usr/${_pmn_mingw_triplet}/bin/${_pmn_dll_candidate}")
|
|
set(_pmn_resolved_dll "/usr/${_pmn_mingw_triplet}/bin/${_pmn_dll_candidate}")
|
|
elseif(_pmn_mingw_sysroot AND EXISTS "${_pmn_mingw_sysroot}/mingw/bin/${_pmn_dll_candidate}")
|
|
set(_pmn_resolved_dll "${_pmn_mingw_sysroot}/mingw/bin/${_pmn_dll_candidate}")
|
|
endif()
|
|
endif()
|
|
if(EXISTS "${_pmn_resolved_dll}")
|
|
list(APPEND PMN_MINGW_RUNTIME_DLLS "${_pmn_resolved_dll}")
|
|
endif()
|
|
endforeach()
|
|
if(PMN_MINGW_RUNTIME_DLLS)
|
|
if(PMN_MINGW_STATIC_RUNTIME)
|
|
message(STATUS "MinGW runtime DLLs (bundled next to SHARED targets only -- PMN_MINGW_STATIC_RUNTIME is ON): ${PMN_MINGW_RUNTIME_DLLS}")
|
|
else()
|
|
message(STATUS "MinGW runtime DLLs (bundled next to every EXECUTABLE/SHARED target that needs them): ${PMN_MINGW_RUNTIME_DLLS}")
|
|
endif()
|
|
endif()
|
|
endif()
|
|
|
|
# -------------------------------------------------------
|
|
# Build machinery (macros/functions) -- see breeze/cmake/BuildHelpers.cmake.
|
|
# This file should change rarely; it holds nothing project-specific.
|
|
# -------------------------------------------------------
|
|
include("${PMN_BREEZE_CMAKE_DIR}/BuildHelpers.cmake")
|
|
|
|
# -------------------------------------------------------
|
|
# Third-party dependencies -- see breeze/cmake/Dependencies.cmake for
|
|
# add_vendor_header_only / add_vendor_subdirectory / add_dependency.
|
|
# A module declares its own dependencies in its own CMakeLists.txt (see
|
|
# source/Core/CMakeLists.txt for an example); nothing to add here.
|
|
# -------------------------------------------------------
|
|
include("${PMN_BREEZE_CMAKE_DIR}/Dependencies.cmake")
|
|
|
|
# -------------------------------------------------------
|
|
# Documentation -- see breeze/cmake/Documentation.cmake. Every module
|
|
# automatically gets its own Doxygen target the moment Doxygen is
|
|
# installed; nothing to add here or in a module's own CMakeLists.txt
|
|
# unless you want to opt a module out (NO_DOCS) or override a Doxyfile
|
|
# setting (-DDOXYGEN_<TAG>=...).
|
|
# -------------------------------------------------------
|
|
include("${PMN_BREEZE_CMAKE_DIR}/Documentation.cmake")
|
|
|
|
# -------------------------------------------------------
|
|
# Module discovery
|
|
#
|
|
# Every module lives in its own directory with its own CMakeLists.txt --
|
|
# usually a single add_lib_module/add_objects_module/add_exe_module/
|
|
# add_test_module/add_example_module(...) call (see breeze/cmake/BuildHelpers.cmake
|
|
# for what each accepts). This file never needs editing to add, remove, or reconfigure
|
|
# a module: just add or remove the module's directory + CMakeLists.txt.
|
|
#
|
|
# source/<Name>/CMakeLists.txt -- library, objects or app/executable module
|
|
# source/tests/<Name>/CMakeLists.txt -- test module
|
|
# source/examples/<Name>/CMakeLists.txt -- example module
|
|
#
|
|
# (source/tests and source/examples aren't picked up by the first call
|
|
# below -- they have no CMakeLists.txt directly inside them, only their
|
|
# own children do, so they're only ever visited by their dedicated calls.)
|
|
#
|
|
# _discover_module_subdirectories() add_subdirectory()s every matching
|
|
# directory automatically; CONFIGURE_DEPENDS means the next build
|
|
# reconfigures on its own once a module directory is added or removed.
|
|
# -------------------------------------------------------
|
|
_discover_module_subdirectories("${CMAKE_SOURCE_DIR}/source")
|
|
_discover_module_subdirectories("${CMAKE_SOURCE_DIR}/source/tests")
|
|
_discover_module_subdirectories("${CMAKE_SOURCE_DIR}/source/examples")
|
|
|
|
# Object modules (add_objects_module) can only be told from libraries once every
|
|
# module exists: this links what each one needs of them -- the pieces behind
|
|
# the pieces, and their headers for a library's consumers. See its comment.
|
|
_resolve_object_modules()
|
|
|
|
# Emits <build-dir>/module_manifest-<CONFIG>.txt ("target_name=path_to_binary"
|
|
# per line) for every executable module discovered above. breeze/scripts/run.sh / run.bat
|
|
# read this to resolve a CMake target name to its binary -- must run after
|
|
# discovery.
|
|
_write_module_manifest("${CMAKE_BINARY_DIR}")
|
|
|
|
# Writes out/docs/index.html (DOCS_OUTPUT_DIR, see project/Configuration.cmake)
|
|
# linking to every module's generated documentation (a no-op if
|
|
# ENABLE_DOXYGEN is off) -- must run after discovery, same reasoning as
|
|
# the manifest above.
|
|
_write_docs_index("${DOCS_OUTPUT_DIR}")
|
|
|
|
# -------------------------------------------------------
|
|
# Copy compile_commands.json to the project root once it's fully
|
|
# populated, so clangd/clang-tidy/IWYU find it without extra config (most
|
|
# editors look for it at the repo root or search upward from the source
|
|
# file; CMake only ever writes it into the build directory). Runs at
|
|
# build time, by which point CMake's own generate step has already
|
|
# written build/compile_commands.json, so this works even on the very
|
|
# first build with no prior build directory. Vendored and fetched
|
|
# dependencies are excluded from it (see breeze/cmake/Dependencies.cmake) so it
|
|
# only reflects code this project owns.
|
|
# -------------------------------------------------------
|
|
if(CMAKE_EXPORT_COMPILE_COMMANDS)
|
|
add_custom_target(copy_compile_commands ALL
|
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
"${CMAKE_BINARY_DIR}/compile_commands.json"
|
|
"${COMPILE_COMMANDS_DESTINATION}"
|
|
COMMENT "Copying compile_commands.json to project root"
|
|
VERBATIM
|
|
)
|
|
endif()
|
|
|
|
# -------------------------------------------------------
|
|
# install
|
|
# -------------------------------------------------------
|
|
set(_pmn_install_target_dir ${INSTALL_OUTPUT_DIR}/${DIST_TARGET})
|
|
|
|
# primary
|
|
set(RUNTIME_OUT_DIR ${_pmn_install_target_dir}/${RUNTIME_OUTPUT_SUBDIR})
|
|
set(LIBRARY_OUT_DIR ${_pmn_install_target_dir}/${LIBRARY_OUTPUT_SUBDIR})
|
|
set(ARCHIVE_OUT_DIR ${_pmn_install_target_dir}/${ARCHIVE_OUTPUT_SUBDIR})
|
|
|
|
# tests -- runtime/library -> bin/tests/ archives -> lib/tests/
|
|
set(TEST_RUNTIME_OUT_DIR ${_pmn_install_target_dir}/${RUNTIME_OUTPUT_SUBDIR}/tests)
|
|
set(TEST_LIBRARY_OUT_DIR ${_pmn_install_target_dir}/${LIBRARY_OUTPUT_SUBDIR}/tests)
|
|
set(TEST_ARCHIVE_OUT_DIR ${_pmn_install_target_dir}/${ARCHIVE_OUTPUT_SUBDIR}/tests)
|
|
|
|
# examples -- runtime/library -> bin/examples/ archives -> lib/examples/
|
|
set(EXAMPLE_RUNTIME_OUT_DIR ${_pmn_install_target_dir}/${RUNTIME_OUTPUT_SUBDIR}/examples)
|
|
set(EXAMPLE_LIBRARY_OUT_DIR ${_pmn_install_target_dir}/${LIBRARY_OUTPUT_SUBDIR}/examples)
|
|
set(EXAMPLE_ARCHIVE_OUT_DIR ${_pmn_install_target_dir}/${ARCHIVE_OUTPUT_SUBDIR}/examples)
|
|
|
|
# Every install(TARGETS ...) below is tagged COMPONENT ${PROJECT_CODE_NAME}.
|
|
# A vendored/fetched dependency's own CMakeLists.txt (see
|
|
# breeze/cmake/Dependencies.cmake) usually has install() rules of its own, with
|
|
# no COMPONENT tag -- installing with `cmake --install build` (no
|
|
# --component filter) would pull those in too, typically under
|
|
# CMAKE_INSTALL_PREFIX (e.g. /usr/local). breeze/scripts/install.sh / install.bat always
|
|
# pass --component ${PROJECT_CODE_NAME} so only this project's own
|
|
# artifacts are installed; use the same flag if you invoke cmake --install
|
|
# directly.
|
|
message(STATUS "Will install primary modules.")
|
|
get_property(_pmn_primary_targets GLOBAL PROPERTY PMN_MODULE_TARGETS_PRIMARY)
|
|
if(_pmn_primary_targets)
|
|
install(TARGETS ${_pmn_primary_targets}
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
ARCHIVE DESTINATION ${ARCHIVE_OUT_DIR}
|
|
LIBRARY DESTINATION ${LIBRARY_OUT_DIR}
|
|
RUNTIME DESTINATION ${RUNTIME_OUT_DIR}
|
|
)
|
|
endif()
|
|
|
|
# MinGW runtime DLLs (see the "MinGW runtime linking" comment above) --
|
|
# installed as plain files, alongside the DLLs above, rather than as
|
|
# TARGETS, since they're not CMake targets this project defines. RUNTIME,
|
|
# not LIBRARY: a .dll is a runtime artifact on Windows no matter which
|
|
# install()-TARGETS category built it. Always into RUNTIME_OUT_DIR: the
|
|
# primary executables land there, and so does core_lib/demo_core.dll,
|
|
# which needs this regardless of PMN_MINGW_STATIC_RUNTIME (see that
|
|
# option's comment -- a SHARED module is never a candidate for it).
|
|
if(PMN_MINGW_RUNTIME_DLLS)
|
|
install(FILES ${PMN_MINGW_RUNTIME_DLLS}
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
DESTINATION ${RUNTIME_OUT_DIR}
|
|
)
|
|
endif()
|
|
|
|
# Both blocks below read whatever test/example modules discovery actually
|
|
# found -- no target names to keep in sync here when a module is added,
|
|
# removed, or renamed.
|
|
if(INSTALL_TESTS)
|
|
get_property(_pmn_test_targets GLOBAL PROPERTY PMN_MODULE_TARGETS_TEST)
|
|
if(_pmn_test_targets)
|
|
message(STATUS "Will install test modules: ${_pmn_test_targets}")
|
|
install(TARGETS ${_pmn_test_targets}
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
ARCHIVE DESTINATION ${TEST_ARCHIVE_OUT_DIR}
|
|
LIBRARY DESTINATION ${TEST_LIBRARY_OUT_DIR}
|
|
RUNTIME DESTINATION ${TEST_RUNTIME_OUT_DIR}
|
|
)
|
|
# Matches BuildHelpers.cmake's add_test_module: skipped under
|
|
# PMN_MINGW_STATIC_RUNTIME, where test executables are
|
|
# self-contained instead.
|
|
if(PMN_MINGW_RUNTIME_DLLS AND NOT PMN_MINGW_STATIC_RUNTIME)
|
|
install(FILES ${PMN_MINGW_RUNTIME_DLLS}
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
DESTINATION ${TEST_RUNTIME_OUT_DIR}
|
|
)
|
|
endif()
|
|
# The install-time match for _copy_target_runtime_dlls(): a test
|
|
# that depends on a SHARED module (core_lib included) needs that
|
|
# module's .dll installed alongside it too, on any Windows
|
|
# compiler -- bin/tests/ is a separate install destination from
|
|
# bin/, and Windows has no relative-RPATH equivalent (see that
|
|
# function's comment). install(FILES) silently installs nothing
|
|
# for a target with no SHARED dependency -- confirmed directly --
|
|
# so this needs no separate "is there anything to install" guard.
|
|
if(WIN32)
|
|
foreach(_pmn_test_target ${_pmn_test_targets})
|
|
install(FILES "$<TARGET_RUNTIME_DLLS:${_pmn_test_target}>"
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
DESTINATION ${TEST_RUNTIME_OUT_DIR}
|
|
)
|
|
endforeach()
|
|
endif()
|
|
else()
|
|
message(WARNING "INSTALL_TESTS is ON but no test modules were found under source/tests/.")
|
|
endif()
|
|
endif()
|
|
|
|
if(INSTALL_EXAMPLES)
|
|
get_property(_pmn_example_targets GLOBAL PROPERTY PMN_MODULE_TARGETS_EXAMPLE)
|
|
if(_pmn_example_targets)
|
|
message(STATUS "Will install example modules: ${_pmn_example_targets}")
|
|
install(TARGETS ${_pmn_example_targets}
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
ARCHIVE DESTINATION ${EXAMPLE_ARCHIVE_OUT_DIR}
|
|
LIBRARY DESTINATION ${EXAMPLE_LIBRARY_OUT_DIR}
|
|
RUNTIME DESTINATION ${EXAMPLE_RUNTIME_OUT_DIR}
|
|
)
|
|
# See the matching pair of comments in the INSTALL_TESTS block above.
|
|
if(PMN_MINGW_RUNTIME_DLLS AND NOT PMN_MINGW_STATIC_RUNTIME)
|
|
install(FILES ${PMN_MINGW_RUNTIME_DLLS}
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
DESTINATION ${EXAMPLE_RUNTIME_OUT_DIR}
|
|
)
|
|
endif()
|
|
if(WIN32)
|
|
foreach(_pmn_example_target ${_pmn_example_targets})
|
|
install(FILES "$<TARGET_RUNTIME_DLLS:${_pmn_example_target}>"
|
|
COMPONENT ${PROJECT_CODE_NAME}
|
|
DESTINATION ${EXAMPLE_RUNTIME_OUT_DIR}
|
|
)
|
|
endforeach()
|
|
endif()
|
|
else()
|
|
message(WARNING "INSTALL_EXAMPLES is ON but no example modules were found under source/examples/.")
|
|
endif()
|
|
endif()
|