# zhengrr
# 2016-10-08 – 2021-09-23
# Unlicense

#[=======================================================================[.rst:
rrAddLibrary
------------

提供若干命令，以便利添加库构建目标。
#]=======================================================================]

cmake_minimum_required(VERSION 3.17)
cmake_policy(VERSION 3.17)

include_guard()  # 3.10

if(NOT COMMAND rr_check_cmake_name)
  include("${CMAKE_CURRENT_LIST_DIR}/rrCheck.cmake")
endif()
if(NOT COMMAND rr_get_aam_tag OR
   NOT COMMAND rr_get_toolset_tag)
  include("${CMAKE_CURRENT_LIST_DIR}/rrTag.cmake")
endif()
if(NOT COMMAND rr_set_target OR
   NOT COMMAND rr_set_target_helper_collect_arguments OR
   NOT COMMAND rr_set_target_helper_parse_arguments)
  include("${CMAKE_CURRENT_LIST_DIR}/rrSetTarget.cmake")
endif()


#[=======================================================================[.rst:
.. command:: rr_add_library

  添加库构建目标。

  该命令将：

  - `add_library`
  - `set_target_properties`
  - `target_compile_definitions`
  - `target_compile_features`
  - `target_compile_options`
  - `target_include_directories`
  - `target_link_directories`
  - `target_link_libraries`
  - `target_link_options`
  - `target_precompile_headers`
  - `target_sources`

  等 11 条命令整合起来，以便于书写。

  .. code-block:: cmake

    rr_add_library(
      <name> <argument-of-"add_library"-command>...
      [PROPERTIES          {<property-key> <property-value>}...]
      [COMPILE_DEFINITIONS {{INTERFACE|PUBLIC|PRIVATE} <definition>...}...]
      [COMPILE_FEATURES    {{INTERFACE|PUBLIC|PRIVATE} <feature>...}...]
      [COMPILE_OPTIONS     {{INTERFACE|PUBLIC|PRIVATE} <option>...}...]
      [INCLUDE_DIRECTORIES {{INTERFACE|PUBLIC|PRIVATE} <directory>...}...]
      [LINK_DIRECTORIES    {{INTERFACE|PUBLIC|PRIVATE} <directory>...}...]
      [LINK_LIBRARIES      {{INTERFACE|PUBLIC|PRIVATE} <library>...}...]
      [LINK_OPTIONS        {{INTERFACE|PUBLIC|PRIVATE} <option>...}...]
      [PRECOMPILE_HEADERS  {{INTERFACE|PUBLIC|PRIVATE} <header>...}...]
      [SOURCES             {{INTERFACE|PUBLIC|PRIVATE} <source>...}...])

  参见：

  - :command:`rr_set_target`
  - `add_library <https://cmake.org/cmake/help/latest/command/add_library.html>`_
#]=======================================================================]
function(rr_add_library sName)
  rr_set_target_helper_parse_arguments(zArgumentsOfAddLibrary ${ARGN})

  rr_check_cmake_name("${sName}" AUTHOR_WARNING)

  add_library("${sName}" ${zArgumentsOfAddLibrary})
  rr_set_target_helper_collect_arguments(zRrSetTargetRestArgs)
  rr_set_target("${sName}" ${zRrSetTargetRestArgs})
endfunction()


#[=======================================================================[.rst:
.. command:: rr_add_library_wcon

  依据惯例，添加库构建目标。

  注意，该命令所指的“惯例”以本人（作者）便利为准，随时可能改变，不应用于生产环境。

  该命令接受与 :command:`rr_add_library` 命令类似的参数，并依据惯例进行更多配置（``wcon``，with convention）：

  - 提供构建开关，以供精确开关目标构建，默认勾选；
  - 依据 Boost 命名规则，为生成的二进制文件附加前缀和后缀；
  - 配置目标的安装目录。

  .. code-block:: cmake

    rr_add_library_wcon(
      <name> <argument-of-"add_library"-command>...
      [NO_OPTION]          [OPTION_NAME    <option-name>]
                           [OPTION_HELP    <help-string>]
                           [OPTION_INITIAL {ON|OFF}]
      [NO_BOOST_NAMING]
      [NO_INSTALL]
      [PROPERTIES          {<property-key> <property-value>}...]
      [COMPILE_DEFINITIONS {{INTERFACE|PUBLIC|PRIVATE} <definition>...}...]
      [COMPILE_FEATURES    {{INTERFACE|PUBLIC|PRIVATE} <feature>...}...]
      [COMPILE_OPTIONS     {{INTERFACE|PUBLIC|PRIVATE} <option>...}...]
      [INCLUDE_DIRECTORIES {{INTERFACE|PUBLIC|PRIVATE} <directory>...}...]
      [LINK_DIRECTORIES    {{INTERFACE|PUBLIC|PRIVATE} <directory>...}...]
      [LINK_LIBRARIES      {{INTERFACE|PUBLIC|PRIVATE} <library>...}...]
      [LINK_OPTIONS        {{INTERFACE|PUBLIC|PRIVATE} <option>...}...]
      [PRECOMPILE_HEADERS  {{INTERFACE|PUBLIC|PRIVATE} <header>...}...]
      [SOURCES             {{INTERFACE|PUBLIC|PRIVATE} <source>...}...])

  参见：

  - :command:`rr_add_library`
  - `option <https://cmake.org/cmake/help/latest/command/option.html>`_
  - `install <https://cmake.org/cmake/help/latest/command/install.html>`_
