mirror of https://github.com/linuxdeepin/linglong
967 lines
30 KiB
CMake
967 lines
30 KiB
CMake
#[[
|
|
SPDX-FileCopyrightText: 2023 Chen Linxuan <me@black-desk.cn>
|
|
SPDX-License-Identifier: LGPL-3.0-or-later
|
|
]]
|
|
|
|
#[[
|
|
|
|
# PFL.cmake
|
|
|
|
This project is design to build c++ project follows [the Pitchfork layout].
|
|
|
|
You can just copy the [PFL.cmake] file into your awesome project,
|
|
then use like this:
|
|
|
|
```cmake
|
|
cmake_minimum_required(VERSION 3.11.4 FATAL_ERROR)
|
|
|
|
project (
|
|
MyAwesomeProject
|
|
VERSION 0.1.0
|
|
HOMEPAGE_URL https://github.com/<your-username>/MyAwesomeProject
|
|
LANGUAGES CXX
|
|
C
|
|
)
|
|
|
|
```
|
|
|
|
For futher information, check [examples] and [documents].
|
|
|
|
[PFL.cmake]: https://github.com/black-desk/PFL.cmake/releases/latest/download/PFL.cmake
|
|
|
|
[the Pitchfork layout]: https://github.com/vector-of-bool/pitchfork/blob/develop/data/spec.bs
|
|
|
|
[examples]: https://github.com/black-desk/PFL.cmake/tree/master/examples
|
|
|
|
[documents]: https://github.com/black-desk/PFL.cmake/tree/master/docs
|
|
|
|
]]
|
|
|
|
# NOTE: RHEL 8 use cmake 3.11.4
|
|
cmake_minimum_required(VERSION 3.11.4 FATAL_ERROR)
|
|
|
|
# Check whether PFL.cmake is added already
|
|
get_property(
|
|
PFL_INITIALIZED GLOBAL
|
|
PROPERTY PFL_INITIALIZED
|
|
SET)
|
|
if(PFL_INITIALIZED)
|
|
return()
|
|
endif()
|
|
|
|
set_property(GLOBAL PROPERTY PFL_INITIALIZED true)
|
|
|
|
set(_PFL_BUG_REPORT_MESSAGE
|
|
[=[
|
|
PFL.cmake bug detected.
|
|
Please fire a bug report on https://github.com/black-desk/PFL.cmake/issues/new.
|
|
]=])
|
|
|
|
function(_pfl_bug)
|
|
message(FATAL_ERROR "${_PFL_BUG_REPORT_MESSAGE}")
|
|
endfunction()
|
|
|
|
function(_pfl_str_join OUT_NAME SEP)
|
|
foreach(STRING ${ARGN})
|
|
if(NOT RESULT)
|
|
set(RESULT ${STRING})
|
|
else()
|
|
set(RESULT "${RESULT}${SEP}${STRING}")
|
|
endif()
|
|
endforeach()
|
|
|
|
set("${OUT_NAME}"
|
|
"${RESULT}"
|
|
PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
set(_PFL_MESSAGE_PREFIX "PFL.cmake: ")
|
|
|
|
function(_pfl_message MODE)
|
|
_pfl_str_join(MESSAGE " " ${ARGN})
|
|
message(${MODE} "${_PFL_MESSAGE_PREFIX}${MESSAGE}")
|
|
endfunction()
|
|
|
|
function(_pfl_debug)
|
|
if(CMAKE_VERSION VERSION_LESS 3.15)
|
|
return()
|
|
endif()
|
|
_pfl_message(DEBUG ${ARGV})
|
|
endfunction()
|
|
|
|
function(_pfl_info)
|
|
_pfl_message(STATUS ${ARGV})
|
|
endfunction()
|
|
|
|
function(_pfl_warn)
|
|
_pfl_message(WARNING ${ARGV})
|
|
endfunction()
|
|
|
|
function(_pfl_fatal)
|
|
_pfl_message(FATAL_ERROR ${ARGV})
|
|
endfunction()
|
|
|
|
set(_PFL_VERSION "v0.5.2")
|
|
|
|
_pfl_info("Version: ${_PFL_VERSION}")
|
|
|
|
set(_PFL_COMPONENT_VALIDATOR "^(_.*|.*__.*|.*_|.*:.*)\$")
|
|
macro(_pfl_check_component COMPONENT) # MESSAGES
|
|
if("${COMPONENT}" MATCHES "${_PFL_COMPONENT_VALIDATOR}")
|
|
_pfl_fatal(${ARGN})
|
|
endif()
|
|
endmacro()
|
|
|
|
macro(_pfl_project_is_top_level)
|
|
if(CMAKE_VERSION VERSION_LESS 3.21)
|
|
# https://www.scivision.dev/cmake-project-is-top-level/
|
|
get_property(
|
|
not_top
|
|
DIRECTORY
|
|
PROPERTY PARENT_DIRECTORY)
|
|
if(NOT not_top)
|
|
set(PROJECT_IS_TOP_LEVEL true)
|
|
else()
|
|
set(PROJECT_IS_TOP_LEVEL false)
|
|
endif()
|
|
endif()
|
|
endmacro()
|
|
|
|
macro(_pfl_parse_arguments)
|
|
foreach(PARAMETER IN LISTS FLAGS ONE_VALUE_KEYWORDS MULTI_VALUE_KEYWORDS)
|
|
unset(PFL_ARG_${PARAMETER})
|
|
endforeach()
|
|
cmake_parse_arguments(PFL_ARG "${FLAGS}" "${ONE_VALUE_KEYWORDS}"
|
|
"${MULTI_VALUE_KEYWORDS}" ${ARGN})
|
|
endmacro()
|
|
|
|
function(_pfl_check_called_from_project_source_dir FUNCTION_NAME)
|
|
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}")
|
|
return()
|
|
endif()
|
|
|
|
_pfl_fatal("${FUNCTION_NAME} must be called at \${PROJECT_SOURCE_DIR}")
|
|
endfunction()
|
|
|
|
function(_pfl_check_project_name)
|
|
_pfl_check_component(
|
|
"${PROJECT_NAME}" "Unsupported project name \"${PROJECT_NAME}\" detected."
|
|
"Please rename your project.")
|
|
endfunction()
|
|
|
|
macro(PFL_init) # NOTE: As we might call enable_testing() in `PFL_init`, it have
|
|
# to be a macro. Otherwise enable_testing() will not take
|
|
# effect.
|
|
set(FLAGS
|
|
# Do not automatically detected parameters from defined variables.
|
|
# NOTE:
|
|
# It's not recommended to disable auto detected.
|
|
NO_AUTO)
|
|
set(ONE_VALUE_KEYWORDS
|
|
# Make `PFL_add_library()` to default to `SHARED` libraries.
|
|
# [ default: ${PROJECT_IS_TOP_LEVEL} ]
|
|
BUILD_SHARED_LIBS # boolean
|
|
# Enable `install` target of this project.
|
|
# [ default: ${PROJECT_IS_TOP_LEVEL} ]
|
|
ENABLE_INSTALL # boolean
|
|
# Enable CTest and compile test executables.
|
|
# Check comments of `TESTS` parameters
|
|
# in PFL_add_library() and PFL_add_libraries() for details.
|
|
# [ default: ${PROJECT_IS_TOP_LEVEL} ]
|
|
ENABLE_TESTING # boolean
|
|
# Compile example executables.
|
|
# Check comments of `EXAMPLES` parameters
|
|
# in PFL_add_library() and PFL_add_libraries() for details.
|
|
# [ default: ${PROJECT_IS_TOP_LEVEL} ]
|
|
ENABLE_EXAMPLES # boolean
|
|
# Compile embed third party projects.
|
|
# Check comments of `EXTERNALS` parameters for details.
|
|
# [ default: ${PROJECT_IS_TOP_LEVEL} ]
|
|
ENABLE_EXTERNALS # boolean
|
|
# Compile applications.
|
|
# Check comments of `APPS` parameters
|
|
# in PFL_add_library() and PFL_add_libraries() for details.
|
|
# [ default: ${PROJECT_IS_TOP_LEVEL} ]
|
|
ENABLE_APPLICATIONS # boolean
|
|
)
|
|
set(MULTI_VALUE_KEYWORDS
|
|
# List of third party projects which current project depends on.
|
|
# Each string in this list indicate a external project in this syntax:
|
|
# "DIRECTORY PACKAGE"
|
|
# It means that the external project
|
|
# in ${CMAKE_CURRENT_SOURCE_DIR}/external/<DIRECTORY>
|
|
# should override find_package(<PACKAGE>),
|
|
# when `ENABLE_EXTERNALS` is true.
|
|
EXTERNALS)
|
|
_pfl_parse_arguments(${ARGN})
|
|
|
|
_pfl_project_is_top_level()
|
|
|
|
_pfl_check_called_from_project_source_dir(PFL_init)
|
|
_pfl_check_project_name()
|
|
|
|
set(auto true)
|
|
if(PFL_ARG_NO_AUTO)
|
|
set(auto false)
|
|
endif()
|
|
|
|
if(auto)
|
|
macro(_pfl_init_produce_auto_argument NAME DEFAULT)
|
|
if(DEFINED ${PROJECT_NAME}_${NAME} AND DEFINED PLF_ARG_${NAME})
|
|
_pfl_fatal(
|
|
"You may either define ${PROJECT_NAME}_${NAME} before call PFL_INIT"
|
|
"or call PFL_INIT with argument ${NAME}.")
|
|
endif()
|
|
|
|
if(NOT DEFINED ${PROJECT_NAME}_${NAME})
|
|
set("${PROJECT_NAME}_${NAME}" ${DEFAULT})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_${NAME})
|
|
set("${PROJECT_NAME}_${NAME}" ${PFL_ARG_${NAME}})
|
|
endif()
|
|
endmacro()
|
|
|
|
foreach(ARG IN LISTS FLAGS ONE_VALUE_KEYWORDS MULTI_VALUE_KEYWORDS)
|
|
if(ARG STREQUAL NO_AUTO)
|
|
continue()
|
|
endif()
|
|
if(ARG STREQUAL EXTERNALS)
|
|
_pfl_init_produce_auto_argument(EXTERNALS "")
|
|
continue()
|
|
endif()
|
|
_pfl_init_produce_auto_argument(${ARG} ${PROJECT_IS_TOP_LEVEL})
|
|
endforeach()
|
|
endif()
|
|
|
|
set(_PFL_PATH)
|
|
list(APPEND _PFL_PATH ${PROJECT_NAME})
|
|
|
|
if(${PROJECT_NAME}_ENABLE_TESTING)
|
|
enable_testing()
|
|
endif()
|
|
|
|
macro(generate_find_for_external PACKAGE TARGET)
|
|
# message(FATAL_ERROR "${PACKAGE} ${TARGET}")
|
|
if(NOT EXISTS
|
|
${CMAKE_CURRENT_SOURCE_DIR}/external/${PACKAGE}/CMakeLists.txt)
|
|
_pfl_fatal(
|
|
"${DIRECTORY} is not a external project:"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/external/${DIRECTORY}/CMakeLists.txt not found."
|
|
)
|
|
endif()
|
|
|
|
set(FIND_FILE
|
|
${CMAKE_CURRENT_BINARY_DIR}/_pfl_external/Find${PACKAGE}.cmake)
|
|
file(
|
|
WRITE ${FIND_FILE}
|
|
"if(TARGET ${TARGET})\n" #
|
|
" return()\n" #
|
|
"endif()\n" #
|
|
"add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/${PACKAGE})\n"
|
|
"set(${PACKAGE}_FOUND YES)")
|
|
endmacro()
|
|
|
|
if(${PROJECT_NAME}_ENABLE_EXTERNALS AND DEFINED ${PROJECT_NAME}_EXTERNALS)
|
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR}/_pfl_external)
|
|
foreach(EXTERNAL ${${PROJECT_NAME}_EXTERNALS})
|
|
string(REPLACE " " ";" EXTERNAL "${EXTERNAL}")
|
|
list(GET EXTERNAL 0 PACKAGE_NAME)
|
|
_pfl_info(
|
|
"Using external project ${PACKAGE_NAME} at ${CMAKE_CURRENT_SOURCE_DIR}/external/${PACKAGE_NAME}"
|
|
)
|
|
generate_find_for_external(${EXTERNAL})
|
|
find_package(${PACKAGE_NAME} REQUIRED)
|
|
endforeach()
|
|
endif()
|
|
endmacro()
|
|
|
|
set(PFL_add_libraries_default_cmake_config
|
|
[=[
|
|
@PACKAGE_INIT@
|
|
|
|
list(APPEND CMAKE_MODULE_PATH "@PACKAGE_cmakeModulesDir@")
|
|
|
|
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
|
|
set(Components ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS})
|
|
else()
|
|
set(Components @PFL_COMPONMENTS@)
|
|
endif()
|
|
|
|
@PFL_EXPORT_FILE_NAME_OF_COMPONMENTS@
|
|
|
|
# Check all required components are available before trying to load any
|
|
foreach(Component ${Components})
|
|
if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${Component})
|
|
continue();
|
|
endif()
|
|
|
|
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${EXPORT_FILE_NAME_OF_${Component}}.cmake)
|
|
continue();
|
|
endif()
|
|
|
|
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
|
|
"Missing required component: ${Component}")
|
|
|
|
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
|
|
return()
|
|
endforeach()
|
|
|
|
foreach(Component ${Components})
|
|
include(${CMAKE_CURRENT_LIST_DIR}/${EXPORT_FILE_NAME_OF_${Component}}.cmake OPTIONAL)
|
|
endforeach()
|
|
]=])
|
|
|
|
function(PFL_add_libraries)
|
|
|
|
set(FLAGS)
|
|
set(ONE_VALUE_KEYWORDS)
|
|
set(MULTI_VALUE_KEYWORDS
|
|
# Directories under ${CMAKE_CURRENT_SOURCE_DIR}/libs,
|
|
# that should be treated as submodule or components of your project.
|
|
# These projects will be added one by one using add_subdirectory().
|
|
LIBS
|
|
# Same as EXAMPLES of PFL_add_library
|
|
EXAMPLES
|
|
# Same as APPS of PFL_add_library
|
|
APPS
|
|
# Same as TESTS of PFL_add_library
|
|
TESTS)
|
|
_pfl_check_called_from_project_source_dir(PFL_add_libraries)
|
|
_pfl_parse_arguments(${ARGN})
|
|
|
|
_pfl_info("Adding libraries at ${CMAKE_CURRENT_SOURCE_DIR}")
|
|
|
|
if(NOT DEFINED PFL_ARG_LIBS)
|
|
_pfl_fatal("LIBS is required.")
|
|
endif()
|
|
|
|
if(NOT PFL_ARG_LIBS)
|
|
_pfl_fatal("At least one LIBS is required.")
|
|
endif()
|
|
|
|
set_property(GLOBAL PROPERTY PFL_COMPONMENTS)
|
|
set_property(GLOBAL PROPERTY PFL_EXPORT_FILE_NAME_OF_COMPONMENTS)
|
|
foreach(LIB ${PFL_ARG_LIBS})
|
|
add_subdirectory(libs/${LIB})
|
|
endforeach()
|
|
|
|
_pfl_add_executables()
|
|
|
|
if(NOT ${PROJECT_NAME}_ENABLE_INSTALL)
|
|
return()
|
|
endif()
|
|
|
|
include(GNUInstallDirs)
|
|
|
|
set(CONFIG_FILE misc/cmake/${PROJECT_NAME}Config.cmake)
|
|
|
|
set(CONFIG_FILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/${CONFIG_FILE}.in)
|
|
if(NOT EXISTS ${CONFIG_FILE_IN})
|
|
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}.in
|
|
${PFL_add_libraries_default_cmake_config})
|
|
set(CONFIG_FILE_IN ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}.in)
|
|
endif()
|
|
|
|
include(CMakePackageConfigHelpers)
|
|
# This will be used to replace @PACKAGE_cmakeModulesDir@
|
|
set(cmakeModulesDir cmake)
|
|
|
|
get_property(PFL_COMPONMENTS GLOBAL PROPERTY PFL_COMPONMENTS)
|
|
get_property(PFL_EXPORT_FILE_NAME_OF_COMPONMENTS GLOBAL
|
|
PROPERTY PFL_EXPORT_FILE_NAME_OF_COMPONMENTS)
|
|
configure_package_config_file(
|
|
${CONFIG_FILE_IN} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}
|
|
PATH_VARS cmakeModulesDir
|
|
INSTALL_DESTINATION
|
|
${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}-${PROJECT_VERSION}
|
|
NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO)
|
|
|
|
set(VERSION_FILE misc/cmake/${PROJECT_NAME}ConfigVersion.cmake)
|
|
write_basic_package_version_file(
|
|
${VERSION_FILE}
|
|
VERSION ${PROJECT_VERSION}
|
|
COMPATIBILITY SameMajorVersion)
|
|
|
|
install(
|
|
FILES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}
|
|
${CMAKE_CURRENT_BINARY_DIR}/${VERSION_FILE}
|
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}-${PROJECT_VERSION}
|
|
)
|
|
endfunction()
|
|
|
|
function(_pfl_add_tests)
|
|
foreach(TEST ${ARGN})
|
|
if(${PROJECT_NAME}_ENABLE_TESTING)
|
|
include(CTest)
|
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests/${TEST})
|
|
else()
|
|
_pfl_info("Skip test: ${CMAKE_CURRENT_SOURCE_DIR}/tests/${TEST}")
|
|
endif()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
function(_pfl_add_examples)
|
|
foreach(EXAMPLE ${ARGV})
|
|
if(${PROJECT_NAME}_ENABLE_EXAMPLES)
|
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/examples/${EXAMPLE})
|
|
else()
|
|
_pfl_info("Skip example: ${CMAKE_CURRENT_SOURCE_DIR}/examples/${EXAMPLE}")
|
|
endif()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
function(_pfl_add_apps)
|
|
foreach(APP ${ARGV})
|
|
if(${PROJECT_NAME}_ENABLE_APPLICATIONS)
|
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/apps/${APP})
|
|
else()
|
|
_pfl_info("Skip app: ${CMAKE_CURRENT_SOURCE_DIR}/apps/${APP}")
|
|
endif()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
macro(_pfl_current_compoent)
|
|
set(PFL_CURRENT_COMPOENT ${PROJECT_NAME})
|
|
|
|
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
|
|
get_filename_component(PFL_CURRENT_COMPOENT ${CMAKE_CURRENT_SOURCE_DIR}
|
|
NAME)
|
|
_pfl_check_component(
|
|
"${PFL_CURRENT_COMPOENT}"
|
|
"Unsupported directory name \"${PFL_CURRENT_COMPOENT}\" detected."
|
|
"Please rename your directory.")
|
|
endif()
|
|
endmacro()
|
|
|
|
function(_pfl_handle_sources PUBLIC_OUT PRIVATE_OUT MERGED_PLACEMENT) # SOURCES
|
|
set(SOURCES ${ARGN})
|
|
|
|
set(PUBLIC_SOURCES)
|
|
set(PRIVATE_SOURCES)
|
|
|
|
foreach(SOURCE ${SOURCES})
|
|
if(SOURCE MATCHES "^\./")
|
|
string(REGEX REPLACE "^\./" "" SOURCE ${SOURCE})
|
|
endif()
|
|
|
|
if(SOURCE MATCHES ".*\\.\\./.*")
|
|
_pfl_fatal("${SOURCE} is invalid:"
|
|
"../ is not allowed when add source to target.")
|
|
endif()
|
|
|
|
set(FULL_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE})
|
|
|
|
if(SOURCE MATCHES "\.in$")
|
|
string(REGEX REPLACE "\.in$" "" OUT_FILE ${SOURCE})
|
|
configure_file("${SOURCE}" "${CMAKE_CURRENT_BINARY_DIR}/${OUT_FILE}"
|
|
@ONLY)
|
|
set(SOURCE "${OUT_FILE}")
|
|
set(FULL_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${OUT_FILE}")
|
|
endif()
|
|
|
|
set(PUBLIC_REGEXP "include/.*\$")
|
|
if(MERGED_PLACEMENT)
|
|
set(PUBLIC_REGEXP "src\/.*\.h(h|pp)?\$")
|
|
endif()
|
|
|
|
if(NOT EXISTS "${FULL_SOURCE}")
|
|
_pfl_fatal("source file do not found at ${FULL_SOURCE}")
|
|
endif()
|
|
|
|
if(SOURCE MATCHES "${PUBLIC_REGEXP}")
|
|
set(TARGET_LIST PUBLIC_SOURCES)
|
|
else()
|
|
set(TARGET_LIST PRIVATE_SOURCES)
|
|
endif()
|
|
|
|
list(APPEND ${TARGET_LIST} ${FULL_SOURCE})
|
|
endforeach()
|
|
|
|
set("${PUBLIC_OUT}"
|
|
${PUBLIC_SOURCES}
|
|
PARENT_SCOPE)
|
|
set("${PRIVATE_OUT}"
|
|
${PRIVATE_SOURCES}
|
|
PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
macro(_pfl_add_target_common)
|
|
if(DEFINED PFL_ARG_LINK_LIBRARIES)
|
|
target_link_libraries(${TARGET} ${PFL_ARG_LINK_LIBRARIES})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_COMPILE_FEATURES)
|
|
target_compile_features(${TARGET} ${PFL_ARG_COMPILE_FEATURES})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_COMPILE_OPTIONS)
|
|
target_compile_options(${TARGET} ${PFL_ARG_COMPILE_OPTIONS})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_DEPENDENCIES)
|
|
foreach(DEPENDENCY ${PFL_ARG_DEPENDENCIES})
|
|
if("${DEPENDENCY}" STREQUAL "PUBLIC" OR "${DEPENDENCY}" STREQUAL
|
|
"PRIVATE")
|
|
continue()
|
|
endif()
|
|
|
|
string(REPLACE " " ";" DEPENDENCY ${DEPENDENCY})
|
|
find_package(${DEPENDENCY})
|
|
endforeach()
|
|
endif()
|
|
endmacro()
|
|
|
|
macro(_pfl_add_executables)
|
|
if(DEFINED PFL_ARG_TESTS)
|
|
_pfl_add_tests(${PFL_ARG_TESTS})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_EXAMPLES)
|
|
_pfl_add_examples(${PFL_ARG_EXAMPLES})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_APPS)
|
|
_pfl_add_apps(${PFL_ARG_APPS})
|
|
endif()
|
|
endmacro()
|
|
|
|
set(PFL_add_library_default_cmake_config
|
|
[=[
|
|
@PACKAGE_INIT@
|
|
|
|
list(APPEND CMAKE_MODULE_PATH "@PACKAGE_cmakeModulesDir@")
|
|
|
|
include(CMakeFindDependencyMacro)
|
|
|
|
|
|
@PFL_FIND_DEPENDENCIES@
|
|
|
|
include(${CMAKE_CURRENT_LIST_DIR}/@PFL_EXPORT_FILE_NAME@.cmake)
|
|
]=])
|
|
|
|
function(pfl_add_library)
|
|
set(FLAGS
|
|
# Disable install target of this library.
|
|
# If install target of this library is enabled,
|
|
# PFL.cmake will export your library automatically as well.
|
|
# [ default: FALSE ]
|
|
# NOTE: It's recommend to set this to true for internal libraries.
|
|
DISABLE_INSTALL
|
|
# Use merged header placement.
|
|
# This make PFL.cmake treats all header files under src as public headers.
|
|
# [ default: FALSE ]
|
|
MERGED_HEADER_PLACEMENT)
|
|
set(ONE_VALUE_KEYWORDS
|
|
# Set the type of this library.
|
|
# Valid options are `STATIC`, `SHARED` or `HEADER_ONLY`.
|
|
# [ default: BUILD_SHARED_LIBS ? `SHARED` : `STATIC` ]
|
|
LIBRARY_TYPE # string
|
|
# The output file name of this library.
|
|
# [ default: All the PFL hierarchies joined with "__" ]
|
|
# NOTE: It's not recommend to use the default value.
|
|
OUTPUT_NAME # string
|
|
# The version of this library.
|
|
# [ default: ${PROJECT_VERSION} ]
|
|
VERSION # string
|
|
# The soversion of this library.
|
|
# [ defualt: ${VERSION} ]
|
|
# NOTE:
|
|
# 1. This option is not available for `HEADER_ONLY` and `STATIC` library.
|
|
# 2. CMake defaults to keep SOVERSION same as VERSION.
|
|
SOVERSION # string
|
|
)
|
|
set(MULTI_VALUE_KEYWORDS
|
|
# List of relative file paths from ${CMAKE_CURRENT_SOURCE_DIR},
|
|
# including input files (.in), sources (.c .cpp .cxx .cc)
|
|
# and headers (.h .hpp .hh).
|
|
# Files without extension treated as headers also.
|
|
# Input files will be configured with @ONLY mode of configure_file.
|
|
# And the output is added to library as sources as well.
|
|
# By default, files under ${CMAKE_CURRENT_SOURCE_DIR}/include is public,
|
|
# while all other files is private.
|
|
# If MERGED_HEADER_PLACEMENT is true,
|
|
# header files under ${CMAKE_CURRENT_SOURCE_DIR}/src is public as well.
|
|
# [ default: All files under ${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
# and ${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
# ]
|
|
# NOTE:
|
|
# We support add (possibly near-empty) cpp files
|
|
# as sources for header only library.
|
|
# And PFL.cmake will build an internal static library that
|
|
# PRIVATE link to the header only library
|
|
# to check if all header file is self-contained.
|
|
SOURCES
|
|
# List of relative paths to
|
|
# directories in ${CMAKE_CURRENT_SOURCE_DIR}/tests
|
|
# which contains source code of tests.
|
|
# If test targets is enabled in your project,
|
|
# PFL.cmake will call add_subdirectory on these directory in order.
|
|
# These directories were expected to have a CMakeLists.txt,
|
|
# which call pfl_add_executable().
|
|
TESTS
|
|
# List of relative paths to
|
|
# directories in ${CMAKE_CURRENT_SOURCE_DIR}/apps
|
|
# which contain source code of executables.
|
|
# If application targets is enabled in your project,
|
|
# PFL.cmake will call add_subdirectory on these directory in order.
|
|
# These directories were expected to have a CMakeLists.txt,
|
|
# which call pfl_add_executable().
|
|
APPS
|
|
# List of relative paths to
|
|
# directories in ${CMAKE_CURRENT_SOURCE_DIR}/examples
|
|
# which contain source code of executables.
|
|
# If example targets is enabled in your project,
|
|
# PFL.cmake will call add_subdirectory on these directory in order.
|
|
# These directories were expected to have a CMakeLists.txt,
|
|
# which call pfl_add_executable().
|
|
EXAMPLES
|
|
# Arguments pass to find_package()
|
|
# to make sure your dependencies is founded.
|
|
# pfl_add_library (
|
|
# ...
|
|
# DEPENDENCIES
|
|
# PUBLIC "aaa COMPONENTS xxx REQUIRED"
|
|
# PRIVATE "bbb REQUIRED"
|
|
# ...
|
|
# )
|
|
# PRIVATE while building and
|
|
# find_dependency() in auto-generated `*Config.cmake` file.
|
|
# NOTE:
|
|
# PFL.cmake is going to use these arguments to:
|
|
# 1. Call find_package for your before build your library.
|
|
# 2. Call find_dependency in the export file of your library,
|
|
# if install targets of your library is enabled.
|
|
DEPENDENCIES
|
|
# Arguments passed to target_link_libraries().
|
|
LINK_LIBRARIES
|
|
# Arguments passed to target_compile_features().
|
|
COMPILE_FEATURES
|
|
# Arguments passed to target_compile_options().
|
|
COMPILE_OPTIONS)
|
|
_pfl_parse_arguments(${ARGN})
|
|
_pfl_current_compoent()
|
|
|
|
set(OLD_PFL_PATH ${_PFL_PATH})
|
|
list(APPEND _PFL_PATH ${PFL_CURRENT_COMPOENT})
|
|
_pfl_str_join(TARGET "__" ${_PFL_PATH})
|
|
_pfl_str_join(TARGET_ALIAS "::" ${_PFL_PATH})
|
|
|
|
_pfl_info("Adding library ${TARGET_ALIAS} at ${CMAKE_CURRENT_SOURCE_DIR}")
|
|
|
|
if(NOT DEFINED PFL_ARG_MERGED_HEADER_PLACEMENT)
|
|
set(PFL_ARG_MERGED_HEADER_PLACEMENT false)
|
|
endif()
|
|
|
|
if((NOT DEFINED PFL_ARG_SOURCES) OR (NOT PFL_ARG_SOURCES))
|
|
_pfl_fatal("SOURCES is missing."
|
|
"Pure interface libraries is not supported now.")
|
|
endif()
|
|
|
|
_pfl_handle_sources(PUBLIC_SOURCES PRIVATE_SOURCES
|
|
${PFL_ARG_MERGED_HEADER_PLACEMENT} ${PFL_ARG_SOURCES})
|
|
|
|
if(NOT DEFINED PFL_ARG_LIBRARY_TYPE)
|
|
set(PFL_ARG_LIBRARY_TYPE "STATIC")
|
|
if(${PROJECT_NAME}_BUILD_SHARED_LIBS)
|
|
set(PFL_ARG_LIBRARY_TYPE "SHARED")
|
|
endif()
|
|
endif()
|
|
|
|
set(LIBRARY_TYPE ${PFL_ARG_LIBRARY_TYPE})
|
|
set(PUBLIC_SOURCES_PROPAGATE PUBLIC)
|
|
if("${PFL_ARG_LIBRARY_TYPE}" STREQUAL "HEADER_ONLY")
|
|
set(LIBRARY_TYPE INTERFACE)
|
|
set(PUBLIC_SOURCES_PROPAGATE INTERFACE)
|
|
endif()
|
|
|
|
add_library(${TARGET} ${LIBRARY_TYPE})
|
|
if(NOT "${TARGET_ALIAS}" STREQUAL "${TARGET}")
|
|
add_library("${TARGET_ALIAS}" ALIAS "${TARGET}")
|
|
endif()
|
|
|
|
foreach(PUBLIC_SOURCE ${PUBLIC_SOURCES})
|
|
target_sources(${TARGET} ${PUBLIC_SOURCES_PROPAGATE}
|
|
$<BUILD_INTERFACE:${PUBLIC_SOURCE}>)
|
|
endforeach()
|
|
|
|
set(PUBLIC_INCLUDE_PREFIX include)
|
|
if(PFL_ARG_MERGED_HEADER_PLACEMENT)
|
|
set(PUBLIC_INCLUDE_PREFIX src)
|
|
endif()
|
|
|
|
target_include_directories(
|
|
${TARGET}
|
|
${PUBLIC_SOURCES_PROPAGATE}
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${PUBLIC_INCLUDE_PREFIX}>
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/${PUBLIC_INCLUDE_PREFIX}>)
|
|
|
|
set(BUILD_LIBRARY_NAME ${TARGET})
|
|
if("${PFL_ARG_LIBRARY_TYPE}" STREQUAL "HEADER_ONLY")
|
|
set(BUILD_LIBRARY_NAME ${TARGET}__BUILD)
|
|
add_library(${BUILD_LIBRARY_NAME} STATIC)
|
|
target_link_libraries(${BUILD_LIBRARY_NAME} PRIVATE ${TARGET})
|
|
endif()
|
|
|
|
target_sources(${BUILD_LIBRARY_NAME} PRIVATE ${PRIVATE_SOURCES})
|
|
|
|
target_include_directories(
|
|
${BUILD_LIBRARY_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
${CMAKE_CURRENT_BINARY_DIR}/src)
|
|
|
|
if(DEFINED PFL_ARG_SOVERSION)
|
|
if("${PFL_ARG_LIBRARY_TYPE}" STREQUAL "HEADER_ONLY")
|
|
_pfl_fatal(
|
|
"SOVERSION is not available for HEADER_ONLY library ${TARGET_ALIAS}.")
|
|
endif()
|
|
|
|
set_target_properties("${TARGET}" PROPERTIES SOVERSION ${PFL_ARG_SOVERSION})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_VERSION)
|
|
if("${PFL_ARG_LIBRARY_TYPE}" STREQUAL "HEADER_ONLY")
|
|
_pfl_fatal(
|
|
"VERSION is not available for HEADER_ONLY library ${TARGET_ALIAS}.")
|
|
endif()
|
|
endif()
|
|
|
|
if(NOT DEFINED PFL_ARG_VERSION)
|
|
set(PFL_ARG_VERSION ${PROJECT_VERSION})
|
|
endif()
|
|
|
|
if(NOT "${PFL_ARG_LIBRARY_TYPE}" STREQUAL "HEADER_ONLY")
|
|
set_target_properties("${TARGET}" PROPERTIES VERSION ${PFL_ARG_VERSION})
|
|
endif()
|
|
|
|
if(DEFINED PFL_ARG_OUTPUT_NAME AND "${PFL_ARG_LIBRARY_TYPE}" STREQUAL
|
|
"HEADER_ONLY")
|
|
_pfl_fatal("OUTPUT_NAME is not available"
|
|
"for HEADER_ONLY library ${TARGET_ALIAS}.")
|
|
endif()
|
|
|
|
if(NOT DEFINED PFL_ARG_OUTPUT_NAME)
|
|
set(PFL_ARG_OUTPUT_NAME "${TARGET}")
|
|
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}")
|
|
set(PFL_ARG_OUTPUT_NAME ${PFL_CURRENT_COMPOENT})
|
|
endif()
|
|
endif()
|
|
|
|
if(NOT "${PFL_ARG_LIBRARY_TYPE}" STREQUAL "HEADER_ONLY")
|
|
set_target_properties("${TARGET}" PROPERTIES OUTPUT_NAME
|
|
${PFL_ARG_OUTPUT_NAME})
|
|
if(NOT PFL_ARG_DISABLE_INSTALL)
|
|
_pfl_warn("OUTPUT_NAME of ${TARGET_ALIAS} not set,"
|
|
"using ${PFL_ARG_OUTPUT_NAME}")
|
|
endif()
|
|
endif()
|
|
|
|
_pfl_add_target_common()
|
|
_pfl_add_executables()
|
|
|
|
set(_PFL_PATH ${OLD_PFL_PATH})
|
|
|
|
if(PFL_ARG_DISABLE_INSTALL OR NOT ${PROJECT_NAME}_ENABLE_INSTALL)
|
|
return()
|
|
endif()
|
|
|
|
include(GNUInstallDirs)
|
|
set(PROJECT_PREFIX ${PROJECT_NAME}-${PROJECT_VERSION})
|
|
set(COMPONENT_INCLUDE_SUBDIR ${PROJECT_PREFIX}/${PFL_CURRENT_COMPOENT})
|
|
|
|
foreach(FILE ${PUBLIC_SOURCES})
|
|
if(FILE MATCHES "${CMAKE_CURRENT_SOURCE_DIR}/include/.*")
|
|
file(RELATIVE_PATH RELATIVE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
${FILE})
|
|
elseif(FILE MATCHES "${CMAKE_CURRENT_BINARY_DIR}/include/.*")
|
|
file(RELATIVE_PATH RELATIVE_FILE ${CMAKE_CURRENT_BINARY_DIR}/include
|
|
${FILE})
|
|
elseif(FILE MATCHES "${CMAKE_CURRENT_SOURCE_DIR}/src/.*")
|
|
file(RELATIVE_PATH RELATIVE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src ${FILE})
|
|
elseif(FILE MATCHES "${CMAKE_CURRENT_BINARY_DIR}/src/.*")
|
|
file(RELATIVE_PATH RELATIVE_FILE ${CMAKE_CURRENT_BINARY_DIR}/src ${FILE})
|
|
else()
|
|
_pfl_bug()
|
|
endif()
|
|
|
|
get_filename_component(DIR ${RELATIVE_FILE} DIRECTORY)
|
|
install(
|
|
FILES ${FILE}
|
|
DESTINATION
|
|
${CMAKE_INSTALL_FULL_INCLUDEDIR}/${COMPONENT_INCLUDE_SUBDIR}/${DIR})
|
|
|
|
target_sources(
|
|
"${TARGET}"
|
|
${PUBLIC_SOURCES_PROPAGATE}
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT_INCLUDE_SUBDIR}/${RELATIVE_FILE}>
|
|
)
|
|
endforeach()
|
|
|
|
target_include_directories(
|
|
${TARGET}
|
|
${PUBLIC_SOURCES_PROPAGATE}
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT_INCLUDE_SUBDIR}>
|
|
)
|
|
|
|
set(PFL_EXPORT_FILE_NAME ${PFL_ARG_OUTPUT_NAME})
|
|
|
|
get_property(PFL_COMPONMENTS GLOBAL PROPERTY PFL_COMPONMENTS)
|
|
list(APPEND PFL_COMPONMENTS ${PFL_CURRENT_COMPOENT})
|
|
set_property(GLOBAL PROPERTY PFL_COMPONMENTS ${PFL_COMPONMENTS})
|
|
|
|
get_property(PFL_EXPORT_FILE_NAME_OF_COMPONMENTS GLOBAL
|
|
PROPERTY PFL_EXPORT_FILE_NAME_OF_COMPONMENTS)
|
|
set(PFL_EXPORT_FILE_NAME_OF_COMPONMENTS
|
|
"${PFL_EXPORT_FILE_NAME_OF_COMPONMENTS}\nset(EXPORT_FILE_NAME_OF_${PFL_CURRENT_COMPOENT} ${PFL_EXPORT_FILE_NAME})"
|
|
)
|
|
set_property(GLOBAL PROPERTY PFL_EXPORT_FILE_NAME_OF_COMPONMENTS
|
|
${PFL_EXPORT_FILE_NAME_OF_COMPONMENTS})
|
|
|
|
install(
|
|
TARGETS "${TARGET}"
|
|
EXPORT "${PFL_ARG_OUTPUT_NAME}"
|
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
|
|
|
set_target_properties("${TARGET}" PROPERTIES EXPORT_NAME
|
|
${PFL_CURRENT_COMPOENT})
|
|
|
|
set(INSTALL_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_PREFIX})
|
|
|
|
install(
|
|
EXPORT "${PFL_ARG_OUTPUT_NAME}"
|
|
DESTINATION ${INSTALL_CMAKE_DIR}
|
|
FILE "${PFL_EXPORT_FILE_NAME}.cmake"
|
|
NAMESPACE ${PROJECT_NAME}::)
|
|
|
|
set(CONFIG_FILE misc/cmake/${PFL_ARG_OUTPUT_NAME}Config.cmake)
|
|
|
|
set(CONFIG_FILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/${CONFIG_FILE}.in)
|
|
if(NOT EXISTS ${CONFIG_FILE_IN})
|
|
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}.in
|
|
${PFL_add_library_default_cmake_config})
|
|
set(CONFIG_FILE_IN ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}.in)
|
|
endif()
|
|
|
|
include(CMakePackageConfigHelpers)
|
|
# This will be used to replace @PACKAGE_cmakeModulesDir@
|
|
set(cmakeModulesDir cmake)
|
|
|
|
set(PFL_FIND_DEPENDENCIES)
|
|
set(PROPAGATE PRIVATE)
|
|
foreach(DEPENDENCY ${PFL_ARG_DEPENDENCIES})
|
|
if("${DEPENDENCY}" STREQUAL "PUBLIC")
|
|
set(PROPAGATE PUBLIC)
|
|
continue()
|
|
elseif("${DEPENDENCY}" STREQUAL "PRIVATE")
|
|
set(PROPAGATE PRIVATE)
|
|
continue()
|
|
endif()
|
|
|
|
if("${PROPAGATE}" STREQUAL "PRIVATE")
|
|
_pfl_warn("Skip private dependency: ${DEPENDENCY}")
|
|
continue()
|
|
endif()
|
|
|
|
set(PFL_FIND_DEPENDENCIES
|
|
"${PFL_FIND_DEPENDENCIES}find_dependency(${DEPENDENCY})\n")
|
|
endforeach()
|
|
configure_package_config_file(
|
|
${CONFIG_FILE_IN} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}
|
|
PATH_VARS cmakeModulesDir
|
|
INSTALL_DESTINATION ${INSTALL_CMAKE_DIR}
|
|
NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO)
|
|
|
|
set(VERSION_FILE misc/cmake/${PFL_ARG_OUTPUT_NAME}ConfigVersion.cmake)
|
|
write_basic_package_version_file(
|
|
${VERSION_FILE}
|
|
VERSION ${PFL_ARG_VERSION}
|
|
COMPATIBILITY SameMajorVersion)
|
|
|
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE}
|
|
${CMAKE_CURRENT_BINARY_DIR}/${VERSION_FILE}
|
|
DESTINATION ${INSTALL_CMAKE_DIR})
|
|
endfunction()
|
|
|
|
function(PFL_add_executable)
|
|
set(FLAGS #
|
|
# Disable install target of this executable.
|
|
# NOTE: It's recommend to set this for tests and examples.
|
|
DISABLE_INSTALL)
|
|
set(ONE_VALUE_KEYWORDS
|
|
# Install this executable in ${CMAKE_INSTALL_LIBEXECDIR}/${LIBEXEC},
|
|
# instand of ${CMAKE_INSTALL_BINDIR}.
|
|
LIBEXEC # string
|
|
# The output file name of this executable.
|
|
# [ default: All the PFL hierarchies joined with "__" ]
|
|
# NOTE: It's not recommend to use the default value.
|
|
OUTPUT_NAME # string
|
|
)
|
|
set(MULTI_VALUE_KEYWORDS
|
|
# List of relative file paths from ${CMAKE_CURRENT_SOURCE_DIR},
|
|
# including input files (.in), sources (.c .cpp .cxx .cc)
|
|
# and headers (.h .hpp .hh).
|
|
# Files without extension treated as headers also.
|
|
# Input files will be configured with @ONLY mode of configure_file.
|
|
# And the output is added to library as sources as well.
|
|
# [ default: All files under ${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
# and ${CMAKE_CURRENT_SOURCE_DIR}/include ]
|
|
SOURCES
|
|
# Arguments pass to find_package() before build your executable.
|
|
DEPENDENCIES
|
|
# Arguments passed to target_link_libraries().
|
|
LINK_LIBRARIES
|
|
# Arguments passed to target_compile_features().
|
|
COMPILE_FEATURES
|
|
# Arguments passed to target_compile_options().
|
|
COMPILE_OPTIONS)
|
|
_pfl_parse_arguments(${ARGN})
|
|
_pfl_current_compoent()
|
|
|
|
set(OLD_PFL_PATH ${_PFL_PATH})
|
|
list(APPEND _PFL_PATH ${PFL_CURRENT_COMPOENT})
|
|
|
|
_pfl_str_join(TARGET "__" ${_PFL_PATH})
|
|
_pfl_str_join(TARGET_ALIAS "::" ${_PFL_PATH})
|
|
|
|
if(NOT DEFINED PFL_ARG_OUTPUT_NAME)
|
|
set(PFL_ARG_OUTPUT_NAME ${PFL_CURRENT_COMPOENT})
|
|
|
|
_pfl_warn("OUTPUT_NAME of ${TARGET_ALIAS} not set,"
|
|
"using ${PFL_ARG_OUTPUT_NAME}")
|
|
endif()
|
|
|
|
_pfl_info("Adding executable ${PFL_ARG_OUTPUT_NAME}"
|
|
"as ${TARGET_ALIAS} at ${CMAKE_CURRENT_SOURCE_DIR}")
|
|
|
|
add_executable(${TARGET})
|
|
target_sources(${TARGET} PRIVATE ${PFL_ARG_SOURCES})
|
|
|
|
if(NOT "${TARGET_ALIAS}" STREQUAL "${TARGET}")
|
|
add_executable("${TARGET_ALIAS}" ALIAS "${TARGET}")
|
|
endif()
|
|
|
|
set_target_properties("${TARGET}" PROPERTIES OUTPUT_NAME
|
|
${PFL_ARG_OUTPUT_NAME})
|
|
|
|
target_include_directories(
|
|
${TARGET}
|
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
${CMAKE_CURRENT_BINARY_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
${CMAKE_CURRENT_BINARY_DIR}/src)
|
|
|
|
_pfl_add_target_common()
|
|
|
|
if(PFL_ARG_DISABLE_INSTALL OR NOT ${PROJECT_NAME}_ENABLE_INSTALL)
|
|
return()
|
|
endif()
|
|
|
|
include(GNUInstallDirs)
|
|
|
|
if(DEFINED PFL_ARG_LIBEXEC)
|
|
install(TARGETS ${TARGET}
|
|
DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/${PFL_ARG_LIBEXEC})
|
|
return()
|
|
endif()
|
|
|
|
install(TARGETS ${TARGET} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|
endfunction()
|