使用cmake管理项目的模板和参数说明

林西索 / 2024-09-26 / 原文

使用cmake管理项目的模板和参数说明

** 假定名为MyCmakeTest,目录结构如下

MyCmakeTest
├── cmake
│   ├── modules
│   │   ├── Findffmpeg.cmake
│   │   ├── FindGRPC.cmake
│   │   └── FindProtobuf.cmake
│   └── utils.cmake
├── CMakeLists.txt
├── external
│   ├── CMakeLists.txt
│   └── ffmpeg
│       ├── CMakeLists.txt
│       └── prebuild
│           ├── CMakeLists.txt
│           ├── include
│           └── lib
│               ├── android
│               │   ├── aarch64
│               │   └── armv7-a
│               └── linux
│                   ├── aarch64
│                   ├── armv7-a
│                   ├── x86
│                   └── x86_64
├── mylibs
│   ├── CMakeLists.txt
│   ├── liba
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   └── src
│   └── libb
│       ├── CMakeLists.txt
│       ├── include
│       └── src
└── tests
    └── CMakeLists.txt

每层目录下的CMakeLists.txt

1.顶层CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

# ---------------------------------------------------------------------------------------
# Start myproject project
# ---------------------------------------------------------------------------------------
set(TEST_VERSION "1.0.0")
project(myproject VERSION ${TEST_VERSION} LANGUAGES C CXX)
message(STATUS "Configuring ${PROJECT_NAME}, version:${TEST_VERSION}")

# ---------------------------------------------------------------------------------------
# Set default build to release
# ---------------------------------------------------------------------------------------
# set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build Debug" FORCE)
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif()
message(STATUS "CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}")

# ---------------------------------------------------------------------------------------
# Compiler config
# ---------------------------------------------------------------------------------------
if(NOT CMAKE_CXX_STANDARD)
    set(CMAKE_CXX_STANDARD 14)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()

# -fno-exceptions -fno-rtti
set(TEST_COMPILE_FLAGS_RELEASE "-O3 -DNDEBUG -flto -fdata-sections -ffunction-sections")
set(TEST_LINK_FLAGS_RELEASE "-O3 -flto -Wl,--gc-sections -Wl,-s -Wl,-Bsymbolic -Wl,--exclude-libs,ALL")
set(CMAKE_C_FLAGS_RELEASE ${TEST_COMPILE_FLAGS_RELEASE})
set(CMAKE_CXX_FLAGS_RELEASE ${TEST_COMPILE_FLAGS_RELEASE})
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE ${TEST_LINK_FLAGS_RELEASE})
#指定安装路径的前缀 如下设置后:安装后的文件将被放置在/usr/local/bin(可执行文件)、/usr/local/lib(库文件)、/usr/local/include(头文件)目录下
set(CMAKE_INSTALL_PREFIX /usr/local)

# message (STATUS "TEST_COMPILE_FLAGS_RELEASE:${TEST_COMPILE_FLAGS_RELEASE}")
# message (STATUS "TEST_LINK_FLAGS_RELEASE:${TEST_LINK_FLAGS_RELEASE}")
message (STATUS "CMAKE_C_FLAGS_RELEASE:${CMAKE_C_FLAGS_RELEASE}")
message (STATUS "CMAKE_CXX_FLAGS_RELEASE:${CMAKE_CXX_FLAGS_RELEASE}")
message (STATUS "CMAKE_SHARED_LINKER_FLAGS_RELEASE:${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")

