基于CMake的C++项目管理实践

使用CMake的find_package和configuration文件进行管理

以一个例子作为说明:

  • liba: lib库
  • libb: lib库,且引用liba
  • tutorial: exe,且引用liba和libb

示例代码项目结构

liba库-项目结构:

liba
├── build_debug/ : Debug模式构建目录
├── build_release/ : Release模式构建目录
├── call/ : call.lib 库目录
│   ├── CMakeLists.txt
│   ├── usage_func.cxx
│   └── usage_func.hpp
├── CMakeLists.txt
├── Config.cmake.in
├── inherit/ : inherit.lib 库目录
│   ├── base_func.cxx
│   ├── base_func.hpp
│   ├── CMakeLists.txt
│   ├── mysqrt.cxx
│   └── mysqrt.h
├── liba_config.hpp.in
└── liba_exports.hpp

libb库-项目结构:

libb
├── build_debug/ : Debug模式构建目录
├── build_release/ : Release模式构建目录
├── CMakeLists.txt
├── Config.cmake.in
├── libb_config.hpp.in
├── libb_exports.hpp
└── redefining/ : redefining.lib 库目录
    ├── CMakeLists.txt
    ├── derived_func.cxx
    └── derived_func.hpp


tutorial库-项目结构:

liba
├── build_debug/ : Debug模式构建目录
├── build_release/ : Release模式构建目录
├── CMakeLists.txt
└── main.cxx

示例代码文件内容

liba库-内容

call/usage_func.hpp:
#pragma once 

#include "liba_exports.hpp"

namespace liba{
    class LIBA_DECLSPEC CUsageFunction {
    public:
        CUsageFunction() = default;
        virtual~CUsageFunction() = default;

        double sqrt(double);

        void message();
    };
}

call/usage_func.cxx:
#include "usage_func.hpp"

#include <iostream>
#include <cmath>

namespace liba {
    double CUsageFunction::sqrt(double num) {

        double v = std::sqrt(num);
        std::cout << "liba CUsageFunction sqrt on cmath: std::sqrt(" << num << ")=" << v << std::endl;  
        return v;
    }

    void CUsageFunction::message() {
        std::cout << "liba CUsageFunction message!" << std::endl;
    }
}

call/CMakeLists.txt:
set(COMPONENT call)

set(SRC_LST usage_func.cxx)

# 设置-DBUILD_SHARED_LIBS=ON后,此处可以不显式设置类型(STATIC, SHARED),也可以生成具体类型的库
add_library(${COMPONENT} ${SRC_LST})

# namespaced alias
add_library(${CMAKE_PROJECT_NAME}::${COMPONENT} ALIAS ${COMPONENT})

message(STATUS "Current COMPONENT:" ${COMPONENT})
message(STATUS "Current COMPONENT:" ${COMPONENT})
message(STATUS "${COMPONENT}-CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "${COMPONENT}-PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
message(STATUS "${COMPONENT}-PROJECT_BINARY_DIR:" ${PROJECT_BINARY_DIR})

# state that anybody linking to us needs to include the current source dir
# to find base_func.hpp, while we don't.
# 调用程序链接inherit本目标时,需要包含base_func.hpp
# 但是inherit本目标编译时,不需要链接base_func.hpp
# 利用INTERFACE作用域限定include作用域:target本身不链接,传递到调用方链接
# ${CMAKE_CURRENT_SOURCE_DIR}: The path to the source directory currently being processed. 
# ${CMAKE_CURRENT_SOURCE_DIR}: 当前处理的源目录,即当前CMakeLists.txt所在目录
# ${PROJECT_SOURCE_DIR}: 表示项目主CMakeLists.txt文件所在目录
# ${CMAKE_INSTALL_INCLUDEDIR}: include
# $<BUILD_INTERFACE:xxx>: 表示库链接过程中需要包含的路径
# $<INSTALL_INTERFACE:xxx>: 表示在安装目录下引用库需要包含的路径
# 项目链接了${PROJECT_SOURCE_DIR}/liba_exports.hpp 故${PROJECT_SOURCE_DIR}作用域需要设置为PUBLIC
target_include_directories(${COMPONENT}
                           INTERFACE
                           $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                           $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
                           $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
                           PUBLIC
                           $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
                           )

# 添加BUILD_SHARED_LIBS判断是否编译动态库和静态库
if(BUILD_SHARED_LIBS)
  # 在Windows平台下,动态库和静态库的导出符号需要区别对待
  target_compile_definitions(${COMPONENT} PRIVATE "SHARED_LIBA_BUILD")
endif()

# link our compiler flags interface library
# project_cxx_compiler_flags: 项目主CMakeLists.txt文件中定义的继承了C++特性的接口库
# 链接接口库的编译标记
# target_link_libraries(${COMPONENT} PUBLIC project_cxx_compiler_flags)
target_link_libraries(${COMPONENT} PRIVATE project_cxx_compiler_flags)

# 定义windows通用的导入导出符号,否则windows平台不会为dll生成导入库lib
target_compile_definitions(${COMPONENT} PRIVATE "SHARED_LIBA_EXPORTING")

# 为target添加DEBUG_POSTFIX属性
set_target_properties(${COMPONENT} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})

