Movimiento de "Cuadrado" veloz en mapa blanco
This commit is contained in:
commit
b695b60153
55 files changed
+9391
No files matched your search
@@ -0,0 +1,126 @@
|
||||
# =============================================================================
|
||||
# BuildDir.cmake
|
||||
#
|
||||
# Script-mode bridge, the same idea as PrintConfig.cmake: tells
|
||||
# scripts/*.sh and 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 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 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.
|
||||
# =============================================================================
|
||||
|
||||
set(PMN_CONFIG_QUERY_ONLY TRUE)
|
||||
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
include("${CMAKE_SOURCE_DIR}/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 scripts/build to create one build directory per preset.")
|
||||
else()
|
||||
set(_pmn_value "No build found under ${_pmn_root_shown}. Run 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,407 @@
|
||||
# =============================================================================
|
||||
# BuildHelpers.cmake
|
||||
#
|
||||
# Reusable, project-agnostic build machinery: module discovery, per-target
|
||||
# compiler/RPATH settings, and the module-registration macros (add_lib_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 the root CMakeLists.txt --
|
||||
# see the "Module discovery" section of CMakeLists.txt and the per-module
|
||||
# CMakeLists.txt convention described there.
|
||||
#
|
||||
# Expects the including CMakeLists.txt to have already set (from project()
|
||||
# and the project-identity block): 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]):
|
||||
#
|
||||
# CMakeLists.txt 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()
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# _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
|
||||
# CMakeLists.txt'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.
|
||||
#
|
||||
# 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 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})
|
||||
|
||||
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})
|
||||
|
||||
if(FIN_OUTPUT_NAME)
|
||||
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 CMakeLists.txt'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)
|
||||
|
||||
# MinGW builds keep this target dynamically linked to
|
||||
# libgcc/libstdc++ (see CMakeLists.txt's "MinGW runtime linking"
|
||||
# comment for why); copy those DLLs next to it in the build tree
|
||||
# so it runs without a manual step. install()-time placement is
|
||||
# the matching install(FILES ...) in CMakeLists.txt's "install"
|
||||
# section -- PMN_MINGW_RUNTIME_DLLS is empty on every other
|
||||
# platform, so this is a no-op there.
|
||||
if(MINGW AND PMN_MINGW_RUNTIME_DLLS)
|
||||
add_custom_command(TARGET ${NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PMN_MINGW_RUNTIME_DLLS} "$<TARGET_FILE_DIR:${NAME}>"
|
||||
COMMENT "Copying MinGW runtime DLLs next to ${NAME}"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
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_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})
|
||||
|
||||
_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})
|
||||
|
||||
_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})
|
||||
|
||||
_finalize_module_target(${NAME} ${_MODULE_NAME} ${ARGN})
|
||||
|
||||
add_dependencies(all_examples ${NAME})
|
||||
endmacro()
|
||||
@@ -0,0 +1,134 @@
|
||||
# =============================================================================
|
||||
# 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 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 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_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.
|
||||
# -----------------------------------------------------------------------
|
||||
macro(add_dependency NAME)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(${NAME} SYSTEM ${ARGN})
|
||||
FetchContent_MakeAvailable(${NAME})
|
||||
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_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 (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 cmake/DoxygenPin.cmake for where) -- it never downloads
|
||||
# anything itself, so a plain `scripts/build.sh` never touches Doxygen or
|
||||
# needs network access. Run `scripts/setup.sh` / `setup.bat` once (which
|
||||
# runs 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 scripts/docs.sh / docs.bat, which builds `docs` and prints the path)
|
||||
# Paths above use the defaults from 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 cmake/DoxygenPin.cmake to match
|
||||
# that release's assets, and re-run 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 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 "
|
||||
"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
|
||||
# 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 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:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# cmake/FetchDoxygen.cmake include()s this when run standalone via
|
||||
# `cmake -P cmake/FetchDoxygen.cmake` (what
|
||||
# scripts/setup.sh / setup.bat call) to know
|
||||
# what to actually download.
|
||||
#
|
||||
# Needs TOOLCACHE_DIR to already be set -- Documentation.cmake gets it from
|
||||
# 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,111 @@
|
||||
# =============================================================================
|
||||
# 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 cmake/FetchDoxygen.cmake
|
||||
#
|
||||
# This is what scripts/setup.sh / setup.bat call. It's deliberately not part
|
||||
# of the main `cmake --preset <name>` configure -- see 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 Configuration.cmake, which isn't
|
||||
# loaded in this standalone script-mode invocation -- read it from there
|
||||
# directly (in query mode, so we get just the value, none of
|
||||
# Configuration.cmake's normal configure-time 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 cmake/FetchDoxygen.cmake
|
||||
if(NOT DEFINED TOOLCACHE_DIR)
|
||||
set(PMN_CONFIG_QUERY_ONLY TRUE)
|
||||
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
include("${CMAKE_SOURCE_DIR}/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 "
|
||||
"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 `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 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,109 @@
|
||||
# =============================================================================
|
||||
# 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 cmake/FetchNinja.cmake
|
||||
#
|
||||
# This is what scripts/setup.sh / setup.bat call, and also what 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 Configuration.cmake, which isn't
|
||||
# loaded in this standalone script-mode invocation -- read it from there
|
||||
# directly (in query mode, so we get just the value, none of
|
||||
# Configuration.cmake's normal configure-time 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 cmake/FetchNinja.cmake
|
||||
if(NOT DEFINED TOOLCACHE_DIR)
|
||||
set(PMN_CONFIG_QUERY_ONLY TRUE)
|
||||
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
include("${CMAKE_SOURCE_DIR}/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 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 -- 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 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,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. Configuration.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. Configuration.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 (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 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
|
||||
# Configuration.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:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# cmake/FetchNinja.cmake include()s this when run standalone via
|
||||
# `cmake -P cmake/FetchNinja.cmake` (what
|
||||
# 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 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 / scripts/setup.sh give a clear error, and
|
||||
# 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,55 @@
|
||||
# =============================================================================
|
||||
# PrintConfig.cmake
|
||||
#
|
||||
# Script-mode bridge: lets scripts/*.sh and scripts/*.bat read values out of
|
||||
# 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: 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 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
|
||||
# scripts/build.sh's _pmn_read_config() or 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.
|
||||
#
|
||||
# Runs with PMN_CONFIG_QUERY_ONLY set before including Configuration.cmake,
|
||||
# so that file skips its normal side effects (creating out/dist/,
|
||||
# rewriting .gitignore -- a value lookup shouldn't touch the filesystem,
|
||||
# and this runs far more often than a real configure) and skips resolving
|
||||
# DIST_TARGET/PLATFORM/ARCHITECTURE/COMPILER_ID, which need real compiler
|
||||
# detection (project(... LANGUAGES CXX)) that a plain `cmake -P` script
|
||||
# never performs and would get wrong. Only ask for names that don't need
|
||||
# that -- PROJECT_CODE_NAME, DEFAULT_BUILD_TYPE, and the path variables
|
||||
# are all safe; see Configuration.cmake's header for the full list of
|
||||
# what's inside vs. outside that guard.
|
||||
# =============================================================================
|
||||
|
||||
if(NOT DEFINED PRINT_VARS)
|
||||
message(FATAL_ERROR "Usage: cmake \"-DPRINT_VARS=NAME1;NAME2;...\" -P cmake/PrintConfig.cmake")
|
||||
endif()
|
||||
|
||||
set(PMN_CONFIG_QUERY_ONLY TRUE)
|
||||
get_filename_component(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
include("${CMAKE_SOURCE_DIR}/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 "
|
||||
"Configuration.cmake, or isn't available in query mode -- see "
|
||||
"that file's header ('How scripts read this file').")
|
||||
endif()
|
||||
message(STATUS "${_pmn_print_name}=${${_pmn_print_name}}")
|
||||
endforeach()
|
||||
@@ -0,0 +1,397 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
# Configuration.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_toolchain_target(<DIST_TARGET>)
|
||||
#
|
||||
# Declares which DIST_TARGET the calling toolchain file is for.
|
||||
# Configuration.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 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 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, 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++ (Debian/Ubuntu: apt install g++-<triplet>)
|
||||
# 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>.
|
||||
#
|
||||
# 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 "Install GCC, e.g.: sudo apt install g++")
|
||||
else()
|
||||
pmn_use_compilers(clang++ clang "Install Clang, e.g.: sudo apt install clang")
|
||||
endif()
|
||||
else()
|
||||
# Cross.
|
||||
pmn_cross_target(Linux "${_pmn_tc_processor}" "/usr/${_pmn_tc_triplet}")
|
||||
if("${_pmn_tc_family}" STREQUAL "gcc")
|
||||
pmn_use_compilers("${_pmn_tc_triplet}-g++" "${_pmn_tc_triplet}-gcc"
|
||||
"Install the cross compiler, e.g.: sudo apt install ${_pmn_tc_gcc_pkg}")
|
||||
else()
|
||||
pmn_use_compilers(clang++ clang
|
||||
"Install Clang plus the GCC cross package that provides the target's libraries, e.g.: sudo apt install clang ${_pmn_tc_gcc_pkg}")
|
||||
set(CMAKE_CXX_COMPILER_TARGET "${_pmn_tc_triplet}")
|
||||
set(CMAKE_C_COMPILER_TARGET "${_pmn_tc_triplet}")
|
||||
endif()
|
||||
pmn_cross_emulator("${_pmn_tc_qemu}" -L "/usr/${_pmn_tc_triplet}")
|
||||
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 Debian/Ubuntu's mingw-w64 packages, 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.")
|
||||
else()
|
||||
pmn_cross_target(Windows "${_pmn_tc_processor}" "/usr/${_pmn_tc_triplet}")
|
||||
pmn_use_compilers("${_pmn_tc_triplet}-g++" "${_pmn_tc_triplet}-gcc"
|
||||
"Install the cross compiler, e.g.: sudo apt install ${_pmn_tc_pkg}")
|
||||
# Some distributions ship only a wine64 binary, others a wine one.
|
||||
pmn_cross_emulator("wine;wine64")
|
||||
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
|
||||
# Configuration.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.")
|
||||
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.")
|
||||
endif()
|
||||
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.")
|
||||
set(CMAKE_CXX_COMPILER_TARGET "${_pmn_tc_triple}")
|
||||
set(CMAKE_C_COMPILER_TARGET "${_pmn_tc_triple}")
|
||||
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.")
|
||||
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
|
||||
# Configuration.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"
|
||||
"(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,14 @@
|
||||
# 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
|
||||
# (sudo apt install clang g++-arm-linux-gnueabihf -- the second package
|
||||
# supplies the target's libstdc++/libc; or -DCMAKE_SYSROOT=...).
|
||||
# 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,13 @@
|
||||
# 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++
|
||||
# (sudo apt install g++-arm-linux-gnueabihf).
|
||||
# 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,14 @@
|
||||
# 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
|
||||
# (sudo apt install clang g++-aarch64-linux-gnu -- the second package
|
||||
# supplies the target's libstdc++/libc; or -DCMAKE_SYSROOT=...).
|
||||
# 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,13 @@
|
||||
# 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++
|
||||
# (sudo apt install g++-aarch64-linux-gnu).
|
||||
# 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,14 @@
|
||||
# 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
|
||||
# (sudo apt install clang g++-x86-64-linux-gnu -- the second package
|
||||
# supplies the target's libstdc++/libc; or -DCMAKE_SYSROOT=...).
|
||||
# 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,13 @@
|
||||
# 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++
|
||||
# (sudo apt install g++-x86-64-linux-gnu).
|
||||
# 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. Configuration.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.
|
||||
# Configuration.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. Configuration.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,13 @@
|
||||
# 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 (sudo apt install g++-mingw-w64-x86-64).
|
||||
# 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. Configuration.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,13 @@
|
||||
# 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 (sudo apt install g++-mingw-w64-i686).
|
||||
# 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