﻿#[[
文件名: CMakeFindExternalProject.cmake
用于定位一个外部项目
若外部项目不存在则构建该项目
]]

# 保存 CMAKE_CURRENT_LIST_DIR
# 因为 CMake 函数是动态作用域的
set(CMakeFindExternalProject_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "CMAKE_CURRENT_LIST_DIR" FORCE)

macro(_cfep_build_found_inline _found)
    set(${name}_CFEP_FOUND ${_found} CACHE INTERNAL "${name} found by CMakeFindExternalProject" FORCE)
    if (${name}_CFEP_FOUND)
        set(${name}_CFEP_BUILD_DIR ${_build_dir} CACHE INTERNAL "${name} install dirs" FORCE)
        set(${name}_CFEP_INSTALL ${_install_dir} CACHE INTERNAL "${name} install dirs" FORCE)
        set(${name}_CFEP_INSTALL_TYPE ${cfp_INSTALL_DIR} CACHE INTERNAL "${name} install dirs type" FORCE)
    else()
        set(${name}_CFEP_BUILD_DIR "" CACHE INTERNAL "${name} install dirs" FORCE)
        set(${name}_CFEP_INSTALL "" CACHE INTERNAL "${name} install dirs" FORCE)
        set(${name}_CFEP_INSTALL_TYPE "" CACHE INTERNAL "${name} install dirs type" FORCE)
    endif()
endmacro()