#]=======================================================================]
function(rr_add_library_wcon sName)
  rr_set_target_helper_parse_arguments(zRestArgn ${ARGN})
  set(zOptKws    NO_OPTION
                 NO_BOOST_NAMING
                 NO_INSTALL)
  set(zOneValKws OPTION_NAME
                 OPTION_HELP
                 OPTION_INITIAL)
  set(zMutValKws)
  cmake_parse_arguments("ARG" "${zOptKws}" "${zOneValKws}" "${zMutValKws}" ${zRestArgn})

  rr_check_cmake_name("${sName}" AUTHOR_WARNING)

  if(MODULE IN_LIST ARG_UNPARSED_ARGUMENTS)
    set(sType "MODULE")
  elseif(SHARED IN_LIST ARG_UNPARSED_ARGUMENTS)
    set(sType "SHARED")
  elseif(STATIC IN_LIST ARG_UNPARSED_ARGUMENTS)
    set(sType "STATIC")
  elseif(BUILD_SHARED_LIBS)
    set(sType "SHARED")
  else()
    set(sType "STATIC")
  endif()
  string(TOUPPER "${sType}" sTypeUpper)
  string(TOLOWER "${sType}" sTypeLower)

  # 构建开关
  if(NOT ARG_NO_OPTION)
    if(DEFINED ARG_OPTION_NAME)
      set(xOptVar "${ARG_OPTION_NAME}")
    else()
      string(TOUPPER "${sName}" sNameUpper)
      string(TOUPPER "${PROJECT_NAME}" sProjectNameUpper)
      string(REGEX REPLACE "^${sProjectNameUpper}_?" "" sTrimmedNameUpper "${sNameUpper}")
      string(LENGTH "${sTrimmedNameUpper}" nLen)
      if(0 LESS nLen)
        set(xOptVar ${sProjectNameUpper}_${sTrimmedNameUpper}_${sTypeUpper}_LIBRARY)
      else()
        set(xOptVar ${sProjectNameUpper}_${sTypeUpper}_LIBRARY)
      endif()
    endif()

    if(DEFINED ARG_OPTION_HELP)
      set(sOptTxt "${ARG_OPTION_HELP}")
    else()
      set(sOptTxt "Build ${sName} ${sTypeLower} library.")
    endif()

    if(DEFINED ARG_OPTION_INITIAL)
      set(bOptVal ${ARG_OPTION_INITIAL})
    else()
      set(bOptVal ON)
    endif()

    option("${xOptVar}" "${sOptTxt}" "${bOptVal}")
    if(NOT "${${xOptVar}}")
      return()
    endif()
  endif()

  # Boost 命令规则
  if(NOT ARG_NO_BOOST_NAMING)
    if(NOT OUTPUT_NAME IN_LIST zProperties)
      if(UNIX OR WIN32 AND sType STREQUAL "STATIC")
        set(sPrefix "lib")
      else()
        set(sPrefix)
      endif()
      rr_get_boost_style_tag(gPostfix)
      list(APPEND zProperties OUTPUT_NAME "${sPrefix}${sName}${gPostfix}")
    endif()
  endif()

  # 添加库构建目标
  add_library("${sName}" ${ARG_UNPARSED_ARGUMENTS})
  rr_set_target_helper_collect_arguments(zRrSetTargetRestArgs)
  rr_set_target("${sName}" ${zRrSetTargetRestArgs})

  # 安装目录
  if(NOT ARG_NO_INSTALL)
    install(
      TARGETS             "${sName}"
      ARCHIVE DESTINATION "lib/"
      LIBRARY DESTINATION "lib/"
      RUNTIME DESTINATION "bin/")
  endif()
