Add project files

This commit is contained in:
alejo committed 2026-10-24 09:53:48 -03:00
commit f2fe6b8855
114 files changed
+20525

No files matched your search

+530
View File
@@ -0,0 +1,530 @@
# =============================================================================
# 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()
+125
View File
@@ -0,0 +1,125 @@
# =============================================================================
# BuildDir.cmake
#
# Script-mode bridge, the same idea as PrintConfig.cmake: tells
# breeze/scripts/*.sh and breeze/scripts/*.bat which build directory to work on, so that
# logic exists once here instead of twice in two shell dialects.
#
# Every preset builds in its own directory, out/build/<name> (see
# BUILD_OUTPUT_DIR in project/Configuration.cmake), which is what lets you switch
# presets without one's cache clobbering another's. The scripts have to
# pick one of them:
#
# build.* knows the name already (--preset <name>, else
# "default") and only needs the path. After a
# successful configure it also records the choice as
# the one built last.
# run.* install.* take an optional --preset <name>; without it they use
# docs.* the directory that was built last, or the only one
# that exists, or say what to do if that's ambiguous.
#
# Usage:
# cmake [-DPMN_NAME=<name>] [-DPMN_RECORD=ON] -P breeze/cmake/BuildDir.cmake
#
# PMN_NAME a preset name, or "default". Given: the answer is
# BUILD_OUTPUT_DIR/<name>, whether or not it exists yet (build
# is about to create it). Not given: discover one -- the one
# built last, else the only one, else an error.
# PMN_RECORD also remember PMN_NAME as the one built last (a one-line
# file, out/build/.last).
#
# Prints exactly one line, via message(STATUS ...) for the reason
# PrintConfig.cmake documents (STATUS is the one message() mode CMake sends
# to stdout, at the cost of a "-- " prefix every caller strips):
#
# -- BUILD_DIR=<path> relative to the project root with forward slashes
# when it's inside the project (the normal case),
# absolute otherwise
# -- ERROR=<text> what to tell the user
#
# The exit status is 0 either way -- callers key on which line they got. The
# error text sticks to plain words and avoids the characters cmd.exe treats
# specially (& | < > ^ % ! and parentheses), so a .bat can echo it back
# verbatim.
# =============================================================================
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
include("${CMAKE_SOURCE_DIR}/project/Configuration.cmake")
# <path> shown relative to the project root when it's inside it.
function(_pmn_display_path ABSOLUTE_PATH OUT_VAR)
file(RELATIVE_PATH _pmn_rel "${CMAKE_SOURCE_DIR}" "${ABSOLUTE_PATH}")
if(_pmn_rel MATCHES "^\\.\\.")
set(${OUT_VAR} "${ABSOLUTE_PATH}" PARENT_SCOPE)
else()
set(${OUT_VAR} "${_pmn_rel}" PARENT_SCOPE)
endif()
endfunction()
set(_pmn_root "${BUILD_OUTPUT_DIR}")
_pmn_display_path("${_pmn_root}" _pmn_root_shown)
set(_pmn_key "")
set(_pmn_value "")
if(NOT "${PMN_NAME}" STREQUAL "")
# ---- A name was given: the answer is just the path. ----------------------
if(PMN_NAME MATCHES "[/\\\\]" OR PMN_NAME STREQUAL "." OR PMN_NAME STREQUAL "..")
set(_pmn_key "ERROR")
set(_pmn_value "'${PMN_NAME}' is not a valid preset name: it must be a single directory name, not a path.")
else()
_pmn_display_path("${_pmn_root}/${PMN_NAME}" _pmn_shown)
set(_pmn_key "BUILD_DIR")
set(_pmn_value "${_pmn_shown}")
if(PMN_RECORD)
file(MAKE_DIRECTORY "${_pmn_root}")
file(WRITE "${_pmn_root}/.last" "${PMN_NAME}\n")
endif()
endif()
else()
# ---- No name: discover one. ---------------------------------------------
# Candidates are the subdirectories that hold a CMake cache.
set(_pmn_candidates "")
file(GLOB _pmn_children LIST_DIRECTORIES true "${_pmn_root}/*")
foreach(_pmn_child IN LISTS _pmn_children)
if(IS_DIRECTORY "${_pmn_child}" AND EXISTS "${_pmn_child}/CMakeCache.txt")
get_filename_component(_pmn_child_name "${_pmn_child}" NAME)
list(APPEND _pmn_candidates "${_pmn_child_name}")
endif()
endforeach()
list(SORT _pmn_candidates)
list(LENGTH _pmn_candidates _pmn_count)
# The one built last, if that pointer still names a real build.
set(_pmn_last "")
if(EXISTS "${_pmn_root}/.last")
file(STRINGS "${_pmn_root}/.last" _pmn_last_lines LIMIT_COUNT 1)
string(STRIP "${_pmn_last_lines}" _pmn_last)
endif()
set(_pmn_chosen "")
if(NOT "${_pmn_last}" STREQUAL "" AND "${_pmn_last}" IN_LIST _pmn_candidates)
set(_pmn_chosen "${_pmn_last}")
elseif(_pmn_count EQUAL 1)
set(_pmn_chosen "${_pmn_candidates}")
endif()
if(NOT "${_pmn_chosen}" STREQUAL "")
_pmn_display_path("${_pmn_root}/${_pmn_chosen}" _pmn_shown)
set(_pmn_key "BUILD_DIR")
set(_pmn_value "${_pmn_shown}")
elseif(_pmn_count EQUAL 0)
set(_pmn_key "ERROR")
if(EXISTS "${_pmn_root}/CMakeCache.txt")
set(_pmn_value "${_pmn_root_shown} holds a build from the old single-directory layout, which this version no longer uses. Delete it, then run breeze/scripts/build to create one build directory per preset.")
else()
set(_pmn_value "No build found under ${_pmn_root_shown}. Run breeze/scripts/build first, optionally with --preset NAME.")
endif()
else()
list(JOIN _pmn_candidates ", " _pmn_names)
set(_pmn_key "ERROR")
set(_pmn_value "Several builds exist under ${_pmn_root_shown} and none was built last through the scripts: ${_pmn_names}. Pass --preset NAME to choose one.")
endif()
endif()
message(STATUS "${_pmn_key}=${_pmn_value}")
+627
View File
@@ -0,0 +1,627 @@
# =============================================================================
# BuildHelpers.cmake
#
# Reusable, project-agnostic build machinery: module discovery, per-target
# compiler/RPATH settings, and the module-registration macros (add_lib_module,
# add_objects_module, add_exe_module, add_test_module, add_example_module) that
# a module's own CMakeLists.txt calls.
#
# This file is meant to change rarely and hold nothing project-specific.
# New targets should not need edits here, or in Breeze.cmake --
# see the "Module discovery" section of Breeze.cmake and the per-module
# CMakeLists.txt convention described there.
#
# Expects the including file (Breeze.cmake) to have already set (from project()
# and project/Configuration.cmake): PROJECT_NAME, PROJECT_VERSION,
# PROJECT_CODE_NAME. Multiplatform: MSVC/WIN32 branches guard the
# Windows-only paths; everything else is generic.
# =============================================================================
include_guard(GLOBAL)
# -----------------------------------------------------------------------
# all_tests / all_examples -- aggregate targets so groups can be built
# without CMake target-name collisions:
# cmake --build . --target all_tests
# cmake --build . --target all_examples
#
# enable_testing() is required for add_test_module()'s add_test() calls to
# actually be discoverable by ctest -- without it, tests build fine but
# `ctest` reports "No tests were found" with no error anywhere else.
# -----------------------------------------------------------------------
enable_testing()
add_custom_target(all_tests COMMENT "Build all test targets")
add_custom_target(all_examples COMMENT "Build all example targets")
# -----------------------------------------------------------------------
# _discover_module_subdirectories(ROOT_DIR): add_subdirectory() every
# immediate child of ROOT_DIR that contains its own CMakeLists.txt.
#
# This is the whole mechanism behind "no root edits to add a module": drop
# a new directory with a one-line CMakeLists.txt under one of the known
# module roots (source/ for libraries and apps, source/tests/ or
# source/examples/ for tests and examples) and it is picked up automatically.
# CONFIGURE_DEPENDS means the *next build* reconfigures on its own after a
# module directory is added or removed -- no manual `cmake` re-run needed.
#
# Order between add_subdirectory() calls does not matter for correctness:
# CMake resolves target_link_libraries() names against the whole project
# at generate time, not sequentially, so a module may DEPENDS on a module
# discovered later (verified empirically -- this isn't a guess).
#
# A macro (not a function) so add_subdirectory() behaves exactly as if it
# were called directly from the caller -- no extra scope layer involved.
# -----------------------------------------------------------------------
macro(_discover_module_subdirectories ROOT_DIR)
if(IS_DIRECTORY "${ROOT_DIR}")
file(GLOB _pmn_module_candidates CONFIGURE_DEPENDS LIST_DIRECTORIES true "${ROOT_DIR}/*")
foreach(_pmn_candidate ${_pmn_module_candidates})
if(IS_DIRECTORY "${_pmn_candidate}" AND EXISTS "${_pmn_candidate}/CMakeLists.txt")
add_subdirectory("${_pmn_candidate}")
endif()
endforeach()
endif()
endmacro()
# -----------------------------------------------------------------------
# _apply_compile_options: per-target warning flags.
# MSVC path : /W4 (high warnings) + /permissive- (strict conformance).
# GCC/Clang : -Wall -Wextra -Wpedantic.
# -----------------------------------------------------------------------
function(_apply_compile_options TARGET)
if(MSVC)
target_compile_options(${TARGET} PRIVATE /W4 /permissive-)
else()
target_compile_options(${TARGET} PRIVATE -Wall -Wextra -Wpedantic)
endif()
endfunction()
# -----------------------------------------------------------------------
# _apply_rpath: RPATH is an ELF (Linux) and Mach-O (macOS) concept; skip
# entirely on Windows. Callers spell "relative to this binary" the ELF way,
# $ORIGIN[/..]; Mach-O's loader has never heard of that token -- its
# equivalent is @loader_path -- so it is translated here, once, instead of
# every call site having to know which platform it's on. (For an executable
# @loader_path is the executable's own directory; for a library, the
# library's. That is exactly the layout this project uses: everything
# lands flat in bin/, tests and examples one level down in bin/tests and
# bin/examples.)
# -----------------------------------------------------------------------
function(_apply_rpath TARGET RPATH_VAL)
if(NOT WIN32)
if(APPLE)
string(REPLACE "$ORIGIN" "@loader_path" RPATH_VAL "${RPATH_VAL}")
endif()
set_target_properties(${TARGET} PROPERTIES
BUILD_RPATH "${RPATH_VAL}"
INSTALL_RPATH "${RPATH_VAL}"
)
endif()
endfunction()
# -----------------------------------------------------------------------
# _set_output_dirs(TARGET RUNTIME_DIR [LIBRARY_DIR]):
#
# Breeze.cmake sets CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> /
# CMAKE_LIBRARY_OUTPUT_DIRECTORY_<CONFIG> globally (once, near the top) so
# every target defaults to the flat bin/ layout. That also means every
# new target's per-config RUNTIME_OUTPUT_DIRECTORY_<CONFIG> /
# LIBRARY_OUTPUT_DIRECTORY_<CONFIG> property is pre-populated at creation
# time -- and a per-config property always wins over the plain
# (config-independent) one. So a module that needs a *different* output
# directory (tests -> bin/tests, examples -> bin/examples) must override
# the per-config properties too, or the override silently has no effect.
# This helper does both in one place.
# -----------------------------------------------------------------------
function(_set_output_dirs TARGET RUNTIME_DIR)
set(_library_dir "${RUNTIME_DIR}")
if(ARGC GREATER 2)
set(_library_dir "${ARGV2}")
endif()
set_target_properties(${TARGET} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_DIR}"
LIBRARY_OUTPUT_DIRECTORY "${_library_dir}"
)
foreach(_cfg Debug Release RelWithDebInfo MinSizeRel)
string(TOUPPER "${_cfg}" _CFG)
set_target_properties(${TARGET} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_${_CFG} "${RUNTIME_DIR}"
LIBRARY_OUTPUT_DIRECTORY_${_CFG} "${_library_dir}"
)
endforeach()
endfunction()
# -----------------------------------------------------------------------
# _copy_mingw_runtime_dlls(TARGET): copies PMN_MINGW_RUNTIME_DLLS (computed
# once in Breeze.cmake's "MinGW runtime linking" section) next to
# TARGET's own built binary, in the build tree, so it runs without a
# manual step. install()-time placement is the matching
# install(FILES ...) in Breeze.cmake's "install" section. A no-op
# whenever PMN_MINGW_RUNTIME_DLLS is empty (every non-MinGW platform, or
# a MinGW toolchain where none of the candidate DLLs were found).
#
# Called for every SHARED module unconditionally (source/Core's
# core_lib/demo_core.dll always uses the dynamic runtime -- see the
# "MinGW runtime linking" comment for why making it static too isn't an
# option), and for EXECUTABLE/test/example targets only when
# PMN_MINGW_STATIC_RUNTIME is OFF (the default): with it ON those are
# statically linked and don't need these DLLs, so the caller skips this
# call entirely rather than copying files nothing imports.
# -----------------------------------------------------------------------
function(_copy_mingw_runtime_dlls TARGET)
if(MINGW AND PMN_MINGW_RUNTIME_DLLS)
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PMN_MINGW_RUNTIME_DLLS} "$<TARGET_FILE_DIR:${TARGET}>"
COMMENT "Copying MinGW runtime DLLs next to ${TARGET}"
VERBATIM
)
endif()
endfunction()
# -----------------------------------------------------------------------
# _copy_target_runtime_dlls(TARGET): on Windows (any compiler -- MSVC,
# clang-cl, or MinGW), copies every SHARED-library dependency's own .dll
# next to TARGET's binary, via CMake's TARGET_RUNTIME_DLLS generator
# expression. This is the OTHER half of running on Windows that
# _copy_mingw_runtime_dlls doesn't cover: that one only bundles the
# compiler's OWN runtime (libgcc/libstdc++/winpthread); this one bundles
# this project's SHARED modules (core_lib/demo_core.dll, and anything
# else a module's own CMakeLists.txt declares SHARED).
#
# Needed specifically for add_test_module/add_example_module: they land
# one directory below the primary modules (bin/tests, bin/examples),
# with an RPATH of "$ORIGIN/.." for the ELF/Mach-O builds where that
# works (see _apply_rpath) -- but Windows has no equivalent of a
# *relative*, directory-based DLL search path, so a test or example that
# depends on a SHARED module built by this project needs that DLL
# copied into ITS OWN directory to run at all, not just the primary
# one. (The primary modules -- add_exe_module -- don't need this: they
# already land in the SAME flat bin/ directory as every SHARED module,
# so they already find it there.) Confirmed directly by cross-building
# for windows-x64-mingw64 and running the result under Wine: a test
# depending on core_lib fails to start (STATUS_DLL_NOT_FOUND,
# "libdemo_core.dll ... not found") without this.
#
# Delegates the actual copy to CopyRuntimeDlls.cmake rather than calling
# `cmake -E copy_if_different` directly, because that fails the whole
# build the moment TARGET_RUNTIME_DLLS is empty -- the common case for
# any test/example that doesn't depend on a SHARED module -- and
# `cmake -E copy_if_different <dest-only, no sources>` is exactly that:
# zero sources. See that script for the confirmed failure and the
# one-line `if(FILES)` fix.
# -----------------------------------------------------------------------
function(_copy_target_runtime_dlls TARGET)
if(WIN32)
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DDEST=$<TARGET_FILE_DIR:${TARGET}>
"-DFILES=$<TARGET_RUNTIME_DLLS:${TARGET}>"
-P "${PMN_BREEZE_CMAKE_DIR}/CopyRuntimeDlls.cmake"
COMMENT "Copying ${TARGET}'s own SHARED-library dependencies next to it"
VERBATIM
)
endif()
endfunction()
# -----------------------------------------------------------------------
# _register_executable_target(TARGET): tracks every executable module
# (exe/test/example) in a global property so _write_module_manifest()
# can find them all without anything needing to know their names ahead
# of time.
# -----------------------------------------------------------------------
function(_register_executable_target TARGET)
set_property(GLOBAL APPEND PROPERTY PMN_EXECUTABLE_TARGETS ${TARGET})
endfunction()
# -----------------------------------------------------------------------
# _register_module_target(CATEGORY TARGET): tracks a target under
# CATEGORY (PRIMARY, TEST, or EXAMPLE) in a global property so
# Breeze.cmake's install() rules can be generated from whatever modules
# actually got discovered, instead of a hardcoded target list that would
# need editing every time a module is added or removed.
# -----------------------------------------------------------------------
function(_register_module_target CATEGORY TARGET)
set_property(GLOBAL APPEND PROPERTY PMN_MODULE_TARGETS_${CATEGORY} ${TARGET})
endfunction()
# -----------------------------------------------------------------------
# _write_module_manifest(OUTPUT_DIR): call once, after every module has
# been discovered, to emit "<target-name>=<absolute-path-to-binary>"
# lines for every executable module into
# OUTPUT_DIR/module_manifest-<CONFIG>.txt.
#
# breeze/scripts/run.sh / run.bat read this file to resolve a CMake target name to its
# actual executable path -- so they never hardcode per-module paths and
# never need editing when a module is added, removed, or renamed.
# $<CONFIG> is part of the filename because file(GENERATE) requires that
# for content (like $<TARGET_FILE:...>) that can differ per build
# configuration under a multi-config generator (e.g. Ninja Multi-Config).
# -----------------------------------------------------------------------
function(_write_module_manifest OUTPUT_DIR)
get_property(_pmn_targets GLOBAL PROPERTY PMN_EXECUTABLE_TARGETS)
set(_manifest_content "")
foreach(_t ${_pmn_targets})
string(APPEND _manifest_content "${_t}=$<TARGET_FILE:${_t}>\n")
endforeach()
file(GENERATE
OUTPUT "${OUTPUT_DIR}/module_manifest-$<CONFIG>.txt"
CONTENT "${_manifest_content}"
)
endfunction()
# -----------------------------------------------------------------------
# _finalize_module_target(TARGET MODULE_NAME [OUTPUT_NAME name] [DEPENDS ...])
#
# The tail every module macro below shares: identity compile definitions,
# warning flags, OUTPUT_NAME, and DEPENDS linking. Centralized so all four
# macros stay in lockstep -- edit once here instead of four times.
# Re-parses the caller's raw ARGN, so it's safe to call as
# _finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN}) even though
# ARGN also contains keywords (TYPE, EXTRA_SOURCES, ...) this function
# doesn't know about -- those are simply ignored.
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# _finalize_module_target(TARGET MODULE_NAME [OUTPUT_NAME name] [DEPENDS ...]
# [NO_DOCS])
#
# The tail every module macro below shares: identity compile definitions,
# warning flags, OUTPUT_NAME, DEPENDS linking, and an automatic per-module
# Doxygen target (see breeze/cmake/Documentation.cmake) unless NO_DOCS is given.
# Centralized so all four macros stay in lockstep -- edit once here
# instead of four times. Re-parses the caller's raw ARGN, so it's safe to
# call as _finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN}) even
# though ARGN also contains keywords (TYPE, EXTRA_SOURCES, ...) this
# function doesn't know about -- those are simply ignored.
# -----------------------------------------------------------------------
function(_finalize_module_target TARGET MODULE_NAME)
cmake_parse_arguments(FIN "NO_DOCS" "OUTPUT_NAME" "DEPENDS" ${ARGN})
# Every module and what it DEPENDS on, for _resolve_object_modules(): object
# modules can't be told apart from libraries here -- the module they name
# may not have been discovered yet -- so that is decided once all exist.
set_property(GLOBAL APPEND PROPERTY PMN_ALL_MODULES ${TARGET})
set_property(GLOBAL PROPERTY PMN_DEPENDS_${TARGET} "${FIN_DEPENDS}")
target_compile_definitions(${TARGET} PRIVATE
$<$<CONFIG:Debug>:DEBUG>
MODULE_NAME="${MODULE_NAME}"
PROJECT_NAME="${PROJECT_NAME}"
PROJECT_NAME_CODE="${PROJECT_CODE_NAME}"
PROJECT_VER="${PROJECT_VERSION}"
)
_apply_compile_options(${TARGET})
# An objects module has no file to name: for it OUTPUT_NAME is only the name
# MODULE_NAME carries (the library it is a piece of, usually).
get_target_property(_pmn_target_type ${TARGET} TYPE)
if(FIN_OUTPUT_NAME AND NOT _pmn_target_type STREQUAL "OBJECT_LIBRARY")
set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME ${FIN_OUTPUT_NAME})
endif()
if(FIN_DEPENDS)
target_link_libraries(${TARGET} PRIVATE ${FIN_DEPENDS})
endif()
if(NOT FIN_NO_DOCS)
_add_module_docs(${TARGET})
endif()
endfunction()
# =============================================================================
# add_lib_module(NAME [TYPE STATIC|SHARED] [OUTPUT_NAME name] [DEPENDS ...]
# [EXTRA_SOURCES ...] [NO_DOCS])
#
# Called from a library module's own CMakeLists.txt (e.g. source/Core/CMakeLists.txt).
# Sources/headers are globbed relative to that module's own directory:
# src/*.cpp and include/*.h(pp) underneath it. TYPE picks STATIC or SHARED
# for this module only; omit it to use the project-wide default
# (BUILD_SHARED_LIBS -- set in Breeze.cmake's OPTIONS section, or pass
# -DBUILD_SHARED_LIBS=ON at configure time).
#
# Headers are globbed in purely so they show up in IDE project trees --
# CMake never compiles headers, so this is inert for the actual build.
# =============================================================================
macro(add_lib_module NAME)
cmake_parse_arguments(ARG "" "OUTPUT_NAME;TYPE" "DEPENDS;EXTRA_SOURCES" ${ARGN})
if(ARG_OUTPUT_NAME)
set(_MODULE_NAME ${ARG_OUTPUT_NAME})
else()
set(_MODULE_NAME ${NAME})
endif()
if(ARG_TYPE)
set(_LIB_TYPE ${ARG_TYPE})
elseif(BUILD_SHARED_LIBS)
set(_LIB_TYPE SHARED)
else()
set(_LIB_TYPE STATIC)
endif()
if(NOT _LIB_TYPE MATCHES "^(STATIC|SHARED)$")
message(FATAL_ERROR "add_lib_module(${NAME}): TYPE must be STATIC or SHARED, got '${_LIB_TYPE}'")
endif()
file(GLOB_RECURSE ${NAME}_SOURCES CONFIGURE_DEPENDS src/*.cpp)
file(GLOB_RECURSE ${NAME}_HEADERS CONFIGURE_DEPENDS include/*.h include/*.hpp)
add_library(${NAME} ${_LIB_TYPE} ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
if(_LIB_TYPE STREQUAL "STATIC")
# Keep static libs relocatable in case they end up linked into a
# shared library or PIE executable later.
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
else()
# LIBRARY_OUTPUT_DIRECTORY controls .so placement on Linux.
# RUNTIME_OUTPUT_DIRECTORY controls .dll placement on Windows.
# WINDOWS_EXPORT_ALL_SYMBOLS auto-generates a .def file so all
# public symbols are exported without __declspec(dllexport).
# Remove if you prefer explicit visibility macros instead.
_set_output_dirs(${NAME} "${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}" "${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}")
set_target_properties(${NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
# A SHARED module always keeps the dynamic MinGW runtime -- see
# Breeze.cmake's "MinGW runtime linking" comment for why it's
# never a candidate for PMN_MINGW_STATIC_RUNTIME -- so this call
# is unconditional, unlike the EXECUTABLE-producing macros below.
_copy_mingw_runtime_dlls(${NAME})
endif()
_register_module_target(PRIMARY ${NAME})
_finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN})
endmacro()
# Back-compat wrappers for the old two-macro spelling.
# Prefer add_lib_module(... TYPE STATIC|SHARED) directly in new code.
macro(add_static_lib_module NAME)
add_lib_module(${NAME} TYPE STATIC ${ARGN})
endmacro()
macro(add_shared_lib_module NAME)
add_lib_module(${NAME} TYPE SHARED ${ARGN})
endmacro()
# =============================================================================
# add_objects_module(NAME [OUTPUT_NAME name] [DEPENDS ...] [EXTRA_SOURCES ...] [NO_DOCS])
#
# Called from an objects module's own CMakeLists.txt (e.g. source/CoreMath/CMakeLists.txt).
# An OBJECT library: its sources are compiled, and that is all -- no archive, no
# DLL, nothing in bin/ or lib/. Name it in the DEPENDS of a library, an
# executable, a test, an example, or another objects module, and its object
# files become part of that target. That is what makes it the way to split
# one big library into pieces: each piece is a module of its own (own
# directory, own include/, built and tested on its own), and the library
# that is left is one line naming them.
#
# source/CoreMath/CMakeLists.txt add_objects_module(core_math)
# source/CoreIo/CMakeLists.txt add_objects_module(core_io DEPENDS core_math)
# source/Core/CMakeLists.txt add_lib_module(core DEPENDS core_io)
#
# Sources/headers are globbed like a library's: src/*.cpp and include/*.h(pp)
# under the module's own directory. include/ is public: whoever takes the
# objects sees the headers -- and for a library, so do ITS consumers (see
# _resolve_object_modules below), because the headers of the pieces are the
# library's headers. Keep a piece's private headers in src/.
#
# OUTPUT_NAME names nothing here -- there is no file -- but is what the
# MODULE_NAME definition carries: pass the library's name so the pieces
# of one library all identify as it.
#
# Not installed, not run, not in the module manifest: the objects live on
# inside whatever took them. Always compiled position-independent, since
# they may land in a shared library.
# =============================================================================
macro(add_objects_module NAME)
cmake_parse_arguments(ARG "" "OUTPUT_NAME" "DEPENDS;EXTRA_SOURCES" ${ARGN})
if(ARG_OUTPUT_NAME)
set(_MODULE_NAME ${ARG_OUTPUT_NAME})
else()
set(_MODULE_NAME ${NAME})
endif()
file(GLOB_RECURSE ${NAME}_SOURCES CONFIGURE_DEPENDS src/*.cpp)
file(GLOB_RECURSE ${NAME}_HEADERS CONFIGURE_DEPENDS include/*.h include/*.hpp)
add_library(${NAME} OBJECT ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
# Its own category: install() (Breeze.cmake) only takes PRIMARY, and an
# OBJECT library has nothing of its own to install.
_register_module_target(OBJECTS ${NAME})
_finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN})
endmacro()
# -----------------------------------------------------------------------
# _resolve_object_modules(): call once, after every module has been
# discovered (Breeze.cmake does).
#
# CMake links an OBJECT library's objects into the target that names it,
# exactly once -- and no further. A piece that DEPENDS on another piece does
# not carry that piece's objects along, so a library naming only the first
# one fails at link time with undefined references. And a library that has
# taken a piece's objects does not pass the piece's headers on to its own
# consumers, unless it links the piece PUBLIC.
#
# So, for every module target, the objects modules it names -- directly, or
# through other objects modules -- are collected, and each is linked:
#
# * library (static/shared) or objects module: PUBLIC. Objects go into it;
# its consumers see the pieces' include/ and compile definitions, and
# get no second copy of the objects -- they are inside it already.
# * executable (app, test, example): PRIVATE. Nothing consumes it.
#
# Only objects modules are followed. A module that is an ordinary library
# keeps whatever it has inside it; what a piece links (a static library, say)
# is reached by CMake on its own at the final link.
#
# Done here and not in the macros because the macros can't know what a
# name in DEPENDS is: modules are discovered in directory order, and the
# one named may not exist yet.
# -----------------------------------------------------------------------
function(_resolve_object_modules)
get_property(_pmn_all_modules GLOBAL PROPERTY PMN_ALL_MODULES)
foreach(_pmn_module IN LISTS _pmn_all_modules)
get_target_property(_pmn_module_type ${_pmn_module} TYPE)
get_property(_pmn_direct GLOBAL PROPERTY PMN_DEPENDS_${_pmn_module})
set(_pmn_queue "${_pmn_direct}")
set(_pmn_seen "")
set(_pmn_pieces "")
while(NOT "${_pmn_queue}" STREQUAL "")
list(POP_FRONT _pmn_queue _pmn_name)
if(_pmn_name IN_LIST _pmn_seen)
continue()
endif()
list(APPEND _pmn_seen ${_pmn_name})
if(NOT TARGET ${_pmn_name})
continue()
endif()
get_target_property(_pmn_name_type ${_pmn_name} TYPE)
if(NOT _pmn_name_type STREQUAL "OBJECT_LIBRARY")
continue()
endif()
if(_pmn_name STREQUAL _pmn_module)
message(FATAL_ERROR
"The objects module '${_pmn_module}' depends on itself, through the "
"objects modules it names (directly or not) in DEPENDS.")
endif()
list(APPEND _pmn_pieces ${_pmn_name})
get_property(_pmn_more GLOBAL PROPERTY PMN_DEPENDS_${_pmn_name})
list(APPEND _pmn_queue ${_pmn_more})
endwhile()
foreach(_pmn_piece IN LISTS _pmn_pieces)
if(_pmn_module_type STREQUAL "EXECUTABLE")
# Already linked PRIVATE when named directly (_finalize_module_target).
if(NOT _pmn_piece IN_LIST _pmn_direct)
target_link_libraries(${_pmn_module} PRIVATE ${_pmn_piece})
endif()
else()
target_link_libraries(${_pmn_module} PUBLIC ${_pmn_piece})
endif()
endforeach()
endforeach()
endfunction()
# =============================================================================
# add_exe_module(NAME [OUTPUT_NAME name] [DEPENDS ...] [EXTRA_SOURCES ...] [NO_DOCS])
#
# Called from an app module's own CMakeLists.txt (e.g. HelloApp/CMakeLists.txt).
# Sources/headers are globbed relative to that module's own directory:
# *.cpp and include/*.h(pp) underneath it.
# =============================================================================
macro(add_exe_module NAME)
cmake_parse_arguments(ARG "" "OUTPUT_NAME" "DEPENDS;EXTRA_SOURCES" ${ARGN})
if(ARG_OUTPUT_NAME)
set(_MODULE_NAME ${ARG_OUTPUT_NAME})
else()
set(_MODULE_NAME ${NAME})
endif()
file(GLOB_RECURSE ${NAME}_SOURCES CONFIGURE_DEPENDS *.cpp)
file(GLOB_RECURSE ${NAME}_HEADERS CONFIGURE_DEPENDS include/*.h include/*.hpp)
add_executable(${NAME} ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
_set_output_dirs(${NAME} "${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}")
_apply_rpath(${NAME} "$ORIGIN")
_register_module_target(PRIMARY ${NAME})
_register_executable_target(${NAME})
# Skipped when PMN_MINGW_STATIC_RUNTIME is ON: that link option (set in
# Breeze.cmake) makes this target self-contained instead, and these
# DLLs would be unused files sitting next to it. See that option's own
# comment for why OFF (dynamic, copied here) is the safe default.
if(NOT PMN_MINGW_STATIC_RUNTIME)
_copy_mingw_runtime_dlls(${NAME})
endif()
_finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN})
endmacro()
# =============================================================================
# add_test_module(NAME [OUTPUT_NAME name] [DEPENDS ...] [EXTRA_SOURCES ...] [NO_DOCS])
#
# Called from a test module's own CMakeLists.txt (e.g. source/tests/mytest/CMakeLists.txt).
# Lands in bin/tests/, so shared-lib deps one level up need $ORIGIN/...
# EXCLUDE_FROM_ALL keeps it out of the default build; BUILD_TESTS opts it
# back in.
# =============================================================================
macro(add_test_module NAME)
cmake_parse_arguments(ARG "" "OUTPUT_NAME" "DEPENDS;EXTRA_SOURCES" ${ARGN})
if(ARG_OUTPUT_NAME)
set(_MODULE_NAME ${ARG_OUTPUT_NAME})
else()
set(_MODULE_NAME ${NAME})
endif()
file(GLOB_RECURSE ${NAME}_SOURCES CONFIGURE_DEPENDS *.cpp)
file(GLOB_RECURSE ${NAME}_HEADERS CONFIGURE_DEPENDS include/*.h include/*.hpp)
if(BUILD_TESTS)
add_executable(${NAME} ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
else()
add_executable(${NAME} EXCLUDE_FROM_ALL ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
endif()
target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
_set_output_dirs(${NAME} "${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/tests")
_apply_rpath(${NAME} "$ORIGIN/..")
_register_module_target(TEST ${NAME})
_register_executable_target(${NAME})
# See add_exe_module's identical guard on the first call: skipped
# under PMN_MINGW_STATIC_RUNTIME, which makes this target
# self-contained instead. The second call is unconditional and
# WIN32-wide (not just MinGW): bin/tests/ is a different directory
# from bin/, so any SHARED module this test depends on (this
# project's core_lib included) needs its own .dll copied here too --
# see _copy_target_runtime_dlls's own comment for why a *different*
# mechanism than the one above is needed for that.
if(NOT PMN_MINGW_STATIC_RUNTIME)
_copy_mingw_runtime_dlls(${NAME})
endif()
_copy_target_runtime_dlls(${NAME})
_finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN})
add_test(NAME ${NAME} COMMAND ${NAME})
add_dependencies(all_tests ${NAME})
endmacro()
# =============================================================================
# add_example_module(NAME [OUTPUT_NAME name] [DEPENDS ...] [EXTRA_SOURCES ...] [NO_DOCS])
#
# Called from an example module's own CMakeLists.txt (e.g. source/examples/myexample/CMakeLists.txt).
# Same bin/examples/ + $ORIGIN/.. layout as tests.
# =============================================================================
macro(add_example_module NAME)
cmake_parse_arguments(ARG "" "OUTPUT_NAME" "DEPENDS;EXTRA_SOURCES" ${ARGN})
if(ARG_OUTPUT_NAME)
set(_MODULE_NAME ${ARG_OUTPUT_NAME})
else()
set(_MODULE_NAME ${NAME})
endif()
file(GLOB_RECURSE ${NAME}_SOURCES CONFIGURE_DEPENDS *.cpp)
file(GLOB_RECURSE ${NAME}_HEADERS CONFIGURE_DEPENDS include/*.h include/*.hpp)
if(BUILD_EXAMPLES)
add_executable(${NAME} ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
else()
add_executable(${NAME} EXCLUDE_FROM_ALL ${${NAME}_SOURCES} ${${NAME}_HEADERS} ${ARG_EXTRA_SOURCES})
endif()
target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
_set_output_dirs(${NAME} "${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/examples")
_apply_rpath(${NAME} "$ORIGIN/..")
_register_module_target(EXAMPLE ${NAME})
_register_executable_target(${NAME})
# See add_test_module's identical pair of calls and comment just above.
if(NOT PMN_MINGW_STATIC_RUNTIME)
_copy_mingw_runtime_dlls(${NAME})
endif()
_copy_target_runtime_dlls(${NAME})
_finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN})
add_dependencies(all_examples ${NAME})
endmacro()
+21
View File
@@ -0,0 +1,21 @@
# =============================================================================
# CopyRuntimeDlls.cmake
#
# Usage (from a POST_BUILD add_custom_command; see BuildHelpers.cmake's
# _copy_target_runtime_dlls()):
# cmake -DDEST=<dir> "-DFILES=$<TARGET_RUNTIME_DLLS:tgt>" -P CopyRuntimeDlls.cmake
#
# Exists only because `cmake -E copy_if_different <dest>` -- the obvious,
# one-line way to do this -- errors out (and fails the whole build) when
# given zero source files, which $<TARGET_RUNTIME_DLLS:tgt> legitimately
# evaluates to for most targets (anything that links no SHARED library,
# which is most executables in most projects, this one's included: only
# a target that depends on a SHARED module -- like core_lib -- has any
# files here at all). Confirmed directly: without this indirection, that
# empty case stops the build with cmake -E's own usage text as the error.
# A plain `if(FILES)` here is all it takes to make "nothing to do" a
# success instead of a build failure.
# =============================================================================
if(FILES)
file(COPY ${FILES} DESTINATION "${DEST}")
endif()
+159
View File
@@ -0,0 +1,159 @@
# =============================================================================
# Dependencies.cmake
#
# Three ways to bring in a third-party dependency, and one helper that ties
# two of them together. All three keep the dependency's own warnings out of
# your build (SYSTEM) and its sources out of compile_commands.json, so
# clangd/clang-tidy stay focused on code this project owns.
#
# add_vendor_header_only(NAME [INCLUDE_DIR <dir>])
# A buildless (header-only) drop checked into vendor/<NAME>/include
# (or vendor/<INCLUDE_DIR>). Wraps it as an INTERFACE target NAME.
#
# add_vendor_subdirectory(SUBDIR)
# A vendored dependency that ships its own CMakeLists.txt, checked
# into vendor/<SUBDIR> (a plain source drop, git submodule, or git
# subtree). Whatever target names that CMakeLists.txt defines are
# what you DEPENDS on.
#
# add_dependency(NAME <FetchContent_Declare args...>)
# CMake's native find_package + FetchContent integration: pass
# FIND_PACKAGE_ARGS ... to try an already-installed copy (an apt/
# vcpkg/conan package) first, and only download + build from source
# if nothing is found. Omit FIND_PACKAGE_ARGS to always fetch.
# https://cmake.org/cmake/help/latest/module/FetchContent.html#integrating-with-find-package
#
# All three are safe to call more than once for the same name (e.g. two
# modules that both need the same dependency) -- later calls are no-ops.
#
# A module DEPENDS on whatever target the chosen path defines, exactly
# like depending on another module -- add_lib_module/add_exe_module/etc. don't
# need to know or care which of the three supplied it.
#
# IMPORTANT -- installing: a vendored/fetched dependency's own
# CMakeLists.txt usually has its own install() rules, and there is no
# reliable, version-independent way to suppress them from here (EXCLUDE_
# FROM_ALL's effect on install() is explicitly documented as undefined,
# and CMAKE_SKIP_INSTALL_RULES breaks `cmake --install` outright for
# FetchContent -- both were tried and rejected). The actual fix lives in
# CMakeLists.txt: our own install(TARGETS ...) calls are tagged
# COMPONENT ${PROJECT_CODE_NAME}, and breeze/scripts/install.sh / install.bat always pass
# --component so a third-party dependency's untagged install rules are
# never pulled in. Installing with plain `cmake --install build` (no
# --component) will also install whatever the dependency wanted to
# install, typically under CMAKE_INSTALL_PREFIX (e.g. /usr/local) --
# use the install script, or pass --component yourself.
# =============================================================================
include_guard(GLOBAL)
# -----------------------------------------------------------------------
# _exclude_from_compile_commands(DIR): drop every target defined under
# DIR from compile_commands.json. Applied to vendored/fetched third-party
# code so the exported compile database stays focused on code this
# project actually owns.
# -----------------------------------------------------------------------
function(_exclude_from_compile_commands DIR)
if(NOT IS_DIRECTORY "${DIR}")
return()
endif()
get_property(_pmn_dir_targets DIRECTORY "${DIR}" PROPERTY BUILDSYSTEM_TARGETS)
foreach(_t ${_pmn_dir_targets})
set_target_properties(${_t} PROPERTIES EXPORT_COMPILE_COMMANDS OFF)
endforeach()
endfunction()
# -----------------------------------------------------------------------
# add_vendor_header_only(NAME [INCLUDE_DIR <dir>])
# -----------------------------------------------------------------------
macro(add_vendor_header_only NAME)
cmake_parse_arguments(ARG "" "INCLUDE_DIR" "" ${ARGN})
if(NOT TARGET ${NAME})
add_library(${NAME} INTERFACE)
if(ARG_INCLUDE_DIR)
target_include_directories(${NAME} SYSTEM INTERFACE "${CMAKE_SOURCE_DIR}/vendor/${ARG_INCLUDE_DIR}")
else()
target_include_directories(${NAME} SYSTEM INTERFACE "${CMAKE_SOURCE_DIR}/vendor/${NAME}/include")
endif()
endif()
endmacro()
# -----------------------------------------------------------------------
# add_vendor_subdirectory(SUBDIR)
#
# EXCLUDE_FROM_ALL means only targets actually depended on via
# target_link_libraries get built. SYSTEM marks its headers so our
# per-module warning flags (added only by add_lib_module/add_objects_module/
# add_exe_module/etc., never globally) don't fire when *our* code includes them.
# -----------------------------------------------------------------------
macro(add_vendor_subdirectory SUBDIR)
get_property(_pmn_vendor_added GLOBAL PROPERTY PMN_VENDOR_ADDED_${SUBDIR} SET)
if(NOT _pmn_vendor_added)
set_property(GLOBAL PROPERTY PMN_VENDOR_ADDED_${SUBDIR} TRUE)
add_subdirectory(
"${CMAKE_SOURCE_DIR}/vendor/${SUBDIR}"
"${CMAKE_BINARY_DIR}/vendor/${SUBDIR}"
EXCLUDE_FROM_ALL SYSTEM
)
_exclude_from_compile_commands("${CMAKE_SOURCE_DIR}/vendor/${SUBDIR}")
endif()
endmacro()
# -----------------------------------------------------------------------
# add_dependency(NAME <FetchContent_Declare args...>)
#
# Example -- prefer an installed copy that's new enough, fetch+build from
# source otherwise:
# add_dependency(fmt
# GIT_REPOSITORY https://github.com/fmtlib/fmt.git
# GIT_TAG 12.1.0
# GIT_SHALLOW TRUE
# FIND_PACKAGE_ARGS 12 NAMES fmt
# )
# add_lib_module(mylib DEPENDS fmt::fmt)
#
# Always give FIND_PACKAGE_ARGS a minimum version (the "12" above). Without
# one, ANY installed copy beats the pinned GIT_TAG -- including a distro
# package years older than the pin -- so a "pinned" dependency isn't. A
# dependency's own CMake options (fmt's FMT_MODULE, say) are set with
# set(<OPTION> <value> CACHE ...) BEFORE this call. source/Core/CMakeLists.txt
# is the real, commented instance of both.
#
# FetchContent_MakeAvailable() is documented idempotent per name, so
# calling this more than once for the same NAME (from multiple modules)
# is safe.
#
# The targets it makes are usable from EVERY module, wherever the call is
# made and whichever module CMake reaches first -- one module can declare
# fmt and another just DEPENDS on fmt::fmt. A copy built from source gets
# that for free (its targets are global). An installed copy found through
# FIND_PACKAGE_ARGS does not: find_package() defines imported targets in the
# directory that called it and below, so a sibling module's
# target_link_libraries(... fmt::fmt) failed with "target was not found" --
# only on machines that happen to have the package installed. Hence
# CMAKE_FIND_PACKAGE_TARGETS_GLOBAL, for the duration of FetchContent_
# MakeAvailable -- not of FetchContent_Declare, which reads the variable too
# and, in CMake 3.24 to 3.28 at least, builds a broken find_package() call
# from it ("Argument not separated from preceding token by whitespace").
# -----------------------------------------------------------------------
macro(add_dependency NAME)
include(FetchContent)
FetchContent_Declare(${NAME} SYSTEM ${ARGN})
if(DEFINED CMAKE_FIND_PACKAGE_TARGETS_GLOBAL)
set(_pmn_prev_targets_global "${CMAKE_FIND_PACKAGE_TARGETS_GLOBAL}")
else()
unset(_pmn_prev_targets_global)
endif()
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
FetchContent_MakeAvailable(${NAME})
if(DEFINED _pmn_prev_targets_global)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL "${_pmn_prev_targets_global}")
else()
unset(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL)
endif()
unset(_pmn_prev_targets_global)
string(TOLOWER "${NAME}" _pmn_lc_name)
if(DEFINED ${_pmn_lc_name}_SOURCE_DIR)
_exclude_from_compile_commands("${${_pmn_lc_name}_SOURCE_DIR}")
endif()
endmacro()
+190
View File
@@ -0,0 +1,190 @@
# =============================================================================
# Documentation.cmake
#
# Automatic, per-module Doxygen documentation, using a pinned Doxygen
# version -- never your system's doxygen, so every machine gets identical
# output. Zero setup required at the CMakeLists.txt level: every module
# registered via add_lib_module/add_objects_module/add_exe_module/
# add_test_module/add_example_module gets its own documentation target for free -- nothing
# to add to a module's own CMakeLists.txt. Vendored and fetched
# dependencies (breeze/cmake/Dependencies.cmake) are never auto-documented, same
# reasoning as their compile_commands.json exclusion: it's not your code.
#
# IMPORTANT: this file only ever LOOKS for an already-downloaded pinned
# Doxygen (see breeze/cmake/DoxygenPin.cmake for where) -- it never downloads
# anything itself, so a plain `breeze/scripts/build.sh` never touches Doxygen or
# needs network access. Run `breeze/scripts/setup.sh` / `setup.bat` once (which
# runs breeze/cmake/FetchDoxygen.cmake) to actually fetch it; until then,
# documentation targets print a message pointing at that script instead
# of building, and everything else builds completely normally.
#
# What gets generated, per module <target>, once set up:
# out/docs/<target>/html/index.html -- via `cmake --build <build-dir> --target docs_<target>`
# Plus one landing page linking to all of them:
# out/docs/index.html -- via `cmake --build <build-dir> --target docs`
# (or just run breeze/scripts/docs.sh / docs.bat, which builds `docs` and prints the path)
# Paths above use the defaults from project/Configuration.cmake (DOCS_OUTPUT_DIR).
#
# Configuring it:
# -DENABLE_DOXYGEN=OFF disable entirely (skips even the presence check)
# -DDOXYGEN_PINNED_VERSION=1.17.0 use a different pinned version (also
# update the two SHA256 hashes in breeze/cmake/DoxygenPin.cmake to match
# that release's assets, and re-run breeze/scripts/setup.sh)
# -DDOXYGEN_WARN_IF_UNDOCUMENTED=YES / -DDOXYGEN_QUIET=NO stricter/louder output
# -DDOXYGEN_<ANY_DOXYFILE_TAG>=... any Doxyfile tag can be set this way --
# see https://www.doxygen.nl/manual/config.html
# A module can opt out with NO_DOCS, e.g.:
# add_lib_module(core_lib TYPE SHARED NO_DOCS ...)
# =============================================================================
include_guard(GLOBAL)
option(ENABLE_DOXYGEN "Generate per-module Doxygen documentation using a pinned, project-managed Doxygen (run breeze/scripts/setup.sh once to fetch it)" ON)
if(ENABLE_DOXYGEN)
include("${CMAKE_CURRENT_LIST_DIR}/DoxygenPin.cmake")
if(NOT DOXYGEN_PIN_PLATFORM_SUPPORTED)
message(WARNING "No pinned Doxygen ${DOXYGEN_PINNED_VERSION} binary is configured for this "
"host platform. Install Doxygen ${DOXYGEN_PINNED_VERSION} manually, make sure "
"it's on PATH or pass -DDOXYGEN_EXECUTABLE=/path/to/doxygen, or extend "
"breeze/cmake/DoxygenPin.cmake with a download URL + hash for this platform.")
set(ENABLE_DOXYGEN OFF)
elseif(NOT EXISTS "${DOXYGEN_PIN_EXECUTABLE}")
# Not fetched yet -- see the file header. Deliberately not a
# message() here: this is the normal, expected state before
# breeze/scripts/setup.sh has ever run, not something to warn about.
set(ENABLE_DOXYGEN OFF)
else()
# Pre-seed DOXYGEN_EXECUTABLE with our pinned path *before*
# find_package(Doxygen) runs, rather than overriding it
# afterward. find_program() (which find_package uses internally)
# does not re-search when the cache variable already holds a
# valid path -- so this makes find_package() treat our pinned
# copy as the one it found, and run its own normal success path
# using it: creating the Doxygen::doxygen imported target,
# determining DOXYGEN_VERSION, and generating the Doxyfile
# template doxygen_add_docs() needs. All of that only happens as
# a side effect of find_package() succeeding -- overriding
# DOXYGEN_EXECUTABLE *after* calling it (the previous approach
# here) leaves those steps never having run at all, which
# doxygen_add_docs() then fails on. This way we don't have to
# reimplement any of find_package's own (undocumented, and
# confirmed version-dependent -- CMake 3.28 and 4.3 both hit
# this differently) internal machinery ourselves.
set(DOXYGEN_EXECUTABLE "${DOXYGEN_PIN_EXECUTABLE}" CACHE FILEPATH "Pinned Doxygen executable (never the system's)" FORCE)
endif()
endif()
if(ENABLE_DOXYGEN)
# Also searches for `dot` (Graphviz) for call graphs -- an optional,
# system-detected enhancement, unrelated to the doxygen executable
# itself, which is pre-seeded above to always resolve to our pinned
# copy rather than a system one.
find_package(Doxygen QUIET OPTIONAL_COMPONENTS dot)
if(NOT DOXYGEN_FOUND)
message(WARNING "Pre-seeding DOXYGEN_EXECUTABLE with the pinned Doxygen at "
"${DOXYGEN_PIN_EXECUTABLE} did not result in find_package(Doxygen) "
"succeeding. Documentation will be disabled for this configure.")
set(ENABLE_DOXYGEN OFF)
endif()
endif()
if(ENABLE_DOXYGEN)
message(STATUS "Doxygen ${DOXYGEN_VERSION} ready (pinned, not system-installed: ${DOXYGEN_EXECUTABLE}) -- per-module documentation enabled")
# Project-wide defaults, one Doxyfile tag per DOXYGEN_<TAG> variable.
# Override any of them with -DDOXYGEN_<TAG>=... at configure time.
# Kept deliberately quiet and lenient by default (no warnings about
# undocumented entities) so a freshly generated module with zero
# doxygen comments still produces clean, useful output -- browsable
# files/functions/call graphs -- instead of a wall of warnings.
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_GENERATE_LATEX NO)
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_STATIC YES)
set(DOXYGEN_EXTRACT_PRIVATE NO)
set(DOXYGEN_WARN_IF_UNDOCUMENTED NO)
set(DOXYGEN_QUIET YES)
set(DOXYGEN_RECURSIVE YES)
set(DOXYGEN_SOURCE_BROWSER YES)
set(DOXYGEN_GENERATE_TREEVIEW YES)
if(DOXYGEN_DOT_FOUND)
set(DOXYGEN_HAVE_DOT YES)
set(DOXYGEN_CALL_GRAPH YES)
set(DOXYGEN_CALLER_GRAPH YES)
endif()
add_custom_target(docs COMMENT "Build documentation for every module")
else()
add_custom_target(docs
COMMAND ${CMAKE_COMMAND} -E echo
"Documentation isn't set up yet (or ENABLE_DOXYGEN=OFF). Run breeze/scripts/setup.sh (or setup.bat) once to fetch the pinned Doxygen, then reconfigure."
VERBATIM
)
endif()
# -----------------------------------------------------------------------
# _add_module_docs(TARGET): called automatically from _finalize_module_target
# for every module -- creates docs_<TARGET> (a Doxygen run scoped to that
# module's own directory) and wires it into the aggregate `docs` target.
# A no-op if ENABLE_DOXYGEN is off. Excluded from the default build same
# as tests/examples -- documentation is opt-in per build, not generated
# on every compile.
#
# If the module's own directory has a README.md, it's used as that
# module's Doxygen mainpage automatically.
# -----------------------------------------------------------------------
function(_add_module_docs TARGET)
if(NOT ENABLE_DOXYGEN)
return()
endif()
set(DOXYGEN_OUTPUT_DIRECTORY "${DOCS_OUTPUT_DIR}/${TARGET}")
set(DOXYGEN_PROJECT_NAME "${TARGET}")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
else()
unset(DOXYGEN_USE_MDFILE_AS_MAINPAGE)
endif()
doxygen_add_docs(docs_${TARGET}
"${CMAKE_CURRENT_SOURCE_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating documentation for module '${TARGET}'"
)
set_target_properties(docs_${TARGET} PROPERTIES EXCLUDE_FROM_ALL TRUE)
add_dependencies(docs docs_${TARGET})
set_property(GLOBAL APPEND PROPERTY PMN_DOC_MODULES ${TARGET})
endfunction()
# -----------------------------------------------------------------------
# _write_docs_index(OUTPUT_DIR): call once, after every module has been
# discovered, to write a plain landing page at OUTPUT_DIR/index.html
# linking to each module's own generated docs -- so "where do I start
# reading" has one obvious answer regardless of how many modules exist.
# -----------------------------------------------------------------------
function(_write_docs_index OUTPUT_DIR)
if(NOT ENABLE_DOXYGEN)
return()
endif()
file(MAKE_DIRECTORY "${OUTPUT_DIR}")
get_property(_pmn_doc_modules GLOBAL PROPERTY PMN_DOC_MODULES)
list(SORT _pmn_doc_modules)
set(_pmn_doc_links "")
foreach(_pmn_mod ${_pmn_doc_modules})
string(APPEND _pmn_doc_links " <li><a href=\"${_pmn_mod}/html/index.html\">${_pmn_mod}</a></li>\n")
endforeach()
file(WRITE "${OUTPUT_DIR}/index.html" "<!DOCTYPE html>
<html>
<head><meta charset=\"utf-8\"><title>${PROJECT_NAME} -- module documentation</title></head>
<body>
<h1>${PROJECT_NAME} module documentation</h1>
<ul>
${_pmn_doc_links} </ul>
</body>
</html>
")
endfunction()
+98
View File
@@ -0,0 +1,98 @@
# =============================================================================
# DoxygenPin.cmake
#
# Pure computation of "where does the pinned Doxygen live, and where would
# it be downloaded from" -- no network calls, no download, no extraction.
# Shared by two very different callers, which is why this is its own file:
#
# breeze/cmake/Documentation.cmake include()s this during the normal CMake
# configure to find out where to LOOK for an
# already-downloaded pinned Doxygen. Never
# downloads anything itself -- see that file.
#
# breeze/cmake/FetchDoxygen.cmake include()s this when run standalone via
# `cmake -P breeze/cmake/FetchDoxygen.cmake` (what
# breeze/scripts/setup.sh / setup.bat call) to know
# what to actually download.
#
# Needs TOOLCACHE_DIR to already be set -- Documentation.cmake gets it from
# project/Configuration.cmake (included earlier by CMakeLists.txt); FetchDoxygen.cmake
# sets it manually since script mode (`cmake -P`) never runs CMakeLists.txt.
#
# Bump DOXYGEN_PINNED_VERSION to change the pinned version project-wide --
# doing so also requires updating the SHA256 hashes below to match the
# new release's assets (each was checked against the digest GitHub
# publishes for the asset AND a local download, not trusted from either
# alone). A stale hash fails loud and hard, on purpose, in
# FetchDoxygen.cmake rather than silently accepting an unexpected file).
# =============================================================================
include_guard(GLOBAL)
if(NOT DEFINED DOXYGEN_PINNED_VERSION)
set(DOXYGEN_PINNED_VERSION "1.16.1")
endif()
string(REPLACE "." "_" _pmn_doxygen_tag_version "${DOXYGEN_PINNED_VERSION}")
set(DOXYGEN_PIN_TAG "Release_${_pmn_doxygen_tag_version}")
set(DOXYGEN_PIN_ROOT "${TOOLCACHE_DIR}/doxygen-${DOXYGEN_PINNED_VERSION}")
# This is a HOST question (what machine is actually running Doxygen), not
# a DIST_TARGET one -- see HostArch.cmake's header. Needed even on the
# cross DIST_TARGETs (an ARM64 Linux build from an x64 machine, an x64
# macOS build on an Apple Silicon one, ...): the machine running Doxygen and
# the DIST_TARGET being built need not share an architecture there, and
# this file only ever cares about the former.
include("${CMAKE_CURRENT_LIST_DIR}/HostArch.cmake")
pmn_host_arch(_pmn_doxygen_host_arch)
if(CMAKE_HOST_WIN32 AND _pmn_doxygen_host_arch STREQUAL "x64")
set(DOXYGEN_PIN_URL "https://github.com/doxygen/doxygen/releases/download/${DOXYGEN_PIN_TAG}/doxygen-${DOXYGEN_PINNED_VERSION}.windows.x64.bin.zip")
set(DOXYGEN_PIN_HASH "ddfa59a4ae9549651330471c2e387ec7a9891080543faed541c859e5ce448653")
set(DOXYGEN_PIN_EXECUTABLE "${DOXYGEN_PIN_ROOT}/doxygen.exe")
set(DOXYGEN_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_UNIX AND NOT CMAKE_HOST_APPLE AND _pmn_doxygen_host_arch STREQUAL "x64")
set(DOXYGEN_PIN_URL "https://github.com/doxygen/doxygen/releases/download/${DOXYGEN_PIN_TAG}/doxygen-${DOXYGEN_PINNED_VERSION}.linux.bin.tar.gz")
set(DOXYGEN_PIN_HASH "a56f885d37e3aae08a99f638d17bbb381224c03a878d9e2dda4f9fa4baf1d8bd")
set(DOXYGEN_PIN_EXECUTABLE "${DOXYGEN_PIN_ROOT}/doxygen-${DOXYGEN_PINNED_VERSION}/bin/doxygen")
set(DOXYGEN_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_APPLE AND _pmn_doxygen_host_arch STREQUAL "arm64")
# macOS ships two plain zips (next to the .dmg GUI installers, which are
# deliberately not used here: mounting a disk image needs hdiutil and a
# GUI app bundle around what is, inside, just this CLI binary). Each
# holds doxygen-<version>/doxygen -- no bin/ level, unlike the Linux
# tarball. Which one is the MACHINE's architecture, not the cmake
# process's, courtesy of HostArch.cmake's Rosetta handling.
set(DOXYGEN_PIN_URL "https://github.com/doxygen/doxygen/releases/download/${DOXYGEN_PIN_TAG}/doxygen-${DOXYGEN_PINNED_VERSION}-mac-arm.zip")
set(DOXYGEN_PIN_HASH "f75d4e7253ad959d691cb65b6c3a9bb0c138ce75bce7cef17f5ebf36c7b54bdc")
set(DOXYGEN_PIN_EXECUTABLE "${DOXYGEN_PIN_ROOT}/doxygen-${DOXYGEN_PINNED_VERSION}/doxygen")
set(DOXYGEN_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_APPLE AND _pmn_doxygen_host_arch STREQUAL "x64")
set(DOXYGEN_PIN_URL "https://github.com/doxygen/doxygen/releases/download/${DOXYGEN_PIN_TAG}/doxygen-${DOXYGEN_PINNED_VERSION}-mac-intel.zip")
set(DOXYGEN_PIN_HASH "1530e5eca05b442e4cdd80a8ad6b22e1bbb3fc14e12b64b07c61de4be48cc5f1")
set(DOXYGEN_PIN_EXECUTABLE "${DOXYGEN_PIN_ROOT}/doxygen-${DOXYGEN_PINNED_VERSION}/doxygen")
set(DOXYGEN_PIN_PLATFORM_SUPPORTED TRUE)
else()
# What's left: Windows or Linux on ARM64/ARM32, and any other exotic
# host. (macOS has its own branches above.) Unlike Ninja (see the
# equivalent comment in NinjaPin.cmake), the upstream Doxygen
# project's GitHub releases don't publish ARM Linux or ARM Windows
# binaries at all as of DOXYGEN_PINNED_VERSION -- the release's asset
# list is the two macOS zips, two macOS disk images, the x64 Linux
# tarball, the x64 Windows zip and installer, the sources, and the
# manual -- so this isn't fillable yet.
# To add support once/if such an asset exists: find its exact name at
# https://github.com/doxygen/doxygen/releases/tag/${DOXYGEN_PIN_TAG},
# download and verify it out-of-band, and add a branch here with its
# URL and SHA256, following the pattern above; this project won't put
# an unverified hash into one meant to catch supply-chain tampering
# (see the file-header comment above). Until then this degrades the
# same way any other unsupported host already does: FetchDoxygen.cmake
# gives a clear error, and Documentation.cmake disables ENABLE_DOXYGEN
# with a warning instead of failing the build (see that file) -- a
# plain build never depends on Doxygen regardless.
set(DOXYGEN_PIN_URL "")
set(DOXYGEN_PIN_HASH "")
set(DOXYGEN_PIN_EXECUTABLE "")
set(DOXYGEN_PIN_PLATFORM_SUPPORTED FALSE)
endif()
+110
View File
@@ -0,0 +1,110 @@
# =============================================================================
# FetchDoxygen.cmake
#
# Downloads, verifies, and extracts the pinned Doxygen (see DoxygenPin.cmake
# for the version/URL/hash) into TOOLCACHE_DIR. Run standalone, NOT included
# by the normal CMake configure:
#
# cmake -P breeze/cmake/FetchDoxygen.cmake
#
# This is what breeze/scripts/setup.sh / setup.bat call. It's deliberately not part
# of the main `cmake --preset <name>` configure -- see breeze/cmake/Documentation.cmake,
# which only ever LOOKS for the result of this script and never downloads
# anything itself, so a plain build never depends on Doxygen or on network
# access. Safe to re-run any time; a no-op if the pinned version is already
# present.
# =============================================================================
# TOOLCACHE_DIR normally comes from project/Configuration.cmake, which isn't
# loaded in this standalone script-mode invocation -- read it from there
# directly (it holds settings only, so including it has no side
# effects) rather than
# hardcoding a second copy of its default. If you've overridden
# TOOLCACHE_DIR via -DTOOLCACHE_DIR=... at configure time, pass the same
# value here to match: cmake -DTOOLCACHE_DIR=... -P breeze/cmake/FetchDoxygen.cmake
if(NOT DEFINED TOOLCACHE_DIR)
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
include("${CMAKE_SOURCE_DIR}/project/Configuration.cmake")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/DoxygenPin.cmake")
if(NOT DOXYGEN_PIN_PLATFORM_SUPPORTED)
set(_pmn_doxygen_platform_hint "")
if(CMAKE_HOST_WIN32)
set(_pmn_doxygen_platform_hint
" If this looks like an ordinary 64-bit machine, double-check this isn't "
"a 32-bit cmake.exe running under WoW64 (installed under \"Program Files "
"(x86)\" is a good sign it is) -- that's a common, easy way to end up here "
"even on x64/ARM64 hardware; installing the 64-bit CMake from cmake.org "
"fixes it.")
endif()
message(FATAL_ERROR "No pinned Doxygen ${DOXYGEN_PINNED_VERSION} binary is configured for this host "
"platform.${_pmn_doxygen_platform_hint} Install Doxygen ${DOXYGEN_PINNED_VERSION} "
"manually, make sure it's on PATH or pass "
"-DDOXYGEN_EXECUTABLE=/path/to/doxygen at project-configure time, or extend "
"breeze/cmake/DoxygenPin.cmake with a download URL + hash for this platform.")
endif()
if(EXISTS "${DOXYGEN_PIN_EXECUTABLE}")
message(STATUS "Doxygen ${DOXYGEN_PINNED_VERSION} already present: ${DOXYGEN_PIN_EXECUTABLE}")
return()
endif()
message(STATUS "Downloading pinned Doxygen ${DOXYGEN_PINNED_VERSION} (cached under ${TOOLCACHE_DIR})")
set(_pmn_doxygen_archive "${TOOLCACHE_DIR}/_doxygen_download.archive")
# No EXPECTED_HASH here on purpose: file(DOWNLOAD ... EXPECTED_HASH ...)
# raises a hard, unrecoverable CMake Error on ANY failure -- including
# plain network/connectivity failures -- which would turn a "you're
# offline" hiccup into a worse error than it needs to be. Downloading
# first and verifying the hash ourselves afterward lets us give a clearer
# message for a network failure, while still treating a genuine hash
# mismatch (see below) as fully fatal.
file(DOWNLOAD "${DOXYGEN_PIN_URL}" "${_pmn_doxygen_archive}"
STATUS _pmn_doxygen_dl_status
SHOW_PROGRESS
)
list(GET _pmn_doxygen_dl_status 0 _pmn_doxygen_dl_code)
if(NOT _pmn_doxygen_dl_code EQUAL 0)
list(GET _pmn_doxygen_dl_status 1 _pmn_doxygen_dl_msg)
file(REMOVE "${_pmn_doxygen_archive}")
message(FATAL_ERROR "Failed to download pinned Doxygen ${DOXYGEN_PINNED_VERSION}: ${_pmn_doxygen_dl_msg}. "
"Check your network connection and try again -- your build doesn't need this to "
"succeed, only `breeze/scripts/docs.sh` / `docs.bat` does.")
endif()
# Verify integrity, deliberately as a hard failure -- a mismatch here means
# the downloaded artifact doesn't match what this project expects
# (corruption or tampering in transit, or a stale pinned hash after
# bumping DOXYGEN_PINNED_VERSION in DoxygenPin.cmake without updating the
# matching hash there): a supply-chain-relevant integrity failure, not
# something to silently continue past.
file(SHA256 "${_pmn_doxygen_archive}" _pmn_doxygen_actual_hash)
string(TOLOWER "${_pmn_doxygen_actual_hash}" _pmn_doxygen_actual_hash)
string(TOLOWER "${DOXYGEN_PIN_HASH}" _pmn_doxygen_expected_hash_lc)
if(NOT _pmn_doxygen_actual_hash STREQUAL _pmn_doxygen_expected_hash_lc)
file(REMOVE "${_pmn_doxygen_archive}")
message(FATAL_ERROR "Pinned Doxygen ${DOXYGEN_PINNED_VERSION} download hash mismatch -- "
"expected ${_pmn_doxygen_expected_hash_lc}, got ${_pmn_doxygen_actual_hash}. "
"This usually means DOXYGEN_PINNED_VERSION was bumped in breeze/cmake/DoxygenPin.cmake "
"without updating the matching hash there, or the download was corrupted or "
"tampered with in transit.")
endif()
file(ARCHIVE_EXTRACT INPUT "${_pmn_doxygen_archive}" DESTINATION "${DOXYGEN_PIN_ROOT}")
file(REMOVE "${_pmn_doxygen_archive}")
if(NOT CMAKE_HOST_WIN32)
file(CHMOD "${DOXYGEN_PIN_EXECUTABLE}" PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
endif()
if(EXISTS "${DOXYGEN_PIN_EXECUTABLE}")
message(STATUS "Doxygen ${DOXYGEN_PINNED_VERSION} ready: ${DOXYGEN_PIN_EXECUTABLE}")
else()
message(FATAL_ERROR "Download/extract completed but the expected executable is missing: "
"${DOXYGEN_PIN_EXECUTABLE}")
endif()
+108
View File
@@ -0,0 +1,108 @@
# =============================================================================
# FetchNinja.cmake
#
# Downloads, verifies, and extracts the pinned ninja (see NinjaPin.cmake for
# the version/URL/hash) into TOOLCACHE_DIR. Run standalone, never included by
# the normal CMake configure -- ninja is needed as the generator itself
# (`cmake -G Ninja`), so it has to be resolved *before* configure runs, unlike
# Doxygen (see FetchDoxygen.cmake), which is only needed for a downstream
# custom target:
#
# cmake -P breeze/cmake/FetchNinja.cmake
#
# This is what breeze/scripts/setup.sh / setup.bat call, and also what breeze/scripts/
# build.sh / build.bat call on demand if neither a pinned nor a system ninja
# is found (see the comment there). Safe to re-run any time; a no-op if the
# pinned version is already present.
# =============================================================================
# TOOLCACHE_DIR normally comes from project/Configuration.cmake, which isn't
# loaded in this standalone script-mode invocation -- read it from there
# directly (it holds settings only, so including it has no side
# effects) rather than
# hardcoding a second copy of its default. If you've overridden
# TOOLCACHE_DIR via -DTOOLCACHE_DIR=... at configure time, pass the same
# value here to match: cmake -DTOOLCACHE_DIR=... -P breeze/cmake/FetchNinja.cmake
if(NOT DEFINED TOOLCACHE_DIR)
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
include("${CMAKE_SOURCE_DIR}/project/Configuration.cmake")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/NinjaPin.cmake")
if(NOT NINJA_PIN_PLATFORM_SUPPORTED)
set(_pmn_ninja_platform_hint "")
if(CMAKE_HOST_WIN32)
set(_pmn_ninja_platform_hint
" If this looks like an ordinary 64-bit machine, double-check this isn't "
"a 32-bit cmake.exe running under WoW64 (installed under \"Program Files "
"(x86)\" is a good sign it is) -- that's a common, easy way to end up here "
"even on x64/ARM64 hardware; installing the 64-bit CMake from cmake.org "
"fixes it.")
endif()
message(FATAL_ERROR "No pinned ninja ${NINJA_PINNED_VERSION} binary is configured for this host "
"platform.${_pmn_ninja_platform_hint} Install ninja manually and make sure "
"it's on PATH, or extend breeze/cmake/NinjaPin.cmake with a download URL + hash for "
"this platform.")
endif()
if(EXISTS "${NINJA_PIN_EXECUTABLE}")
message(STATUS "ninja ${NINJA_PINNED_VERSION} already present: ${NINJA_PIN_EXECUTABLE}")
return()
endif()
message(STATUS "Downloading pinned ninja ${NINJA_PINNED_VERSION} (cached under ${TOOLCACHE_DIR})")
set(_pmn_ninja_archive "${TOOLCACHE_DIR}/_ninja_download.archive")
# No EXPECTED_HASH here on purpose -- see the identical comment in
# FetchDoxygen.cmake: file(DOWNLOAD ... EXPECTED_HASH ...) raises a hard,
# unrecoverable CMake Error on ANY failure, including plain network
# failures. Downloading first and verifying the hash ourselves afterward
# lets a network failure and a genuine integrity failure be told apart and
# handled differently (see below).
file(DOWNLOAD "${NINJA_PIN_URL}" "${_pmn_ninja_archive}"
STATUS _pmn_ninja_dl_status
SHOW_PROGRESS
)
list(GET _pmn_ninja_dl_status 0 _pmn_ninja_dl_code)
if(NOT _pmn_ninja_dl_code EQUAL 0)
list(GET _pmn_ninja_dl_status 1 _pmn_ninja_dl_msg)
file(REMOVE "${_pmn_ninja_archive}")
message(FATAL_ERROR "Failed to download pinned ninja ${NINJA_PINNED_VERSION}: ${_pmn_ninja_dl_msg}. "
"Check your network connection and try again, or install ninja yourself and "
"make sure it's on PATH -- breeze/scripts/build.sh / build.bat will use a system ninja "
"if no pinned one is found.")
endif()
# Verify integrity, deliberately as a hard failure -- see the identical
# comment in FetchDoxygen.cmake: a mismatch here means corruption/tampering
# in transit, or a stale pinned hash after bumping NINJA_PINNED_VERSION in
# NinjaPin.cmake without updating the matching hash there.
file(SHA256 "${_pmn_ninja_archive}" _pmn_ninja_actual_hash)
string(TOLOWER "${_pmn_ninja_actual_hash}" _pmn_ninja_actual_hash)
string(TOLOWER "${NINJA_PIN_HASH}" _pmn_ninja_expected_hash_lc)
if(NOT _pmn_ninja_actual_hash STREQUAL _pmn_ninja_expected_hash_lc)
file(REMOVE "${_pmn_ninja_archive}")
message(FATAL_ERROR "Pinned ninja ${NINJA_PINNED_VERSION} download hash mismatch -- "
"expected ${_pmn_ninja_expected_hash_lc}, got ${_pmn_ninja_actual_hash}. "
"This usually means NINJA_PINNED_VERSION was bumped in breeze/cmake/NinjaPin.cmake "
"without updating the matching hash there, or the download was corrupted or "
"tampered with in transit.")
endif()
file(ARCHIVE_EXTRACT INPUT "${_pmn_ninja_archive}" DESTINATION "${NINJA_PIN_ROOT}")
file(REMOVE "${_pmn_ninja_archive}")
if(NOT CMAKE_HOST_WIN32)
file(CHMOD "${NINJA_PIN_EXECUTABLE}" PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
endif()
if(EXISTS "${NINJA_PIN_EXECUTABLE}")
message(STATUS "ninja ${NINJA_PINNED_VERSION} ready: ${NINJA_PIN_EXECUTABLE}")
else()
message(FATAL_ERROR "Download/extract completed but the expected executable is missing: "
"${NINJA_PIN_EXECUTABLE}")
endif()
+114
View File
@@ -0,0 +1,114 @@
# =============================================================================
# .gitignore sync
#
# Keeps a marked block in .gitignore in sync with the actual, current
# values of the path variables above -- every configure, automatically.
# Nothing outside that block (e.g. the Visual Studio template this
# project's .gitignore starts from) is ever touched. This is what makes
# this file the real source of truth for these paths specifically: change
# OUT_DIR (or override it with -DOUT_DIR=...) and .gitignore updates on
# the next configure instead of silently drifting out of sync with what
# actually gets generated.
#
# If .gitignore doesn't exist yet, it's created with just this block. If
# it exists but has no markers yet, the block is appended. If the markers
# are already there, only the content between them is replaced. The file
# is only rewritten when the computed content actually differs, so a
# normal reconfigure doesn't spuriously touch it (or its mtime).
#
# A path is only added if it resolves inside the repo (CMAKE_SOURCE_DIR)
# -- e.g. an overridden TOOLCACHE_DIR pointing somewhere else entirely is
# silently skipped, since .gitignore has nothing meaningful to say about
# a location outside the repository it lives in.
#
# Skipped entirely in query mode -- see "How scripts read this file"
# above.
# =============================================================================
include_guard(GLOBAL)
# out/dist/ is reserved (see the output layout in project/Configuration.cmake) and
# nothing writes to it yet, so it is created eagerly here -- otherwise it simply
# would not exist until something populates it, which could easily read as "this
# was never wired up" rather than "intentionally reserved for later". A file
# system side effect, so it lives here and not in the settings file: a script's
# query of those settings runs far more often than a real configure.
file(MAKE_DIRECTORY "${DIST_OUTPUT_DIR}")
set(_pmn_gitignore_begin "# >>> Configuration.cmake generated paths -- BEGIN (do not edit by hand; edit Configuration.cmake and reconfigure) >>>")
set(_pmn_gitignore_end "# <<< Configuration.cmake generated paths -- END <<<")
function(_pmn_gitignore_relpath ABSOLUTE_VALUE OUT_VAR)
file(RELATIVE_PATH _pmn_rel "${CMAKE_SOURCE_DIR}" "${ABSOLUTE_VALUE}")
if(_pmn_rel MATCHES "^\\.\\.")
set(${OUT_VAR} "" PARENT_SCOPE)
else()
set(${OUT_VAR} "${_pmn_rel}" PARENT_SCOPE)
endif()
endfunction()
set(_pmn_gitignore_entries "")
foreach(_pmn_dir_var OUT_DIR BUILD_OUTPUT_DIR DIST_OUTPUT_DIR INSTALL_OUTPUT_DIR DOCS_OUTPUT_DIR TOOLCACHE_DIR)
_pmn_gitignore_relpath("${${_pmn_dir_var}}" _pmn_rel)
if(_pmn_rel)
list(APPEND _pmn_gitignore_entries "${_pmn_rel}/")
endif()
endforeach()
_pmn_gitignore_relpath("${COMPILE_COMMANDS_DESTINATION}" _pmn_cc_rel)
if(_pmn_cc_rel)
list(APPEND _pmn_gitignore_entries "${_pmn_cc_rel}")
endif()
# breeze/scripts/run.sh keeps the wine prefixes it creates in .cache/wine/ -- a
# fixed place, not one of the path variables above. Listed here rather
# than added to .gitignore by hand: this block is rewritten on every
# configure, which silently deleted a hand-added line.
list(APPEND _pmn_gitignore_entries ".cache/wine/")
# Likewise the target sysroots breeze/cmake/toolchains/_common.cmake looks for
# in .cache/sysroots/linux-<arch> (see pmn_find_sysroot there): root
# filesystems for cross builds, hundreds of megabytes, never to commit.
list(APPEND _pmn_gitignore_entries ".cache/sysroots/")
list(REMOVE_DUPLICATES _pmn_gitignore_entries)
list(SORT _pmn_gitignore_entries)
set(_pmn_gitignore_block_content "")
foreach(_pmn_entry ${_pmn_gitignore_entries})
string(APPEND _pmn_gitignore_block_content "${_pmn_entry}\n")
endforeach()
set(_pmn_gitignore_path "${CMAKE_SOURCE_DIR}/.gitignore")
set(_pmn_gitignore_new_block "${_pmn_gitignore_begin}\n${_pmn_gitignore_block_content}${_pmn_gitignore_end}")
if(EXISTS "${_pmn_gitignore_path}")
file(READ "${_pmn_gitignore_path}" _pmn_gitignore_content)
else()
set(_pmn_gitignore_content "")
endif()
string(FIND "${_pmn_gitignore_content}" "${_pmn_gitignore_begin}" _pmn_gitignore_begin_pos)
string(FIND "${_pmn_gitignore_content}" "${_pmn_gitignore_end}" _pmn_gitignore_end_pos)
if(_pmn_gitignore_begin_pos GREATER -1 AND _pmn_gitignore_end_pos GREATER -1
AND _pmn_gitignore_end_pos GREATER _pmn_gitignore_begin_pos)
# Replace only the content between the existing markers.
string(SUBSTRING "${_pmn_gitignore_content}" 0 ${_pmn_gitignore_begin_pos} _pmn_gitignore_before)
string(LENGTH "${_pmn_gitignore_end}" _pmn_gitignore_end_marker_len)
math(EXPR _pmn_gitignore_after_start "${_pmn_gitignore_end_pos} + ${_pmn_gitignore_end_marker_len}")
string(SUBSTRING "${_pmn_gitignore_content}" ${_pmn_gitignore_after_start} -1 _pmn_gitignore_after)
set(_pmn_gitignore_final "${_pmn_gitignore_before}${_pmn_gitignore_new_block}${_pmn_gitignore_after}")
else()
# No markers yet -- append (trimming trailing blank lines first so we
# don't accumulate extra blank lines across repeated appends).
string(REGEX REPLACE "\n+$" "" _pmn_gitignore_trimmed "${_pmn_gitignore_content}")
if(_pmn_gitignore_trimmed STREQUAL "")
set(_pmn_gitignore_final "${_pmn_gitignore_new_block}\n")
else()
set(_pmn_gitignore_final "${_pmn_gitignore_trimmed}\n\n${_pmn_gitignore_new_block}\n")
endif()
endif()
if(NOT _pmn_gitignore_final STREQUAL _pmn_gitignore_content)
file(WRITE "${_pmn_gitignore_path}" "${_pmn_gitignore_final}")
endif()
+155
View File
@@ -0,0 +1,155 @@
# =============================================================================
# HostArch.cmake
#
# One place to turn this project's own architecture labels -- x64, arm64,
# or arm32 (empty if unrecognized) -- out of a processor string. Two entry
# points, because "which architecture" has two different questions behind it:
#
# pmn_host_arch() the machine CMake is actually running ON.
# NinjaPin.cmake / DoxygenPin.cmake use it to decide
# whether a pinned-tool download exists for the machine
# running CMake -- always a HOST question, independent
# of what's being built. TargetSelection.cmake also uses
# it for Clang's GNU-style driver on Windows (see
# below for why MSVC/clang-cl need a different check,
# done separately in that file).
#
# pmn_target_arch() the machine the build is FOR, i.e.
# CMAKE_SYSTEM_PROCESSOR. TargetSelection.cmake's Linux
# DIST_TARGET detection uses it. On a native build it
# is the same answer as pmn_host_arch() (CMake sets
# CMAKE_SYSTEM_PROCESSOR from the host unless told
# otherwise); the difference is a cross build, where a
# toolchain file (breeze/cmake/toolchains/) sets it to the
# target -- asking the host there would label an
# aarch64 cross build "x64".
#
# Both share one aarch64/armv7l/... spelling list, and the platform-specific
# unreliability below is only handled once. Needs no project()/compiler
# detection, so it's safe to include from a bare `cmake -P` script.
#
# About pmn_host_arch() on each platform: on Linux it is
# CMAKE_HOST_SYSTEM_PROCESSOR, which is just `uname -m` -- reliable, with
# one catch that used to break the pinned-tool download on every Linux
# machine: `cmake -P` (script mode -- how breeze/scripts/setup.sh runs
# FetchNinja.cmake and FetchDoxygen.cmake) never runs CMake's system
# detection, so CMAKE_HOST_SYSTEM_PROCESSOR is simply not defined there.
# In that case this asks the OS itself, with `uname -m`. On macOS it is
# the same `uname -m`, corrected for Rosetta 2 (see the comment inside the
# function). On
# Windows it deliberately does NOT use CMAKE_HOST_SYSTEM_
# PROCESSOR (or CMAKE_SYSTEM_PROCESSOR): both are just the
# PROCESSOR_ARCHITECTURE environment variable, which reports the
# architecture of the CURRENT PROCESS, not the machine -- a 32-bit
# cmake.exe (still a completely ordinary, common download; nothing exotic)
# running under WoW64 on an ordinary 64-bit machine reports "x86" here even
# though the machine itself is x64 or ARM64. That's the same class of
# problem as the MSVC/clang-cl architecture-detection trap in
# TargetSelection.cmake (CMake issue #25466: gitlab.kitware.com/cmake/cmake/-/issues/25466),
# just via WoW64 process redirection rather than a cross-targeting
# compiler -- confirmed the hard way, as exactly what broke the pinned-
# ninja/-doxygen lookup below on an entirely ordinary 64-bit machine with a
# 32-bit cmake.exe on PATH. PROCESSOR_ARCHITEW6432 is what Windows itself
# sets to expose the TRUE architecture to a WoW64-redirected 32-bit
# process; it's simply unset outside WoW64, which is exactly when
# PROCESSOR_ARCHITECTURE was already correct anyway -- so preferring it
# when present, falling back to PROCESSOR_ARCHITECTURE otherwise, is
# correct either way.
# =============================================================================
include_guard(GLOBAL)
# _pmn_arch_label(<processor-string> <out-var>)
#
# The one place the aarch64/x86_64/armv7l/... spellings are mapped to this
# project's x64/arm64/arm32 labels. <out-var> gets one of those, or "" if
# unrecognized.
function(_pmn_arch_label PROCESSOR OUT_VAR)
if(PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(${OUT_VAR} "arm64" PARENT_SCOPE)
elseif(PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
set(${OUT_VAR} "x64" PARENT_SCOPE)
elseif(PROCESSOR MATCHES "^[Aa][Rr][Mm]")
# Every other spelling of 32-bit ARM this project has seen: armv6l
# (Raspberry Pi 1/Zero), armv7l (the common case -- Raspberry Pi
# 2/3/4 32-bit OS, generic armhf), armv8l (a 32-bit personality/
# chroot on 64-bit ARM hardware), or bare "arm"/"ARM". Checked
# after arm64/aarch64 above since those also start with "arm".
set(${OUT_VAR} "arm32" PARENT_SCOPE)
else()
set(${OUT_VAR} "" PARENT_SCOPE)
endif()
endfunction()
# pmn_host_arch(<out-var> [<raw-out-var>])
#
# <out-var> gets one of x64/arm64/arm32/"" (unrecognized) for the machine
# running CMake. The optional <raw-out-var> gets whatever raw string was
# actually checked to produce that -- callers building an error message for
# the "" case should use this rather than assuming which underlying
# variable/environment variable was consulted, since that differs by
# platform (see above).
function(pmn_host_arch OUT_VAR)
if(CMAKE_HOST_WIN32)
if(NOT "$ENV{PROCESSOR_ARCHITEW6432}" STREQUAL "")
set(_pmn_ha_processor "$ENV{PROCESSOR_ARCHITEW6432}")
else()
set(_pmn_ha_processor "$ENV{PROCESSOR_ARCHITECTURE}")
endif()
elseif(CMAKE_HOST_SYSTEM_PROCESSOR)
set(_pmn_ha_processor "${CMAKE_HOST_SYSTEM_PROCESSOR}")
elseif(CMAKE_HOST_UNIX)
# Script mode (cmake -P): not defined -- see the header. This is
# exactly what CMake itself does to fill it in during a configure.
execute_process(COMMAND uname -m
OUTPUT_VARIABLE _pmn_ha_processor
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
else()
set(_pmn_ha_processor "${CMAKE_SYSTEM_PROCESSOR}")
endif()
# macOS: a process running under Rosetta 2 -- an x86_64 cmake on an Apple
# Silicon Mac, entirely ordinary since Intel builds of CMake and of most
# developer tools still circulate -- is told it's on an x86_64 machine,
# by `uname -m` and so by CMAKE_HOST_SYSTEM_PROCESSOR alike. The same
# class of trap as a 32-bit process under WoW64 on Windows (see the
# header), and fixed the same way: ask about the MACHINE.
# hw.optional.arm64 is 1 on any Apple Silicon Mac no matter whether the
# asking process is translated, and doesn't exist on Intel (-i makes a
# missing OID a silent empty answer instead of an error).
if(CMAKE_HOST_APPLE)
_pmn_arch_label("${_pmn_ha_processor}" _pmn_ha_probe)
if(_pmn_ha_probe STREQUAL "x64")
execute_process(COMMAND sysctl -in hw.optional.arm64
OUTPUT_VARIABLE _pmn_ha_arm64
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
if(_pmn_ha_arm64 STREQUAL "1")
set(_pmn_ha_processor "arm64")
endif()
endif()
endif()
if(ARGC GREATER 1)
set(${ARGV1} "${_pmn_ha_processor}" PARENT_SCOPE)
endif()
_pmn_arch_label("${_pmn_ha_processor}" _pmn_ha_label)
set(${OUT_VAR} "${_pmn_ha_label}" PARENT_SCOPE)
endfunction()
# pmn_target_arch(<out-var> [<raw-out-var>])
#
# Same contract as pmn_host_arch(), but for the machine the build is FOR:
# CMAKE_SYSTEM_PROCESSOR. Only meaningful where that variable can be
# trusted -- Linux, where it is `uname -m` for a native build and whatever
# the toolchain file says for a cross build. Not for MSVC/clang-cl, where
# it reflects the cmake.exe process instead (see the header).
function(pmn_target_arch OUT_VAR)
if(ARGC GREATER 1)
set(${ARGV1} "${CMAKE_SYSTEM_PROCESSOR}" PARENT_SCOPE)
endif()
_pmn_arch_label("${CMAKE_SYSTEM_PROCESSOR}" _pmn_ta_label)
set(${OUT_VAR} "${_pmn_ta_label}" PARENT_SCOPE)
endfunction()
+109
View File
@@ -0,0 +1,109 @@
# =============================================================================
# NinjaPin.cmake
#
# Pure computation of "where does the pinned ninja live, and where would it
# be downloaded from" -- no network calls, no download, no extraction.
# Mirrors DoxygenPin.cmake exactly; see that file for the full rationale.
# Shared by two very different callers:
#
# breeze/scripts/build.sh / build.bat include() this (via `cmake -P`, same
# mechanism as FetchNinja.cmake below) to
# find out where to LOOK for an
# already-downloaded pinned ninja, before
# CMake configure even runs -- ninja is
# needed as the generator itself, unlike
# Doxygen, which is only ever needed for a
# downstream custom target. Never
# downloads anything itself.
#
# breeze/cmake/FetchNinja.cmake include()s this when run standalone via
# `cmake -P breeze/cmake/FetchNinja.cmake` (what
# breeze/scripts/setup.sh / setup.bat call) to
# know what to actually download.
#
# Needs TOOLCACHE_DIR to already be set. Both callers above set it manually
# (matching project/Configuration.cmake's default) since neither runs through
# CMakeLists.txt.
#
# Bump NINJA_PINNED_VERSION to change the pinned version project-wide --
# doing so also requires updating the SHA256 hashes below to match the
# new release's assets (a stale hash fails loud and hard, on purpose, in
# FetchNinja.cmake rather than silently accepting an unexpected file).
# GitHub publishes a sha256 digest for every release asset (the
# "digest" field of its releases API, shown on the release page too); the
# hashes below were each checked against it AND against a local download,
# rather than trusted from either alone.
# =============================================================================
include_guard(GLOBAL)
if(NOT DEFINED NINJA_PINNED_VERSION)
set(NINJA_PINNED_VERSION "1.13.2")
endif()
set(NINJA_PIN_TAG "v${NINJA_PINNED_VERSION}")
set(NINJA_PIN_ROOT "${TOOLCACHE_DIR}/ninja-${NINJA_PINNED_VERSION}")
# This is a HOST question (what machine is actually running ninja), not a
# DIST_TARGET one -- see HostArch.cmake's header. Needed even on the
# cross DIST_TARGETs (an ARM64 Linux build from an x64 machine, an x64
# macOS build on an Apple Silicon one, ...): the machine running ninja and
# the DIST_TARGET being built need not share an architecture there, and
# this file only ever cares about the former.
include("${CMAKE_CURRENT_LIST_DIR}/HostArch.cmake")
pmn_host_arch(_pmn_ninja_host_arch)
if(CMAKE_HOST_WIN32 AND _pmn_ninja_host_arch STREQUAL "x64")
set(NINJA_PIN_URL "https://github.com/ninja-build/ninja/releases/download/${NINJA_PIN_TAG}/ninja-win.zip")
set(NINJA_PIN_HASH "07fc8261b42b20e71d1720b39068c2e14ffcee6396b76fb7a795fb460b78dc65")
set(NINJA_PIN_EXECUTABLE "${NINJA_PIN_ROOT}/ninja.exe")
set(NINJA_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_UNIX AND NOT CMAKE_HOST_APPLE AND _pmn_ninja_host_arch STREQUAL "x64")
set(NINJA_PIN_URL "https://github.com/ninja-build/ninja/releases/download/${NINJA_PIN_TAG}/ninja-linux.zip")
set(NINJA_PIN_HASH "5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6")
set(NINJA_PIN_EXECUTABLE "${NINJA_PIN_ROOT}/ninja")
set(NINJA_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_UNIX AND NOT CMAKE_HOST_APPLE AND _pmn_ninja_host_arch STREQUAL "arm64")
# Ninja has shipped ARM64 builds since v1.12.0 (see the comment on the
# unsupported fallback below).
set(NINJA_PIN_URL "https://github.com/ninja-build/ninja/releases/download/${NINJA_PIN_TAG}/ninja-linux-aarch64.zip")
set(NINJA_PIN_HASH "fd2cacc8050a7f12a16a2e48f9e06fca5c14fc4c2bee2babb67b58be17a607fc")
set(NINJA_PIN_EXECUTABLE "${NINJA_PIN_ROOT}/ninja")
set(NINJA_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_WIN32 AND _pmn_ninja_host_arch STREQUAL "arm64")
set(NINJA_PIN_URL "https://github.com/ninja-build/ninja/releases/download/${NINJA_PIN_TAG}/ninja-winarm64.zip")
set(NINJA_PIN_HASH "e52f0bdef9dfb1003229dbd6508a508c4073fd017247002adc66e5e806cb0391")
set(NINJA_PIN_EXECUTABLE "${NINJA_PIN_ROOT}/ninja.exe")
set(NINJA_PIN_PLATFORM_SUPPORTED TRUE)
elseif(CMAKE_HOST_APPLE)
# ONE asset for both Intel and Apple Silicon: ninja-mac.zip holds a
# universal (x86_64 + arm64) Mach-O binary, so unlike Linux/Windows
# this branch doesn't care which architecture the host is -- which also
# sidesteps the Rosetta trap HostArch.cmake describes.
set(NINJA_PIN_URL "https://github.com/ninja-build/ninja/releases/download/${NINJA_PIN_TAG}/ninja-mac.zip")
set(NINJA_PIN_HASH "c99048673aa765960a99cf10c6ddb9f1fad506099ff0a0e137ad8960a88f321b")
set(NINJA_PIN_EXECUTABLE "${NINJA_PIN_ROOT}/ninja")
set(NINJA_PIN_PLATFORM_SUPPORTED TRUE)
else()
# What's left: 32-bit ARM Linux, and any other exotic host. (macOS and
# ARM64 Linux/Windows have their own branches above -- Ninja's GitHub
# releases have shipped those binaries since v1.12.0, "ARM binaries are
# now available for Windows and Linux, too" per that release's notes.)
# Not a guess left blank -- there is simply no 32-bit ARM Linux asset
# upstream at NINJA_PINNED_VERSION. This project won't put an
# unverified SHA256 into a hash meant to catch supply-chain tampering
# (see the file-header comment above), so to add support for another
# host: find that asset's exact name at
# https://github.com/ninja-build/ninja/releases/tag/${NINJA_PIN_TAG},
# download and verify it out-of-band, and add a branch here with its
# URL and SHA256, following the pattern above. Until then this
# degrades the same way any other unsupported host already does:
# FetchNinja.cmake / breeze/scripts/setup.sh give a clear error, and
# breeze/scripts/build.sh / build.bat fall back to a system ninja with a
# warning (see those files) -- the build itself still works, just
# without a pinned/version-locked ninja.
set(NINJA_PIN_URL "")
set(NINJA_PIN_HASH "")
set(NINJA_PIN_EXECUTABLE "")
set(NINJA_PIN_PLATFORM_SUPPORTED FALSE)
endif()
+53
View File
@@ -0,0 +1,53 @@
# =============================================================================
# PrintConfig.cmake
#
# Script-mode bridge: lets breeze/scripts/*.sh and breeze/scripts/*.bat read values out of
# project/Configuration.cmake -- this project's single source of truth (see that
# file) -- instead of hardcoding a second copy that can silently drift out
# of sync. This is exactly the bug it replaces: breeze/scripts/install.sh and
# install.bat used to each hardcode their own literal "demo", with a
# comment warning it had to be kept in sync with PROJECT_CODE_NAME by hand.
#
# Usage:
# cmake "-DPRINT_VARS=NAME1;NAME2;..." -P breeze/cmake/PrintConfig.cmake
#
# Prints one "NAME=value" line per requested variable, in the order given.
# Every line is emitted via message(STATUS ...) -- deliberately: it's the
# only message() mode CMake sends to stdout (verified directly: the
# no-keyword default, NOTICE, and WARNING all go to stderr; STATUS is the
# one exception), which is what lets a caller capture it with plain output
# redirection/command substitution instead of needing a temp file. The
# tradeoff is that STATUS always prefixes the line with "-- ", so every
# caller strips that off along with the "NAME=" it already knows: see
# breeze/scripts/build.sh's _pmn_read_config() or breeze/scripts/build.bat's
# :read_config for the one small wrapper each shell dialect uses -- every
# script that needs a value calls that wrapper rather than re-deriving
# this convention itself.
#
# Includes project/Configuration.cmake, which holds settings only: nothing in
# it needs a compiler and it touches nothing on disk, which is what makes it
# safe here -- a value lookup shouldn't have side effects, and this runs far
# more often than a real configure. What does need a real compiler
# (DIST_TARGET/PLATFORM/ARCHITECTURE/COMPILER_ID, which a plain `cmake -P`
# script would get wrong: project(... LANGUAGES CXX) never runs) is
# breeze/cmake/TargetSelection.cmake, not included here. So only ask for names
# the settings file defines -- PROJECT_CODE_NAME, DEFAULT_BUILD_TYPE, the path
# variables; see that file's header for the rules on adding to it.
# =============================================================================
if(NOT DEFINED PRINT_VARS)
message(FATAL_ERROR "Usage: cmake \"-DPRINT_VARS=NAME1;NAME2;...\" -P breeze/cmake/PrintConfig.cmake")
endif()
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
include("${CMAKE_SOURCE_DIR}/project/Configuration.cmake")
foreach(_pmn_print_name ${PRINT_VARS})
if(NOT DEFINED ${_pmn_print_name})
message(FATAL_ERROR
"PrintConfig.cmake: '${_pmn_print_name}' is not defined by "
"project/Configuration.cmake -- see that file's header "
"('How scripts read this file').")
endif()
message(STATUS "${_pmn_print_name}=${${_pmn_print_name}}")
endforeach()
+610
View File
@@ -0,0 +1,610 @@
# =============================================================================
# TargetSelection.cmake
#
# What the build is FOR: DIST_TARGET -- a single "<platform>-<arch>-<compiler>"
# string -- and the PLATFORM, ARCHITECTURE and COMPILER_ID derived from it.
# Included by breeze/cmake/Breeze.cmake right after project(), since it needs
# real compiler detection; the project's own settings (project/Configuration.cmake)
# deliberately have none of it, so that a plain `cmake -P` script can read them.
#
# Everything below is the template's: nothing in it is configuration. To build
# for a different target, pick a CMakePresets.json preset or pass -DDIST_TARGET.
#
# -----------------------------------------------------------------------
# Target selection (DIST_TARGET)
# -----------------------------------------------------------------------
# DIST_TARGET identifies what you're building for as a single
# "<platform>-<arch>-<compiler>" string. It is declared by the toolchain
# file in use (breeze/cmake/toolchains/<DIST_TARGET>.cmake -- what every
# CMakePresets.json preset selects, and the recommended way to build), or
# given explicitly with -DDIST_TARGET=<value> (if both are present they
# must agree), or, with neither, auto-detected from the compiler CMake
# found. Every value in PMN_VALID_DIST_TARGETS below is supported:
#
# linux-x64-gcc GCC on x86-64 Linux
# linux-x64-clang Clang on x86-64 Linux
# linux-arm64-gcc GCC on 64-bit ARM Linux (aarch64)
# linux-arm64-clang Clang on 64-bit ARM Linux (aarch64)
# linux-arm32-gcc GCC on 32-bit ARM Linux (armhf/armv7l and similar)
# linux-arm32-clang Clang on 32-bit ARM Linux
# macos-arm64-clang Clang on Apple Silicon macOS -- Apple Clang from
# the Xcode Command Line Tools, or LLVM's own
# Clang (e.g. Homebrew's); both are "clang" here
# macos-x64-clang Clang on Intel macOS (or, given Rosetta 2, an
# Apple Silicon Mac: the toolchain just asks the
# compiler for -arch x86_64)
# windows-x64-msvc cl.exe (MSVC) targeting x64
# windows-arm64-msvc cl.exe (MSVC) targeting ARM64
# windows-x64-clang-cl clang-cl targeting x64 (Clang targeting the
# MSVC ABI -- CMake's MSVC variable is TRUE for
# this too)
# windows-arm64-clang-cl clang-cl targeting ARM64
# windows-x64-clang Clang with its own GNU-style driver on Windows,
# targeting x64 (e.g. an MSYS2 CLANG64
# environment) -- distinct from clang-cl above,
# which mimics cl.exe instead of gcc
# windows-arm64-clang Clang with its own GNU-style driver on Windows,
# targeting ARM64 (e.g. an MSYS2 CLANGARM64
# environment)
# windows-x86-mingw32 32-bit x86 GCC via MinGW
# windows-x64-mingw64 64-bit x64 GCC via MinGW-w64
#
# No macOS GCC target and no universal one. Apple's own toolchain IS Clang
# (a Homebrew GCC would be a second, unrelated compiler ecosystem needing
# its own SDK and sysroot handling), and a universal binary is a
# CMAKE_OSX_ARCHITECTURES list with two entries -- which doesn't fit a
# DIST_TARGET that names ONE architecture, and is rejected below with the
# reason rather than mislabeled. To ship one universal file, build both
# macOS presets and combine the results: `lipo -create <arm64 file>
# <x64 file> -output <file>`.
#
# No windows-arm32-* and no ARM64 MinGW target: 32-bit ARM Windows has no
# real toolchain support left in any of the compilers above (MSVC dropped
# it; it only ever existed for Windows RT / Windows 10 IoT Core), and
# mainline MinGW-w64's GCC has no ARM64 Windows (PE/COFF) backend -- ARM64
# Windows toolchains in that ecosystem are LLVM-based (MSYS2's
# CLANGARM64, covered by windows-arm64-clang above), not GCC-based.
#
# Architecture (x64/arm64/arm32) is detected differently depending on
# compiler family, because CMAKE_SYSTEM_PROCESSOR turns out not to be
# trustworthy for all of them:
# - Linux (GCC/Clang) goes through breeze/cmake/HostArch.cmake's
# pmn_target_arch(), i.e. CMAKE_SYSTEM_PROCESSOR: `uname -m` for a
# native build, and whatever the toolchain file sets it to for a
# cross build (so an aarch64 cross build from an x64 machine is
# labeled arm64, not x64). Clang's GNU-style driver on Windows goes
# through pmn_host_arch() instead, an environment-variable check --
# see that file for why CMAKE_SYSTEM_PROCESSOR/
# CMAKE_HOST_SYSTEM_PROCESSOR can't be trusted there (a second,
# related trap: a 32-bit cmake.exe under WoW64, independent of the
# MSVC one below).
# - macOS goes through CMAKE_OSX_ARCHITECTURES, which the macOS toolchain
# files set to exactly one architecture, and only falls back to
# CMAKE_SYSTEM_PROCESSOR (`uname -m`) without one. Apple's clang is a
# single multi-architecture compiler, so an x86_64 build on an Apple
# Silicon Mac is just a different -arch -- not a cross compiler, and
# nothing that CMAKE_SYSTEM_PROCESSOR (which under Rosetta 2 reports
# the architecture of the cmake PROCESS, the same class of trap as
# WoW64 on Windows) could describe reliably.
# - MSVC and clang-cl use CMAKE_CXX_COMPILER_ARCHITECTURE_ID instead --
# CMAKE_SYSTEM_PROCESSOR is a documented, confirmed-in-the-wild trap
# here (tracked as CMake issue #25466): it reflects the architecture
# of the cmake.exe process itself, not what the selected cl.exe/
# clang-cl actually targets, and those two routinely differ (a
# same-machine cross build -- e.g. an x64 Visual Studio install's
# "HostX64/ARM64" tools -- is the normal way to target ARM64 from an
# x64 dev machine).
#
# Toolchain files and cross-compiling: breeze/cmake/toolchains/ has one toolchain
# file per DIST_TARGET, named after it, and every CMakePresets.json preset
# selects the one named after itself. A toolchain file settles the compiler
# (and, for clang-cl, the --target triple) before project() runs, declares
# the DIST_TARGET it is for, and -- where the target isn't the machine
# running CMake -- sets up a real cross build: the linux-arm64-* and
# linux-arm32-* targets cross-compile from any other Linux machine (an x64
# one, say), and the windows-*-mingw* targets from Linux via mingw-w64,
# while on matching hardware (ARM64 for linux-arm64-*) the very same
# preset just builds natively. On macOS the toolchain pins the
# architecture with CMAKE_OSX_ARCHITECTURES and a minimum macOS version
# with CMAKE_OSX_DEPLOYMENT_TARGET (11.0 unless you pass your own) -- both
# are only honored when set BEFORE project(), which is exactly when a
# toolchain file runs. breeze/cmake/toolchains/_common.cmake documents
# exactly what each one does and which packages it needs. The exception is
# MSVC: cl.exe takes its architecture from the Visual Studio developer
# environment it is started from (vcvarsall.bat), which no CMake file can
# switch, so the windows-*-msvc toolchains only pick cl and check it is
# there -- the architecture check below then verifies it is the right one.
#
# Without a toolchain file, pointing -DCMAKE_CXX_COMPILER at a cross
# compiler is not enough on its own: CMake is never told the target is a
# different architecture, so the auto-detected DIST_TARGET follows the
# HOST and you'll get a cross-check mismatch (see below) instead, same as
# any other unmet assumption this file's auto-detection makes.
#
# IMPORTANT: -DDIST_TARGET=... on its own only labels the build (which
# install subdirectory it lands in, which metadata gets printed); it does
# NOT select a compiler or an architecture. CMake still uses whatever
# CMAKE_CXX_COMPILER it would otherwise find -- your PATH, your
# environment, or an explicit -DCMAKE_CXX_COMPILER=... -- same as if
# DIST_TARGET had never been mentioned. Passing
# -DDIST_TARGET=windows-x64-mingw64 alone, with no MinGW compiler actually
# configured, does not make CMake go find one; it configures whatever
# compiler it would have anyway (often MSVC, since CMake looks for that
# first on Windows) and mislabels the result. TargetSelection.cmake catches
# exactly this: a DIST_TARGET that disagrees with what the real compiler
# (and architecture) auto-detects to is a FATAL_ERROR, not a silent
# mislabel -- see the "cross-check" step below. A toolchain file doesn't
# have the problem, because it sets the label and the compiler together:
# use the toolchain file for that target
# (-DCMAKE_TOOLCHAIN_FILE=breeze/cmake/toolchains/<DIST_TARGET>.cmake), or simply
# the matching CMakePresets.json preset, which selects it.
#
# Auto-detection FATAL_ERRORs rather than guessing when the compiler
# doesn't match any of the above, instead of silently assuming MSVC --
# that silent assumption is exactly what used to misidentify a MinGW GCC
# build as MSVC (COMPILER_ID "msvc" for an actual GNU compiler), which in
# turn misfiled its `cmake --install` output under .../windows-x64-msvc/.
#
# PLATFORM, ARCHITECTURE, and COMPILER_ID below are metadata derived from
# DIST_TARGET; Breeze.cmake and the other breeze/cmake/*.cmake modules consume these
# rather than re-deriving them.
# =============================================================================
include_guard(GLOBAL)
set(PMN_VALID_DIST_TARGETS
linux-x64-gcc
linux-x64-clang
linux-arm64-gcc
linux-arm64-clang
linux-arm32-gcc
linux-arm32-clang
macos-arm64-clang
macos-x64-clang
windows-x64-msvc
windows-arm64-msvc
windows-x64-clang-cl
windows-arm64-clang-cl
windows-x64-clang
windows-arm64-clang
windows-x86-mingw32
windows-x64-mingw64
)
list(JOIN PMN_VALID_DIST_TARGETS " " _pmn_valid_dist_targets_str)
# Where DIST_TARGET came from, for the messages below. A toolchain file
# (breeze/cmake/toolchains/<DIST_TARGET>.cmake -- every CMakePresets.json preset
# selects the one named after itself) declares the target it is for via
# PMN_TOOLCHAIN_DIST_TARGET (see toolchains/_common.cmake), so the label
# and the compiler the toolchain picked travel together. It is resolved
# here, ahead of the auto-detection below, because
# _pmn_dist_target_undetected() decides how hard to fail based on
# whether DIST_TARGET is already known. An explicit -DDIST_TARGET is
# still accepted alongside a toolchain, but it has to agree with it.
set(_pmn_dist_target_source "")
if(PMN_TOOLCHAIN_DIST_TARGET)
if(DIST_TARGET AND NOT DIST_TARGET STREQUAL PMN_TOOLCHAIN_DIST_TARGET)
message(FATAL_ERROR
"DIST_TARGET was explicitly set to '${DIST_TARGET}', but the toolchain file in use "
"(${CMAKE_TOOLCHAIN_FILE}) is for '${PMN_TOOLCHAIN_DIST_TARGET}'.\n"
"Drop -DDIST_TARGET (the toolchain already says which target this is), or "
"use the toolchain file for '${DIST_TARGET}':\n"
" ${CMAKE_SOURCE_DIR}/breeze/cmake/toolchains/${DIST_TARGET}.cmake")
endif()
set(DIST_TARGET "${PMN_TOOLCHAIN_DIST_TARGET}")
set(_pmn_dist_target_source "toolchain")
elseif(DIST_TARGET)
set(_pmn_dist_target_source "explicit")
endif()
# _pmn_dist_target_undetected(REASON...): an auto-detection branch
# below hit a case with no DIST_TARGET for it. REASON explains why --
# shown whether or not DIST_TARGET was explicitly given (e.g. via a
# CMakePresets.json preset), so a preset user gets the same specific
# diagnosis a plain, no-DIST_TARGET user already did.
#
# Previously REASON was only ever shown when DIST_TARGET was unset;
# setting DIST_TARGET (which every preset does -- the recommended way
# to build, per CMakePresets.json and the README) silently discarded
# it in favor of the generic "auto-detects to none of the values this
# project recognizes" cross-check message further down, with no
# explanation of WHY -- exactly the confusing, unhelpful failure this
# macro exists to prevent.
#
# No DIST_TARGET means there's nothing to fall back on, so REASON is
# the final word: FATAL_ERROR, with the full valid-values list. With
# DIST_TARGET set, REASON is advisory context for the unified
# cross-check FATAL_ERROR further down (which still runs, and has the
# copy-pasteable fix) -- so this is STATUS, and _pmn_detected_dist_target
# is left at "" for that check to pick up, same as before this macro
# existed.
macro(_pmn_dist_target_undetected)
if(DIST_TARGET)
set(_pmn_detected_dist_target "")
message(STATUS "Could not auto-detect a DIST_TARGET: " ${ARGN})
else()
message(FATAL_ERROR "Could not auto-detect a DIST_TARGET: " ${ARGN}
"\nValid values: ${_pmn_valid_dist_targets_str}")
endif()
endmacro()
include("${CMAKE_CURRENT_LIST_DIR}/HostArch.cmake")
# What DIST_TARGET the CXX compiler CMake actually found auto-detects
# to -- computed unconditionally, not only when DIST_TARGET is unset,
# so an explicitly-given DIST_TARGET can be cross-checked against it
# below rather than trusted as a label. See the file header for why
# that check exists.
if(WIN32)
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
# MSVC, and clang-cl (Clang's cl.exe-compatible driver
# personality, selected either by invoking a binary literally
# named clang-cl(.exe) or via `clang++ --driver-mode=cl`;
# CMake surfaces this as CMAKE_CXX_COMPILER_FRONTEND_VARIANT
# rather than a distinct COMPILER_ID, since it's the same
# Clang either way -- just a different argument dialect and
# default target ABI). Both get their architecture from
# CMAKE_CXX_COMPILER_ARCHITECTURE_ID, deliberately NOT from
# CMAKE_SYSTEM_PROCESSOR/CMAKE_HOST_SYSTEM_PROCESSOR (see
# HostArch.cmake's header for why those can't be trusted for
# this specific case). CMAKE_CXX_COMPILER_ARCHITECTURE_ID is
# what CMake itself derives by probing the compiler -- it
# needs this internally for the right /machine: linker flag --
# so it reflects the real target regardless of which cl.exe/
# clang-cl variant ended up on PATH. Its values are
# mixed-case by long-standing CMake convention (X86, x64,
# ARM, ARM64); normalized to uppercase here so the
# comparisons below aren't tripped by that inconsistency.
string(TOUPPER "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" _pmn_msvc_arch_id)
if(_pmn_msvc_arch_id STREQUAL "X64")
set(_pmn_win_arch "x64")
elseif(_pmn_msvc_arch_id STREQUAL "ARM64")
set(_pmn_win_arch "arm64")
else()
# Includes X86 and ARM (32-bit) -- real MSVC targets, but
# not ones this project has a DIST_TARGET for (32-bit ARM
# Windows has no real toolchain support left in any
# compiler; see the file header). Treated as unrecognized
# rather than silently mislabeled as x64 or arm64.
set(_pmn_win_arch "")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(_pmn_win_compiler_id "msvc")
else()
set(_pmn_win_compiler_id "clang-cl")
endif()
if(_pmn_win_arch)
set(_pmn_detected_dist_target "windows-${_pmn_win_arch}-${_pmn_win_compiler_id}")
else()
# X86 specifically gets its own hint rather than the
# generic message below: it's by far the most likely way
# to actually hit this branch in practice (ARM on Windows
# without ARM64 is rare) -- not a machine that's genuinely
# 32-bit-only (vanishingly rare today), almost always
# PATH/environment handing CMake the 32-bit-hosted,
# 32-bit-targeting cl.exe/clang-cl instead of the x64 one,
# e.g. from opening a plain shell instead of an "x64
# Native Tools Command Prompt for VS". Reported whether or
# not DIST_TARGET was explicitly given -- see
# _pmn_dist_target_undetected above.
if(_pmn_msvc_arch_id STREQUAL "X86")
_pmn_dist_target_undetected(
"${_pmn_win_compiler_id} is "
"targeting X86 (32-bit) -- almost always a sign that PATH/the "
"current shell is handing CMake the 32-bit-hosted, "
"32-bit-targeting ${_pmn_win_compiler_id}, not that this machine "
"is actually 32-bit-only.\n"
"Open an \"x64 Native Tools Command Prompt for VS\" (or a "
"\"Developer PowerShell for VS\" defaulted to x64) so an x64 "
"${_pmn_win_compiler_id} is what's first on PATH, delete this "
"build directory (a failed configure's CMakeCache.txt won't just "
"fix itself), and reconfigure. breeze\\scripts\\build.bat enters the right environment on "
"its own, but the build directory still has to go.\n"
"If 32-bit really is intentional, this project doesn't have a "
"DIST_TARGET for it -- set one explicitly: -DDIST_TARGET=<value>"
)
else()
_pmn_dist_target_undetected(
"${_pmn_win_compiler_id} is "
"targeting an architecture ('${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}') "
"this project doesn't have a DIST_TARGET for -- set one "
"explicitly: -DDIST_TARGET=<value>"
)
endif()
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Clang with its own GNU-style driver on Windows (e.g. an
# MSYS2 CLANG64/CLANGARM64 environment) -- distinct from
# clang-cl above, which mimics cl.exe instead of gcc.
# CMAKE_CXX_COMPILER_ARCHITECTURE_ID is specific to the MSVC-
# style detection used above and isn't populated for this
# driver mode. Falls back to HostArch.cmake instead -- unlike
# the raw cl.exe/clang-cl case, a GNU-style environment like
# MSYS2 is used as a coherent whole (its own cmake, its own
# clang++, both built for and normally run on the same
# architecture), the same assumption the MinGW pointer-size
# check below already makes for mingw32 vs. mingw64.
pmn_host_arch(_pmn_win_clang_arch _pmn_win_clang_raw)
if(_pmn_win_clang_arch STREQUAL "arm64")
set(_pmn_detected_dist_target "windows-arm64-clang")
elseif(_pmn_win_clang_arch STREQUAL "x64")
set(_pmn_detected_dist_target "windows-x64-clang")
else()
_pmn_dist_target_undetected(
"unrecognized host "
"architecture '${_pmn_win_clang_raw}' for Clang's GNU-style driver "
"on Windows -- set one explicitly: -DDIST_TARGET=<value>"
)
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# A real GCC on Windows is MinGW (mingw.org's original,
# 32-bit-only project) or MinGW-w64 (adds 64-bit; also
# provides a 32-bit target, conventionally called
# "mingw32" too since it's the direct successor). Tell
# them apart by pointer size rather than parsing the
# compiler's own executable name, which is a config
# choice, not something to depend on. No ARM MinGW target
# here on purpose, unlike the Clang branch above: mainline
# MinGW-w64's GCC has no ARM64 Windows (PE/COFF) backend --
# ARM64 Windows toolchains in that ecosystem are LLVM-based
# (MSYS2's CLANGARM64), not GCC-based.
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_pmn_detected_dist_target "windows-x64-mingw64")
else()
set(_pmn_detected_dist_target "windows-x86-mingw32")
endif()
elseif(DIST_TARGET)
# Can't cross-check an explicit value against a compiler this
# doesn't recognize at all -- the mismatch message below
# handles an empty detected value.
set(_pmn_detected_dist_target "")
else()
message(FATAL_ERROR
"Could not auto-detect a DIST_TARGET for CXX compiler "
"'${CMAKE_CXX_COMPILER_ID}' on Windows -- set one "
"explicitly: -DDIST_TARGET=<value>\n"
"Valid values: ${_pmn_valid_dist_targets_str}")
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# macOS (CMAKE_SYSTEM_NAME is "Darwin" for macOS only; iOS and
# friends have their own names). The architecture is what
# CMAKE_OSX_ARCHITECTURES says when it's set -- the macOS toolchain
# files always set it, to one value -- else CMAKE_SYSTEM_PROCESSOR.
# See the file header for why not CMAKE_SYSTEM_PROCESSOR first.
# Mapped explicitly rather than through HostArch.cmake's spelling
# table, which files anything starting with "arm" -- arm64e, say --
# under 32-bit ARM.
set(_pmn_macos_arch "")
if(CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES _pmn_macos_arch_count)
if(_pmn_macos_arch_count GREATER 1)
message(FATAL_ERROR
"CMAKE_OSX_ARCHITECTURES is '${CMAKE_OSX_ARCHITECTURES}' -- several "
"architectures, i.e. a universal binary. A DIST_TARGET names exactly one "
"architecture (macos-arm64-clang, macos-x64-clang): build each with its "
"preset and combine the results with `lipo -create <arm64 file> <x64 file> "
"-output <file>` if you need a single universal file.")
elseif(CMAKE_OSX_ARCHITECTURES STREQUAL "arm64")
set(_pmn_macos_arch "arm64")
elseif(CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64")
set(_pmn_macos_arch "x64")
endif()
set(_pmn_macos_arch_raw "CMAKE_OSX_ARCHITECTURES '${CMAKE_OSX_ARCHITECTURES}'")
else()
pmn_target_arch(_pmn_macos_arch _pmn_macos_arch_processor)
set(_pmn_macos_arch_raw "CMAKE_SYSTEM_PROCESSOR '${_pmn_macos_arch_processor}'")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(_pmn_macos_arch)
set(_pmn_detected_dist_target "macos-${_pmn_macos_arch}-clang")
else()
_pmn_dist_target_undetected(
"unrecognized macOS architecture (${_pmn_macos_arch_raw}) -- set one "
"explicitly: -DDIST_TARGET=<value>"
)
endif()
elseif(DIST_TARGET)
set(_pmn_detected_dist_target "")
else()
message(FATAL_ERROR
"Could not auto-detect a DIST_TARGET for CXX compiler "
"'${CMAKE_CXX_COMPILER_ID}' on macOS -- only Clang (Apple's, from the "
"Xcode Command Line Tools, or LLVM's own) is a supported macOS target. "
"Set one explicitly: -DDIST_TARGET=<value>\n"
"Valid values: ${_pmn_valid_dist_targets_str}")
endif()
else()
# Linux: architecture comes from CMAKE_SYSTEM_PROCESSOR via
# HostArch.cmake -- reliable here (unlike the raw cl.exe/clang-cl
# case above). For a native build that is simply `uname -m`; for a
# cross build the toolchain file (breeze/cmake/toolchains/) sets it to the
# TARGET's architecture, which is exactly what should be labeled --
# asking the host machine instead would call an aarch64 cross
# build "x64".
pmn_target_arch(_pmn_linux_arch)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Not a blind TOLOWER of CMAKE_CXX_COMPILER_ID: CMake's own ID
# for GCC is "GNU" (a case-fold gives "gnu", which none of
# PMN_VALID_DIST_TARGETS above or CMakePresets.json spell --
# every one of them, and everyone outside CMake, calls it
# "gcc"). Same reasoning the MSVC/clang-cl branch above
# already applies to its own two compiler IDs.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(_pmn_linux_compiler_id "gcc")
else()
set(_pmn_linux_compiler_id "clang")
endif()
if(_pmn_linux_arch)
set(_pmn_detected_dist_target "linux-${_pmn_linux_arch}-${_pmn_linux_compiler_id}")
else()
_pmn_dist_target_undetected(
"unrecognized "
"CMAKE_SYSTEM_PROCESSOR '${CMAKE_SYSTEM_PROCESSOR}' -- set one "
"explicitly: -DDIST_TARGET=<value>"
)
endif()
elseif(DIST_TARGET)
set(_pmn_detected_dist_target "")
else()
message(FATAL_ERROR
"Could not auto-detect a DIST_TARGET for CXX compiler "
"'${CMAKE_CXX_COMPILER_ID}' -- set one explicitly: "
"-DDIST_TARGET=<value>\n"
"Valid values: ${_pmn_valid_dist_targets_str}")
endif()
endif()
if(NOT DIST_TARGET)
set(DIST_TARGET "${_pmn_detected_dist_target}")
message(STATUS "DIST_TARGET not set -- auto-detected: ${DIST_TARGET}")
message(STATUS " (use a CMakePresets.json preset -- or -DCMAKE_TOOLCHAIN_FILE=breeze/cmake/toolchains/<target>.cmake -- to set it explicitly)")
else()
if(NOT DIST_TARGET IN_LIST PMN_VALID_DIST_TARGETS)
message(FATAL_ERROR
"Unsupported DIST_TARGET: '${DIST_TARGET}'.\n"
"Valid values: ${_pmn_valid_dist_targets_str}\n"
"Tip: open CMakePresets.json in your IDE to select a preset, "
"or pass -DDIST_TARGET=<value> on the command line.")
endif()
if(NOT DIST_TARGET STREQUAL _pmn_detected_dist_target)
# This is the check described in the file header: a DIST_TARGET
# that disagrees with the compiler CMake actually found.
if(_pmn_detected_dist_target)
set(_pmn_detected_desc "'${_pmn_detected_dist_target}'")
else()
set(_pmn_detected_desc "none of the values this project recognizes")
endif()
# The fix depends on where DIST_TARGET came from. From a
# toolchain file the compiler was already chosen to match, so
# something overrode it afterwards; from a bare -DDIST_TARGET
# nothing ever chose a compiler at all.
#
# (string(CONCAT) rather than a multi-value set(): a list would
# swallow every ";" in the text when expanded into message().)
if(_pmn_dist_target_source STREQUAL "toolchain")
string(CONCAT _pmn_fix
"The toolchain file picked its own compiler, so this means it was overridden "
"afterwards -- most likely an explicit -DCMAKE_CXX_COMPILER=... (on macOS "
"also -DCMAKE_OSX_ARCHITECTURES=...), or a CMakeCache.txt left in this "
"build directory by an earlier configure for a different target.\n"
"Remove that override, or configure into a fresh build directory. The default "
"layout already gives every preset its own (out/build/<preset>); "
"${CMAKE_BINARY_DIR} is being shared between targets.")
else()
string(CONCAT _pmn_fix
"-DDIST_TARGET=... only labels the build; it does not select a compiler. Use "
"the toolchain file made for that target, which sets both together:\n"
" -DCMAKE_TOOLCHAIN_FILE=${CMAKE_SOURCE_DIR}/breeze/cmake/toolchains/${DIST_TARGET}.cmake\n"
"or the matching CMakePresets.json preset, which selects it for you:\n"
" cmake --preset ${DIST_TARGET}")
endif()
# Some compilers can't be redirected from CMake at all; say so
# rather than only pointing at a toolchain file that cannot
# help.
set(_pmn_family_hint "")
if(DIST_TARGET MATCHES "-msvc$")
set(_pmn_family_hint
"\ncl.exe's architecture is decided by the Visual Studio developer environment \
it is run from, not by CMake: open the matching Native Tools / Cross Tools Command Prompt \
(vcvarsall.bat), or build with breeze\\scripts\\build.bat, which does that for you.")
elseif(DIST_TARGET MATCHES "^windows-(x64|arm64)-clang$")
set(_pmn_family_hint
"\nThis GNU-style Clang gets its target from the shell environment it runs in: \
use an MSYS2 CLANG64 (x64) or CLANGARM64 (ARM64) shell, or build with breeze\\scripts\\build.bat, which puts that toolchain first on PATH.")
endif()
message(FATAL_ERROR
"DIST_TARGET is '${DIST_TARGET}', but the CXX compiler CMake actually found -- "
"${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} -- auto-detects to "
"${_pmn_detected_desc}, not '${DIST_TARGET}'.\n"
"${_pmn_fix}\n"
"${_pmn_family_hint}")
endif()
message(STATUS "DIST_TARGET = ${DIST_TARGET}")
endif()
# Platform / compiler metadata from DIST_TARGET.
if(DIST_TARGET STREQUAL "linux-x64-gcc")
set(PLATFORM "linux")
set(ARCHITECTURE "x64")
set(COMPILER_ID "gcc")
elseif(DIST_TARGET STREQUAL "linux-x64-clang")
set(PLATFORM "linux")
set(ARCHITECTURE "x64")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "linux-arm64-gcc")
set(PLATFORM "linux")
set(ARCHITECTURE "arm64")
set(COMPILER_ID "gcc")
elseif(DIST_TARGET STREQUAL "linux-arm64-clang")
set(PLATFORM "linux")
set(ARCHITECTURE "arm64")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "linux-arm32-gcc")
set(PLATFORM "linux")
set(ARCHITECTURE "arm32")
set(COMPILER_ID "gcc")
elseif(DIST_TARGET STREQUAL "linux-arm32-clang")
set(PLATFORM "linux")
set(ARCHITECTURE "arm32")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "macos-arm64-clang")
set(PLATFORM "macos")
set(ARCHITECTURE "arm64")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "macos-x64-clang")
set(PLATFORM "macos")
set(ARCHITECTURE "x64")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "windows-x64-msvc")
set(PLATFORM "windows")
set(ARCHITECTURE "x64")
set(COMPILER_ID "msvc")
elseif(DIST_TARGET STREQUAL "windows-arm64-msvc")
set(PLATFORM "windows")
set(ARCHITECTURE "arm64")
set(COMPILER_ID "msvc")
elseif(DIST_TARGET STREQUAL "windows-x64-clang-cl")
set(PLATFORM "windows")
set(ARCHITECTURE "x64")
set(COMPILER_ID "clang-cl")
elseif(DIST_TARGET STREQUAL "windows-arm64-clang-cl")
set(PLATFORM "windows")
set(ARCHITECTURE "arm64")
set(COMPILER_ID "clang-cl")
elseif(DIST_TARGET STREQUAL "windows-x64-clang")
set(PLATFORM "windows")
set(ARCHITECTURE "x64")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "windows-arm64-clang")
set(PLATFORM "windows")
set(ARCHITECTURE "arm64")
set(COMPILER_ID "clang")
elseif(DIST_TARGET STREQUAL "windows-x86-mingw32")
set(PLATFORM "windows")
set(ARCHITECTURE "x86")
set(COMPILER_ID "mingw32")
elseif(DIST_TARGET STREQUAL "windows-x64-mingw64")
set(PLATFORM "windows")
set(ARCHITECTURE "x64")
set(COMPILER_ID "mingw64")
else()
message(FATAL_ERROR
"Unsupported DIST_TARGET: '${DIST_TARGET}'.\n"
"Valid values: ${_pmn_valid_dist_targets_str}\n"
"Tip: open CMakePresets.json in your IDE to select a preset, "
"or pass -DDIST_TARGET=<value> on the command line.")
endif()
message(STATUS "Platform = ${PLATFORM}")
message(STATUS "Architecture = ${ARCHITECTURE}")
message(STATUS "Compiler = ${COMPILER_ID} (${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION})")
+850
View File
@@ -0,0 +1,850 @@
# =============================================================================
# toolchains/_common.cmake
#
# Helpers shared by the per-DIST_TARGET toolchain files next to this one
# (linux-x64-gcc.cmake, windows-arm64-clang-cl.cmake, ...). This is not a
# toolchain file itself -- nothing should point CMAKE_TOOLCHAIN_FILE here.
#
# Why toolchain files at all
# --------------------------
# A toolchain file is CMake's own mechanism for "build with THIS compiler
# for THIS target". CMake reads it before project() detects anything, so
# the compiler and target are settled first instead of being discovered
# and then cross-checked after the fact. Every CMakePresets.json preset
# points at the toolchain file with its own name (see the "base" preset),
# and each file does three things:
#
# 1. Declares which DIST_TARGET it is for (PMN_TOOLCHAIN_DIST_TARGET), so
# TargetSelection.cmake no longer has to guess from the compiler and
# can't be handed a mislabeled build -- the label and the compiler
# travel together.
# 2. Picks the compiler (and, for clang-cl, the --target triple).
# 3. Where the target isn't the machine running CMake, sets up a real
# cross-compile: CMAKE_SYSTEM_NAME / CMAKE_SYSTEM_PROCESSOR, restricts
# find_package/find_library to the target's sysroot (so a host-arch
# library is never linked into a foreign-arch binary), and registers
# an emulator (qemu-user / wine) if one is installed, so ctest can run
# what it just built.
#
# Two behaviors worth knowing, both verified rather than assumed:
#
# * CMAKE_SYSTEM_NAME is only set when the build really is a cross build.
# Setting it -- even to the value the host already has -- flips
# CMAKE_CROSSCOMPILING to TRUE, which changes how try_run and
# several third-party CMakeLists behave. Native builds leave it alone.
#
# * A compiler chosen here never overrides an explicit
# -DCMAKE_CXX_COMPILER=... on the command line (an unguarded set()
# in a toolchain file silently would). The toolchain DOES beat a stray
# CXX environment variable, since a target called "linux-x64-gcc"
# shouldn't be built with whatever CXX=clang++ happens to be exported.
#
# * macOS is deliberately not a "cross build" even when the architecture
# differs from the machine's (an x86_64 build on an Apple Silicon Mac):
# Apple's clang is one multi-architecture compiler, so that is a
# different -arch flag, not a different compiler. pmn_macos_toolchain()
# leaves CMAKE_SYSTEM_NAME alone and pins CMAKE_OSX_ARCHITECTURES.
#
# Toolchain files are read more than once per configure (CMake re-reads
# them for every try_compile), so everything here must stay cheap and
# idempotent.
# =============================================================================
include_guard(GLOBAL)
include("${CMAKE_CURRENT_LIST_DIR}/../HostArch.cmake")
# -----------------------------------------------------------------------
# pmn_warn_once(<key> <text>...)
#
# message(WARNING ...) at most once per configure. This file is read again
# for the same scope (see the note at the top), so a plain warning would
# print as many times as the file is read -- and the same multi-line
# warning twice in a row reads as two problems. The <text> arguments are
# joined with nothing between them, like message()'s own, so they must not
# contain a semicolon (a list separator, which would be lost).
# -----------------------------------------------------------------------
function(pmn_warn_once _pmn_key)
get_property(_pmn_seen GLOBAL PROPERTY "PMN_WARNED_${_pmn_key}")
if(NOT _pmn_seen)
set_property(GLOBAL PROPERTY "PMN_WARNED_${_pmn_key}" TRUE)
string(JOIN "" _pmn_text ${ARGN})
message(WARNING "${_pmn_text}")
endif()
endfunction()
# -----------------------------------------------------------------------
# pmn_check_msvc_target(<arch>)
#
# For the presets that link against MSVC's runtime libraries (msvc and
# clang-cl): asks the Visual Studio developer prompt CMake runs in what it is
# set up for, before CMake's own compiler test fails with an error that
# names neither -- "cannot open msvcrt.lib", or lld-link's "could not open
# 'msvcrtd.lib'". Both are warnings: the configure carries on, and either
# works anyway or fails right below the explanation.
#
# * VSCMD_ARG_TGT_ARCH, which the developer prompts set, names another
# target than the preset builds for.
# * VCToolsInstallDir, set by the same prompts, has no lib/<arch>/msvcrt.lib:
# the component that provides the target is not installed. From an x64
# machine ARM64 is a cross build and needs the ARM64 build tools; vcvarsall
# still "succeeds" without them, leaving the host's x64 cl first on PATH
# and the ARM64 library directory in LIB.
# -----------------------------------------------------------------------
function(pmn_check_msvc_target _pmn_arch)
if(DEFINED ENV{VSCMD_ARG_TGT_ARCH} AND NOT "$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "${_pmn_arch}")
pmn_warn_once(msvc-target-arch
"This Visual Studio developer prompt targets $ENV{VSCMD_ARG_TGT_ARCH}, but the "
"'${PMN_TOOLCHAIN_DIST_TARGET}' toolchain builds for ${_pmn_arch}. Open the matching prompt "
"(for example an x64 Native Tools or x64_arm64 Cross Tools Command Prompt), or build with "
"breeze\\scripts\\build.bat, which enters the right environment for you, and delete the "
"build directory after switching.")
endif()
if(DEFINED ENV{VCToolsInstallDir} AND NOT EXISTS "$ENV{VCToolsInstallDir}/lib/${_pmn_arch}/msvcrt.lib")
pmn_warn_once(msvc-target-libs
"The MSVC runtime libraries for ${_pmn_arch} are missing: $ENV{VCToolsInstallDir}lib/${_pmn_arch}/msvcrt.lib "
"does not exist, so linking will fail with 'cannot open msvcrt.lib'. Add the component for "
"${_pmn_arch} in the Visual Studio Installer (Modify, Individual components): \"MSVC ... C++ "
"ARM64/ARM64EC build tools (Latest)\" for arm64, which an x64 machine needs too because it "
"cross-compiles, or \"MSVC ... C++ x64/x86 build tools (Latest)\" for x64. breeze\\scripts\\setup.bat "
"offers to add the ARM64 one.")
endif()
endfunction()
# -----------------------------------------------------------------------
# pmn_note_once(<key> <text>...)
#
# message(STATUS ...) at most once per configure -- pmn_warn_once's quiet
# sibling, for information that is useful in the log but is not a problem
# (which target sysroot is in use; that there is none).
# -----------------------------------------------------------------------
function(pmn_note_once _pmn_key)
get_property(_pmn_seen GLOBAL PROPERTY "PMN_NOTED_${_pmn_key}")
if(NOT _pmn_seen)
set_property(GLOBAL PROPERTY "PMN_NOTED_${_pmn_key}" TRUE)
string(JOIN "" _pmn_text ${ARGN})
message(STATUS "${_pmn_text}")
endif()
endfunction()
# -----------------------------------------------------------------------
# pmn_find_sysroot(<x64|arm64|arm32> <dir-var> <why-var>)
#
# Which TARGET SYSROOT, if any, a Linux cross build should use: a directory
# laid out like the target machine's root filesystem (usr/include, usr/lib,
# ...) that holds the libraries and headers the project's dependencies need
# beyond the C/C++ runtime -- X11, OpenGL, ALSA and the like. A cross
# compiler package brings only the runtime (/usr/<triplet>), so a dependency
# that asks for anything else stops with "Could NOT find X11" (raylib's GLFW
# does exactly that on Linux). In order:
#
# 1. -DCMAKE_SYSROOT=<dir> on the command line or in a preset: used as is.
# 2. <project>/.cache/sysroots/linux-<arch>, if that directory exists
# (linux-arm64, linux-arm32, linux-x64): one per architecture, shared
# by the gcc and clang presets, found without any option -- which is
# what lets several cross presets be built one after the other.
#
# Both outputs are empty when there is none. A directory that does not exist
# is an error; one without usr/ in it only a warning (the usual mistake is
# pointing at the folder that CONTAINS the root filesystem).
# -----------------------------------------------------------------------
function(pmn_find_sysroot _pmn_arch _pmn_out_dir _pmn_out_why)
set(_pmn_dir "")
set(_pmn_why "")
# The cache, not the plain variable: a toolchain file sets a plain
# CMAKE_SYSROOT itself (below), and a re-read must not mistake that for
# the user's choice.
if(DEFINED CACHE{CMAKE_SYSROOT} AND NOT "$CACHE{CMAKE_SYSROOT}" STREQUAL "")
set(_pmn_dir "$CACHE{CMAKE_SYSROOT}")
set(_pmn_why "CMAKE_SYSROOT")
else()
get_filename_component(_pmn_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
if(IS_DIRECTORY "${_pmn_root}/.cache/sysroots/linux-${_pmn_arch}")
set(_pmn_dir "${_pmn_root}/.cache/sysroots/linux-${_pmn_arch}")
set(_pmn_why ".cache/sysroots/linux-${_pmn_arch}")
endif()
endif()
if(_pmn_dir)
if(NOT IS_DIRECTORY "${_pmn_dir}")
message(FATAL_ERROR
"The target sysroot '${_pmn_dir}' (${_pmn_why}) is not a directory.\n"
"Point CMAKE_SYSROOT at a root filesystem for the target (it has usr/include and "
"usr/lib in it), or drop the option. See \"Cross builds and system libraries\" "
"in the README.")
endif()
if(NOT IS_DIRECTORY "${_pmn_dir}/usr")
pmn_warn_once(sysroot-layout
"The target sysroot '${_pmn_dir}' has no usr/ directory, so it does not look like "
"a root filesystem -- is it the folder that contains one? Headers are looked for in "
"<sysroot>/usr/include and libraries in <sysroot>/usr/lib.")
endif()
endif()
set(${_pmn_out_dir} "${_pmn_dir}" PARENT_SCOPE)
set(${_pmn_out_why} "${_pmn_why}" PARENT_SCOPE)
endfunction()
# -----------------------------------------------------------------------
# pmn_use_sysroot(<dir> <triplet>)
#
# Makes <dir> the target's root for this build:
#
# * CMAKE_SYSROOT -- CMake passes --sysroot= to GCC and Clang, and
# find_package/find_library/find_path search under it. It also goes
# FIRST in CMAKE_FIND_ROOT_PATH, ahead of the cross compiler's own
# /usr/<triplet>, so what the target really has wins.
# * CMAKE_LIBRARY_ARCHITECTURE, when the sysroot is Debian-style
# multiarch (libraries in usr/lib/<triplet>/): without it find_library
# doesn't look there.
# * pkg-config: PKG_CONFIG_SYSROOT_DIR / PKG_CONFIG_LIBDIR point at the
# sysroot's .pc files only, and PKG_CONFIG_PATH is dropped, so
# pkg_check_modules() answers for the target instead of the host (whose
# .pc files would hand the compiler x86 include paths and libraries).
#
# A macro, like the rest of this file's helpers that set variables: the
# toolchain file's own scope is where they must land.
# -----------------------------------------------------------------------
macro(pmn_use_sysroot _pmn_sr_dir _pmn_sr_triplet)
set(CMAKE_SYSROOT "${_pmn_sr_dir}")
list(PREPEND CMAKE_FIND_ROOT_PATH "${_pmn_sr_dir}")
list(REMOVE_DUPLICATES CMAKE_FIND_ROOT_PATH)
if(NOT CMAKE_LIBRARY_ARCHITECTURE AND IS_DIRECTORY "${_pmn_sr_dir}/usr/lib/${_pmn_sr_triplet}")
set(CMAKE_LIBRARY_ARCHITECTURE "${_pmn_sr_triplet}")
endif()
set(_pmn_sr_pc "")
foreach(_pmn_sr_sub
usr/lib/pkgconfig
usr/lib64/pkgconfig
usr/lib/${_pmn_sr_triplet}/pkgconfig
usr/share/pkgconfig)
list(APPEND _pmn_sr_pc "${_pmn_sr_dir}/${_pmn_sr_sub}")
endforeach()
list(JOIN _pmn_sr_pc ":" _pmn_sr_pc_joined)
set(ENV{PKG_CONFIG_SYSROOT_DIR} "${_pmn_sr_dir}")
set(ENV{PKG_CONFIG_LIBDIR} "${_pmn_sr_pc_joined}")
unset(ENV{PKG_CONFIG_PATH})
endmacro()
# -----------------------------------------------------------------------
# pmn_toolchain_target(<DIST_TARGET>)
#
# Declares which DIST_TARGET the calling toolchain file is for.
# TargetSelection.cmake reads PMN_TOOLCHAIN_DIST_TARGET and uses it as
# DIST_TARGET (an explicit, different -DDIST_TARGET is a FATAL_ERROR).
# -----------------------------------------------------------------------
macro(pmn_toolchain_target _pmn_tc_dist_target)
set(PMN_TOOLCHAIN_DIST_TARGET "${_pmn_tc_dist_target}")
endmacro()
# -----------------------------------------------------------------------
# pmn_use_compilers(<cxx-program> <c-program> "<install hint>")
#
# Selects the C++ (and C) compiler by program name, resolved on PATH to an
# absolute path so it's identical in every try_compile. Skipped for
# whichever of CMAKE_CXX_COMPILER / CMAKE_C_COMPILER the user already
# chose explicitly.
#
# A missing C++ compiler is a FATAL_ERROR here, with the install hint,
# rather than CMake's generic "compiler ... is not a full path and was not
# found in the PATH". The C compiler is set too even though the project
# is C++-only: if someone later adds a C source or a C dependency, CMake
# must not silently auto-detect the HOST's cc for a cross build and emit
# wrong-architecture objects. If the C compiler can't be found, its bare
# name is set anyway so that mistake fails loudly.
#
# A function (not a macro) on purpose: the hint is free text that a macro
# would evaluate twice, and its temporaries stay out of the caller's scope.
# PARENT_SCOPE lands in the toolchain file's own scope because the macros
# below, which call this, don't create one.
# -----------------------------------------------------------------------
function(pmn_use_compilers CXX_PROGRAM C_PROGRAM HINT)
if(NOT CMAKE_CXX_COMPILER)
find_program(_pmn_tc_cxx_path NAMES "${CXX_PROGRAM}" NO_CACHE)
if(NOT _pmn_tc_cxx_path)
message(FATAL_ERROR
"The '${PMN_TOOLCHAIN_DIST_TARGET}' toolchain needs the C++ compiler "
"'${CXX_PROGRAM}', but it isn't on PATH.\n${HINT}\n"
"(Or name a different compiler explicitly: -DCMAKE_CXX_COMPILER=<path>)")
endif()
set(CMAKE_CXX_COMPILER "${_pmn_tc_cxx_path}" PARENT_SCOPE)
endif()
if(NOT CMAKE_C_COMPILER)
find_program(_pmn_tc_c_path NAMES "${C_PROGRAM}" NO_CACHE)
if(_pmn_tc_c_path)
set(CMAKE_C_COMPILER "${_pmn_tc_c_path}" PARENT_SCOPE)
else()
set(CMAKE_C_COMPILER "${C_PROGRAM}" PARENT_SCOPE)
endif()
endif()
endfunction()
# -----------------------------------------------------------------------
# pmn_cross_target(<system-name> <system-processor> <find-root>)
#
# Turns this into a cross build. <find-root> is the target's sysroot-like
# prefix (Debian/Ubuntu put a cross toolchain's target headers and
# libraries under /usr/<triplet>); an explicit -DCMAKE_SYSROOT=... and
# -DCMAKE_FIND_ROOT_PATH=... from the user are kept and extended, not
# replaced.
#
# The four MODE settings are the standard cross-compile split: programs
# (Doxygen, a pinned ninja, ...) come from the HOST; libraries, headers
# and CMake packages only from the target's root. That last one is what
# makes FetchContent's find_package(fmt) miss the host's libfmt -- which
# would link, then fail at load time as the wrong architecture -- and fall
# through to fetching and building fmt for the target.
# -----------------------------------------------------------------------
macro(pmn_cross_target _pmn_tc_system _pmn_tc_processor _pmn_tc_root)
set(CMAKE_SYSTEM_NAME "${_pmn_tc_system}")
set(CMAKE_SYSTEM_PROCESSOR "${_pmn_tc_processor}")
list(APPEND CMAKE_FIND_ROOT_PATH "${_pmn_tc_root}")
list(REMOVE_DUPLICATES CMAKE_FIND_ROOT_PATH)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endmacro()
# -----------------------------------------------------------------------
# pmn_cross_emulator(<program>|"<program>;<program>" [<args>...])
#
# Registers the first of the given program names that is installed
# (qemu-aarch64, or "wine;wine64", ...) as CMAKE_CROSSCOMPILING_EMULATOR,
# so ctest and add_custom_command(COMMAND <target>) can run a
# foreign-architecture binary, and so can breeze/scripts/run.sh. Silent if none is
# installed -- the build itself doesn't need one. An emulator the user set
# explicitly is left alone.
#
# It's a cache entry rather than a plain variable so that breeze/scripts/run.sh,
# which runs long after configure, can read it back out of CMakeCache.txt.
# CMake treats a cached CMAKE_CROSSCOMPILING_EMULATOR exactly like a
# variable set here (it's also what -DCMAKE_CROSSCOMPILING_EMULATOR=...
# creates). Only set once found, so installing qemu later still gets
# picked up on the next configure.
# -----------------------------------------------------------------------
function(pmn_cross_emulator PROGRAM)
if(NOT CMAKE_CROSSCOMPILING_EMULATOR)
find_program(_pmn_tc_emulator_path NAMES ${PROGRAM} NO_CACHE)
if(_pmn_tc_emulator_path)
set(CMAKE_CROSSCOMPILING_EMULATOR "${_pmn_tc_emulator_path}" ${ARGN}
CACHE STRING "Emulator used to run cross-compiled executables (ctest, breeze/scripts/run.sh)")
endif()
endif()
endfunction()
# -----------------------------------------------------------------------
# pmn_linux_toolchain(<x64|arm64|arm32> <gcc|clang>)
#
# One implementation behind all six linux-* toolchain files.
#
# Native or cross is decided by comparing the requested architecture with
# the machine CMake is running on (`uname -m`, via HostArch.cmake): the same
# linux-arm64-gcc preset builds natively on ARM64 hardware with the ordinary
# g++, and cross-builds from an x64 machine with aarch64-linux-gnu-g++.
#
# GCC cross: <triplet>-g++ -- breeze/scripts/setup.sh installs this for you on
# apt (Debian/Ubuntu), pacman (Arch, arm64 only), and dnf
# (Fedora/RHEL, AArch64/32-bit ARM only, from a COPR since
# Fedora's own cross-gcc ships no libstdc++) -- see its own
# "why" text for exactly what and why, including the gaps:
# x86-64-from-arm64 has no known working package anywhere
# yet, and 32-bit ARM on Arch is AUR-only (x64 hosts only).
# Clang cross: clang++ --target=<triplet>, which additionally needs the
# target's libstdc++/libc: the GCC cross package above
# provides them under /usr/<triplet>, or pass
# -DCMAKE_SYSROOT=<sysroot>. <triplet> is the one the cross
# g++ says it was built for (-dumpmachine) when that is the
# same architecture -- the preset's own default otherwise.
#
# Both families check what the installed cross compiler tells them instead of
# trusting it: a sysroot it reports that isn't on disk is dropped, with a
# warning; a GCC that can't find the assembler for its own target (and would
# quietly run the host's) is warned about up front, before CMake's compiler
# test fails with an error that names neither. Both are the signature of a
# compiler installed beside binutils or a C library from a different package
# set -- see "32-bit ARM on Arch" in the README.
#
# A cross build can also have a TARGET SYSROOT -- a root filesystem for the
# target with the libraries and headers its dependencies need beyond the
# C/C++ runtime (X11, OpenGL, ALSA, ...), which no cross compiler package
# ships: -DCMAKE_SYSROOT=<dir>, or just a directory at
# .cache/sysroots/linux-<arch>. pmn_find_sysroot / pmn_use_sysroot below
# say what it changes; without one the configure says so in its first lines,
# and a dependency that wants such a library stops with "Could NOT find".
#
# One unusual case falls outside this: a 32-bit userland running on a
# 64-bit ARM kernel reports aarch64 from `uname -m` while its ordinary g++
# already builds 32-bit code. For that, pass -DCMAKE_CXX_COMPILER=g++ (the
# build is then labeled arm32, correctly, but is treated as a cross build).
#
# NOTE for anyone editing the macros in this file: a macro's parameters
# are textual substitutions, so every use inside if() is quoted --
# unquoted, `if(x64 STREQUAL ...)` would treat "x64" as a variable name.
# -----------------------------------------------------------------------
macro(pmn_linux_toolchain _pmn_tc_arch _pmn_tc_family)
if("${_pmn_tc_arch}" STREQUAL "x64")
set(_pmn_tc_processor "x86_64")
set(_pmn_tc_triplet "x86_64-linux-gnu")
set(_pmn_tc_gcc_pkg "g++-x86-64-linux-gnu")
set(_pmn_tc_qemu "qemu-x86_64")
elseif("${_pmn_tc_arch}" STREQUAL "arm64")
set(_pmn_tc_processor "aarch64")
set(_pmn_tc_triplet "aarch64-linux-gnu")
set(_pmn_tc_gcc_pkg "g++-aarch64-linux-gnu")
set(_pmn_tc_qemu "qemu-aarch64")
elseif("${_pmn_tc_arch}" STREQUAL "arm32")
set(_pmn_tc_processor "arm")
set(_pmn_tc_triplet "arm-linux-gnueabihf")
set(_pmn_tc_gcc_pkg "g++-arm-linux-gnueabihf")
set(_pmn_tc_qemu "qemu-arm")
else()
message(FATAL_ERROR "pmn_linux_toolchain: unknown architecture '${_pmn_tc_arch}'")
endif()
pmn_host_arch(_pmn_tc_host_arch)
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND "${_pmn_tc_host_arch}" STREQUAL "${_pmn_tc_arch}")
# Native: this machine already IS the target.
if("${_pmn_tc_family}" STREQUAL "gcc")
pmn_use_compilers(g++ gcc "Run breeze/scripts/setup.sh to install one, or by hand -- e.g. on Debian/Ubuntu: sudo apt install g++")
else()
pmn_use_compilers(clang++ clang "Run breeze/scripts/setup.sh to install one, or by hand -- e.g. on Debian/Ubuntu: sudo apt install clang")
endif()
else()
# Cross.
pmn_cross_target(Linux "${_pmn_tc_processor}" "/usr/${_pmn_tc_triplet}")
# The actual sysroot the installed cross package for this triplet
# was built with -- nothing meaningful on a flat layout (apt's,
# and pacman's -- see the "/" normalization below for what their
# cross-gcc actually prints), a real path on a nested one (dnf's
# COPR toolchain, which mirrors Fedora's own cross-gcc convention:
# /usr/<triplet>/sys-root). Queried once, up front, because both
# families need it below: GCC already knows its own paths for
# compiling and linking, but qemu-user doesn't -- given the flat
# guess on a nested layout, it looks for the target's dynamic
# linker (ld-linux-aarch64.so.1, etc.) in the wrong place and
# doesn't find it ("Could not open '/lib/ld-linux-aarch64.so.1'",
# confirmed on Fedora) -- the same class of problem as Clang's
# link-time one below, just at run time and for either family.
# Clang additionally can't compile at all on a nested layout
# without being told the same thing explicitly -- see below.
find_program(_pmn_tc_cross_gxx NAMES "${_pmn_tc_triplet}-g++" NO_CACHE)
set(_pmn_tc_cross_sysroot "")
set(_pmn_tc_cross_machine "")
if(_pmn_tc_cross_gxx)
execute_process(
COMMAND "${_pmn_tc_cross_gxx}" -print-sysroot
OUTPUT_VARIABLE _pmn_tc_cross_sysroot
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
# A cross-gcc that was never configured with a sysroot of its
# own (apt's -- confirmed on Debian, where this broke qemu for
# every cross preset the moment this query was added) doesn't
# print an empty line for that: it prints "/", gcc's ordinary
# "no special sysroot" default. That is not a sysroot to
# hand anyone -- as qemu's -L it means "the target's root
# filesystem is THIS machine's", so it goes looking for the
# target's dynamic linker in the host's own /lib
# ("Could not open '/lib/ld-linux-aarch64.so.1'") -- and as
# Clang's --sysroot it points header and library lookup at the
# host's /usr too. Treated exactly like empty from here on.
if(_pmn_tc_cross_sysroot STREQUAL "/")
set(_pmn_tc_cross_sysroot "")
endif()
# A sysroot the compiler reports but that isn't on disk is what a
# toolchain whose parts don't belong together looks like: GCC
# relocates its configured sysroot next to itself, but only
# where the relocated directory exists -- otherwise it keeps the
# configured, unrelocated path. Seen with ARM's prebuilt GCC
# (built with --with-sysroot=/arm-none-linux-gnueabihf/libc)
# installed beside a binutils and glibc from somewhere else:
# -print-sysroot says "/arm-none-linux-gnueabihf/libc", nothing is
# there, and Clang -- handed that as --sysroot -- fell back to the
# HOST's crt files and failed to link ("Scrt1.o: file not
# recognized"). A path that doesn't exist is worse than none, so
# it is dropped, and said so.
if(_pmn_tc_cross_sysroot AND NOT IS_DIRECTORY "${_pmn_tc_cross_sysroot}")
pmn_warn_once(sysroot
"The cross compiler ${_pmn_tc_cross_gxx} reports the sysroot "
"'${_pmn_tc_cross_sysroot}', which does not exist, so it is ignored. That is "
"what a toolchain whose parts don't belong together looks like -- a compiler "
"from one package set, binutils or the C library from another -- and linking "
"will most likely fail (\"file not recognized\", a missing crt1.o or libc). "
"Install the compiler, binutils and C library for this target from the same "
"source: breeze/scripts/setup.sh does that where it can (on Arch, for 32-bit ARM, "
"see \"32-bit ARM on Arch\" in the README), or pass -DCMAKE_SYSROOT=<the "
"target's root filesystem>.")
set(_pmn_tc_cross_sysroot "")
endif()
# The triple this compiler itself was built for -- not necessarily
# the one the preset is named after: ARM's own GCC release is
# arm-none-linux-gnueabihf, and its libraries live under
# lib/gcc/arm-none-linux-gnueabihf/, which Clang only finds when
# told that exact triple. (Debian's, Fedora's COPR and Arch's own
# cross compilers all say the same triple the preset uses, so
# nothing changes for them.)
execute_process(
COMMAND "${_pmn_tc_cross_gxx}" -dumpmachine
OUTPUT_VARIABLE _pmn_tc_cross_machine
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
# What the cross compiler package itself reports (or /usr/<triplet>):
# the root qemu runs the result against unless a target sysroot of the
# user's (below) can do it better.
if(_pmn_tc_cross_sysroot)
set(_pmn_tc_emulator_root "${_pmn_tc_cross_sysroot}")
else()
set(_pmn_tc_emulator_root "/usr/${_pmn_tc_triplet}")
endif()
# A target sysroot of the user's, if there is one: where the libraries
# and headers that a cross compiler package doesn't ship (X11,
# OpenGL, ALSA, ...) come from. See pmn_find_sysroot for where it is
# looked for and pmn_use_sysroot for what it changes. It replaces
# the compiler-reported sysroot for Clang's flags below, and for
# qemu's -L when it can run programs, i.e. holds the target's dynamic
# loader -- a partial copy (headers and link libraries only) must not
# take qemu's root away from the toolchain's.
pmn_find_sysroot("${_pmn_tc_arch}" _pmn_tc_sysroot _pmn_tc_sysroot_why)
# A build directory keeps what CMake detected about the compiler -- its library
# directories, the multiarch name -- from the FIRST configure on. A sysroot
# added, changed or removed since then leaves that stale, and the link lines
# go quietly wrong (a stray -L<sysroot>/usr/lib ahead of the compiler's own,
# say). The usual way to get here: a configure stops on a library the
# target's root lacks, the sysroot that has it is put in place, and the same
# build directory is configured again. So stop, and say what to do.
if(_pmn_tc_sysroot)
set(_pmn_tc_sysroot_id "${_pmn_tc_sysroot}")
else()
set(_pmn_tc_sysroot_id "(none)")
endif()
if(DEFINED CACHE{PMN_TC_SYSROOT_USED} AND NOT "$CACHE{PMN_TC_SYSROOT_USED}" STREQUAL "${_pmn_tc_sysroot_id}")
message(FATAL_ERROR
"The target sysroot changed since this build directory was first configured: "
"it was $CACHE{PMN_TC_SYSROOT_USED}, it is now ${_pmn_tc_sysroot_id}.\n"
"CMake keeps what it detected about the compiler in the build directory, and "
"that was detected against the old sysroot, so the link lines would go subtly "
"wrong. Delete the build directory and configure again:\n"
" rm -rf ${CMAKE_BINARY_DIR}")
endif()
set(PMN_TC_SYSROOT_USED "${_pmn_tc_sysroot_id}" CACHE INTERNAL
"The target sysroot this build directory was first configured with")
if(_pmn_tc_sysroot)
pmn_use_sysroot("${_pmn_tc_sysroot}" "${_pmn_tc_triplet}")
pmn_note_once(sysroot "Target sysroot: ${_pmn_tc_sysroot} (${_pmn_tc_sysroot_why})")
set(_pmn_tc_cross_sysroot "${_pmn_tc_sysroot}")
file(GLOB _pmn_tc_loaders
"${_pmn_tc_sysroot}/lib/ld-linux*"
"${_pmn_tc_sysroot}/lib64/ld-linux*"
"${_pmn_tc_sysroot}/usr/lib/ld-linux*"
"${_pmn_tc_sysroot}/usr/lib64/ld-linux*"
"${_pmn_tc_sysroot}/lib/${_pmn_tc_triplet}/ld-linux*"
"${_pmn_tc_sysroot}/usr/lib/${_pmn_tc_triplet}/ld-linux*")
if(_pmn_tc_loaders)
set(_pmn_tc_emulator_root "${_pmn_tc_sysroot}")
endif()
else()
pmn_note_once(no-sysroot
"Cross build without a target sysroot: only ${_pmn_tc_emulator_root} is searched for \
libraries and headers, so a dependency that needs system libraries (X11, OpenGL, ALSA, ...) will \
stop with \"Could NOT find ...\". Put a root filesystem for the target in \
.cache/sysroots/linux-${_pmn_tc_arch}, or pass -DCMAKE_SYSROOT=<dir> -- see \"Cross builds and \
system libraries\" in the README.")
endif()
if("${_pmn_tc_family}" STREQUAL "gcc")
pmn_use_compilers("${_pmn_tc_triplet}-g++" "${_pmn_tc_triplet}-gcc"
"Run breeze/scripts/setup.sh to install the cross compiler for your package manager (apt for every target; dnf for AArch64 and 32-bit ARM, from a COPR; pacman for AArch64, and for 32-bit ARM from the AUR, which breeze/scripts/setup.sh offers -- see its own \"why\" text for details), or by hand -- e.g. on Debian/Ubuntu: sudo apt install ${_pmn_tc_gcc_pkg}")
# A cross GCC finds the assembler for its target in its own
# directories (<prefix>/<target>/bin/as and the like); when it
# doesn't, it quietly runs whatever `as` is on PATH -- the HOST's
# -- and the first file fails with an error that names neither
# (x86's `as` says: "invalid -march= option: `armv7-a+simd'").
# -print-prog-name=as says what GCC would run: an absolute path
# when it found one, the bare name when it didn't.
if(_pmn_tc_cross_gxx AND "${CMAKE_CXX_COMPILER}" STREQUAL "${_pmn_tc_cross_gxx}")
execute_process(
COMMAND "${_pmn_tc_cross_gxx}" -print-prog-name=as
OUTPUT_VARIABLE _pmn_tc_cross_as
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(_pmn_tc_cross_as AND NOT _pmn_tc_cross_as MATCHES "/")
pmn_warn_once(assembler
"The cross compiler ${_pmn_tc_cross_gxx} cannot find the assembler for its own "
"target (-print-prog-name=as gives the bare name '${_pmn_tc_cross_as}'), so "
"it will run the HOST's assembler instead, and the first file will fail with "
"something like \"invalid -march= option\". The compiler and its binutils have "
"to come from the same package set. breeze/scripts/setup.sh checks this with a test "
"compile and, on Arch for 32-bit ARM, installs a matching set (see "
"\"32-bit ARM on Arch\" in the README). Elsewhere install the binutils that "
"belong to this compiler.")
endif()
endif()
else()
# Clang itself is multi-target and happily emits object code for
# ${_pmn_tc_triplet} with no cross package installed at all --
# the failure this check guards against only shows up much
# later, and far more cryptically, at the LINK step: Clang
# still hands linking to whatever plain `ld` it finds (the
# host's own, unless told otherwise), which doesn't recognize
# the target's ELF flavor ("unrecognised emulation mode:
# armelf_linux_eabi", say) since it was never built with that
# target enabled. The GCC branch above never has this problem
# -- "${_pmn_tc_triplet}-g++" simply doesn't exist without the
# cross package, so pmn_use_compilers already fails clearly --
# so checking for that same binary here (found above, for the
# sysroot query), even though Clang doesn't need it to
# compile, catches the same missing-package case before it
# reaches a linker error with no obvious connection to the
# real cause.
if(NOT _pmn_tc_cross_gxx)
message(FATAL_ERROR
"The '${PMN_TOOLCHAIN_DIST_TARGET}' toolchain needs the ${_pmn_tc_triplet} cross "
"package (for its linker and the target's libstdc++/libc) even to use Clang, and "
"it isn't on PATH: run breeze/scripts/setup.sh to install it for your package manager "
"(apt for every target; dnf for AArch64 and 32-bit ARM, from a COPR; pacman for "
"AArch64, and for 32-bit ARM from the AUR, which breeze/scripts/setup.sh offers -- "
"see its own \"why\" text for details), or by hand -- e.g. on Debian/Ubuntu: "
"sudo apt install ${_pmn_tc_gcc_pkg}\n"
"(Without it, configuring would proceed but linking would fail with an obscure "
"\"unrecognised emulation mode\" or similar error instead of this one.)")
endif()
pmn_use_compilers(clang++ clang
"Run breeze/scripts/setup.sh to install Clang plus the matching cross package, or by hand -- e.g. on Debian/Ubuntu: sudo apt install clang ${_pmn_tc_gcc_pkg} (the second package provides the target's libraries)")
set(_pmn_tc_clang_triple "${_pmn_tc_triplet}")
if(_pmn_tc_cross_machine MATCHES "^${_pmn_tc_processor}")
set(_pmn_tc_clang_triple "${_pmn_tc_cross_machine}")
endif()
if(NOT _pmn_tc_clang_triple STREQUAL _pmn_tc_triplet)
message(STATUS "Clang targets ${_pmn_tc_clang_triple}, the triple ${_pmn_tc_cross_gxx} was built for (the preset's default is ${_pmn_tc_triplet})")
endif()
set(CMAKE_CXX_COMPILER_TARGET "${_pmn_tc_clang_triple}")
set(CMAKE_C_COMPILER_TARGET "${_pmn_tc_clang_triple}")
# Unlike GCC, Clang has no distro-specific packaging knowledge
# baked in, so it falls back to a generic guess for where
# THIS triplet's crt1.o/crtbegin.o/libgcc live -- one that
# happens to match a flat /usr/<triplet> layout (apt's,
# pacman's) but not one nested under its own sys-root (dnf's
# COPR toolchain). Guessed wrong, it silently resolves to the
# HOST's own crt1.o instead -- still a real file, just the
# wrong architecture, which surfaces as an obscure linker
# error ("Relocations in generic ELF", confirmed on a Fedora
# aarch64 build) with no obvious connection to a missing
# flag. The real sysroot queried above, from the cross-gcc
# that's actually installed rather than guessed at here,
# fixes this the same way for either family; only Clang needs
# telling. --gcc-toolchain additionally points Clang at
# crtbegin.o/libgcc, which a sysroot alone doesn't cover on
# every layout (confirmed necessary for this same class of
# problem on Arch by the Linux kernel's own cross-build
# tooling, tools/scripts/Makefile.include -- included here
# only alongside a real sysroot, for the same "don't touch
# what Clang already gets right" reason); /usr is where a
# package manager's own cross-gcc lives on every platform
# this toolchain supports.
if(_pmn_tc_cross_sysroot)
set(CMAKE_SYSROOT "${_pmn_tc_cross_sysroot}")
# Appended only if absent: this file is read again for the
# same scope (project() and each enabled language, not just
# try_compile), and a plain append put the flag twice on every
# compile line and four times on every link line.
foreach(_pmn_tc_flags_var CMAKE_CXX_FLAGS_INIT CMAKE_C_FLAGS_INIT
CMAKE_EXE_LINKER_FLAGS_INIT CMAKE_SHARED_LINKER_FLAGS_INIT)
if(NOT "${${_pmn_tc_flags_var}}" MATCHES "--gcc-toolchain=/usr")
string(APPEND ${_pmn_tc_flags_var} " --gcc-toolchain=/usr")
endif()
endforeach()
endif()
endif()
pmn_cross_emulator("${_pmn_tc_qemu}" -L "${_pmn_tc_emulator_root}")
endif()
endmacro()
# -----------------------------------------------------------------------
# pmn_mingw_toolchain(<x64|x86>)
#
# Behind windows-x64-mingw64 and windows-x86-mingw32. On a Windows host
# (an MSYS2 MINGW64/MINGW32 shell, or a standalone MinGW-w64) this only
# picks the compiler by its triplet-prefixed name. On a Linux host it is a
# real cross build using the distro's own mingw-w64 packages -- apt, dnf
# and pacman all ship a working one (breeze/scripts/setup.sh knows each; pacman's
# single mingw-w64-gcc covers both x64 and x86, unlike apt/dnf's separate
# packages per architecture) -- with wine as the emulator if installed.
# -----------------------------------------------------------------------
macro(pmn_mingw_toolchain _pmn_tc_arch)
if("${_pmn_tc_arch}" STREQUAL "x64")
set(_pmn_tc_processor "x86_64")
set(_pmn_tc_triplet "x86_64-w64-mingw32")
set(_pmn_tc_pkg "g++-mingw-w64-x86-64")
elseif("${_pmn_tc_arch}" STREQUAL "x86")
set(_pmn_tc_processor "i686")
set(_pmn_tc_triplet "i686-w64-mingw32")
set(_pmn_tc_pkg "g++-mingw-w64-i686")
else()
message(FATAL_ERROR "pmn_mingw_toolchain: unknown architecture '${_pmn_tc_arch}'")
endif()
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
pmn_use_compilers("${_pmn_tc_triplet}-g++" "${_pmn_tc_triplet}-gcc"
"Install MinGW-w64 (e.g. MSYS2's mingw-w64 packages) and put its bin directory on PATH. breeze\\scripts\\build.bat puts MSYS2's folder on PATH by itself.")
else()
pmn_cross_target(Windows "${_pmn_tc_processor}" "/usr/${_pmn_tc_triplet}")
pmn_use_compilers("${_pmn_tc_triplet}-g++" "${_pmn_tc_triplet}-gcc"
"Run breeze/scripts/setup.sh to install the MinGW-w64 cross compiler for your package manager, or by hand -- e.g. on Debian/Ubuntu: sudo apt install ${_pmn_tc_pkg}")
# Tried architecture-matched name first, the other as fallback --
# not just for distros that ship only one of the two (the
# original reason for trying both), but because on at least one
# that ships both, plain "wine" turned out not to actually be
# 64-bit capable: windows-x64-mingw64 failed to start at all
# through it ("Bad EXE format") while windows-x86-mingw32 through
# that same "wine" worked fine, and explicitly preferring
# "wine64" for the x64 build fixed it. Confirmed harmless where
# this was never a problem (Arch, Fedora): wine64 there either
# already matches or doesn't exist, so the fallback is what runs
# either way, unchanged from before.
if("${_pmn_tc_arch}" STREQUAL "x64")
pmn_cross_emulator("wine64;wine")
else()
pmn_cross_emulator("wine;wine64")
endif()
endif()
endmacro()
# -----------------------------------------------------------------------
# pmn_msvc_toolchain(<x64|arm64>)
#
# Behind windows-x64-msvc and windows-arm64-msvc. cl.exe has no --target:
# which architecture it produces is decided by WHICH cl.exe comes first on
# PATH, i.e. by the Visual Studio developer environment the shell was
# started from. A toolchain file therefore can't switch architectures for
# MSVC. What it can do is pick cl instead of letting CMake fall through to
# whatever other compiler is on PATH, and fail early with the instruction
# that fixes the usual cause. Whether the cl.exe it found really targets
# the architecture in the DIST_TARGET is then verified by
# TargetSelection.cmake, from CMAKE_CXX_COMPILER_ARCHITECTURE_ID.
# -----------------------------------------------------------------------
macro(pmn_msvc_toolchain _pmn_tc_arch)
if("${_pmn_tc_arch}" STREQUAL "x64")
pmn_use_compilers(cl cl
"Open an \"x64 Native Tools Command Prompt for VS\" (or a Developer PowerShell for VS defaulted to x64) and run this again. Or build with breeze\\scripts\\build.bat --preset ${PMN_TOOLCHAIN_DIST_TARGET}, which enters that environment for you.")
else()
pmn_use_compilers(cl cl
"Open a Visual Studio developer prompt configured for ARM64 -- an \"ARM64 Native Tools Command Prompt for VS\", or on an x64 machine an \"x64_arm64 Cross Tools Command Prompt for VS\" (vcvarsall.bat x64_arm64) -- and run this again. Or build with breeze\\scripts\\build.bat --preset ${PMN_TOOLCHAIN_DIST_TARGET}, which enters that environment for you.")
endif()
pmn_check_msvc_target("${_pmn_tc_arch}")
endmacro()
# -----------------------------------------------------------------------
# pmn_clang_cl_toolchain(<x64|arm64>)
#
# Behind windows-x64-clang-cl and windows-arm64-clang-cl: LLVM's clang-cl,
# the cl.exe-compatible driver, targeting the MSVC ABI. Unlike cl.exe,
# clang-cl takes --target, so the architecture is pinned here
# (CMAKE_CXX_COMPILER_TARGET -> --target=<triple> on every compile and
# link line, and while CMake identifies the compiler) instead of depending
# on whatever the environment or the machine's own architecture defaults
# to. That is what lets windows-x64-clang-cl mean x64 on an ARM64 Windows
# machine too.
#
# The MSVC STL headers and the Windows SDK libraries still come from a
# Visual Studio / Build Tools install, as they do for any clang-cl build.
# -----------------------------------------------------------------------
macro(pmn_clang_cl_toolchain _pmn_tc_arch)
if("${_pmn_tc_arch}" STREQUAL "x64")
set(_pmn_tc_triple "x86_64-pc-windows-msvc")
elseif("${_pmn_tc_arch}" STREQUAL "arm64")
set(_pmn_tc_triple "aarch64-pc-windows-msvc")
else()
message(FATAL_ERROR "pmn_clang_cl_toolchain: unknown architecture '${_pmn_tc_arch}'")
endif()
pmn_use_compilers(clang-cl clang-cl
"Install LLVM (e.g.: winget install LLVM.LLVM) or Visual Studio's \"C++ Clang tools for Windows\" component, and make sure clang-cl's directory is on PATH. breeze\\scripts\\build.bat finds LLVM's and Visual Studio's copies by itself.")
set(CMAKE_CXX_COMPILER_TARGET "${_pmn_tc_triple}")
set(CMAKE_C_COMPILER_TARGET "${_pmn_tc_triple}")
pmn_check_msvc_target("${_pmn_tc_arch}")
endmacro()
# -----------------------------------------------------------------------
# pmn_msys_clang_toolchain()
#
# Behind windows-x64-clang and windows-arm64-clang: Clang with its own
# GNU-style driver (clang++), as in an MSYS2 CLANG64 / CLANGARM64 shell
# -- distinct from clang-cl. Which architecture that clang++ targets is
# the shell environment's default, so nothing is pinned here; the
# environment is what makes clang++ a mingw-ABI compiler for the right
# machine.
# -----------------------------------------------------------------------
macro(pmn_msys_clang_toolchain)
pmn_use_compilers(clang++ clang
"Open an MSYS2 CLANG64 (x64) or CLANGARM64 (ARM64) shell, whose clang++ targets the matching mingw ABI by default. breeze\\scripts\\build.bat puts that shell's toolchain folder on PATH for you.")
endmacro()
# -----------------------------------------------------------------------
# pmn_macos_toolchain(<arm64|x64>)
#
# Behind macos-arm64-clang and macos-x64-clang. Apple's clang is ONE
# multi-architecture compiler: which architecture it emits is a flag
# (-arch), not a different program. So unlike the Linux cross builds there
# is no cross compiler to find, no sysroot to restrict lookups to and no
# emulator to register (an x86_64 binary runs on an Apple Silicon Mac
# through Rosetta 2, and try_run works as usual) -- and CMAKE_CROSSCOMPILING
# correctly stays FALSE. What this pins instead:
#
# CMAKE_OSX_ARCHITECTURES the ONE architecture, always -- even when
# it is the machine's own. That is the only reliable statement of what
# gets built: without it the compiler follows the process that
# launched it, so a cmake running under Rosetta 2 on an Apple Silicon
# Mac would quietly produce x86_64 for a target that says arm64.
# CMAKE_OSX_DEPLOYMENT_TARGET the oldest macOS the result runs on:
# 11.0, the first release that runs on Apple Silicon. Left unset it
# defaults to the SDK's own version, and everything built then
# refuses to start on any macOS older than the build machine's.
#
# Both only take effect if set BEFORE the first project(), which is
# exactly when a toolchain file runs, and both yield to an explicit
# -DCMAKE_OSX_...= on the command line (the label check in
# TargetSelection.cmake then catches an architecture that no longer matches
# the target). Homebrew's LLVM clang instead of Apple's works the same
# way: -DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++.
# -----------------------------------------------------------------------
macro(pmn_macos_toolchain _pmn_tc_arch)
if("${_pmn_tc_arch}" STREQUAL "arm64")
set(_pmn_tc_osx_arch "arm64")
elseif("${_pmn_tc_arch}" STREQUAL "x64")
set(_pmn_tc_osx_arch "x86_64")
else()
message(FATAL_ERROR "pmn_macos_toolchain: unknown architecture '${_pmn_tc_arch}'")
endif()
if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES "${_pmn_tc_osx_arch}")
endif()
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
endif()
# /usr/bin/clang++ exists on every Mac, as a stub that only works once
# the Command Line Tools (or Xcode) are installed -- without them CMake
# would fail inside its own compiler check with "xcrun: error: invalid
# active developer path". Say what's actually wrong instead.
if(CMAKE_HOST_APPLE)
execute_process(COMMAND xcode-select -p
RESULT_VARIABLE _pmn_tc_xcode_rc
OUTPUT_QUIET ERROR_QUIET)
if(NOT _pmn_tc_xcode_rc EQUAL 0)
message(FATAL_ERROR
"The '${PMN_TOOLCHAIN_DIST_TARGET}' toolchain needs Apple's compiler, but no "
"developer directory is set (xcode-select -p failed).\n"
"Install the Xcode Command Line Tools: xcode-select --install\n"
"(breeze/scripts/setup.sh offers to do this for you.)")
endif()
endif()
pmn_use_compilers(clang++ clang
"Install the Xcode Command Line Tools: xcode-select --install")
endmacro()
@@ -0,0 +1,21 @@
# linux-arm32-clang: Clang building for 32-bit ARM (armhf) Linux.
#
# On 32-bit ARM hardware: a native build with the ordinary clang++.
# Any other Linux machine: a cross build with clang++ --target=arm-linux-gnueabihf
# -- or whatever triple the installed cross g++ reports for
# itself (-dumpmachine): ARM's own GCC, the one on Arch, says
# arm-none-linux-gnueabihf, and Clang only finds its libraries
# under that name
# (needs Clang plus the GCC cross package for the target's
# libstdc++/libc, or -DCMAKE_SYSROOT=...; breeze/scripts/setup.sh
# installs both on apt -- clang g++-arm-linux-gnueabihf --
# and, from a COPR, dnf -- see linux-arm32-gcc.cmake for
# details, and for why not on pacman).
# If qemu-arm is installed it is registered as the
# emulator, so ctest can run the result.
#
# See _common.cmake (pmn_linux_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("linux-arm32-clang")
pmn_linux_toolchain(arm32 clang)
@@ -0,0 +1,23 @@
# linux-arm32-gcc: GCC building for 32-bit ARM (armhf) Linux.
#
# On 32-bit ARM hardware: a native build with the ordinary g++.
# Any other Linux machine: a cross build with arm-linux-gnueabihf-g++.
# breeze/scripts/setup.sh installs this for you on apt
# (g++-arm-linux-gnueabihf) or, from a COPR, dnf
# (arm-linux-gnueabihf-{binutils,gcc,glibc}); not through
# pacman -- on Arch it exists only in the AUR, as ARM's own
# prebuilt toolchain split into four packages
# (arm-linux-gnueabihf-{binutils,glibc-headers,glibc,gcc}-bin;
# x64 hosts only -- see the README, "32-bit ARM on Arch").
# That GCC is built for the triple arm-none-linux-gnueabihf,
# and finds its assembler and C library by that name, so the
# four have to be installed together: a source-built binutils
# or glibc beside it is the mix that fails (_common.cmake
# warns about both symptoms). If qemu-arm is installed it
# is registered as the emulator, so ctest can run the result.
#
# See _common.cmake (pmn_linux_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("linux-arm32-gcc")
pmn_linux_toolchain(arm32 gcc)
@@ -0,0 +1,17 @@
# linux-arm64-clang: Clang building for 64-bit ARM (aarch64) Linux.
#
# On ARM64 hardware: a native build with the ordinary clang++.
# Any other Linux machine: a cross build with clang++ --target=aarch64-linux-gnu
# (needs Clang plus the GCC cross package for the target's
# libstdc++/libc, or -DCMAKE_SYSROOT=...; breeze/scripts/setup.sh
# installs both on apt -- clang g++-aarch64-linux-gnu --
# pacman -- clang aarch64-linux-gnu-gcc -- or, from a COPR,
# dnf -- clang aarch64-linux-gnu-{binutils,gcc,glibc}).
# If qemu-aarch64 is installed it is registered as the
# emulator, so ctest can run the result.
#
# See _common.cmake (pmn_linux_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("linux-arm64-clang")
pmn_linux_toolchain(arm64 clang)
@@ -0,0 +1,15 @@
# linux-arm64-gcc: GCC building for 64-bit ARM (aarch64) Linux.
#
# On ARM64 hardware: a native build with the ordinary g++.
# Any other Linux machine: a cross build with aarch64-linux-gnu-g++.
# breeze/scripts/setup.sh installs this for you on apt
# (g++-aarch64-linux-gnu), pacman (aarch64-linux-gnu-gcc), or,
# from a COPR, dnf (aarch64-linux-gnu-{binutils,gcc,glibc}).
# If qemu-aarch64 is installed it is registered as the
# emulator, so ctest can run the result.
#
# See _common.cmake (pmn_linux_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("linux-arm64-gcc")
pmn_linux_toolchain(arm64 gcc)
@@ -0,0 +1,16 @@
# linux-x64-clang: Clang building for 64-bit x86 (x86_64) Linux.
#
# On x64 hardware: a native build with the ordinary clang++.
# Any other Linux machine: a cross build with clang++ --target=x86_64-linux-gnu
# (needs Clang plus the GCC cross package for the target's
# libstdc++/libc, or -DCMAKE_SYSROOT=...; breeze/scripts/setup.sh
# installs both on apt -- clang g++-x86-64-linux-gnu -- see
# _common.cmake's own comment for other package managers).
# If qemu-x86_64 is installed it is registered as the
# emulator, so ctest can run the result.
#
# See _common.cmake (pmn_linux_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("linux-x64-clang")
pmn_linux_toolchain(x64 clang)
@@ -0,0 +1,16 @@
# linux-x64-gcc: GCC building for 64-bit x86 (x86_64) Linux.
#
# On x64 hardware: a native build with the ordinary g++.
# Any other Linux machine: a cross build with x86_64-linux-gnu-g++.
# breeze/scripts/setup.sh installs this for you on apt
# (g++-x86-64-linux-gnu); not through dnf or pacman -- see
# _common.cmake's own comment (and this is a rare case in
# the first place: it only matters on a non-x64 Linux host).
# If qemu-x86_64 is installed it is registered as the
# emulator, so ctest can run the result.
#
# See _common.cmake (pmn_linux_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("linux-x64-gcc")
pmn_linux_toolchain(x64 gcc)
@@ -0,0 +1,16 @@
# macos-arm64-clang: Clang building for Apple Silicon macOS.
#
# Apple Clang from the Xcode Command Line Tools (xcode-select --install),
# or LLVM's own Clang if you point -DCMAKE_CXX_COMPILER at it.
#
# The architecture is pinned with CMAKE_OSX_ARCHITECTURES (arm64) rather
# than left to whichever process launched the compiler, so this means
# arm64 even when cmake itself is an x86_64 build running under Rosetta 2.
# The minimum macOS is 11.0, the first release for Apple Silicon, unless
# you pass -DCMAKE_OSX_DEPLOYMENT_TARGET=... yourself.
#
# See _common.cmake (pmn_macos_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("macos-arm64-clang")
pmn_macos_toolchain(arm64)
@@ -0,0 +1,16 @@
# macos-x64-clang: Clang building for Intel macOS.
#
# Apple Clang from the Xcode Command Line Tools (xcode-select --install),
# or LLVM's own Clang if you point -DCMAKE_CXX_COMPILER at it.
#
# The architecture is pinned with CMAKE_OSX_ARCHITECTURES (x86_64) rather
# than left to whichever process launched the compiler, so this means x64
# on an Apple Silicon Mac too -- where the result runs through Rosetta 2.
# The minimum macOS is 11.0 unless you pass
# -DCMAKE_OSX_DEPLOYMENT_TARGET=... yourself.
#
# See _common.cmake (pmn_macos_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("macos-x64-clang")
pmn_macos_toolchain(x64)
@@ -0,0 +1,13 @@
# windows-arm64-clang-cl: LLVM's clang-cl (the cl.exe-compatible driver)
# building for ARM64 Windows with the MSVC ABI.
#
# The target is pinned to aarch64-pc-windows-msvc (--target=), so this means
# ARM64 regardless of the machine's own architecture (an x64 machine
# cross-compiles). The MSVC STL headers and Windows SDK libraries, including
# their ARM64 variants, still come from a Visual Studio / Build Tools install.
#
# See _common.cmake (pmn_clang_cl_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-arm64-clang-cl")
pmn_clang_cl_toolchain(arm64)
@@ -0,0 +1,12 @@
# windows-arm64-clang: Clang with its own GNU-style driver (clang++) building
# for ARM64 Windows, as in an MSYS2 CLANGARM64 shell. Not clang-cl -- see
# windows-arm64-clang-cl for that.
#
# Nothing is pinned: the CLANGARM64 environment is what makes clang++ target
# ARM64 with the mingw ABI. TargetSelection.cmake checks that afterwards.
#
# See _common.cmake (pmn_msys_clang_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-arm64-clang")
pmn_msys_clang_toolchain()
@@ -0,0 +1,13 @@
# windows-arm64-msvc: Microsoft's cl.exe building for ARM64 Windows.
#
# Run from an "ARM64 Native Tools Command Prompt for VS" (on an ARM64
# machine) or an "x64_arm64 Cross Tools Command Prompt for VS" (on an x64
# machine cross-building for ARM64). cl.exe cannot be pointed at an
# architecture from here, the developer environment decides it.
# TargetSelection.cmake verifies the cl.exe it got really targets ARM64.
#
# See _common.cmake (pmn_msvc_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-arm64-msvc")
pmn_msvc_toolchain(arm64)
@@ -0,0 +1,12 @@
# windows-x64-clang-cl: LLVM's clang-cl (the cl.exe-compatible driver)
# building for x64 Windows with the MSVC ABI.
#
# The target is pinned to x86_64-pc-windows-msvc (--target=), so this means
# x64 regardless of the machine's own architecture. The MSVC STL headers and
# Windows SDK libraries still come from a Visual Studio / Build Tools install.
#
# See _common.cmake (pmn_clang_cl_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-x64-clang-cl")
pmn_clang_cl_toolchain(x64)
@@ -0,0 +1,12 @@
# windows-x64-clang: Clang with its own GNU-style driver (clang++) building
# for x64 Windows, as in an MSYS2 CLANG64 shell. Not clang-cl -- see
# windows-x64-clang-cl for that.
#
# Nothing is pinned: the CLANG64 environment is what makes clang++ target
# x64 with the mingw ABI. TargetSelection.cmake checks that afterwards.
#
# See _common.cmake (pmn_msys_clang_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-x64-clang")
pmn_msys_clang_toolchain()
@@ -0,0 +1,15 @@
# windows-x64-mingw64: MinGW-w64 GCC building for 64-bit Windows.
#
# Windows host: x86_64-w64-mingw32-g++ from an MSYS2 MINGW64 shell (or any
# MinGW-w64 on PATH).
# Linux host: a cross build. breeze/scripts/setup.sh installs this for you on
# apt (g++-mingw-w64-x86-64), dnf (mingw64-gcc-c++) or
# pacman (mingw-w64-gcc, which also covers the 32-bit
# preset below). If wine is installed it is registered as
# the emulator, so ctest can run the result.
#
# See _common.cmake (pmn_mingw_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-x64-mingw64")
pmn_mingw_toolchain(x64)
@@ -0,0 +1,12 @@
# windows-x64-msvc: Microsoft's cl.exe building for x64 Windows.
#
# Run from an "x64 Native Tools Command Prompt for VS" (or any shell where
# vcvars64.bat has been run): cl.exe cannot be pointed at an architecture
# from here, the developer environment decides it. TargetSelection.cmake
# verifies the cl.exe it got really targets x64.
#
# See _common.cmake (pmn_msvc_toolchain).
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-x64-msvc")
pmn_msvc_toolchain(x64)
@@ -0,0 +1,15 @@
# windows-x86-mingw32: MinGW-w64 GCC building for 32-bit Windows.
#
# Windows host: i686-w64-mingw32-g++ from an MSYS2 MINGW32 shell (or any
# MinGW-w64 on PATH).
# Linux host: a cross build. breeze/scripts/setup.sh installs this for you on
# apt (g++-mingw-w64-i686), dnf (mingw32-gcc-c++) or pacman
# (mingw-w64-gcc, which also covers the 64-bit preset
# above). If wine is installed it is registered as the
# emulator, so ctest can run the result.
#
# See _common.cmake (pmn_mingw_toolchain) for the details and overrides.
include("${CMAKE_CURRENT_LIST_DIR}/_common.cmake")
pmn_toolchain_target("windows-x86-mingw32")
pmn_mingw_toolchain(x86)