使用CMake的find_package和configuration文件进行管理
以一个例子作为说明:
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
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
#include
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
# $: 表示库链接过程中需要包含的路径
# $: 表示在安装目录下引用库需要包含的路径
# 项目链接了${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
#ifdef USE_MYMATH
# include "mysqrt.h"
#else
# include
#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
# $: 表示库链接过程中需要包含的路径
# $: 表示在安装目录下引用库需要包含的路径
# 项目链接了${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" )
# _FOUND=False
# __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")
# 判断当前使用的编译器
# 基于生成器表达式
# $: 当项目所用编程语言匹配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 "$" )
set(msvc_cxx "$" )
# 为接口库project_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $ : 当condition为真则返回true_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}:$>"
"$<${msvc_cxx}:$>"
)
# 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"
)
redefining/derived_func.hpp:
#pragma once
#include "libb_exports.hpp"
#include
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
#include
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
# $: 表示库链接过程中需要包含的路径
# $: 表示在安装目录下引用库需要包含的路径
# 项目链接了${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)
# _FOUND=False
# __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库可以传递过来
# 判断当前使用的编译器
# 基于生成器表达式
# $: 当项目所用编程语言匹配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 "$" )
# set(msvc_cxx "$" )
# project_cxx_compiler_flags可以从LIBA库可以传递过来
# 为接口库project_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $ : 当condition为真则返回true_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}:$>"
# "$<${msvc_cxx}:$>"
# )
# 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"
)
main.cxx:
// A simple program that computes the square root of a number
#include
#include
#include
#include
#include
#include
#include
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 "$" )
set(msvc_cxx "$" )
# 为接口库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}:$>"
"$<${msvc_cxx}:$>"
)
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>