#[[
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()