endfunction()


#[=======================================================================[.rst:
.. command:: rr_add_library_wconswig

  类似 ``rr_add_library_wcon`` 命令，并引入 SWIG 支持（wconswig，with convention & swig）。

  .. code-block:: cmake

    rr_add_library_wconswig(
      <name> <argument-of-"add_library"-command>...
      {SWIG_LANGUAGE       {CSHARP|D|GO|GUILE|JAVA|JAVASCRIPT|LUA|OCTAVE|PERL5|PHP7|PYTHON|R|RUBY|SCILAB|TCL8|XML}}
      {SWIG_INTERFACE      <path-to-interface.swg>}
      [SWIG_OUTPUT_DIR     <path-to-output-diractory>]
      [SWIG_ARGUMENTS      <arguments>...]
      [PROPERTIES          {<property-key> <property-value>}...]
      [COMPILE_DEFINITIONS {{INTERFACE|PUBLIC|PRIVATE} <definition>...}...]
      [COMPILE_FEATURES    {{INTERFACE|PUBLIC|PRIVATE} <feature>...}...]
      [COMPILE_OPTIONS     {{INTERFACE|PUBLIC|PRIVATE} <option>...}...]
      [INCLUDE_DIRECTORIES {{INTERFACE|PUBLIC|PRIVATE} <directory>...}...]
      [LINK_DIRECTORIES    {{INTERFACE|PUBLIC|PRIVATE} <directory>...}...]
      [LINK_LIBRARIES      {{INTERFACE|PUBLIC|PRIVATE} <library>...}...]
      [LINK_OPTIONS        {{INTERFACE|PUBLIC|PRIVATE} <option>...}...]
      [SOURCES             {{INTERFACE|PUBLIC|PRIVATE} <source>...}...])

  参见：

  - :command:`rr_add_library_wcon`