# 安装lib
# set(installable_libs ${COMPONENT} project_cxx_compiler_flags)
set(installable_libs ${COMPONENT})
# 若需要提供SqrtLibrary安装,则执行如下代码
# if(TARGET SqrtLibrary)
#   # 将SqrtLibrary添加到installable_libs 变量中
#   list(APPEND installable_libs SqrtLibrary)
# endif()

# EXPORT可以导出其他CMake项目直接使用此项目的信息
# 导出后可以将信息输出到xxx.cmake文件,方便其它项目find_package调用
install(TARGETS ${installable_libs}
        EXPORT ${COMPONENT}-Targets
        LIBRARY DESTINATION $<IF:$<CONFIG:Debug>,libd,lib>
        ARCHIVE DESTINATION $<IF:$<CONFIG:Debug>,libd,lib>
        RUNTIME DESTINATION $<IF:$<CONFIG:Debug>,bind,bin>
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT}")
        
# 安装include
# 方法一:
# install(FILES usage_func.hpp DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT}")
# 方法二:
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
        FILES_MATCHING PATTERN "*.hpp")

# 生成和安装export file
# generate and install export file
install(EXPORT ${COMPONENT}-Targets
        FILE ${CMAKE_PROJECT_NAME}-${COMPONENT}Targets.cmake
        NAMESPACE ${CMAKE_PROJECT_NAME}::
        DESTINATION "cmake")

inherit/base_func.hpp:
#pragma once 

#include "liba_exports.hpp"

namespace liba{
    class LIBA_DECLSPEC CBaseFunction {
    public:
        CBaseFunction() = default;
        virtual~CBaseFunction() = default;

        double sqrt(double);

        void message();
    };
}

inherit/base_func.cxx:
#include "base_func.hpp"

#include <iostream>

#ifdef USE_MYMATH
#   include "mysqrt.h"
#else 
#   include <cmath>
#endif

namespace liba {
    double CBaseFunction::sqrt(double num) {
#ifdef USE_MYMATH
        double v = detail::mysqrt(num);
        std::cout << "liba CBaseFunction sqrt on mysqrt: mysqrt(" << num << ")=" << v << std::endl;  
        return v;
#else
        double v = std::sqrt(num);
        std::cout << "liba CBaseFunction sqrt on cmath: std::sqrt(" << num << ")=" << v << std::endl;  
        return v;
#endif
    }

    void CBaseFunction::message() {
        std::cout << "liba CBaseFunction message!" << std::endl;
    }
}

inherit/CMakeLists.txt:
set(COMPONENT inherit)

set(SRC_LST base_func.cxx)

# 设置-DBUILD_SHARED_LIBS=ON后,此处可以不显式设置类型(STATIC, SHARED),也可以生成具体类型的库
add_library(${COMPONENT} ${SRC_LST})

# namespaced alias
add_library(${CMAKE_PROJECT_NAME}::${COMPONENT} ALIAS ${COMPONENT})