message (STATUS "CMAKE_C_FLAGS_DEBUG:${CMAKE_C_FLAGS_DEBUG}")
message (STATUS "CMAKE_CXX_FLAGS_DEBUG:${CMAKE_CXX_FLAGS_DEBUG}")
message (STATUS "CMAKE_C_FLAGS_MINSIZEREL:${CMAKE_C_FLAGS_MINSIZEREL}")
message (STATUS "CMAKE_CXX_FLAGS_MINSIZEREL:${CMAKE_CXX_FLAGS_MINSIZEREL}")
message (STATUS "CMAKE_C_FLAGS_RELWITHDEBINFO:${CMAKE_C_FLAGS_RELWITHDEBINFO}")
message (STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO:${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")

#下面几个变量在交叉编译时:可以根据不同的目标系统进行条件判断或设置特定的编译选项和路径
#目标系统信息
message (STATUS "CMAKE_SYSTEM:${CMAKE_SYSTEM}")
#目标系统名字
message (STATUS "CMAKE_SYSTEM_NAME:${CMAKE_SYSTEM_NAME}")
#目标系统的处理器类型
message (STATUS "CMAKE_SYSTEM_PROCESSOR:${CMAKE_SYSTEM_PROCESSOR}")
#目标系统使用的C++编译器 如:GNU Clang MSVC
message (STATUS "CMAKE_CXX_COMPILER_ID:${CMAKE_CXX_COMPILER_ID}")
#当前编译环境下指针类型(通常是void*)的大小(以字节为单位)
message (STATUS "CMAKE_SIZEOF_VOID_P:${CMAKE_SIZEOF_VOID_P}")
#当前实际所在系统的处理器类型
message (STATUS "CMAKE_HOST_SYSTEM_PROCESSOR:${CMAKE_HOST_SYSTEM_PROCESSOR}")
message (STATUS "ANDROID:${ANDROID}")

#CMAKE_SYSTEM_PROCESSOR 目标系统处理器类型。交叉编译时,编译好的库只能在CMAKE_SYSTEM_PROCESSOR平台上运行
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86")
    message("x86 32")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
    message("x86_64 64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
    message("arm 64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7-a")
    message("arm 32")
endif()

message (STATUS "CMAKE_INSTALL_PREFIX:${CMAKE_INSTALL_PREFIX}")

#增加设置项  在执行cmake时,可以通过 -DDEBUG_LOG=ON来开启此flag
option(BUILD_TEST "Build test" OFF)

message (STATUS "BUILD_TEST:${BUILD_TEST}")

#utils.cmake中定义了一些方法,后面可以的CMakeLists.txt中可以直接调用
include(${CMAKE_CURRENT_LIST_DIR}/cmake/utils.cmake)
#设置find_package时,查找.cmake的路径
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/modules")

#用ndk交叉编译
#设置ndk工具链路径先
#export ANDROID_NDK=/toolchain_path
#cmake ../files -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_ABI="arm64-v8a" -DANDROID_NDK=$ANDROID_NDK -DANDROID_PLATFORM=android-26

#external 目录下包含三方库
add_subdirectory(external)

add_subdirectory(mylibs)

if (BUILD_TEST)
    message(STATUS "Build tests")
    add_subdirectory(tests)
    enable_testing()
endif()

2.external目录

** 放外部引用的三方库

  • 1../MyCmakeTest/external/CMakeLists.txt
message(STATUS "Configuring ${PROJECT_NAME} external")

add_subdirectory(ffmpeg)
  • 2../MyCmakeTest/external/ffmpeg/CMakeLists.txt
add_subdirectory(prebuild)
  • 3../MyCmakeTest/external/ffmpeg/prebuild/CMakeLists.txt
message(STATUS "Configuring ${PROJECT_NAME} ffmpeg6.1")

#使用find_package引入ffmpeg
#list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
#find_package(ffmpeg REQUIRED GLOBAL)
#if(ffmpeg_FOUND)
#    include_directories(${ffmpeg_INCLUDE_DIRS})
#endif()

#接口库的方式导入ffmpeg
if (NOT ANDROID)
    set(FFMPEG_LIB_PATH ${PROJECT_SOURCE_DIR}/external/ffmpeg/prebuild/lib/linux/${CMAKE_SYSTEM_PROCESSOR})
else()
    set(FFMPEG_LIB_PATH ${PROJECT_SOURCE_DIR}/external/ffmpeg/prebuild/lib/android/${CMAKE_SYSTEM_PROCESSOR})
endif()

set(FFMPEG_LIBS
    "\
    ${FFMPEG_LIB_PATH}/libavfilter.a;\
    ${FFMPEG_LIB_PATH}/libavformat.a;\
    ${FFMPEG_LIB_PATH}/libavcodec.a;\
    ${FFMPEG_LIB_PATH}/libavutil.a;\
    ${FFMPEG_LIB_PATH}/libswresample.a;\
    ${FFMPEG_LIB_PATH}/libswscale.a;\
    ${FFMPEG_LIB_PATH}/libavdevices.a"
    )

#去除空格
string(REPLACE " " "" FFMPEG_LIBS "${FFMPEG_LIBS}")

#x86_64平台编译的ffmpeg依赖libx264-dev libx265-dev库
#string(FIND "${CMAKE_SYSTEM_PROCESSOR}" "x86_64" NEED_X264)
#if(NEED_X264 GREATER -1)
#    string(APPEND FFMPEG_LIBS ";${FFMPEG_LIB_PATH}/libx264.so;${FFMPEG_LIB_PATH}/libx265.so")
#    file(COPY ${FFMPEG_LIB_PATH}/libx264.so.152 DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} FOLLOW_SYMLINK_CHAIN)
#    file(COPY ${FFMPEG_LIB_PATH}/libx265.so.146 DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} FOLLOW_SYMLINK_CHAIN)
#endif()

add_library(ffmpeg INTERFACE IMPORTED GLOBAL)

set_target_properties(ffmpeg PROPERTIES
INTERFACE_LINK_LIBRARIES
"${FFMPEG_LIBS}"
)

set_target_properties(ffmpeg PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES
"${PROJECT_SOURCE_DIR}/external/ffmpeg/prebuild/include"
)

file(GLOB FFMPEG_INSTALL_FILES ${FFMPEG_LIB_PATH}/lib*)
install(FILES ${FFMPEG_INSTALL_FILES} DESTINATION "${CMAKE_INSTALL_LIBDIR}")

3.mylibs目录

  • 1../MyCmakeTest/mylibs/CMakeLists.txt
message(STATUS "Configuring ${PROJECT_NAME} mylibs")

add_subdirectory(liba)
add_subdirectory(libb)
  • 2../MyCmakeTest/mylibs/liba/CMakeLists.txt
message(STATUS "Configuring ${PROJECT_NAME} liba")

#头文件单独导出为接口库
add_library(liba_headers INTERFACE)

target_include_directories(liba_headers
    INTERFACE
    include
)

#找ffmpeg
find_package(ffmpeg REQUIRED GLOBAL)

#遍历src文件夹下所有的cpp
file(GLOB srcs src/*.cpp)

add_library(liba SHARED ${srcs})

target_include_directories(liba
    PUBLIC
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")

target_link_libraries(liba
    PRIVATE
    ffmpeg)

target_compile_options(liba
    PRIVATE
    "-fPIC"
    "-Wall"
    "-Wextra"
    "-Werror")

install(TARGETS liba
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}")
  • 3../MyCmakeTest/mylibs/libb/CMakeLists.txt
message(STATUS "Configuring ${PROJECT_NAME} libb")

#递归遍历src目录下的所有cpp
file(GLOB_RECURSE srcs src/*.cpp)

#创建静态库
add_library(libb STATIC ${srcs})

target_include_directories(libb
    PUBLIC
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")

target_link_libraries(libb
    PRIVATE
    liba)

target_compile_options(libb
    PRIVATE
    "-fPIC"
    "-Wall"
    "-Wextra"
    "-Werror")

install(TARGETS libb
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}")

4.tests目录

** 放测试用例

message(STATUS "Configuring ${PROJECT_NAME} tests")

5.cmake目录

** 存放预先写好的cmake,方便复用

  • 1../MyCmakeTest/cmake/utils.cmake,定义一些通用的函数
# Turn on warnings on the given target
# 函数名:enable_warnings
# 参数:target_name
function(enable_warnings target_name)
    if(BUILD_WARNINGS)
        if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
            list(APPEND MSVC_OPTIONS "/W3")
            if(MSVC_VERSION GREATER 1900) # Allow non fatal security warnings for msvc 2015
                list(APPEND MSVC_OPTIONS "/WX")
            endif()
        endif()

        target_compile_options(
            ${target_name}
            PRIVATE $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
                    -Wall
                    -Wextra
                    -Wconversion
                    -pedantic
                    -Werror
                    -Wfatal-errors>
                    $<$<CXX_COMPILER_ID:MSVC>:${MSVC_OPTIONS}>)
    endif()
endfunction()

# Enable address sanitizer (gcc/clang only)
function(enable_sanitizer target_name)
    if(SANITIZE_ADDRESS)
        if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
            message(FATAL_ERROR "Sanitizer supported only for gcc/clang")
        endif()
        message(STATUS "Address sanitizer enabled")
        target_compile_options(${target_name} PRIVATE -fsanitize=address,undefined)
        target_compile_options(${target_name} PRIVATE -fno-sanitize=signed-integer-overflow)
        target_compile_options(${target_name} PRIVATE -fno-sanitize-recover=all)
        target_compile_options(${target_name} PRIVATE -fno-omit-frame-pointer)
        target_link_libraries(${target_name} PRIVATE -fsanitize=address,undefined -fuse-ld=gold)
    endif()
endfunction()

function(prepare_test test_name test_sources test_link_libraries)
    message(STATUS "test_name:${test_name}")
    if(CMAKE_DEBUG_LOG)
        message(STATUS "test_sources:${${test_sources}}")
        message(STATUS "test_link_libraries:${${test_link_libraries}}")
    endif()
    set(test_target TEST_${test_name})
    add_executable(${test_target} ${${test_sources}})
    # enable_warnings(${test_target})
    # enable_sanitizer(${test_target})
    target_compile_options(${test_target}
        PRIVATE
        "-Wall"
        "-Wextra"
        "-Werror")
    target_link_libraries(${test_target} ${${test_link_libraries}})
    add_test(NAME ${test_target} COMMAND $<TARGET_FILE:${test_target}>)
    # set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction()

function(add_copy_directory target_name src_dirs dst_dir)
    if(CMAKE_DEBUG_LOG)
        message(STATUS "add_copy_directory, target_name:${target_name} src_dirs:${src_dirs} dst_dir:${dst_dir}")
    endif()
    set(target CPDIR_${target_name})
    add_custom_target(${target}
        COMMAND ${CMAKE_COMMAND} -E remove_directory ${dst_dir}
        COMMAND ${CMAKE_COMMAND} -E make_directory ${dst_dir}
        COMMAND ${CMAKE_COMMAND} -E copy_directory ${src_dirs} ${dst_dir})
endfunction()

  • 2../MyCmakeTest/cmake/modules/Findffmpeg.cmake,ffmpeg的find_package
if(CMAKE_VERSION VERSION_LESS 3.10)
    message(FATAL_ERROR "CMake 3.10 is required by Findffmpeg.cmake")
endif()

set(ffmpeg_VERSION 6.1)

set(HEADS_PATH ${PROJECT_SOURCE_DIR}/external/ffmpeg/prebuild/include)
set(LIB_PATH ${PROJECT_SOURCE_DIR}/external/ffmpeg/prebuild/lib/${CMAKE_SYSTEM_PROCESSOR}/linux/lib64)

find_path(ffmpeg_INCLUDE_DIR
        NAMES libavcodec/avcodec.h
        HINTS ${HEADS_PATH}
    )

find_library(ffmpeg_LIBRARY
        NAMES
        avfilter
        avformat
        avcodec
        avutil
        swresample
        swscale
        avdevice
        HINTS ${LIB_PATH}
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ffmpeg
    FOUND_VAR
        ffmpeg_FOUND
    REQUIRED_VARS
        ffmpeg_LIBRARY
        ffmpeg_INCLUDE_DIR
    VERSION_VAR
        ffmpeg_VERSION
)

if(ffmpeg_FOUND)
    add_library(ffmpeg UNKNOWN IMPORTED)
    set_target_properties(ffmpeg PROPERTIES
        IMPORTED_LOCATION "${ffmpeg_LIBRARY}"
        INTERFACE_COMPILE_OPTIONS "${ffmpeg_DEFINITIONS}"
        INTERFACE_INCLUDE_DIRECTORIES "${ffmpeg_INCLUDE_DIR}"
    )
endif()

  • 3../MyCmakeTest/cmake/modules/FindGRPC.cmake,GRPC的find_package
#
# Locate and configure the gRPC library
#
# Adds the following targets:
#
#  gRPC::grpc - gRPC library
#  gRPC::grpc++ - gRPC C++ library
#  gRPC::grpc++_reflection - gRPC C++ reflection library
#  gRPC::grpc_cpp_plugin - C++ generator plugin for Protocol Buffers
#

#
# Generates C++ sources from the .proto files
#
# grpc_generate_cpp (<SRCS> <HDRS> <DEST> [<ARGN>...])
#
#  SRCS - variable to define with autogenerated source files
#  HDRS - variable to define with autogenerated header files
#  DEST - directory where the source files will be created
#  ARGN - .proto files
#
function(GRPC_GENERATE_CPP SRCS HDRS DEST)
  if(NOT ARGN)
    message(SEND_ERROR "Error: GRPC_GENERATE_CPP() called without any proto files")
    return()
  endif()

  if(GRPC_GENERATE_CPP_APPEND_PATH)
    # Create an include path for each file specified
    foreach(FIL ${ARGN})
      get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
      get_filename_component(ABS_PATH ${ABS_FIL} PATH)
      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
      if(${_contains_already} EQUAL -1)
          list(APPEND _protobuf_include_path -I ${ABS_PATH})
      endif()
    endforeach()
  else()
    set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
  endif()

  if(DEFINED PROTOBUF_IMPORT_DIRS)
    foreach(DIR ${PROTOBUF_IMPORT_DIRS})
      get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
      if(${_contains_already} EQUAL -1)
          list(APPEND _protobuf_include_path -I ${ABS_PATH})
      endif()
    endforeach()
  endif()

  set(${SRCS})
  set(${HDRS})
  foreach(FIL ${ARGN})
    get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
    get_filename_component(FIL_WE ${FIL} NAME_WE)

    list(APPEND ${SRCS} "${DEST}/${FIL_WE}.grpc.pb.cc")
    list(APPEND ${HDRS} "${DEST}/${FIL_WE}.grpc.pb.h")

    add_custom_command(
      OUTPUT "${DEST}/${FIL_WE}.grpc.pb.cc"
             "${DEST}/${FIL_WE}.grpc.pb.h"
      COMMAND protobuf::protoc
      ARGS --grpc_out ${DEST} ${_protobuf_include_path} --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${ABS_FIL}
      DEPENDS ${ABS_FIL} protobuf::protoc gRPC::grpc_cpp_plugin
      COMMENT "Running C++ gRPC compiler on ${FIL}"
      VERBATIM )
  endforeach()

  set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)
  set(${SRCS} ${${SRCS}} PARENT_SCOPE)
  set(${HDRS} ${${HDRS}} PARENT_SCOPE)
endfunction()

# By default have GRPC_GENERATE_CPP macro pass -I to protoc
# for each directory where a proto file is referenced.
if(NOT DEFINED GRPC_GENERATE_CPP_APPEND_PATH)
  set(GRPC_GENERATE_CPP_APPEND_PATH TRUE)
endif()

# Find gRPC include directory
find_path(GRPC_INCLUDE_DIR grpc/grpc.h)
mark_as_advanced(GRPC_INCLUDE_DIR)

# Find gRPC library
find_library(GRPC_LIBRARY NAMES grpc)
mark_as_advanced(GRPC_LIBRARY)
add_library(gRPC::grpc UNKNOWN IMPORTED)
set_target_properties(gRPC::grpc PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES "-lpthread;-ldl"
    IMPORTED_LOCATION ${GRPC_LIBRARY}
)

# Find gRPC C++ library
find_library(GRPC_GRPC++_LIBRARY NAMES grpc++)
mark_as_advanced(GRPC_GRPC++_LIBRARY)
add_library(gRPC::grpc++ UNKNOWN IMPORTED)
set_target_properties(gRPC::grpc++ PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES gRPC::grpc
    IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY}
)

# Find gRPC C++ reflection library
find_library(GRPC_GRPC++_REFLECTION_LIBRARY NAMES grpc++_reflection)
mark_as_advanced(GRPC_GRPC++_REFLECTION_LIBRARY)
add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED)
set_target_properties(gRPC::grpc++_reflection PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES gRPC::grpc++
    IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY}
)

# Find gRPC CPP generator
find_program(GRPC_CPP_PLUGIN NAMES grpc_cpp_plugin)
mark_as_advanced(GRPC_CPP_PLUGIN)
add_executable(gRPC::grpc_cpp_plugin IMPORTED)
set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
    IMPORTED_LOCATION ${GRPC_CPP_PLUGIN}
)

include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(gRPC DEFAULT_MSG
    GRPC_LIBRARY GRPC_INCLUDE_DIR GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN)

  • 4../MyCmakeTest/cmake/modules/FindProtobuf.cmake,protobuf的find_package
#
# Locate and configure the Google Protocol Buffers library
#
# Adds the following targets:
#
#  protobuf::libprotobuf - Protobuf library
#  protobuf::libprotobuf-lite - Protobuf lite library
#  protobuf::libprotoc - Protobuf Protoc Library
#  protobuf::protoc - protoc executable
#

#
# Generates C++ sources from the .proto files
#
# protobuf_generate_cpp (<SRCS> <HDRS> <DEST> [<ARGN>...])
#
#  SRCS - variable to define with autogenerated source files
#  HDRS - variable to define with autogenerated header files
#  DEST - directory where the source files will be created
#  ARGN - .proto files
#
function(PROTOBUF_GENERATE_CPP SRCS HDRS DEST)
  if(NOT ARGN)
    message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files")
    return()
  endif()

  if(PROTOBUF_GENERATE_CPP_APPEND_PATH)
    # Create an include path for each file specified
    foreach(FIL ${ARGN})
      get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
      get_filename_component(ABS_PATH ${ABS_FIL} PATH)
      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
      if(${_contains_already} EQUAL -1)
          list(APPEND _protobuf_include_path -I ${ABS_PATH})
      endif()
    endforeach()
  else()
    set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
  endif()

  if(DEFINED PROTOBUF_IMPORT_DIRS)
    foreach(DIR ${PROTOBUF_IMPORT_DIRS})
      get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
      if(${_contains_already} EQUAL -1)
          list(APPEND _protobuf_include_path -I ${ABS_PATH})
      endif()
    endforeach()
  endif()

  set(${SRCS})
  set(${HDRS})
  foreach(FIL ${ARGN})
    get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
    get_filename_component(FIL_WE ${FIL} NAME_WE)

    list(APPEND ${SRCS} "${DEST}/${FIL_WE}.pb.cc")
    list(APPEND ${HDRS} "${DEST}/${FIL_WE}.pb.h")

    add_custom_command(
      OUTPUT "${DEST}/${FIL_WE}.pb.cc"
             "${DEST}/${FIL_WE}.pb.h"
      COMMAND protobuf::protoc
      ARGS --cpp_out ${DEST} ${_protobuf_include_path} ${ABS_FIL}
      DEPENDS ${ABS_FIL} protobuf::protoc
      COMMENT "Running C++ protocol buffer compiler on ${FIL}"
      VERBATIM )
  endforeach()

  set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)
  set(${SRCS} ${${SRCS}} PARENT_SCOPE)
  set(${HDRS} ${${HDRS}} PARENT_SCOPE)
endfunction()

# By default have PROTOBUF_GENERATE_CPP macro pass -I to protoc
# for each directory where a proto file is referenced.
if(NOT DEFINED PROTOBUF_GENERATE_CPP_APPEND_PATH)
  set(PROTOBUF_GENERATE_CPP_APPEND_PATH TRUE)
endif()

# Find the include directory
find_path(PROTOBUF_INCLUDE_DIR google/protobuf/service.h)
mark_as_advanced(PROTOBUF_INCLUDE_DIR)

# The Protobuf library
find_library(PROTOBUF_LIBRARY NAMES protobuf)
mark_as_advanced(PROTOBUF_LIBRARY)
add_library(protobuf::libprotobuf UNKNOWN IMPORTED)
set_target_properties(protobuf::libprotobuf PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${PROTOBUF_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES pthread
    IMPORTED_LOCATION ${PROTOBUF_LIBRARY}
)

# The Protobuf lite library
find_library(PROTOBUF_LITE_LIBRARY NAMES protobuf-lite)
mark_as_advanced(PROTOBUF_LITE_LIBRARY)
add_library(protobuf::libprotobuf-lite UNKNOWN IMPORTED)
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${PROTOBUF_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES pthread
    IMPORTED_LOCATION ${PROTOBUF_LITE_LIBRARY}
)

# The Protobuf Protoc Library
find_library(PROTOBUF_PROTOC_LIBRARY NAMES protoc)
mark_as_advanced(PROTOBUF_PROTOC_LIBRARY)
add_library(protobuf::libprotoc UNKNOWN IMPORTED)
set_target_properties(protobuf::libprotoc PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${PROTOBUF_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES protobuf::libprotobuf
    IMPORTED_LOCATION ${PROTOBUF_PROTOC_LIBRARY}
)

# Find the protoc Executable
find_program(PROTOBUF_PROTOC_EXECUTABLE NAMES protoc)
mark_as_advanced(PROTOBUF_PROTOC_EXECUTABLE)
add_executable(protobuf::protoc IMPORTED)
set_target_properties(protobuf::protoc PROPERTIES
    IMPORTED_LOCATION ${PROTOBUF_PROTOC_EXECUTABLE}
)

include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Protobuf DEFAULT_MSG
    PROTOBUF_LIBRARY PROTOBUF_INCLUDE_DIR PROTOBUF_PROTOC_EXECUTABLE)