function(_cfep_build_inline name)
    set(options NOT_INFO FORCE)
    set(oneValueArgs
        BUILD_DIR
        SOURCE_DIR  # 指定源文件的位置, 指定后不可再设定DOWNLOAD_COMMAND
        INSTALL_DIR  # 有三个选择: base, deps, binary
        TIMEOUT)
    set(multiValueArgs
        DOWNLOAD_COMMAND
        CMAKE_ARGS
        BUILD_CMAKE_ARGS
        BUILD_CMAKE_CACHE_ARGS
        BUILD_CMAKE_CACHE_DEFAULT_ARGS)

    cmake_parse_arguments(cfp
                          "${options}"
                          "${oneValueArgs}"
                          "${multiValueArgs}"
                          ${ARGN})
    if (NOT cfp_BUILD_DIR)
        set(_build_dir "${CMAKE_BINARY_DIR}/deps/${name}")
    else()
        set(_build_dir "${CMAKE_BINARY_DIR}/deps/${cfp_BUILD_DIR}")
    endif()

    set(_force ${cfp_FORCE})
    if (cfp_NOT_INFO)
        set(_info false)
    else()
        set(_info true)
    endif()

    set(_cmake_dir ${_build_dir}/cmake.dir)
    set(_binary_dir ${_build_dir}/binary)
    set(_timeout ${cfp_TIMEOUT})

    if (cfp_SOURCE_DIR)
        if(EXISTS ${cfp_SOURCE_DIR})  # 若指定的SOURCE_DIR存在
            if (cfp_DOWNLOAD_COMMAND)  # 若仍有DOWNLOAD_COMMAND
                if (_info)
                    message(WARNING "DOWNLOAD_COMMAND will be ignore.")
                endif()
                unset(cfp_DOWNLOAD_COMMAND)  # 清空cfp_DOWNLOAD_COMMAND
            endif()
            set(_source_dir ${cfp_SOURCE_DIR})
        else()  # 若指定的SOURCE_DIR不存在
            if (_info)
                message(WARNING "SOURCE_DIR will be ignore.")
            endif()
            set(_source_dir ${_build_dir}/source)
        endif()
    else()  # 若没有SOURCE_DIR则按默认_source_dir
        set(_source_dir ${_build_dir}/source)
    endif()

    if (cfp_INSTALL_DIR STREQUAL "base")
        set(_install_dir ${CMAKE_BINARY_DIR}/${name})
    elseif(cfp_INSTALL_DIR STREQUAL "binary")
        set(_install_dir ${CMAKE_BINARY_DIR}/${name})
    else()
        set(_install_dir ${_build_dir}/install)
    endif()

    if (_info)
        message(STATUS "Project ${name} will be build.")
        message(STATUS "Project ${name} source at ${_source_dir}")
        message(STATUS "Project ${name} install to ${_install_dir}")
        message(STATUS "Project ${name} build at ${_binary_dir}")
        message(STATUS "Project ${name} other info at ${_build_dir}")
    endif()

    if(NOT _force)
        if ((${_name}_CFEP_FOUND) AND (EXISTS ${_cmake_dir}))  # 若 _cmake_dir 被删除则重新构建项目
            return()
        endif()
    endif()

    foreach(dir cmake install source binary install)  # 构建目录
        execute_process(
                COMMAND "${CMAKE_COMMAND}" -E make_directory "${_${dir}_dir}"
                RESULT_VARIABLE re)
        if (re)
            if (_info)
                message(WARNING "cmake make directory ${_${dir}_dir} fail(${re}): ${_${dir}_dir}")
            endif()
            _cfep_build_found_inline(FALSE)
            return()
        endif()

        unset(re)
        unset(_stderr)
        unset(_stdout)
    endforeach()

    set(_download ${cfp_DOWNLOAD_COMMAND})

    set(_cmake_args ${cfp_CMAKE_ARGS})
    set(_build_cmake_args
        ${cfp_BUILD_CMAKE_ARGS}
        "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")

    set(_build_cmake_cache
        ${cfp_BUILD_CMAKE_CACHE_ARGS}
        \"-DCMAKE_INSTALL_PREFIX:PATH=${_install_dir}\")  # ${_install_dir}不需要分号
    set(_build_cmake_default_cache ${BUILD_CMAKE_CACHE_DEFAULT_ARGS})

    set(CEFP_CMAKE_VERSION ${CMAKE_VERSION})
    set(CFEP_NAME ${name})
    set(SHOW_INFO ${_info})

    set(CEFP_BUILD_PREFIX ${_build_dir})
    set(CEFP_SOURCE_DIR ${_source_dir})
    set(CEFP_BINARY_DIR ${_binary_dir})
    set(CEFP_INSTALL_DIR ${_install_dir})

    set(_CEFP_COMMAND
        TIMEOUT ${_timeout}

        ${_download}

        CMAKE_COMMAND \"${CMAKE_COMMAND}\"  # "需要转义, 作为字符串的一部分内容
        CMAKE_GENERATOR \"${CMAKE_GENERATOR}\"
        CMAKE_GENERATOR_PLATFORM \"${CMAKE_GENERATOR_PLATFORM}\"
        CMAKE_GENERATOR_TOOLSET \"${CMAKE_GENERATOR_TOOLSET}\"
        CMAKE_GENERATOR_INSTANCE \"${CMAKE_GENERATOR_INSTANCE}\"

        CMAKE_ARGS ${_build_cmake_args}
        CMAKE_CACHE_ARGS ${_build_cmake_cache}
        CMAKE_CACHE_DEFAULT_ARGS ${_build_cmake_default_cache}
        )

    string(REPLACE ";" " \n" CEFP_COMMAND "${_CEFP_COMMAND}")  # 把;替换为空格
    unset(_CEFP_COMMAND)

    set(config_file TRUE)  # 生成文件
    if ((NOT _force) AND (EXISTS ${_cmake_dir}/CMakeLists.txt))  # 非强制模式, 文件已存在
        if (CFEP_${_cmake_dir}_CMAKE_FILE)  # CFEP_${_cmake_dir}_CMAKE_FILE记录文件的md5值
            file(MD5 ${_cmake_dir}/CMakeLists.txt _md5)  # 计算md5
            if (_md5 STREQUAL CFEP_${_cmake_dir}_CMAKE_FILE)
                set(config_file FALSE)  # 不需要构建文件
            endif()
            unset(_md5)
        endif()
    endif()

    if (config_file)
        configure_file(${CMakeFindExternalProject_DIR}/CMakeLists.txt.in
                       ${_cmake_dir}/CMakeLists.txt
                       @ONLY)
        file(MD5 ${_cmake_dir}/CMakeLists.txt _md5)  # 计算md5
        set(CFEP_${_cmake_dir}_CMAKE_FILE ${_md5} CACHE INTERNAL "md5" FORCE)
        unset(_md5)
    endif()

    unset(CEFP_CMAKE_VERSION)
    unset(CFEP_NAME)
    unset(SHOW_INFO)

    unset(CEFP_BUILD_PREFIX)
    unset(CEFP_SOURCE_DIR)
    unset(CEFP_BINARY_DIR)
    unset(CEFP_INSTALL_DIR)

    unset(CEFP_COMMAND)

    # 定义项目
    if (_info)
        message(STATUS "CMake Config ${name}...(Please Wait)")
    endif()
    execute_process(
                    COMMAND "${CMAKE_COMMAND}" . -B ./build -G ${CMAKE_GENERATOR} ${_cmake_args}
                    WORKING_DIRECTORY "${_cmake_dir}"
                    RESULT_VARIABLE re
                    OUTPUT_VARIABLE _stdout  # stdout的输出内容
                    ERROR_VARIABLE _stderr  # stderr的输出内容
                    OUTPUT_STRIP_TRAILING_WHITESPACE
                    ERROR_STRIP_TRAILING_WHITESPACE
    )

    if(re)
        _cfep_build_found_inline(FALSE)
        if (_info)
            message(WARNING "CMake config ${name} fail(${re}): \n${_stderr}")
        endif()
        return()
    endif()

    unset(re)
    unset(_stderr)
    unset(_stdout)

    # 构建项目
    if (_info)
        message(STATUS "CMake Build ${name}...(Please Wait)")
    endif()
    execute_process(
                    COMMAND "${CMAKE_COMMAND}" --build .
                    WORKING_DIRECTORY "${_cmake_dir}/build"
                    RESULT_VARIABLE re
                    OUTPUT_VARIABLE _stdout  # stdout的输出内容
                    ERROR_VARIABLE _stderr  # stderr的输出内容
                    OUTPUT_STRIP_TRAILING_WHITESPACE
                    ERROR_STRIP_TRAILING_WHITESPACE
    )

    if(re)
        _cfep_build_found_inline(FALSE)
        if (_info)
            message(WARNING "CMake build ${name} fail(${re}): \n${_stderr}")
        endif()
        return()
    endif()

    unset(re)
    unset(_stderr)
    unset(_stdout)
    _cfep_build_found_inline(TRUE)
endfunction()

function(_cfep_check_build_inline name var)
    set(options)
    set(oneValueArgs
        INSTALL_DIR  # 有三个选择: base, deps, binary
        BUILD_DIR)

    cmake_parse_arguments(cfp "" "${oneValueArgs}" "" ${ARGN})
    if (NOT cfp_BUILD_DIR)
        set(_build_dir "${CMAKE_BINARY_DIR}/deps/${name}")
    else()
        set(_build_dir "${CMAKE_BINARY_DIR}/deps/${cfp_BUILD_DIR}")
    endif()

    if (cfp_INSTALL_DIR STREQUAL "base")
        set(_install_dir ${CMAKE_BINARY_DIR}/${name})
    elseif(cfp_INSTALL_DIR STREQUAL "binary")
        set(_install_dir ${CMAKE_BINARY_DIR}/${name})
    else()
        set(_install_dir ${_build_dir}/install)
    endif()

    if (EXISTS ${_install_dir})
        set(${var} ${_install_dir} PARENT_SCOPE)
        _cfep_build_found_inline(TRUE)
    else()
        set(${var} ${var}-NOFOUND PARENT_SCOPE)
        _cfep_build_found_inline(FALSE)
    endif()
endfunction()

# 从url下载程序
function(_cfep_build_url_inline name dir git git_tag url)
    _cfep_build_inline(${name} DOWNLOAD_COMMAND URL ${url} ${ARGN})
    if (${name}_CFEP_FOUND)
        set(${name}_BUILD TRUE PARENT_SCOPE)
    else()
        set(${name}_BUILD FALSE PARENT_SCOPE)
    endif()
endfunction()

# 从git下载程序
function(_cfep_build_git_inline name dir git git_tag url)
    _cfep_build_inline(${name}
                 DOWNLOAD_COMMAND
                 GIT_REPOSITORY ${git}
                 GIT_TAG ${git_tag}
                 GIT_PROGRESS 0
                 GIT_REMOTE_UPDATE_STRATEGY REBASE_CHECKOUT
                 ${ARGN})
    if (${name}_CFEP_FOUND)
        set(${name}_BUILD TRUE PARENT_SCOPE)
    else()
        set(${name}_BUILD FALSE PARENT_SCOPE)
    endif()
endfunction()

# 指定源文件下载程序
function(_cfep_build_dir_inline name dir git git_tag url)
    _cfep_build_inline(${name} SOURCE_DIR ${dir} ${ARGN})
    if (${name}_CFEP_FOUND)
        set(${name}_BUILD TRUE PARENT_SCOPE)
    else()
        set(${name}_BUILD FALSE PARENT_SCOPE)
    endif()
endfunction()

macro(_cfep_found_inline _found)
    set(__found ${_found})
    if (NOT ${__found})
        set(${_name}_FOUND ${_name}-NOTFOUND)
        if (_required)
            message(FATAL_ERROR "Package not found: ${_name}")
        endif()
        if (NOT _quiet)
            message(WARNING "Package not found: ${_name}")
        endif()
    endif()
    unset(__found)
endmacro()

macro(_cfep_first_find_inline name _cmake)
    if (NOT ${name}_MUST_BUILD)  # _MUST_BUILD则先不构建, 而是寻找该库
        find_package(${name} QUIET ${_find_args})  # 尝试搜索
    else()
        set(${name}_MUST_BUILD FALSE CACHE BOOL "" FORCE)
    endif()
    if (NOT ${name}_FOUND)
        _cfep_check_build_inline(${name} re ${_external_args})
        if (re)
            if (_cmake)
                set(${name}_DIR "${re}/${_cmake}" CACHE PATH "" FORCE)
            elseif(WIN32 OR CYGWIN)
                set(${name}_DIR "${re}/cmake" CACHE PATH "" FORCE)
            else()
                set(${name}_DIR "${re}/share/cmake/${name}" CACHE PATH "" FORCE)
            endif()
            find_package(${name} QUIET ${_find_args})  # 尝试搜索
        endif()
    endif()
endmacro()

macro(_cfep_find_xxx_inline name func)
    while (1)  # 宏无法处理return, 所以使用while+break来模拟return
        set(options REQUIRED QUIET MODULE)
        set(oneValueArgs SOURCE_DIR URL GIT GIT_TAG CMAKE_DIR)
        set(multiValueArgs
            PACKAGE
            EXTERNAL)

        cmake_parse_arguments(cfpu
                              "${options}"
                              "${oneValueArgs}"
                              "${multiValueArgs}"
                              ${ARGN})

        set(_name ${name})
        set(_func ${func})

        set(_dir ${cfpu_SOURCE_DIR})
        set(_git ${cfpu_GIT})
        set(_git_tag ${cfpu_GIT_TAG})
        set(_url ${cfpu_URL})

        set(_cmake ${cfpu_CMAKE_DIR})
        set(_find_args ${cfpu_PACKAGE})
        set(_external_args ${cfpu_EXTERNAL})
        set(_required ${cfpu_REQUIRED})
        set(_quiet ${cfpu_QUIET})
        set(_module ${cfpu_MODULE})

        set(_CMAKE_MODULE_PATH_bak ${CMAKE_MODULE_PATH})
        if (_module)
            list(APPEND CMAKE_MODULE_PATH ${_cmake})  # 此时 _cmake 必须是绝对路径
        endif()

        _cfep_first_find_inline("${name}" "${_cmake}")
        if (${name}_FOUND)
            _cfep_found_inline(TRUE)
            break()
        endif()

        cmake_language(CALL "${_func}" "${_name}" "${_dir}" "${_git}" "${_git_tag}" "${_url}" "${_external_args}")
        if (NOT ${name}_BUILD)  # 未构建
            _cfep_found_inline(FALSE)
            break()
        endif()

        set(${name}_ROOT "${${name}_CFEP_INSTALL}" CACHE PATH "" FORCE)
        if (NOT _module)
            if (_cmake)
                set(${name}_DIR "${${name}_CFEP_INSTALL}/${_cmake}" CACHE PATH "" FORCE)
            elseif(WIN32 OR CYGWIN)
                set(${name}_DIR "${${name}_CFEP_INSTALL}/cmake" CACHE PATH "" FORCE)
            else()
                set(${name}_DIR "${${name}_CFEP_INSTALL}/share/cmake/${name}" CACHE PATH "" FORCE)
            endif()
            list(APPEND CMAKE_MODULE_PATH ${${name}_DIR})  #  追加到MODULE_PATH中应对Module模式
        endif()

        find_package(${name} QUIET ${_find_args})  # 最后搜索
        if (${name}_FOUND)
            _cfep_found_inline(TRUE)
            break()
        endif()
        _cfep_found_inline(FALSE)
        break()
    endwhile()

    unset(_name)

    unset(_dir)
    unset(_git)
    unset(_git_tag)
    unset(_url)
    unset(_cmake)
    unset(_find_args)
    unset(_external_args)
    unset(_required)
    unset(_quiet)

    unset(cfpu_SOURCE_DIR)
    unset(cfpu_CMAKE_DIR)
    unset(cfpu_PACKAGE)
    unset(cfpu_EXTERNAL)
    unset(cfpu_REQUIRED)
    unset(cfpu_QUIET)

    set(CMAKE_MODULE_PATH ${_CMAKE_MODULE_PATH_bak})
    unset(_CMAKE_MODULE_PATH_bak)
endmacro()


# 从url下载文件
macro(cfep_find_url name)
    _cfep_find_xxx_inline(${name} _cfep_build_url_inline ${ARGN})
endmacro()

# 从git下载文件
macro(cfep_find_git name)
    _cfep_find_xxx_inline(${name} _cfep_build_git_inline ${ARGN})
endmacro()

# 从dir下载文件
macro(cfep_find_dir name)
    _cfep_find_xxx_inline(${name} _cfep_build_dir_inline "${name}" ${ARGN})
endmacro()


# 安装程序
function(cfep_install name)
    cmake_parse_arguments(ci "NOT_QUIET" "PREFIX" "" ${ARGN})

    if (NOT ci_PREFIX)
        set(prefix ${CMAKE_INSTALL_PREFIX})
    else()
        set(prefix ${ci_PREFIX})
    endif()

    if (NOT ${name}_CFEP_FOUND)
        if (ci_NOT_QUIET)
            message(WARNING "Cannot install ${name}.")
        endif()
        set(${name}_CFEP_INSTALL_SUCCESS FALSE PARENT_SCOPE)
        return()
    endif()

    if (NOT prefix)
        set(prefix ${CMAKE_INSTALL_PREFIX})
    endif()

    install(DIRECTORY "${${name}_CFEP_INSTALL}/"  # /表示把${${name}_CFEP_INSTALL}的内容安装到${prefix}
            DESTINATION ${prefix}
            USE_SOURCE_PERMISSIONS)
    set(${name}_CFEP_INSTALL_SUCCESS TRUE PARENT_SCOPE)
endfunction()

function(cfep_copy_install name)
    cmake_parse_arguments(cci "NOT_QUIET" "DEST" "" ${ARGN})

    if (NOT cci_DEST)
        set(dest ${CMAKE_BINARY_DIR})
    else()
        set(dest ${cci_DEST})
    endif()

    if (NOT ${name}_CFEP_FOUND)
        if (cci_NOT_QUIET)
            message(WARNING "Cannot copy install ${name}.")
        endif()
        set(${name}_CFEP_COPY_SUCCESS FALSE PARENT_SCOPE)
        return()
    endif()

    execute_process(COMMAND "${CMAKE_COMMAND}" -E copy_directory ${${name}_CFEP_INSTALL} ${dest}
                    RESULT_VARIABLE re)
    if(re)
        if (cci_NOT_QUIET)
            message(WARNING "Cannot copy install ${name}.")
        endif()
        set(${name}_CFEP_COPY_SUCCESS TRUE PARENT_SCOPE)
    endif()
endfunction()