message(STATUS "Current COMPONENT:" ${COMPONENT})
message(STATUS "Current COMPONENT:" ${COMPONENT})
message(STATUS "${COMPONENT}-CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "${COMPONENT}-PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
message(STATUS "${COMPONENT}-PROJECT_BINARY_DIR:" ${PROJECT_BINARY_DIR})

# state that anybody linking to us needs to include the current source dir
# to find base_func.hpp, while we don't.
# 调用程序链接inherit本目标时,需要包含base_func.hpp
# 但是inherit本目标编译时,不需要链接base_func.hpp
# 利用INTERFACE作用域限定include作用域:target本身不链接,传递到调用方链接
# ${CMAKE_CURRENT_SOURCE_DIR}: The path to the source directory currently being processed. 
# ${CMAKE_CURRENT_SOURCE_DIR}: 当前处理的源目录,即当前CMakeLists.txt所在目录
# ${PROJECT_SOURCE_DIR}: 表示项目主CMakeLists.txt文件所在目录
# ${CMAKE_INSTALL_INCLUDEDIR}: include
# $<BUILD_INTERFACE:xxx>: 表示库链接过程中需要包含的路径
# $<INSTALL_INTERFACE:xxx>: 表示在安装目录下引用库需要包含的路径
# 项目链接了${PROJECT_SOURCE_DIR}/liba_exports.hpp 故${PROJECT_SOURCE_DIR}作用域需要设置为PUBLIC
target_include_directories(${COMPONENT}
                           INTERFACE
                           $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                           $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
                           $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
                           PUBLIC
                           $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
                           )

# should we use our own math functions
# option定义CMake构建编译系统的选项
option(USE_MYMATH "Use mymath implementation" ON)

if(USE_MYMATH)
  # target_compile_definitions定义CMake编译选项
  # 如下面语句为C++源码定义了USE_MYMATH宏定义
  # PRIVATE: 作用域只限于本项目
  target_compile_definitions(${COMPONENT} PRIVATE "USE_MYMATH")

  # USE_MYMATH模式下 使用项目自定义sqrt实现
  # 定义SqrtLibrary静态库
  add_library(SqrtLibrary STATIC mysqrt.cxx)

  # state that SqrtLibrary need PIC when the default is shared libraries
  # 当inherit构建动态库时,SqrtLibrary静态库的POSITION_INDEPENDENT_CODE属性不设置为True,构建会失败
  set_target_properties(SqrtLibrary PROPERTIES
                        POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
                        )
  
  # link our compiler flags interface library
  # project_cxx_compiler_flags: 项目主CMakeLists.txt文件中定义的继承了C++特性的接口库
  # 链接接口库的编译标记
  target_link_libraries(SqrtLibrary PUBLIC project_cxx_compiler_flags)

  # 为inherit目标添加链接库SqrtLibrary
  # PRIVATE:SqrtLibrary作用域仅限于本项目
  target_link_libraries(${COMPONENT} PRIVATE SqrtLibrary)
endif()

# 添加BUILD_SHARED_LIBS判断是否编译动态库和静态库
if(BUILD_SHARED_LIBS)
  # 在Windows平台下,动态库和静态库的导出符号需要区别对待
  target_compile_definitions(${COMPONENT} PRIVATE "SHARED_LIBA_BUILD")
endif()

# link our compiler flags interface library
# project_cxx_compiler_flags: 项目主CMakeLists.txt文件中定义的继承了C++特性的接口库
# 链接接口库的编译标记
# target_link_libraries(${COMPONENT} PUBLIC project_cxx_compiler_flags)
target_link_libraries(${COMPONENT} PRIVATE project_cxx_compiler_flags)

# 定义windows通用的导入导出符号,否则windows平台不会为dll生成导入库lib
target_compile_definitions(${COMPONENT} PRIVATE "SHARED_LIBA_EXPORTING")

# 为target添加DEBUG_POSTFIX属性
set_target_properties(${COMPONENT} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})

# 安装lib
# set(installable_libs ${COMPONENT} project_cxx_compiler_flags)
set(installable_libs ${COMPONENT})
# 若需要提供SqrtLibrary安装,则执行如下代码
# if(TARGET SqrtLibrary)
#   # 将SqrtLibrary添加到installable_libs 变量中
#   list(APPEND installable_libs SqrtLibrary)
# endif()

# EXPORT可以导出其他CMake项目直接使用此项目的信息
# 导出后可以将信息输出到xxx.cmake文件,方便其它项目find_package调用
install(TARGETS ${installable_libs}
        EXPORT ${COMPONENT}-Targets
        LIBRARY DESTINATION $<IF:$<CONFIG:Debug>,libd,lib>
        ARCHIVE DESTINATION $<IF:$<CONFIG:Debug>,libd,lib>
        RUNTIME DESTINATION $<IF:$<CONFIG:Debug>,bind,bin>
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT}")
        
# 安装include
# 方法一:
# install(FILES base_func.hpp DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT}")
# 方法二:
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
        FILES_MATCHING PATTERN "*.hpp")

# 生成和安装export file
# generate and install export file
install(EXPORT ${COMPONENT}-Targets
        FILE ${CMAKE_PROJECT_NAME}-${COMPONENT}Targets.cmake
        NAMESPACE ${CMAKE_PROJECT_NAME}::
        DESTINATION "cmake")

liba_exports.hpp:
#pragma once 

#if (defined(_WIN32) || defined(_WIN64))
#  if defined(SHARED_LIBA_BUILD)
#    if defined(SHARED_LIBA_EXPORTING)
#      define LIBA_DECLSPEC __declspec(dllexport)
#    else
#      define LIBA_DECLSPEC __declspec(dllimport)
#    endif
#  else // non shared
#    define LIBA_DECLSPEC
#  endif
#else // non windows
#  define LIBA_DECLSPEC
#endif

liba_config.hpp.in:
// the configured options and settings for liba
// 
//# ${LIBA_VERSION_MAJOR} = ${PROJECT_VERSION_MAJOR}
//# ${LIBA_VERSION_MINOR} = ${PROJECT_VERSION_MINOR}

#pragma once

#define LIBA_PROJ_VERSION "@PROJECT_VERSION@"
#define LIBA_PROJ_VERSION_MAJOR @LIBA_VERSION_MAJOR@
#define LIBA_PROJ_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define LIBA_PROJ_VERSION_PATCH @LIBA_VERSION_PATCH@
#define LIBA_PROJ_VERSION_TWEAK @PROJECT_VERSION_TWEAK@
#define TEST_VERBOSE "@TEST_VERBOSE_FLAG@"

Config.cmake.in:
@PACKAGE_INIT@

# include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

# <Package>_FOUND=False
# <Package>_<Component>_FOUND=True

set(_liba_supported_components inherit call)

