Add project files
This commit is contained in:
commit
f2fe6b8855
114 files changed
+20525
No files matched your search
@@ -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()
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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})")
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user