diff --git a/.gitignore b/.gitignore index c8daf6e..e577cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ * +!cmake/ !.gitignore !CMakeLists.txt diff --git a/cmake/myx/MyxCMakeConfig.cmake b/cmake/myx/MyxCMakeConfig.cmake new file mode 100644 index 0000000..842c9f7 --- /dev/null +++ b/cmake/myx/MyxCMakeConfig.cmake @@ -0,0 +1,32 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0057 NEW) # IN_LIST operator + +get_filename_component(MYX_CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + +set(MYX_CMAKE_BACKPORTS_DIR "${MYX_CMAKE_SOURCE_DIR}/backports") +set(MYX_CMAKE_LIB_DIR "${MYX_CMAKE_SOURCE_DIR}/lib") + +include(${MYX_CMAKE_BACKPORTS_DIR}/IncludeGuard.cmake) +include(${MYX_CMAKE_BACKPORTS_DIR}/TopLevelProject.cmake) +if(${CMAKE_VERSION} VERSION_LESS "3.11.0") + include(${MYX_CMAKE_BACKPORTS_DIR}/FetchContent.cmake) +endif() + +include(${MYX_CMAKE_LIB_DIR}/Includes.cmake) +include(${MYX_CMAKE_LIB_DIR}/ColoredMessages.cmake) +include(${MYX_CMAKE_LIB_DIR}/NinjaGeneratorWarning.cmake) +include(${MYX_CMAKE_LIB_DIR}/DirectoriesGuards.cmake) +include(${MYX_CMAKE_LIB_DIR}/SemanticProjectVersion.cmake) +include(${MYX_CMAKE_LIB_DIR}/NinjaGeneratorWrapper.cmake) +include(${MYX_CMAKE_LIB_DIR}/FetchContentAdd.cmake) + +include(${MYX_CMAKE_LIB_DIR}/AddLibrary.cmake) +include(${MYX_CMAKE_LIB_DIR}/InstallLibrary.cmake) +include(${MYX_CMAKE_LIB_DIR}/TargetSetup.cmake) +include(${MYX_CMAKE_LIB_DIR}/Qt5TargetSetup.cmake) + +unset(MYX_CMAKE_SOURCE_DIR) +unset(MYX_CMAKE_BACKPORTS_DIR) +unset(MYX_CMAKE_LIB_DIR) + +cmake_policy(POP) diff --git a/cmake/myx/MyxCMakeConfigVersion.cmake b/cmake/myx/MyxCMakeConfigVersion.cmake new file mode 100644 index 0000000..18f147a --- /dev/null +++ b/cmake/myx/MyxCMakeConfigVersion.cmake @@ -0,0 +1,9 @@ +set(MYX_CMAKE_PACKAGE_VERSION "1.99.11") +if(MYX_CMAKE_PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/cmake/myx/backports/FetchContent.cmake b/cmake/myx/backports/FetchContent.cmake new file mode 100644 index 0000000..a8a2c2e --- /dev/null +++ b/cmake/myx/backports/FetchContent.cmake @@ -0,0 +1,464 @@ +include_guard(GLOBAL) + +set(__FetchContent_privateDir "${CMAKE_CURRENT_LIST_DIR}/FetchContent") + +#======================================================================= +# Recording and retrieving content details for later population +#======================================================================= + +# Internal use, projects must not call this directly. It is +# intended for use by FetchContent_Declare() only. +# +# Sets a content-specific global property (not meant for use +# outside of functions defined here in this file) which can later +# be retrieved using __FetchContent_getSavedDetails() with just the +# same content name. If there is already a value stored in the +# property, it is left unchanged and this call has no effect. +# This allows parent projects to define the content details, +# overriding anything a child project may try to set (properties +# are not cached between runs, so the first thing to set it in a +# build will be in control). +function(__FetchContent_declareDetails contentName) + + string(TOLOWER ${contentName} contentNameLower) + set(propertyName "_FetchContent_${contentNameLower}_savedDetails") + get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED) + if(NOT alreadyDefined) + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} ${ARGN}) + endif() + +endfunction() + + +# Internal use, projects must not call this directly. It is +# intended for use by the FetchContent_Declare() function. +# +# Retrieves details saved for the specified content in an +# earlier call to __FetchContent_declareDetails(). +function(__FetchContent_getSavedDetails contentName outVar) + + string(TOLOWER ${contentName} contentNameLower) + set(propertyName "_FetchContent_${contentNameLower}_savedDetails") + get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED) + if(NOT alreadyDefined) + message(FATAL_ERROR "No content details recorded for ${contentName}") + endif() + get_property(propertyValue GLOBAL PROPERTY ${propertyName}) + set(${outVar} "${propertyValue}" PARENT_SCOPE) + +endfunction() + + +# Saves population details of the content, sets defaults for the +# SOURCE_DIR and BUILD_DIR. +function(FetchContent_Declare contentName) + + set(options "") + set(oneValueArgs SVN_REPOSITORY) + set(multiValueArgs "") + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + unset(srcDirSuffix) + unset(svnRepoArgs) + if(ARG_SVN_REPOSITORY) + # Add a hash of the svn repository URL to the source dir. This works + # around the problem where if the URL changes, the download would + # fail because it tries to checkout/update rather than switch the + # old URL to the new one. We limit the hash to the first 7 characters + # so that the source path doesn't get overly long (which can be a + # problem on windows due to path length limits). + string(SHA1 urlSHA ${ARG_SVN_REPOSITORY}) + string(SUBSTRING ${urlSHA} 0 7 urlSHA) + set(srcDirSuffix "-${urlSHA}") + set(svnRepoArgs SVN_REPOSITORY ${ARG_SVN_REPOSITORY}) + endif() + + string(TOLOWER ${contentName} contentNameLower) + __FetchContent_declareDetails( + ${contentNameLower} + SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src${srcDirSuffix}" + BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build" + ${svnRepoArgs} + # List these last so they can override things we set above + ${ARG_UNPARSED_ARGUMENTS} + ) + +endfunction() + + +#======================================================================= +# Set/get whether the specified content has been populated yet. +# The setter also records the source and binary dirs used. +#======================================================================= + +# Internal use, projects must not call this directly. It is +# intended for use by the FetchContent_Populate() function to +# record when FetchContent_Populate() is called for a particular +# content name. +function(__FetchContent_setPopulated contentName sourceDir binaryDir) + + string(TOLOWER ${contentName} contentNameLower) + set(prefix "_FetchContent_${contentNameLower}") + + set(propertyName "${prefix}_sourceDir") + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} ${sourceDir}) + + set(propertyName "${prefix}_binaryDir") + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} ${binaryDir}) + + set(propertyName "${prefix}_populated") + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} True) + +endfunction() + + +# Set variables in the calling scope for any of the retrievable +# properties. If no specific properties are requested, variables +# will be set for all retrievable properties. +# +# This function is intended to also be used by projects as the canonical +# way to detect whether they should call FetchContent_Populate() +# and pull the populated source into the build with add_subdirectory(), +# if they are using the populated content in that way. +function(FetchContent_GetProperties contentName) + + string(TOLOWER ${contentName} contentNameLower) + + set(options "") + set(oneValueArgs SOURCE_DIR BINARY_DIR POPULATED) + set(multiValueArgs "") + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARG_SOURCE_DIR AND + NOT ARG_BINARY_DIR AND + NOT ARG_POPULATED) + # No specific properties requested, provide them all + set(ARG_SOURCE_DIR ${contentNameLower}_SOURCE_DIR) + set(ARG_BINARY_DIR ${contentNameLower}_BINARY_DIR) + set(ARG_POPULATED ${contentNameLower}_POPULATED) + endif() + + set(prefix "_FetchContent_${contentNameLower}") + + if(ARG_SOURCE_DIR) + set(propertyName "${prefix}_sourceDir") + get_property(value GLOBAL PROPERTY ${propertyName}) + if(value) + set(${ARG_SOURCE_DIR} ${value} PARENT_SCOPE) + endif() + endif() + + if(ARG_BINARY_DIR) + set(propertyName "${prefix}_binaryDir") + get_property(value GLOBAL PROPERTY ${propertyName}) + if(value) + set(${ARG_BINARY_DIR} ${value} PARENT_SCOPE) + endif() + endif() + + if(ARG_POPULATED) + set(propertyName "${prefix}_populated") + get_property(value GLOBAL PROPERTY ${propertyName} DEFINED) + set(${ARG_POPULATED} ${value} PARENT_SCOPE) + endif() + +endfunction() + + +#======================================================================= +# Performing the population +#======================================================================= + +# The value of contentName will always have been lowercased by the caller. +# All other arguments are assumed to be options that are understood by +# ExternalProject_Add(), except for QUIET and SUBBUILD_DIR. +function(__FetchContent_directPopulate contentName) + + set(options + QUIET + ) + set(oneValueArgs + SUBBUILD_DIR + SOURCE_DIR + BINARY_DIR + # Prevent the following from being passed through + CONFIGURE_COMMAND + BUILD_COMMAND + INSTALL_COMMAND + TEST_COMMAND + # We force both of these to be ON since we are always executing serially + # and we want all steps to have access to the terminal in case they + # need input from the command line (e.g. ask for a private key password) + # or they want to provide timely progress. We silently absorb and + # discard these if they are set by the caller. + USES_TERMINAL_DOWNLOAD + USES_TERMINAL_UPDATE + ) + set(multiValueArgs "") + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARG_SUBBUILD_DIR) + message(FATAL_ERROR "Internal error: SUBBUILD_DIR not set") + elseif(NOT IS_ABSOLUTE "${ARG_SUBBUILD_DIR}") + set(ARG_SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SUBBUILD_DIR}") + endif() + + if(NOT ARG_SOURCE_DIR) + message(FATAL_ERROR "Internal error: SOURCE_DIR not set") + elseif(NOT IS_ABSOLUTE "${ARG_SOURCE_DIR}") + set(ARG_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SOURCE_DIR}") + endif() + + if(NOT ARG_BINARY_DIR) + message(FATAL_ERROR "Internal error: BINARY_DIR not set") + elseif(NOT IS_ABSOLUTE "${ARG_BINARY_DIR}") + set(ARG_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_BINARY_DIR}") + endif() + + # Ensure the caller can know where to find the source and build directories + # with some convenient variables. Doing this here ensures the caller sees + # the correct result in the case where the default values are overridden by + # the content details set by the project. + set(${contentName}_SOURCE_DIR "${ARG_SOURCE_DIR}" PARENT_SCOPE) + set(${contentName}_BINARY_DIR "${ARG_BINARY_DIR}" PARENT_SCOPE) + + # The unparsed arguments may contain spaces, so build up ARG_EXTRA + # in such a way that it correctly substitutes into the generated + # CMakeLists.txt file with each argument quoted. + unset(ARG_EXTRA) + foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS) + set(ARG_EXTRA "${ARG_EXTRA} \"${arg}\"") + endforeach() + + # Hide output if requested, but save it to a variable in case there's an + # error so we can show the output upon failure. When not quiet, don't + # capture the output to a variable because the user may want to see the + # output as it happens (e.g. progress during long downloads). Combine both + # stdout and stderr in the one capture variable so the output stays in order. + if (ARG_QUIET) + set(outputOptions + OUTPUT_VARIABLE capturedOutput + ERROR_VARIABLE capturedOutput + ) + else() + set(capturedOutput) + set(outputOptions) + message(STATUS "Populating ${contentName}") + endif() + + if(CMAKE_GENERATOR) + set(generatorOpts "-G${CMAKE_GENERATOR}") + if(CMAKE_GENERATOR_PLATFORM) + list(APPEND generatorOpts "-A${CMAKE_GENERATOR_PLATFORM}") + endif() + if(CMAKE_GENERATOR_TOOLSET) + list(APPEND generatorOpts "-T${CMAKE_GENERATOR_TOOLSET}") + endif() + + if(CMAKE_MAKE_PROGRAM) + list(APPEND generatorOpts "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}") + endif() + + else() + # Likely we've been invoked via CMake's script mode where no + # generator is set (and hence CMAKE_MAKE_PROGRAM could not be + # trusted even if provided). We will have to rely on being + # able to find the default generator and build tool. + unset(generatorOpts) + endif() + + # Create and build a separate CMake project to carry out the population. + # If we've already previously done these steps, they will not cause + # anything to be updated, so extra rebuilds of the project won't occur. + # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project + # has this set to something not findable on the PATH. + configure_file("${__FetchContent_privateDir}/CMakeLists.cmake.in" + "${ARG_SUBBUILD_DIR}/CMakeLists.txt") + execute_process( + COMMAND ${CMAKE_COMMAND} ${generatorOpts} . + RESULT_VARIABLE result + ${outputOptions} + WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" + ) + if(result) + if(capturedOutput) + message("${capturedOutput}") + endif() + message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}") + endif() + execute_process( + COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + ${outputOptions} + WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" + ) + if(result) + if(capturedOutput) + message("${capturedOutput}") + endif() + message(FATAL_ERROR "Build step for ${contentName} failed: ${result}") + endif() + +endfunction() + + +option(FETCHCONTENT_FULLY_DISCONNECTED "Disables all attempts to download or update content and assumes source dirs already exist") +option(FETCHCONTENT_UPDATES_DISCONNECTED "Enables UPDATE_DISCONNECTED behavior for all content population") +option(FETCHCONTENT_QUIET "Enables QUIET option for all content population" ON) +set(FETCHCONTENT_BASE_DIR "${CMAKE_BINARY_DIR}/_deps" CACHE PATH "Directory under which to collect all populated content") + +# Populate the specified content using details stored from +# an earlier call to FetchContent_Declare(). +function(FetchContent_Populate contentName) + + if(NOT contentName) + message(FATAL_ERROR "Empty contentName not allowed for FetchContent_Populate()") + endif() + + string(TOLOWER ${contentName} contentNameLower) + + if(ARGN) + # This is the direct population form with details fully specified + # as part of the call, so we already have everything we need + __FetchContent_directPopulate( + ${contentNameLower} + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-build" + ${ARGN} # Could override any of the above ..._DIR variables + ) + + # Pass source and binary dir variables back to the caller + set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE) + set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE) + + # Don't set global properties, or record that we did this population, since + # this was a direct call outside of the normal declared details form. + # We only want to save values in the global properties for content that + # honours the hierarchical details mechanism so that projects are not + # robbed of the ability to override details set in nested projects. + return() + endif() + + # No details provided, so assume they were saved from an earlier call + # to FetchContent_Declare(). Do a check that we haven't already + # populated this content before in case the caller forgot to check. + FetchContent_GetProperties(${contentName}) + if(${contentNameLower}_POPULATED) + message(FATAL_ERROR "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}") + endif() + + string(TOUPPER ${contentName} contentNameUpper) + set(FETCHCONTENT_SOURCE_DIR_${contentNameUpper} + "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}" + CACHE PATH "When not empty, overrides where to find pre-populated content for ${contentName}") + + if(FETCHCONTENT_SOURCE_DIR_${contentNameUpper}) + # The source directory has been explicitly provided in the cache, + # so no population is required + set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}") + set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build") + + elseif(FETCHCONTENT_FULLY_DISCONNECTED) + # Bypass population and assume source is already there from a previous run + set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src") + set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build") + + else() + # Support both a global "disconnect all updates" and a per-content + # update test (either one being set disables updates for this content). + option(FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper} + "Enables UPDATE_DISCONNECTED behavior just for population of ${contentName}") + if(FETCHCONTENT_UPDATES_DISCONNECTED OR + FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper}) + set(disconnectUpdates True) + else() + set(disconnectUpdates False) + endif() + + if(FETCHCONTENT_QUIET) + set(quietFlag QUIET) + else() + unset(quietFlag) + endif() + + __FetchContent_getSavedDetails(${contentName} contentDetails) + if("${contentDetails}" STREQUAL "") + message(FATAL_ERROR "No details have been set for content: ${contentName}") + endif() + + __FetchContent_directPopulate( + ${contentNameLower} + ${quietFlag} + UPDATE_DISCONNECTED ${disconnectUpdates} + SUBBUILD_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-subbuild" + SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src" + BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build" + # Put the saved details last so they can override any of the + # the options we set above (this can include SOURCE_DIR or + # BUILD_DIR) + ${contentDetails} + ) + endif() + + __FetchContent_setPopulated( + ${contentName} + ${${contentNameLower}_SOURCE_DIR} + ${${contentNameLower}_BINARY_DIR} + ) + + # Pass variables back to the caller. The variables passed back here + # must match what FetchContent_GetProperties() sets when it is called + # with just the content name. + set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE) + set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE) + set(${contentNameLower}_POPULATED True PARENT_SCOPE) + +endfunction() + +# Arguments are assumed to be the names of dependencies that have been +# declared previously and should be populated. It is not an error if +# any of them have already been populated (they will just be skipped in +# that case). The command is implemented as a macro so that the variables +# defined by the FetchContent_GetProperties() and FetchContent_Populate() +# calls will be available to the caller. +macro(FetchContent_MakeAvailable) + + foreach(contentName IN ITEMS ${ARGV}) + string(TOLOWER ${contentName} contentNameLower) + FetchContent_GetProperties(${contentName}) + if(NOT ${contentNameLower}_POPULATED) + FetchContent_Populate(${contentName}) + + # Only try to call add_subdirectory() if the populated content + # can be treated that way. Protecting the call with the check + # allows this function to be used for projects that just want + # to ensure the content exists, such as to provide content at + # a known location. + if(EXISTS ${${contentNameLower}_SOURCE_DIR}/CMakeLists.txt) + add_subdirectory(${${contentNameLower}_SOURCE_DIR} + ${${contentNameLower}_BINARY_DIR}) + endif() + endif() + endforeach() + +endmacro() diff --git a/cmake/myx/backports/FetchContent/CMakeLists.cmake.in b/cmake/myx/backports/FetchContent/CMakeLists.cmake.in new file mode 100644 index 0000000..d94b0f4 --- /dev/null +++ b/cmake/myx/backports/FetchContent/CMakeLists.cmake.in @@ -0,0 +1,28 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION ${CMAKE_VERSION}) + +# We name the project and the target for the ExternalProject_Add() call +# to something that will highlight to the user what we are working on if +# something goes wrong and an error message is produced. + +project(${contentName}-populate NONE) + +@__FETCHCONTENT_CACHED_INFO@ + +include(ExternalProject) +ExternalProject_Add(${contentName}-populate + ${ARG_EXTRA} + SOURCE_DIR "${ARG_SOURCE_DIR}" + BINARY_DIR "${ARG_BINARY_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + USES_TERMINAL_DOWNLOAD YES + USES_TERMINAL_UPDATE YES + USES_TERMINAL_PATCH YES +) + +@__FETCHCONTENT_COPY_FILE@ diff --git a/cmake/myx/backports/IncludeGuard.cmake b/cmake/myx/backports/IncludeGuard.cmake new file mode 100644 index 0000000..1e577c3 --- /dev/null +++ b/cmake/myx/backports/IncludeGuard.cmake @@ -0,0 +1,12 @@ +# Защита для однократного включения файла *.cmake +# Функция include_guard() реализована в версии 3.10 +# Макрос реализован для обратной совместимости +if(${CMAKE_VERSION} VERSION_LESS "3.10.0") + macro(include_guard) + if (CMAKE_FILE_${CMAKE_CURRENT_LIST_FILE}_ALREADY_INCLUDED) + return() + endif() + set(CMAKE_FILE_${CMAKE_CURRENT_LIST_FILE}_ALREADY_INCLUDED TRUE) + endmacro() +endif() + diff --git a/cmake/myx/backports/TopLevelProject.cmake b/cmake/myx/backports/TopLevelProject.cmake new file mode 100644 index 0000000..b1b89ee --- /dev/null +++ b/cmake/myx/backports/TopLevelProject.cmake @@ -0,0 +1,10 @@ +include_guard(GLOBAL) + +if(CMAKE_VERSION VERSION_LESS 3.21) + get_property(nt DIRECTORY PROPERTY PARENT_DIRECTORY) + if(NOT nt) + set(PROJECT_IS_TOP_LEVEL true) + endif() + unset(nt) +endif() + diff --git a/cmake/myx/lib/AddLibrary.cmake b/cmake/myx/lib/AddLibrary.cmake new file mode 100644 index 0000000..75b5ea8 --- /dev/null +++ b/cmake/myx/lib/AddLibrary.cmake @@ -0,0 +1,101 @@ +#[=======================================================================[.rst: +myx_add_library +--------------- + +Вспомогательная функция для создания библиотеки:: + + myx_add_library(NAME TYPE) + +Обязательные параметры: `NAME` - имя библиотеки и `TYPE` - тип цели. +Все остальные параметры передаются стандартной функции `add_library()` + +#]=======================================================================] + +include_guard(GLOBAL) + +include(GenerateExportHeader) + +function(myx_add_library NAME TYPE) + set(options) + set(oneValueArgs) + set(multiValueArgs) + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Вызов стандартной функции `add_library()` + add_library(${NAME} ${TYPE} ${ARG_UNPARSED_ARGUMENTS}) + + # Если вызов был выполнен не из проекта верхнего уровня, + # то созданная цель исключается из цели `all`. + # При этом сама цель `${NAME}` может участвовать в сборке, + # если окажется в перечне зависимостей. + if(NOT PROJECT_IS_TOP_LEVEL) + set_target_properties(${NAME} PROPERTIES + EXCLUDE_FROM_ALL True + ) + endif() + + if(TYPE STREQUAL INTERFACE) + # Стандартные пути к заголовочным файлам + target_include_directories(${NAME} + INTERFACE + $ + $ + ) + else() + string(TOUPPER ${NAME} PROJECT_NAME_UPPER) + # Опция для разрешения сборки разделяемой библиотеки + option(${PROJECT_NAME_UPPER}_BUILD_SHARED "Build shared library" ON) + # Опция для разрешения сборки статической библиотеки + option(${PROJECT_NAME_UPPER}_BUILD_STATIC "Build static library" ON) + + # Стандартные пути к заголовочным файлам + target_include_directories(${NAME} + PUBLIC + $ + PRIVATE + $ + $ + ) + + set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) + + string(TOUPPER ${NAME} suffix) + generate_export_header(${NAME} + BASE_NAME ${suffix} + EXPORT_MACRO_NAME "EXPORT_${suffix}" + DEPRECATED_MACRO_NAME "DEPRECATED_${suffix}" + NO_DEPRECATED_MACRO_NAME "NO_DEPRECATED_${suffix}" + NO_EXPORT_MACRO_NAME "NO_EXPORT_${suffix}" + STATIC_DEFINE "STATIC_DEFINE_${suffix}" + EXPORT_FILE_NAME "${PROJECT_SOURCE_DIR}/include/${NAME}/${NAME}_export.hpp" + DEFINE_NO_DEPRECATED + ) + + # Цель для создания разделяемой библиотеки из объектных файлов + if(${PROJECT_NAME_UPPER}_BUILD_SHARED) + # Для создания разделяемой библиотеки используются объектные файлы цели ${NAME} + add_library(${NAME}_shared SHARED $) + # Установка дополнительных свойств для цели ${NAME}_shared + # OUTPUT_NAME: базовое имя выходного файла (без учёта расширения) + # VERSION: версия библиотеки + # SOVERSION: мажорная версия библиотеки + set_target_properties(${NAME}_shared + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + OUTPUT_NAME ${NAME}) + endif() + + # Цель для создания статической библиотеки из объектных файлов + if(${PROJECT_NAME_UPPER}_BUILD_STATIC) + # Для создания статической библиотеки используются объектные файлы цели ${NAME} + add_library(${NAME}_static STATIC $) + # Установка дополнительных свойств для цели ${NAME}_static + # OUTPUT_NAME: базовое имя выходного файла (без учёта расширения) + set_target_properties(${NAME}_static + PROPERTIES + OUTPUT_NAME ${NAME}) + endif() + endif() +endfunction() diff --git a/cmake/myx/lib/ColoredMessages.cmake b/cmake/myx/lib/ColoredMessages.cmake new file mode 100644 index 0000000..8517f6c --- /dev/null +++ b/cmake/myx/lib/ColoredMessages.cmake @@ -0,0 +1,56 @@ +#[=======================================================================[.rst: + +Обёртки для функции `message()`, которые в терминале UNIX +подсвечиают сообщения в зависимости от важности. + +#]=======================================================================] + +include_guard(GLOBAL) + +if(DEFINED ENV{TERM} AND UNIX) + string(ASCII 27 Esc) + set(MyxColorReset "${Esc}[m") + set(MyxColorBold "${Esc}[1m") + set(MyxColorRed "${Esc}[31m") + set(MyxColorGreen "${Esc}[32m") + set(MyxColorYellow "${Esc}[33m") + set(MyxColorBlue "${Esc}[34m") + set(MyxColorMagenta "${Esc}[35m") + set(MyxColorCyan "${Esc}[36m") + set(MyxColorWhite "${Esc}[37m") + set(MyxColorBoldRed "${Esc}[1;31m") + set(MyxColorBoldGreen "${Esc}[1;32m") + set(MyxColorBoldYellow "${Esc}[1;33m") + set(MyxColorBoldBlue "${Esc}[1;34m") + set(MyxColorBoldMagenta "${Esc}[1;35m") + set(MyxColorBoldCyan "${Esc}[1;36m") + set(MyxColorBoldWhite "${Esc}[1;37m") +endif() + +function(myx_message_fatal_error) + message(FATAL_ERROR ${MyxColorBoldRed}${ARGV}${MyxColorReset}) +endfunction() + +function(myx_message_send_error) + message(SEND_ERROR ${MyxColorBoldRed}${ARGV}${MyxColorReset}) +endfunction() + +function(myx_message_warning) + message(WARNING ${MyxColorRed}${ARGV}${MyxColorReset}) +endfunction() + +function(myx_message_deprecation) + message(DEPRECATION ${MyxColorBoldMagenta}${ARGV}${MyxColorReset}) +endfunction() + +function(myx_message_status) + message(STATUS ${MyxColorMagenta}${ARGV}${MyxColorReset}) +endfunction() + +function(myx_message_notice) + message(${MyxColorBold}${ARGV}${MyxColorReset}) +endfunction() + +function(myx_message) + message(${MyxColorReset}${ARGV}${MyxColorReset}) +endfunction() diff --git a/cmake/myx/lib/DirectoriesGuards.cmake b/cmake/myx/lib/DirectoriesGuards.cmake new file mode 100644 index 0000000..459cfbe --- /dev/null +++ b/cmake/myx/lib/DirectoriesGuards.cmake @@ -0,0 +1,53 @@ +#[=======================================================================[.rst: + +Запись результатов сборки проекта внутрь иерархии каталогов с исходными текстами +приводит к засорению файлами формируемыми на этапе сборки, которые затрудняют +разработку, поиск в оригинальных файлах и мешают ориентироваться в проекте. +При работе с несколькими типами сборки, например, отладка и выпуск, появляется +необходимость корректного полного удаления результатов предыдущего варианта. + +#]=======================================================================] + +include_guard(GLOBAL) + +get_filename_component(cmake_source_dir "${CMAKE_SOURCE_DIR}" REALPATH) +get_filename_component(cmake_binary_dir "${CMAKE_BINARY_DIR}" REALPATH) +get_filename_component(project_source_dir "${PROJECT_SOURCE_DIR}" REALPATH) +get_filename_component(project_binary_dir "${PROJECT_BINARY_DIR}" REALPATH) +get_filename_component(cmake_install_prefix "${CMAKE_INSTALL_PREFIX}" REALPATH) + +if(cmake_install_prefix STREQUAL cmake_binary_dir) + myx_message_error( + "Myx: Cannot install into build directory ${CMAKE_INSTALL_PREFIX}.") +endif() + +if(cmake_install_prefix STREQUAL cmake_source_dir) + myx_message_error( + "Myx: Cannot install into source directory ${CMAKE_INSTALL_PREFIX}.") +endif() + +if(cmake_install_prefix STREQUAL project_binary_dir) + myx_message_error( + "Myx: Cannot install into build directory ${CMAKE_INSTALL_PREFIX}.") +endif() + +if(cmake_install_prefix STREQUAL project_source_dir) + myx_message_error( + "Myx: Cannot install into source directory ${CMAKE_INSTALL_PREFIX}.") +endif() + +if(cmake_binary_dir STREQUAL cmake_source_dir) + myx_message_error( + "Myx: Cannot build in source directory ${CMAKE_SOURCE_DIR}") +endif() + +if(project_binary_dir STREQUAL project_source_dir) + myx_message_error( + "Myx: Cannot build in source directory ${CMAKE_SOURCE_DIR}") +endif() + +unset(cmake_source_dir) +unset(cmake_binary_dir) +unset(project_source_dir) +unset(project_binary_dir) +unset(cmake_install_prefix) diff --git a/cmake/myx/lib/FetchContentAdd.cmake b/cmake/myx/lib/FetchContentAdd.cmake new file mode 100644 index 0000000..e531de4 --- /dev/null +++ b/cmake/myx/lib/FetchContentAdd.cmake @@ -0,0 +1,35 @@ +include_guard(GLOBAL) + +function(FetchContent_Add NAME) + set(options "") + set(oneValueArgs GIT_REPOSITORY GIT_REMOTE GIT_PATH) + set(multiValueArgs "") + + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + find_package(Git) + if(GIT_FOUND AND arg_GIT_REMOTE AND arg_GIT_PATH) + execute_process(COMMAND ${GIT_EXECUTABLE} config --get remote.${arg_GIT_REMOTE}.url OUTPUT_VARIABLE REMOTE_URL ERROR_QUIET) + if(REMOTE_URL) + string(REGEX REPLACE ":.*" "" SERVER ${REMOTE_URL}) + string(FIND ${SERVER} "http" POS) + if(NOT POS EQUAL 0) + if(NOT SERVER STREQUAL REMOTE_URL) + set(arg_GIT_REPOSITORY "${SERVER}:${arg_GIT_PATH}") + endif() + endif() + endif() + endif() + + FetchContent_Declare( + ${NAME} + ${arg_UNPARSED_ARGUMENTS} + GIT_REPOSITORY ${arg_GIT_REPOSITORY} + ) + + if(NOT ${NAME}_POPULATED) + FetchContent_Populate(${NAME}) + add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR}) + endif() + +endfunction() diff --git a/cmake/myx/lib/Includes.cmake b/cmake/myx/lib/Includes.cmake new file mode 100644 index 0000000..8b54c46 --- /dev/null +++ b/cmake/myx/lib/Includes.cmake @@ -0,0 +1,4 @@ +include_guard(GLOBAL) + +include(FetchContent) +include(GNUInstallDirs) diff --git a/cmake/myx/lib/InstallLibrary.cmake b/cmake/myx/lib/InstallLibrary.cmake new file mode 100644 index 0000000..21296ed --- /dev/null +++ b/cmake/myx/lib/InstallLibrary.cmake @@ -0,0 +1,75 @@ +include_guard(GLOBAL) + +if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + set(CMAKE_CURRENT_FUNCTION_LIST_DIR ${CMAKE_CURRENT_LIST_DIR}) +endif() + +include(CMakePackageConfigHelpers) + +function(myx_install_library NAME) + if(NOT PROJECT_IS_TOP_LEVEL) + return() + endif() + + get_target_property(type ${NAME} TYPE) + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion + ) + + configure_package_config_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/InstallLibraryConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${NAME}Config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${NAME} + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO + ) + + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT dev + ) + + install(EXPORT ${NAME}Targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${NAME} + COMPONENT dev + ) + + install( + FILES + ${PROJECT_BINARY_DIR}/${NAME}ConfigVersion.cmake + ${PROJECT_BINARY_DIR}/${NAME}Config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${NAME} + COMPONENT dev + ) + + if(${type} STREQUAL "OBJECT_LIBRARY") + if(TARGET ${NAME}_shared) + install( + TARGETS ${NAME}_shared + EXPORT ${NAME}Targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT lib + ) + endif() + + if(TARGET ${NAME}_static) + install( + TARGETS ${NAME}_static + EXPORT ${NAME}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT lib + ) + endif() + endif() + + if(${type} STREQUAL "INTERFACE_LIBRARY") + install( + TARGETS ${NAME} + EXPORT ${NAME}Targets + COMPONENT lib + ) + endif() +endfunction() diff --git a/cmake/myx/lib/InstallLibraryConfig.cmake.in b/cmake/myx/lib/InstallLibraryConfig.cmake.in new file mode 100644 index 0000000..5e1d770 --- /dev/null +++ b/cmake/myx/lib/InstallLibraryConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") + +check_required_components("@PROJECT_NAME@") diff --git a/cmake/myx/lib/NinjaGeneratorWarning.cmake b/cmake/myx/lib/NinjaGeneratorWarning.cmake new file mode 100644 index 0000000..b9118d7 --- /dev/null +++ b/cmake/myx/lib/NinjaGeneratorWarning.cmake @@ -0,0 +1,8 @@ +# Версии CMake, как минимум до 3.8.0, генерируют некорректные +# правила для ninja. + +include_guard(GLOBAL) + +if(${CMAKE_VERSION} VERSION_LESS "3.8.0" AND CMAKE_GENERATOR MATCHES Ninja) + myx_message_error("Myx: Old CMake versions should use Makefile generator") +endif() diff --git a/cmake/myx/lib/NinjaGeneratorWrapper.cmake b/cmake/myx/lib/NinjaGeneratorWrapper.cmake new file mode 100644 index 0000000..be97d52 --- /dev/null +++ b/cmake/myx/lib/NinjaGeneratorWrapper.cmake @@ -0,0 +1,11 @@ +# Если выбран генератор Ninja, то в основном сборочном каталоге создаётся +# файл Makefile, который обрабатывается командой make и передаёт исполнение +# системе сборки ninja. Таким образом можно выполнять команду make, +# даже если правила сборки проекта сгенерированы для ninja. + +include_guard(GLOBAL) + +if(CMAKE_GENERATOR MATCHES Ninja AND PROJECT_IS_TOP_LEVEL) + file(WRITE ${CMAKE_BINARY_DIR}/Makefile + ".PHONY: build\n" "%:\n" "\t@ninja \$@\n" "build:\n" "\t@ninja\n") +endif() diff --git a/cmake/myx/lib/Qt5TargetSetup.cmake b/cmake/myx/lib/Qt5TargetSetup.cmake new file mode 100644 index 0000000..f225130 --- /dev/null +++ b/cmake/myx/lib/Qt5TargetSetup.cmake @@ -0,0 +1,42 @@ +include_guard(GLOBAL) + +function(myx_qt5_target_setup NAME) + set(options) + set(oneValueArgs) + set(multiValueArgs COMPONENTS PRIVATE SOURCES MOC UI QRC LANGS) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(arg_COMPONENTS) + find_package(Qt5 COMPONENTS ${arg_COMPONENTS} REQUIRED) + foreach(iter ${arg_COMPONENTS}) + target_include_directories(${NAME} PRIVATE ${Qt5${iter}_INCLUDE_DIRS}) + endforeach() + endif() + + if(arg_PRIVATE) + foreach(iter ${arg_PRIVATE}) + find_package(Qt5${iter} COMPONENTS Private REQUIRED) + endforeach() + endif() + + qt5_wrap_cpp(moc_cpp ${arg_MOC}) + qt5_wrap_ui(ui_h ${arg_UI}) + qt5_add_resources(qrc_cpp ${arg_QRC}) + + if("LinguistTools" IN_LIST arg_COMPONENTS) + set(ts) + foreach(iter ${arg_LANGS}) + list(APPEND ts ${PROJECT_SOURCE_DIR}/l10n/${NAME}_${iter}.ts) + endforeach() + + qt5_create_translation(qm + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/src + ${ts} + ) + endif() + + target_sources(${NAME} + PRIVATE + ${arg_SOURCES} ${moc_cpp} ${ui_h} ${qrc_cpp} ${qm}) +endfunction() diff --git a/cmake/myx/lib/SemanticProjectVersion.cmake b/cmake/myx/lib/SemanticProjectVersion.cmake new file mode 100644 index 0000000..4d410e6 --- /dev/null +++ b/cmake/myx/lib/SemanticProjectVersion.cmake @@ -0,0 +1,17 @@ +include_guard(GLOBAL) + +function(myx_is_semantic_project_version) + if(NOT ${PROJECT_VERSION_PATCH} MATCHES "([0-9]+)") + myx_message_error("Myx: Please set project version in X.Y.Z format") + endif() +endfunction() + +function(myx_project_version_int) + # cmake-format: off + myx_is_semantic_project_version() + math(EXPR v "(${PROJECT_VERSION_MAJOR} << 32) + (${PROJECT_VERSION_MINOR} << 16) + ${PROJECT_VERSION_PATCH}") + set_property(GLOBAL PROPERTY PROJECT_VERSION_INT ${v}) + # cmake-format: on +endfunction() + +myx_is_semantic_project_version() diff --git a/cmake/myx/lib/TargetSetup.cmake b/cmake/myx/lib/TargetSetup.cmake new file mode 100644 index 0000000..9501472 --- /dev/null +++ b/cmake/myx/lib/TargetSetup.cmake @@ -0,0 +1,28 @@ +include_guard(GLOBAL) + +function(myx_target_setup NAME) + set(options) + set(oneValueArgs PCH) + set(multiValueArgs COMPILE_DEFINITIONS FIND_PACKAGES LINK_LIBRARIES SOURCES) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(iter ${arg_FIND_PACKAGES}) + find_package(${iter} CONFIG REQUIRED) + target_include_directories(${NAME} PRIVATE ${${iter}_INCLUDE_DIRS}) + target_compile_definitions(${NAME} PRIVATE ${${iter}_COMPILE_DEFINITIONS}) + endforeach() + target_compile_definitions(${NAME} PRIVATE ${arg_COMPILE_DEFINITIONS}) + + if(arg_PCH) + if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.16) + target_precompile_headers(${NAME} PRIVATE ${PROJECT_SOURCE_DIR}/${arg_PCH}) + else() + target_compile_options(${NAME} PRIVATE -include ${PROJECT_SOURCE_DIR}/${arg_PCH}) + endif() + target_sources(${NAME} PRIVATE ${PROJECT_SOURCE_DIR}/${arg_PCH}) + endif() + + target_sources(${NAME} PRIVATE ${arg_SOURCES}) + target_link_libraries(${NAME} PRIVATE ${arg_LINK_LIBRARIES}) + target_compile_definitions(${NAME} PRIVATE ${arg_COMPILE_DEFINITIONS}) +endfunction()