foreach(_comp ${LIBA_FIND_COMPONENTS})
  if (NOT _comp IN_LIST _liba_supported_components)
    set(LIBA_FOUND False)
    set(LIBA_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
  endif()
  include("${CMAKE_CURRENT_LIST_DIR}/LIBA-${_comp}Targets.cmake")
endforeach()

CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)

# project指定项目名、版本号、编程语言
# 关于软件版本命名规则:主版本号.子版本号.修订版本号.日期-阶段版本号
# * 主版本号:当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定是否修改。
# * 子版本号:当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定是否修改
# * 修订版本号:一般是 Bug 修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定是否修改。
# * 日期-阶段版本号:日期用于记录修改项目的当前日期,此版本号由开发人员决定是否修改;阶段版本号用于标注当前版本的软件处于哪个开发阶段,此版本号由项目决定是否修改,一般是希腊字母。
# 例如,1.1.12.20230117-Release
# 阶段版本号说明:
# * Base:此版本表示该软件仅仅是一个假页面链接,通常包括所有的功能和页面布局,但是页面中的功能都没有做完整的实现,只是做为整体网站的一个基础架构。
# * Alpha:此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改。
# * Beta: 该版本相对于Alpha版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过多次测试来进一步消除。
# * RC:该版本已经相当成熟了,基本上不存在导致错误的BUG,与即将发行的正式版相差无几。
# * Release:该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。
# 关于CMake的版本规则:MAJOR.MINOR.PATCH.TWEAK
# CMake的版本号各个部分必须是数字组成
# 例如:1.1.1.20231017
project(LIBA VERSION 1.1.1.20231017 LANGUAGES C CXX)

# make cache variables for install destinations
include(GNUInstallDirs)

# 指定C++编译标准
# 方式一:利用set指定 the C++ standard
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
# 方式二:利用接口库的形式
add_library(project_cxx_compiler_flags INTERFACE)
target_compile_features(project_cxx_compiler_flags INTERFACE cxx_std_11)

# debug模式以'd'结尾
set(CMAKE_DEBUG_POSTFIX d)

# 指定自定义变量
set(TEST_VERBOSE_FLAG "${PROJECT_NAME} Demo")

# 判断当前使用的编译器
# 基于生成器表达式
# $<COMPILE_LANG_AND_ID:language,compiler_ids>: 当项目所用编程语言匹配language,
# 且项目所用编译器匹配compiler_ids时,此表达式返回1,否则返回0
# * Create a new variable gcc_like_cxx that is true if we are using CXX and
#   any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
# * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")

# 为接口库project_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $<condition:true_string> : 当condition为真则返回true_string,否则返回空字符串
# * $<IF:condition,true_string,false_string> : 当condition为真则返回true_string,否则返回false_string
# 生成器表达式支持嵌套
# INTERFACE作用域表示project_cxx_compiler_flags 本身不需要,但是project_cxx_compiler_flags 调用方需要使用
# BUILD_INTERFACE生成器表达式
# 我们只想在编译阶段使用警告选项,而不会在安装阶段使用,可以利用BUILD_INTERFACE限制
target_compile_options(project_cxx_compiler_flags INTERFACE 
  "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
  "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

# BUILD_SHARED_LIBS控制库类型的生成
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

# configure a header file to pass some of the CMake settings
# to the source code
# 利用configure_file()命令可以实现文件复制,并且替换文件中@var@的变量值
configure_file(liba_config.hpp.in ${PROJECT_BINARY_DIR}/include/liba_config.hpp)

message(STATUS "PROJECT_NAME:" ${PROJECT_NAME})
message(STATUS "CMAKE_PROJECT_NAME:" ${CMAKE_PROJECT_NAME})
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "CMAKE_CURRENT_BINARY_DIR:" ${CMAKE_CURRENT_BINARY_DIR})

message(STATUS "LIBA_BINARY_DIR:" ${LIBA_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
message(STATUS "CMAKE_SOURCE_DIR:" ${CMAKE_SOURCE_DIR})

# 配置默认安装路径
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  # ${CMAKE_SOURCE_DIR}表示源码树顶层目录路径
  set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/../install/${PROJECT_NAME})
endif()

# 添加inherit库和call库
add_subdirectory(${PROJECT_SOURCE_DIR}/inherit inherit_build)
add_subdirectory(${PROJECT_SOURCE_DIR}/call call_build)

# 安装config.hpp exports.hpp
install(FILES "${PROJECT_BINARY_DIR}/include/liba_config.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${PROJECT_SOURCE_DIR}/liba_exports.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# include CMakePackageConfigHelpers macro
include(CMakePackageConfigHelpers)

# 针对target设置版本属性
#set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${PROJECT_VERSION})
#set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR})
#set_property(TARGET ${PROJECT_NAME} PROPERTY
#  INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR})
#set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY
#  COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION
#)

# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  VERSION "${PROJECT_VERSION}"
  COMPATIBILITY AnyNewerVersion
)
# generate the config file that includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
  INSTALL_DESTINATION "cmake"
  NO_SET_AND_CHECK_MACRO
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# install config files
install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
  DESTINATION "cmake"
)

libb库-内容