#]=======================================================================]
function(rr_add_library_wconswig sName)
  rr_set_target_helper_parse_arguments(zRestArgn ${ARGN})
  set(zOptKws    )
  set(zOneValKws SWIG_INTERFACE
                 SWIG_LANGUAGE
                 SWIG_OUTPUT_DIR)
  set(zMutValKws SWIG_ARGUMENTS)
  cmake_parse_arguments("" "${zOptKws}" "${zOneValKws}" "${zMutValKws}" ${zRestArgn})

  # <name>
  # -> sName
  rr_check_cmake_name("${sName}" AUTHOR_WARNING)

  # <argument-of-"add_library"-command>...
  # -> zArgumentsOfAddLibraryCommand
  set(zArgumentsOfAddLibraryCommand ${_UNPARSED_ARGUMENTS})

  # SWIG_LANGUAGE
  # -> sSwigLanguage
  # -> sSwigLanguageLower
  if(NOT DEFINED _SWIG_LANGUAGE)
    message(FATAL_ERROR "Missing SWIG_LANGUAGE argument.")
  endif()
  set(sSwigLanguage "${_SWIG_LANGUAGE}")
  string(TOLOWER "${sSwigLanguage}" sSwigLanguageLower)

  # SWIG_INTERFACE
  # -> pSwigInterface
  # -> sSwigInterfaceWle
  if(NOT DEFINED _SWIG_INTERFACE)
    message(FATAL_ERROR "Missing SWIG_INTERFACE argument.")
  endif()
  set(pSwigInterface "${_SWIG_INTERFACE}")
  if(NOT IS_ABSOLUTE "${pSwigInterface}")
    set(pSwigInterface "${CMAKE_CURRENT_SOURCE_DIR}/${pSwigInterface}")
  endif()
  if(NOT EXISTS "${pSwigInterface}")
    message(FATAL_ERROR "The SWIG interface file isn't exists: ${_SWIG_INTERFACE}.")
  endif()
  get_filename_component(sSwigInterfaceWle "${pSwigInterface}" NAME_WLE)

  # SWIG_OUTPUT_DIR
  # -> pSwigOutputDir
  if(DEFINED _SWIG_OUTPUT_DIR)
    set(pSwigOutputDir "${_SWIG_OUTPUT_DIR}")
  else()
    file(RELATIVE_PATH pRelPath "${CMAKE_CURRENT_SOURCE_DIR}" "${pSwigInterface}")
    set(pSwigOutputDir "${CMAKE_CURRENT_BINARY_DIR}/${pRelPath}/${sSwigLanguageLower}")
    file(MAKE_DIRECTORY "${pSwigOutputDir}")
  endif()
  if(NOT IS_ABSOLUTE "${pSwigOutputDir}")
    set(pSwigOutputDir "${CMAKE_CURRENT_BINARY_DIR}/${pSwigOutputDir}")
  endif()
  if(NOT IS_DIRECTORY "${pSwigOutputDir}")
    message(FATAL_ERROR "The SWIG output directory isn't a directory: ${_SWIG_OUTPUT_DIR}.")
  endif()

  # SWIG_ARGUMENTS
  # -> zSwigArguments
  set(zSwigArguments ${_SWIG_ARGUMENTS})

  # PROPERTIES          -> zProperties
  # COMPILE_DEFINITIONS -> zCompileDefinitions
  # COMPILE_FEATURES    -> zCompileFeatures
  # COMPILE_OPTIONS     -> zCompileOptions
  # INCLUDE_DIRECTORIES -> zIncludeDirectories
  # LINK_DIRECTORIES    -> zLinkDirectories
  # LINK_LIBRARIES      -> zLinkLibraries
  # LINK_OPTIONS        -> zLinkOptions
  # SOURCES             -> zSources
  rr_set_target_helper_setargalias("")

  # 引入 SWIG 支持：
  # 在 CMake 配置时生成，并为目标加入前置构建，在每次编译前自动重新生成

  find_package(SWIG REQUIRED)  # CMP0074 3.12

  # C#
  if(sSwigLanguage STREQUAL CSHARP)
    if(NOT "-dllimport" IN_LIST zSwigArguments)
      list(APPEND zSwigArguments "-dllimport" "${sName}$<$<CONFIG:Debug>:d>")
    endif()
  endif()

  # Go
  if(sSwigLanguage STREQUAL GO)
    if(NOT "-intgosize" IN_LIST zSwigArguments)
      math(EXPR nIntGoZize "8 * ${CMAKE_SIZEOF_VOID_P}")
      list(APPEND zSwigArguments "-intgosize" "${nIntGoZize}")
    endif()
  endif()

  # Java
  if(sSwigLanguage STREQUAL JAVA)
    find_package(JNI)
    if(JNI_FOUND)
      list(APPEND zIncludeDirectories PRIVATE "${JAVA_INCLUDE_PATH}" "${JAVA_INCLUDE_PATH2}")
    endif()
  endif()

  # JavaScript
  if(sSwigLanguage STREQUAL JAVASCRIPT)
    if(NOT "-node" IN_LIST zSwigArguments)
      list(APPEND zSwigArguments "-node")
    endif()
  endif()

  # 配置时生成
  set(pSwigCxxWrap "${pSwigOutputDir}/${sSwigInterfaceWle}_wrap.cxx")
  execute_process(
    COMMAND "${SWIG_EXECUTABLE}"
            "-c++"                   "-o"      "${pSwigCxxWrap}"
            "-${sSwigLanguageLower}" "-outdir" "${pSwigOutputDir}"
            ${zSwigArguments}
            "${pSwigInterface}"
    RESULTS_VARIABLE nResultCode)
  if(NOT nResultCode EQUAL 0)
    message(FATAL_ERROR "Gennerate SWIG ${sSwigLanguage} files failed: ${nResultCode}")
  endif()

  # INCLUDE_DIRECTORIES
  list(APPEND zIncludeDirectories PRIVATE "${pSwigOutputDir}")

  # SOURCES
  list(APPEND zSources PRIVATE "${pSwigInterface}" "${pSwigCxxWrap}")
  source_group("generated" FILES "${pSwigCxxWrap}")

  rr_set_target_helper_collect_arguments(zRrSetTargetRestArgs)
  rr_add_library_wcon("${sName}" ${zArgumentsOfAddLibraryCommand} ${zRrSetTargetRestArgs})

  # 构建前生成
  add_custom_command(
    TARGET "${sName}" PRE_BUILD
    COMMAND "${SWIG_EXECUTABLE}"
            "-c++"                   "-o"      "${pSwigCxxWrap}"
            "-${sSwigLanguageLower}" "-outdir" "${pSwigOutputDir}"
            ${zSwigArguments}
            "${pSwigInterface}")

  rr_post_build_copy_link_library_files("${sName}" INCLUDE_ITSELF RECURSE DESTINATION "${pSwigOutputDir}")
endfunction()