redefining/derived_func.hpp:
#pragma once

#include "libb_exports.hpp"

#include <base_func.hpp>

namespace libb {
    class LIBB_DECLSPEC CDerivedFunction : public liba::CBaseFunction {
    public:
        CDerivedFunction() = default;
        virtual~CDerivedFunction() = default;

        // 重定义(redefining)
        double sqrt(double);

        void message();
    };
}

redefining/derived_func.cxx:
#include "derived_func.hpp"

#include <iostream>
#include <cmath>

namespace libb {
    double CDerivedFunction::sqrt(double num) {

        double v = std::sqrt(num);
        std::cout << "liba CDerivedFunction sqrt on cmath: std::sqrt(" << num << ")=" << v << std::endl;
        return v;  
    }

    void CDerivedFunction::message() {
        std::cout << "liba CDerivedFunction message!" << std::endl;
    }
}

redefining/CMakeLists.txt:
set(COMPONENT redefining)

set(SRC_LST derived_func.cxx)

message(STATUS "${COMPONENT}-find_package location(CMAKE_CURRENT_SOURCE_DIR):" "${CMAKE_CURRENT_SOURCE_DIR}/../install/LIBA/cmake")
message(STATUS "${COMPONENT}-find_package location(CMAKE_SOURCE_DIR):" "${CMAKE_SOURCE_DIR}/../install/LIBA/cmake")

# 添加路径到CMAKE_PREFIX_PATH 使find_package可以搜索加载package
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/../install/LIBA/cmake")

foreach(_dir ${CMAKE_PREFIX_PATH})
  message(STATUS "${CMAKE_PROJECT_NAME}::${COMPONENT}-find_package in dir:${_dir}")
endforeach()

find_package(LIBA 1.1.0 REQUIRED COMPONENTS inherit)
if(NOT LIBA_FOUND)
  message(FATAL_ERROR "${COMPONENT}-package: LIBA not found!")
endif()

# project_cxx_compiler_flags可以从LIBA库可以传递过来
# 设置-DBUILD_SHARED_LIBS=ON后,此处可以不显式设置类型(STATIC, SHARED),也可以生成具体类型的库
# add_library(${COMPONENT} ${SRC_LST} ${project_cxx_compiler_flags})
add_library(${COMPONENT} ${SRC_LST})

# namespaced alias
add_library(${CMAKE_PROJECT_NAME}::${COMPONENT} ALIAS ${COMPONENT})

message(STATUS "Current COMPONENT:" ${COMPONENT})
message(STATUS "Current COMPONENT:" ${COMPONENT})
message(STATUS "${COMPONENT}-CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "${COMPONENT}-PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
message(STATUS "${COMPONENT}-PROJECT_BINARY_DIR:" ${PROJECT_BINARY_DIR})

# state that anybody linking to us needs to include the current source dir
# to find base_func.hpp, while we don't.
# 调用程序链接inherit本目标时,需要包含base_func.hpp
# 但是inherit本目标编译时,不需要链接base_func.hpp
# 利用INTERFACE作用域限定include作用域:target本身不链接,传递到调用方链接
# ${CMAKE_CURRENT_SOURCE_DIR}: The path to the source directory currently being processed. 
# ${CMAKE_CURRENT_SOURCE_DIR}: 当前处理的源目录,即当前CMakeLists.txt所在目录
# ${PROJECT_SOURCE_DIR}: 表示项目主CMakeLists.txt文件所在目录
# ${CMAKE_INSTALL_INCLUDEDIR}: include
# $<BUILD_INTERFACE:xxx>: 表示库链接过程中需要包含的路径
# $<INSTALL_INTERFACE:xxx>: 表示在安装目录下引用库需要包含的路径
# 项目链接了${PROJECT_SOURCE_DIR}/libb_exports.hpp 故${PROJECT_SOURCE_DIR}作用域需要设置为PUBLIC
target_include_directories(${COMPONENT}
                           INTERFACE
                           $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                           $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
                           $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
                           PUBLIC
                           $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
                           )

# 添加BUILD_SHARED_LIBS判断是否编译动态库和静态库
if(BUILD_SHARED_LIBS)
  # 在Windows平台下,动态库和静态库的导出符号需要区别对待
  target_compile_definitions(${COMPONENT} PRIVATE "SHARED_LIBB_BUILD")
endif()

# project_cxx_compiler_flags可以从LIBA库可以传递过来
# link our compiler flags interface library
# project_cxx_compiler_flags: 项目主CMakeLists.txt文件中定义的继承了C++特性的接口库
# 链接接口库的编译标记
# target_link_libraries(${COMPONENT} PUBLIC project_cxx_compiler_flags LIBA::inherit)
target_link_libraries(${COMPONENT} PUBLIC LIBA::inherit)

# 定义windows通用的导入导出符号,否则windows平台不会为dll生成导入库lib
target_compile_definitions(${COMPONENT} PRIVATE "SHARED_LIBB_EXPORTING")

# 为target添加DEBUG_POSTFIX属性
set_target_properties(${COMPONENT} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})

# project_cxx_compiler_flags可以从LIBA库可以传递过来
# 安装lib
# set(installable_libs ${COMPONENT} project_cxx_compiler_flags)
set(installable_libs ${COMPONENT})

# EXPORT可以导出其他CMake项目直接使用此项目的信息
# 导出后可以将信息输出到xxx.cmake文件,方便其它项目find_package调用
install(TARGETS ${installable_libs}
        EXPORT ${COMPONENT}-Targets
        LIBRARY DESTINATION $<IF:$<CONFIG:Debug>,libd,lib>
        ARCHIVE DESTINATION $<IF:$<CONFIG:Debug>,libd,lib>
        RUNTIME DESTINATION $<IF:$<CONFIG:Debug>,bind,bin>
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT}")
        
# 安装include
# 方法一:
# install(FILES usage_func.hpp DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${COMPONENT}")
# 方法二:
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
        FILES_MATCHING PATTERN "*.hpp")

# 生成和安装export file
# generate and install export file
install(EXPORT ${COMPONENT}-Targets
        FILE ${CMAKE_PROJECT_NAME}-${COMPONENT}Targets.cmake
        NAMESPACE ${CMAKE_PROJECT_NAME}::
        DESTINATION "cmake")

libb_exports.hpp:
#pragma once 

#if (defined(_WIN32) || defined(_WIN64))
#  if defined(SHARED_LIBB_BUILD)
#    if defined(SHARED_LIBB_EXPORTING)
#      define LIBB_DECLSPEC __declspec(dllexport)
#    else
#      define LIBB_DECLSPEC __declspec(dllimport)
#    endif
#  else // non shared
#    define LIBB_DECLSPEC
#  endif
#else // non windows
#  define LIBB_DECLSPEC
#endif

libb_config.hpp.in:
// the configured options and settings for libb
// 
//# ${LIBB_VERSION_MAJOR} = ${PROJECT_VERSION_MAJOR}
//# ${LIBB_VERSION_MINOR} = ${PROJECT_VERSION_MINOR}

#pragma once

#define LIBB_PROJ_VERSION "@PROJECT_VERSION@"
#define LIBB_PROJ_VERSION_MAJOR @LIBB_VERSION_MAJOR@
#define LIBB_PROJ_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define LIBB_PROJ_VERSION_PATCH @LIBB_VERSION_PATCH@
#define LIBB_PROJ_VERSION_TWEAK @PROJECT_VERSION_TWEAK@
#define TEST_VERBOSE "@TEST_VERBOSE_FLAG@"

Config.cmake.in:
@PACKAGE_INIT@

#include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

foreach(_dir ${CMAKE_CURRENT_LIST_DIR})
  message(STATUS "MONO CMAKE_CURRENT_LIST_DIR:" ${_dir})
endforeach()

# find dependency 
# libb <- liba
include(CMakeFindDependencyMacro)
# needing LIBA's location
#find_dependency(LIBA 1.1.0)
find_dependency(LIBA 1.1.0 COMPONENTS inherit NO_DEFAULT_PATH PATHS ${CMAKE_CURRENT_LIST_DIR}/../../LIBA/cmake)

set(_libb_supported_components redefining)

# <Package>_FOUND=False
# <Package>_<Component>_FOUND=True

foreach(_comp ${LIBB_FIND_COMPONENTS})
  if (NOT _comp IN_LIST _libb_supported_components)
    set(LIBB_FOUND False)
    set(LIBB_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
  endif()
  include("${CMAKE_CURRENT_LIST_DIR}/LIBB-${_comp}Targets.cmake")
endforeach()

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)

# project指定项目名、版本号、编程语言
# 关于软件版本命名规则:主版本号.子版本号.修订版本号.日期-阶段版本号
# * 主版本号:当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定是否修改。
# * 子版本号:当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定是否修改
# * 修订版本号:一般是 Bug 修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定是否修改。
# * 日期-阶段版本号:日期用于记录修改项目的当前日期,此版本号由开发人员决定是否修改;阶段版本号用于标注当前版本的软件处于哪个开发阶段,此版本号由项目决定是否修改,一般是希腊字母。
# 例如,1.1.12.20230117-Release
# 阶段版本号说明:
# * Base:此版本表示该软件仅仅是一个假页面链接,通常包括所有的功能和页面布局,但是页面中的功能都没有做完整的实现,只是做为整体网站的一个基础架构。
# * Alpha:此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改。
# * Beta: 该版本相对于Alpha版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过多次测试来进一步消除。
# * RC:该版本已经相当成熟了,基本上不存在导致错误的BUG,与即将发行的正式版相差无几。
# * Release:该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。
# 关于CMake的版本规则:MAJOR.MINOR.PATCH.TWEAK
# CMake的版本号各个部分必须是数字组成
# 例如:1.1.1.20231017
project(LIBB VERSION 1.1.1.20231017 LANGUAGES C CXX)

# make cache variables for install destinations
include(GNUInstallDirs)


# project_cxx_compiler_flags可以从LIBA库可以传递过来
# 指定C++编译标准
# 方式一:利用set指定 the C++ standard
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
# 方式二:利用接口库的形式
# add_library(project_cxx_compiler_flags INTERFACE)
# target_compile_features(project_cxx_compiler_flags INTERFACE cxx_std_11)

# debug模式以'd'结尾
set(CMAKE_DEBUG_POSTFIX d)

# 指定自定义变量
set(TEST_VERBOSE_FLAG "${PROJECT_NAME} Demo")

# project_cxx_compiler_flags可以从LIBA库可以传递过来
# 判断当前使用的编译器
# 基于生成器表达式
# $<COMPILE_LANG_AND_ID:language,compiler_ids>: 当项目所用编程语言匹配language,
# 且项目所用编译器匹配compiler_ids时,此表达式返回1,否则返回0
# * Create a new variable gcc_like_cxx that is true if we are using CXX and
#   any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
# * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
# set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
# set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")

# project_cxx_compiler_flags可以从LIBA库可以传递过来
# 为接口库project_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $<condition:true_string> : 当condition为真则返回true_string,否则返回空字符串
# * $<IF:condition,true_string,false_string> : 当condition为真则返回true_string,否则返回false_string
# 生成器表达式支持嵌套
# INTERFACE作用域表示project_cxx_compiler_flags 本身不需要,但是project_cxx_compiler_flags 调用方需要使用
# BUILD_INTERFACE生成器表达式
# 我们只想在编译阶段使用警告选项,而不会在安装阶段使用,可以利用BUILD_INTERFACE限制
# target_compile_options(project_cxx_compiler_flags INTERFACE 
#  "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
#  "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
# )

# BUILD_SHARED_LIBS控制库类型的生成
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

# configure a header file to pass some of the CMake settings
# to the source code
# 利用configure_file()命令可以实现文件复制,并且替换文件中@var@的变量值
configure_file(libb_config.hpp.in ${PROJECT_BINARY_DIR}/include/libb_config.hpp)

message(STATUS "PROJECT_NAME:" ${PROJECT_NAME})
message(STATUS "CMAKE_PROJECT_NAME:" ${CMAKE_PROJECT_NAME})
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "CMAKE_CURRENT_BINARY_DIR:" ${CMAKE_CURRENT_BINARY_DIR})

message(STATUS "LIBB_BINARY_DIR:" ${LIBB_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
message(STATUS "CMAKE_SOURCE_DIR:" ${CMAKE_SOURCE_DIR})

# 配置默认安装路径
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  # ${CMAKE_SOURCE_DIR}表示源码树顶层目录路径
  set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/../install/${PROJECT_NAME})
endif()

# 添加redefining库
add_subdirectory(${PROJECT_SOURCE_DIR}/redefining redefining_build)

# 安装config.hpp exports.hpp
install(FILES "${PROJECT_BINARY_DIR}/include/libb_config.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${PROJECT_SOURCE_DIR}/libb_exports.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# include CMakePackageConfigHelpers macro
include(CMakePackageConfigHelpers)

# 针对target设置版本属性
#set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${PROJECT_VERSION})
#set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR})
#set_property(TARGET ${PROJECT_NAME} PROPERTY
#  INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR})
#set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY
#  COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION
#)

# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  VERSION "${PROJECT_VERSION}"
  COMPATIBILITY AnyNewerVersion
)
# generate the config file that includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
  INSTALL_DESTINATION "cmake"
  NO_SET_AND_CHECK_MACRO
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# install config files
install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
  DESTINATION "cmake"
)

tutorial库-内容

main.cxx:
// A simple program that computes the square root of a number
#include <iostream>
#include <sstream>
#include <string>

#include <liba_config.hpp>
#include <libb_config.hpp>
#include <call/usage_func.hpp>
#include <redefining/derived_func.hpp>

int main(int argc, char* argv[])
{
    if (argc < 2) {
        // report version
        std::cout << argv[0] << "liba version:" << LIBA_PROJ_VERSION
                << " --- libb version:" << LIBB_PROJ_VERSION << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double iv = std::stod(argv[1]);

    std::cout << "liba::CUsageFunction: " << std::endl;
    liba::CUsageFunction uf;
    double ufv = uf.sqrt(iv);
    std::cout << "The square root of " << iv << " is " << ufv
            << std::endl;
    uf.message();

    std::cout << "libb::CDerivedFunction: " << std::endl;
    libb::CDerivedFunction df;
    double dfv = df.sqrt(iv);
    std::cout << "The square root of " << iv << " is " << dfv
            << std::endl;
    df.message();

    return 0;
}

CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)

# project指定项目名、版本号、编程语言
# 关于软件版本命名规则:主版本号.子版本号.修订版本号.日期-阶段版本号
# * 主版本号:当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定是否修改。
# * 子版本号:当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定是否修改
# * 修订版本号:一般是 Bug 修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定是否修改。
# * 日期-阶段版本号:日期用于记录修改项目的当前日期,此版本号由开发人员决定是否修改;阶段版本号用于标注当前版本的软件处于哪个开发阶段,此版本号由项目决定是否修改,一般是希腊字母。
# 例如,1.1.12.20230117-Release
# 阶段版本号说明:
# * Base:此版本表示该软件仅仅是一个假页面链接,通常包括所有的功能和页面布局,但是页面中的功能都没有做完整的实现,只是做为整体网站的一个基础架构。
# * Alpha:此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改。
# * Beta: 该版本相对于Alpha版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过多次测试来进一步消除。
# * RC:该版本已经相当成熟了,基本上不存在导致错误的BUG,与即将发行的正式版相差无几。
# * Release:该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。
# 关于CMake的版本规则:MAJOR.MINOR.PATCH.TWEAK
# CMake的版本号各个部分必须是数字组成
# 例如:1.1.1.20231017
project(TUTORIAL VERSION 1.1.1.20231017 LANGUAGES C CXX)

# make cache variables for install destinations
include(GNUInstallDirs)

# 指定C++编译标准
# 方式一:利用set指定 the C++ standard
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
# 方式二:利用接口库的形式
add_library(tutorial_cxx_compiler_flags INTERFACE)
target_compile_features(tutorial_cxx_compiler_flags INTERFACE cxx_std_11)

# debug模式以'd'结尾
set(CMAKE_DEBUG_POSTFIX d)

# 指定自定义变量
set(TEST_VERBOSE_FLAG "${PROJECT_NAME} Demo")

# 判断当前使用的编译器
# 基于生成器表达式
# $<COMPILE_LANG_AND_ID:language,compiler_ids>: 当项目所用编程语言匹配language,
# 且项目所用编译器匹配compiler_ids时,此表达式返回1,否则返回0
# * Create a new variable gcc_like_cxx that is true if we are using CXX and
#   any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
# * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")

# 为接口库tutorial_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $<condition:true_string> : 当condition为真则返回true_string,否则返回空字符串
# * $<IF:condition,true_string,false_string> : 当condition为真则返回true_string,否则返回false_string
# 生成器表达式支持嵌套
# INTERFACE作用域表示tutorial_cxx_compiler_flags 本身不需要,但是tutorial_cxx_compiler_flags 调用方需要使用
# BUILD_INTERFACE生成器表达式
# 我们只想在编译阶段使用警告选项,而不会在安装阶段使用,可以利用BUILD_INTERFACE限制
target_compile_options(tutorial_cxx_compiler_flags INTERFACE 
  "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
  "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

message(STATUS "PROJECT_NAME:" ${PROJECT_NAME})
message(STATUS "CMAKE_PROJECT_NAME:" ${CMAKE_PROJECT_NAME})
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "CMAKE_CURRENT_BINARY_DIR:" ${CMAKE_CURRENT_BINARY_DIR})

message(STATUS "LIBB_BINARY_DIR:" ${LIBB_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
message(STATUS "CMAKE_SOURCE_DIR:" ${CMAKE_SOURCE_DIR})


# 添加路径到CMAKE_PREFIX_PATH 使find_package可以搜索加载package
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../install/LIBA/cmake")
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../install/LIBB/cmake")

foreach(_dir ${CMAKE_PREFIX_PATH})
  message(STATUS "${CMAKE_PROJECT_NAME}-find_package in dir:${_dir}")
endforeach()

# TUTORIAL <-- LIBB <-- LIBA
# TUTORIAL <-- LIBA
# LIBB <-- LIBA
find_package(LIBB 1.1.0 REQUIRED COMPONENTS redefining)
if(NOT LIBB_FOUND)
  message(FATAL_ERROR "${COMPONENT}-package: LIBB not found!")
endif()

find_package(LIBA 1.1.0 REQUIRED COMPONENTS call)
if(NOT LIBA_FOUND)
  message(FATAL_ERROR "${COMPONENT}-package: LIBA not found!")
endif()

# add the executable
add_executable(${PROJECT_NAME} main.cxx)

target_link_libraries(${PROJECT_NAME} LIBB::redefining LIBA::call tutorial_cxx_compiler_flags)

# 为target添加DEBUG_POSTFIX属性
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})

构建使用示例:

以Release模式为例:先构建liba,再构建libb,最后构建tutorial

liba-构建命令

> cd @liba目录
> mkdir build_release
> cd build_release
> cmake -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release ..
> cmake --build .
> cmake --install .

libb-构建命令

> cd @libb目录
> mkdir build_release
> cd build_release
> cmake -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release ..
> cmake --build .
> cmake --install .

tutorial-构建命令

> cd @liba目录
> mkdir build_release
> cd build_release
> cmake -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release ..
> cmake --build .

注意:针对multi-configuration生成器,比如Visual Studio,是会忽略CMAKE_BUILD_TYPE选项的;只有single-configuration生成器,比如Makefile,才支持CMAKE_BUILD_TYPE选项。
故在CMakeLists.txt中判断Debug模式还是Release模式,需要使用生成器表达式:

$<IF:$<CONFIG:Debug>,true_string,false_string>
10-19 07:42