构建管理
构建管理
目录
CMakeLists.txt
LincZero:在这一篇笔记开始之前,首先声明:我个人更倾向与使用小写的CMakeLists指令,所以笔记中会以小写为主
各语言的关键管理器
CMakeLists.txt和Makefile都是用来管理和自动化编译、构建C/C++项目的文件。它们可以指定编译器设置、链接库依赖和构建目标等信息。
Cpp
CMakeLists.txt
:这是CMake构建系统用的配置文件。CMake是一个跨平台的自动化建构系统,可以产生 Makefile 或其他构建工具所需的配置文件,以在多种平台上构建项目。Makefile
:这是GNU Make的配置文件。GNU Make是一个普遍用于自动化构建的工具,可以自动决定哪些文件需要被重新编译,哪些文件需要被重新链接,并自动处理这些任务。其他:QT的
.pro
,其实本质上也会生成make文件,可以使用QMake/CMake
Python:在Python中使用
setup.py
文件,用于管理项目的包依赖和构建过程。Node.js/JavaScript:使用
package.json
文件,管理项目的模块依赖、运行脚本和项目元数据等Java:使用
pom.xml
(Maven)或build.gradle
(Gradle)文件,进行项目构建和依赖管理Ruby:使用
Gemfile
(Bundler),进行依赖管理和构建Ruby项目.NET/C#: 使用
.csproj
、.vbproj
或者.fsproj
文件,用于指定项目设置、依赖和构建过程
0
参考:https://www.cnblogs.com/ybqjymy/p/13409050.html
单个源文件
- Demo1/
- main.cc
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
# 指定生成目标
add_executable(Demo main.cc)
同一目录,多个源文件
- Demo2/
- main.cc
- MathFunctions.cc
- MathFunctions.h
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc)
或
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
多个目录,多个源文件
- Demo3/
- main.cc
- math/
- MathFunctions.cc
- MathFunctions.h
- (NEW) CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math) # 这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理
# 指定生成目标
add_executable(Demo main.cc)
# 添加链接库
target_link_libraries(Demo MathFunctions)
子目录的 CMakeLists.txt
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})
自定义编译选项
/Demo4
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo4)
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
"Use provided math implementation" ON)
# 是否加入 MathFunctions 库
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})
……未完
其他
INSTALL与COPY
- cmakelist中,copy和INSTALL files的区别和选用
- 如果我想要生成一个只有头文件没有源文件的include文件夹应该使用哪种,为什么?
在cmakelist中,copy和INSTALL files的功能很相似,它们都可以将文件从源目录复制到目标目录。主要区别在于他们的使用场景和时机:
- copy命令:会在构建(build)阶段,复制文件或目录到某个具体的位置(但我实测好像在cmake的时候就会复制)
- INSTALL:则是在安装(install)阶段执行的,可以将文件复制到安装目录下,或者制定具体的位置。它通常会结合CPACK相关命令一起使用,用于生成发布包。
对于开发者来说,通常在开发或调试阶段使用copy命令,而在准备发布软件的时候则使用INSTALL命令。
如果想要生成一个只包含了头文件的include文件夹,通常建议是使用INSTALL命令。
因为只有头文件的库通常是被设计为可以被其他应用或库引用的,所以往往需要安装到一个公共的或者标准的位置,而INSTALL命令在制作软件发布包或者进行软件安装时更为适用。
但是,如果你只是在开发阶段需要复制头文件,那么使用copy命令也是可行的。
CMake构建系统阶段
在CMake生成的构建系统中,有几个重要的阶段:
- CMake 配置生成阶段:CMake根据CMakeLists.txt及其子目录中的所有CMakeLists.txt生成Makefile或者其他形式的构建项目文件。
- Make 构建阶段:使用Make或相当于Make的其他工具(如Ninja、Visual Studio)根据CMake生成的项目文件执行编译、链接等操作,生成最终的可执行程序、库文件等。
- Install 阶段:这个阶段是在make构建阶段后面的,使用
make install
或者等效的命令执行。在此阶段,根据CMakeLists.txt中的INSTALL命令,将构建的结果(比如可执行文件、库文件、头文件等)复制或者安装到指定的目标路径,这个目标路径可以是系统的库路径、include路径,也可以是某个自定义的目录。
因此,INSTALL阶段通常是在程序或库的编译链接后面的,它是把编译结果分发到指定位置的阶段。在搭建开发环境,或者生成程序发布包的时候,往往会执行这个阶段。
简单精炼总结版:
- CMake:多个CMakeLists.txt -> Makefile等
- Make:Makefile -> 构建结果(可执行文件、库等)
- Install:将构建结果 -> 复制或安装到目标路径
CLion执行Install
参考:https://blog.csdn.net/qq_30326609/article/details/127995878
< GPT
我使用CLion时,如何执行Install阶段,我点击cmake和编译按钮后,库文件.a都生成出来了,但似乎没有执行Install相关的内容
在CLion中,执行Install阶段的操作一般不会在默认的构建操作中出现,需要通过设置额外的目标来实现。
- 首先打开"Run/Debug Configurations"对话框,可以通过"Run"菜单,或者工具栏上的下拉按钮来打开。
- 在弹出的对话框中,点击左上角的+号,然后在弹出的菜单中选择"CMake Application"。
- 新建的配置默认的Target是"all",该Target对应的是整个项目的构建。你需要更改Target为"install",这样这个配置就会执行install阶段。
- 完成上述设置后,你可以通过点击右上角的运行按钮,或者在"Run"菜单中选择对应的配置来启动,CLion会自动执行对应的构建和install过程。
请注意,如果你的CMakeLists.txt中没有设置安装路径,可能会出现权限问题,因为默认可能会安装到系统目录下。你可以通过设置CMAKE_INSTALL_PREFIX变量来制定安装路径,例如:
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/output)
cmake install命令
install命令才会把这些库添加到环境变量中,与这个有关的变量是:CMAKE_INSTALL_PREFIX。 CMAKE_INSTALL_PREFIX默认值在linux系统中位置是/usr/local,windows则是C:/Program Files (x86)
install默认点击debug/run的时候并不会触发,需要一点手动操作。
详情可以参考官网install说明:cmake install clion官网操作说明
对应图像界面位置:
下图位置可以设置run/debug的时候自动install,具体操作参考上面给出的官网。
install中的findPackage
作用:在环境变量下查找某个包,查找后的包可以用到后面。
原理:执行FindXXX.cmake程序,这个程序在cmake2.x的时候需要手写,3.x后可以使用自动生成的XXXConfig.cmake来配置找到程序包,XXX是库的名字。
install中的configure_file
该命令将 <input>
指定的文件拷贝为 <output>
指定的文件,并将 <input>
文件中 @VAR@ 或 ${VAR} 的位置替换为使用 configure_file() 命令的当前 CMakeLists.txt 中变量 VAR 的值。该命令用于外部文件获取 CMakeLists.txt 文件中变量的值。
configure_file(<input> <output) 中的 <output> 指定的文件名必须是 xxxConfig.cmake 或 xxx-config.cmake(xxx必须为小写)。
cmake install文件demo
完整项目地址:my_oatpp
#######################################################################################
# 生成Target文件,将目标文件 targets 的可导出信息存储在 <export_name> 中(内存),用于生成可导出文件。
install(
TARGETS ${OATPP_THIS_MODULE_TARGETS}
EXPORT "${OATPP_MODULE_NAME}Targets"
ARCHIVE
DESTINATION "${CMAKE_INSTALL_LIBDIR}/oatpp-${OATPP_MODULE_VERSION}"
COMPONENT Devel
LIBRARY
DESTINATION "${CMAKE_INSTALL_LIBDIR}/oatpp-${OATPP_MODULE_VERSION}"
COMPONENT Library
RUNTIME
DESTINATION "${CMAKE_INSTALL_BINDIR}/oatpp-${OATPP_MODULE_VERSION}"
COMPONENT Library
INCLUDES
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/oatpp-${OATPP_MODULE_VERSION}/${OATPP_MODULE_NAME}"
)
# 安装hpp类型文件到include文件夹下
install(DIRECTORY ${OATPP_DIRS_TO_INSTALL}
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/oatpp-${OATPP_MODULE_VERSION}/${OATPP_MODULE_NAME}"
COMPONENT Devel
FILES_MATCHING PATTERN "*.hpp"
)
# 使用生成的导出信息,生成导出目标文件
install(EXPORT "${OATPP_MODULE_NAME}Targets"
FILE "${OATPP_MODULE_NAME}Targets.cmake"
NAMESPACE oatpp::
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${OATPP_MODULE_NAME}-${OATPP_MODULE_VERSION}"
COMPONENT Devel
)
###################################################
# packaging Config
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${OATPP_MODULE_NAME}ConfigVersion.cmake"
VERSION ${OATPP_MODULE_VERSION}
COMPATIBILITY ExactVersion ## Use exact version matching.
)
## Take module-config.cmake.in file in this direcory as a template
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/module-config.cmake.in"
"${OATPP_MODULE_NAME}Config.cmake"
INSTALL_DESTINATION
"${CMAKE_INSTALL_LIBDIR}/cmake/${OATPP_MODULE_NAME}-${OATPP_MODULE_VERSION}"
PATH_VARS
OATPP_MODULE_NAME
OATPP_MODULE_VERSION
OATPP_MODULE_LIBRARIES
OATPP_MODULE_LIBDIR
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
###################################################
# 安装Config文件
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${OATPP_MODULE_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${OATPP_MODULE_NAME}ConfigVersion.cmake"
DESTINATION
"${CMAKE_INSTALL_LIBDIR}/cmake/${OATPP_MODULE_NAME}-${OATPP_MODULE_VERSION}"
COMPONENT Devel
)
实战经验 - 多层CMakeLists
系统教程
参考:【Bilibili】从零开始详细介绍CMake,有自动字幕,带md笔记资料,总长01:10:50,进度10p_s/10p
准备
说明
cmake的定义是什么 ?-----高级编译配置工具
当多个人用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件或者共享库(dll,so等等)这时候神器就出现了-----CMake!
所有操作都是通过编译CMakeLists.txt来完成的—简单
官方网站是 www.cmake.org,可以通过访问官方网站获得更多关于 cmake 的信息
学习CMake的目的,为将来处理大型的C/C++/JAVA项目做准备
CMake安装
1、绝大多数的linux系统已经安装了CMake
2、Windows或某些没有安装过的linux系统,去http://www.cmake.org/HTML/Download.html 可以下载安装
CMake一个HelloWord
1、步骤一,写一个HelloWord
#main.cpp
#include <iostream>
int main(){
std::cout << "hello word" << std::endl;
}
2、步骤二,写CMakeLists.txt
#CMakeLists.txt
PROJECT (HELLO)
SET(SRC_LIST main.cpp)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
3、步骤三、使用cmake,生成makefile文件
$ cmake . # 当前目录用cmake编译
输出:
[root@localhost cmake]# cmake .
CMake Warning (dev) in CMakeLists.txt:
Syntax Warning in cmake code at
/root/cmake/CMakeLists.txt:7:37
Argument not separated from preceding token by whitespace.
This warning is for project developers. Use -Wno-dev to suppress it.
-- The C compiler identification is GNU 10.2.1
-- The CXX compiler identification is GNU 10.2.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- This is BINARY dir /root/cmake
-- This is SOURCE dir /root/cmake
-- Configuring done
-- Generating done
-- Build files have been written to: /root/cmake
目录下就生成了这些文件-CMakeFiles, CMakeCache.txt, cmake_install.cmake 等文件,并且生成了Makefile. 现在不需要理会这些文件的作用,以后你也可以不去理会。最关键的是,它自动生成了Makefile.
4、步骤4、使用make命令编译
root@localhost cmake]$ make
Scanning dependencies of target hello
[100%] Building CXX object CMakeFiles/hello.dir/main.cpp.o
Linking CXX executable hello
[100%] Built target hello
5、最终生成了Hello的可执行程序
【字典】CMakeLists常用命令
以上一章的HelloWord为例
项目管理类
一个最简单的demo:
cmake_minimum_required(VERSION 3.16)
project(ProjectName1)
set(CMAKE_CXX_STANDARD 23)
add_executable(ppp1 main.cpp)
project
指定工程名与语言
可以用来指定工程的名字和支持的语言,默认支持所有语言
PROJECT (HELLO) 指定了工程的名字,并且支持所有语言—建议
PROJECT (HELLO CXX) 指定了工程的名字,并且支持语言是C++
PROJECT (HELLO C CXX) 指定了工程的名字,并且支持语言是C和C++
该指定隐式定义了两个CMAKE的变量
<projectname>_BINARY_DIR
,本例中是 HELLO_BINARY_DIR
<projectname>_SOURCE_DIR
,本例中是 HELLO_SOURCE_DIR
MESSAGE关键字就可以直接使用者两个变量,当前都指向当前的工作目录,后面会讲外部编译
问题:如果改了工程名,这两个变量名也会改变
解决:又定义两个预定义变量:PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,这两个变量和HELLO_BINARY_DIR,HELLO_SOURCE_DIR是一致的。所以改了工程名也没有关系
add_executable
生成可执行文件
生成可执行文件
add_executable(hello ${SRC_LIST})
生成的可执行文件名是hello,源文件读取变量SRC_LIST中的内容
也可以直接写
add_executable(hello main.cpp)
上述例子可以简化的写成
PROJECT(HELLO)
add_executable(hello main.cpp)
注意:工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的
add_library
生成静态/动态编译库
例如
add_library(TSDB ./TSDB.c)
add_subdirectory
添加子项目
例如
# 添加子项目
add_subdirectory(src)
add_subdirectory(examples)
add_subdirectory(tests)
include_directories
搜索头文件
include_directories
,一般使用target_include_directories
,有时会用
增加头文件搜索路径,可以用于简化缩短工程内部或系统中的头文件路径
也可以用于指定在工程的某个外部文件夹中寻找头文件
例如
include_directories(/usr/include/hello)
link_libraries
搜索 (链接) 库
link_libraries
target_link_libraries
,一般用的是这个
例如
target_link_libraries(main libhello.a) # 注意这个只能写在 add_executable 的后面
常用、通用类
set
指定变量
用来显示的指定变量的
SET(SRC_LIST main.cpp) SRC_LIST变量就包含了main.cpp
也可以 SET(SRC_LIST main.cpp t1.cpp t2.cpp)
message
输出自定义信息
向终端输出用户自定义的信息
主要包含三种信息:
- SEND_ERROR,产生错误,生成过程被跳过。
- SATUS,输出前缀为—的信息。
- FATAL_ERROR,立即终止所有 cmake 过程.
也可以不加这个信息类别,例如:
# 编译信息
message("Compile Info")
message(" Compile Platform")
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(" Linux")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
message(" Windows")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
message(" MacOS")
else()
message(WARNING, " Other")
endif()
message(" Compile ToolChain")
if (MINGW)
message(" MINGW")
elseif (MSVC)
message(" MSVC")
else ()
message(WARNING, " Other")
endif ()
message(" Compiler Version")
if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
message(" MSVC ${CMAKE_CXX_COMPILER_VERSION}")
elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
message(" GCC ${CMAKE_CXX_COMPILER_VERSION}")
elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
message(" Clang ${CMAKE_CXX_COMPILER_VERSION}")
else()
message(WARNING, " Other ${CMAKE_CXX_COMPILER_VERSION}")
endif()
路径类
XXX_SOURCE_DIR
工程路径、或CMakeLists所在路径
修改:固定,不可修改
PROJECT_SOURCE_DIR 或 HELLO_SOURCE_DIR (HELLO是当前项目名),工程路径,是 “/root/cmake01”
XXX_BINARY_DIR
执行cmake的路径
修改:固定,不可修改
PROJECT_BINARY_DIR 或 HELLO_BINARY_DIR (HELLO是当前项目名),编译路径,比如在下面 “外部构建” 的例子中,是 “/root/cmake01/bulid”
EXECUTABLE_OUTPUT_PATH
构建的可执行文件的路径
修改:XXX_OUTPUT_PATH 可以使用 SET
进行设置 重定向
LIBRARY_OUTPUT_PATH
构建的库文件的路径
修改:XXX_OUTPUT_PATH 可以使用 SET
进行设置 重定向
CMAKE_INSTALL_PREFIX
CMake路径的前缀
用处:
CMake Install
命令中,相对路径执行的前缀
修改:
默认值:在 /usr/local/
CLion默认值及设置:在CLion中可以在设置 > 构建执行部署 > CMake 中看到和设置CMake选项
默认CMake选项:
-G Ninja -DCMAKE_BUILD_TYPE=Debug "-DCMAKE_INSTALL_PREFIX=D:/CLion 2022.2.4/bin/ninja/win/ninja.exe"
可以改这个CMake选项也可以改他下面的缓存变量
cmake时设置:cmake -D CMAKE_INSTALL_PREFIX=/usr 在cmake的时候指定CMAKE_INSTALL_PREFIX变量的路径
CMakeLists文件中设置:set(CMAKE_INSTALL_PREFIX "../")
CMAKE_INCLUDE_PATH
和 CMAKE_LIBRARY_PATH
修改:
这两个是环境变量而不是 cmake 变量,cmake中无法修改,但可以在linux的bash中进行设置
例如使用环境变量
export CMAKE_INCLUDE_PATH=/usr/include/hello
其他
set_target_properties
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
install
安装共享库和头文件
find_package
寻找库
check_header
例如
check_header("stdbool.h")
check_header("stdint.h")
check_function
例如
check_function("gettid" "unistd.h")
check_function("strlcpy" "string.h")
file
各种文件操作
文件操作,根据参数一的不同有多种不同的用法
例如
file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/web/) # 递归移动?
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/web/) # 创建文件夹
file(COPY ${PROJECT_SOURCE_DIR}/web/release/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/web/) # 复制
file(GLOB_RECURSE SRCS src/*.cpp src/*.c) # 包含文件路径到对应变量
【字典】CMake常用指令
-D CMake_install_prefix
cmake -DCMAKE_INSTALL_PREFIX=/usr
-D CMake_build_type
cmake -DCMAKE_BUILD_TYPE=debug
入门
语法的基本原则
变量使用
${}
方式取值,但是在 IF 控制语句中是直接使用变量名指令(参数 1 参数 2...)
参数使用括弧括起,参数之间使用空格或分号分开。 以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.cpp 源文件就要写成:
ADD_EXECUTABLE(hello main.cpp func.cpp)
或者ADD_EXECUTABLE(hello main.cpp;func.cpp)
指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令(LincZero:我喜欢用小写)
语法注意事项
- SET(SRC_LIST main.cpp) 可以写成 SET(SRC_LIST “main.cpp”),如果源文件名中含有空格,就必须要加双引号
- ADD_EXECUTABLE(hello main) 后缀可以不写,他会自动去找.c和.cpp,最好不要这样写,可能会有这两个文件main.cpp和main
内部构建和外部构建
- 上述例子就是内部构建,他生产的临时文件特别多,不方便清理
- 外部构建,就会把生成的临时文件放在build目录下,不会对源文件有任何影响强烈使用外部构建方式
外部构建方式举例:
# 例子目录,CMakeLists.txt和上面例子一致
[root@localhost cmake]$ pwd
/root/cmake
[root@localhost cmake]$ ll
total 8
-rw-r--r--. 1 root root 198 Dec 28 20:59 CMakeLists.txt
-rw-r--r--. 1 root root 76 Dec 28 00:18 main.cpp
- 建立一个build目录,可以在任何地方,建议在当前目录下
- 进入build,运行cmake .. 当然..表示上一级目录,你可以写CMakeLists.txt所在的绝对路径,生产的文件都在build目录下了
- 在build目录下,运行make来构建工程
$ mkdir build
$ cd build/
$ cmake .. # 从上一级目录构建
注意外部构建的两个变量
- HELLO_SOURCE_DIR 还是工程路径
- HELLO_BINARY_DIR 编译路径 也就是 /root/cmake/bulid
多CMakeLists管理 —— 让Hello World看起来更像一个工程
- 为工程添加一个子目录 src,用来放置工程源代码
- 添加一个子目录 doc,用来放置这个工程的文档 hello.txt
- 在工程目录添加文本文件 COPYRIGHT, README
- 在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制
- 将构建后的目标文件放入构建目录的 bin 子目录
- 将 doc 目录 的内容以及 COPYRIGHT/README 安装到/usr/share/doc/cmake/
将目标文件放入构建目录的 bin 子目录
每个目录下都要有一个CMakeLists.txt说明
[root@localhost cmake]$ tree
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
外层 CMakeLists.txt
project(HELLO)
add_subdirectory(src bin)
src下的 CMakeLists.txt
add_executable(hello main.cpp)
add_subdirectory
添加子项目
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
EXCLUDE_FROM_ALL函数是将写的目录从编译中排除,如程序中的example
ADD_SUBDIRECTORY(src bin)
将 src 子目录加入工程并指定编译输出(包含编译中间结果)路径为bin 目录
如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录
用 SET 更改二进制的保存路径
SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量 来指定最终的目标二进制的位置
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
思考:加载哪个CMakeLists.txt当中
哪里要改变目标存放路径,就在哪里加入上述的定义,所以应该在src下的CMakeLists.txt下写
Install 安装
- 一种是从代码编译后直接 make install 安装
- 一种是打包时的指定 目录安装。
- 简单的可以这样指定目录:make install DESTDIR=/tmp/test
- 稍微复杂一点可以这样指定目录:./configure –prefix=/usr
如何安装HelloWord
使用CMAKE一个新的指令:INSTALL
INSTALL的安装可以包括:二进制、动态库、静态库以及文件、目录、脚本等
使用CMAKE一个新的变量:CMAKE_INSTALL_PREFIX
// 目录树结构
[root@localhost cmake]$ tree
.
├── build
├── CMakeLists.txt
├── COPYRIGHT
├── doc
│ └── hello.txt
├── README
├── runhello.sh
└── src
├── CMakeLists.txt
└── main.cpp
3 directories, 7 files
(1) 安装 文件COPYRIGHT和README
这里安装三个文件到指定路径
/**
* @param FILES后面 文件
* @param DESTINATION后
* 1. 写绝对路径
* 2. 可以写相对路径,相对路径实际路径是:${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
*/
install(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
CMAKE_INSTALL_PREFIX 问题
- 默认值:在 /usr/local/
- cmake时设置:cmake -DCMAKE_INSTALL_PREFIX=/usr 在cmake的时候指定CMAKE_INSTALL_PREFIX变量的路径
- CMakeLists文件中设置:set(CMAKE_INSTALL_PREFIX "../")
(2) 安装脚本 runhello.sh
PROGRAMS:非目标文件的可执行程序安装(比如脚本之类)
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
说明:实际安装到的是 /usr/bin
(3) 安装 doc 中的 hello.txt
一、是通过在 doc 目录建立CMakeLists.txt ,通过install下的file
二、是直接在工程目录通过
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)
DIRECTORY 后面连接的是所在 Source 目录的相对路径
注意:abc 和 abc/有很大的区别
目录名不以/结尾:这个目录将被安装为目标路径下的
目录名以/结尾:将这个目录中的内容安装到目标路径
安装过程
命令
cmake ..
make
make install
最终成果
$ make install
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/share/doc/cmake/COPYRIGHT
-- Installing: /usr/local/share/doc/cmake/README
-- Installing: /usr/local/bin/runhello.sh
-- Installing: /usr/local/share/doc/cmake
-- Installing: /usr/local/share/doc/cmake/hello.txt
静态库和动态库的构建
任务:
1,建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出 Hello World 字符串。
2,安装头文件与共享库。
静态库和动态库的区别
扩展名
- 静态库:一般为“.a”或“.lib”
- 动态库:一般为“.so”或“.dll”
是否编译入可执行文件
静态库:是。静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行
动态库:否。在编译时不会放到连接的目标程序中,即可执行文件无法单独运行
构建实例
[root@localhost cmake2]$ tree
.
├── build
├── CMakeLists.txt
└── lib
├── CMakeLists.txt
├── hello.cpp
└── hello.h
hello.h中的内容
#ifndef HELLO_H
#define Hello_H
void HelloFunc();
#endif
hello.cpp中的内容
#include "hello.h"
#include <iostream>
void HelloFunc(){
std::cout << "Hello World" << std::endl;
}
项目中的cmake内容
PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin)
lib中CMakeLists.txt中的内容
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY
/**
* @param arg1 hello:就是正常的库名,生成的名字前面会加上lib,最终产生的文件是libhello.so
* @param arg2 SHARED,动态库 STATIC,静态库
* @param arg3 ${LIBHELLO_SRC} :源文件
*/
add_library(hello SHARED ${LIBHELLO_SRC})
同时构建静态和动态库
同名冲突问题
一般来说,同名的话,只会生成一个
# 如果用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是.a
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
不同名解决冲突
一个解决方法是让他们不同名,这样比较简单粗暴
# 修改静态库的名字,这样是可以的,但是我们往往希望他们的名字是相同的,只是后缀不同而已
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
set_target_properties
设置输出名以解决冲突
另一种解决方法是使用 SET_TARGET_PROPERTIES
,这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
同时构建静态和动态库
SET(LIBHELLO_SRC hello.cpp)
# 静态库
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC}) # 这里的名字依然是不弄冲突,但下面可以设置输出名来让他们输出名相同
# 静态库 - 设置一些属性
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 动态库
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
# 动态库 - 设置一些属性
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
常见设置属性
# 输出名,OUTPUT_NAME,对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# 清理同名库,CLEAN_DIRECT_OUTPUT
# cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 版本号,VERSION
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
动态库版本号
一般动态库都有一个版本号的关联,静态库没有也不需要
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
CMakeLists.txt 插入如下
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION 指代动态库版本,SOVERSION 指代 API 版本。
install
安装共享库和头文件
本例中我们将 hello 的共享库安装到 <prefix>/lib
目录,
将 hello.h 安装到 <prefix>/include/hello
目录
# 头文件,FILES
# 文件放到该目录下
install(FILES hello.h DESTINATION include/hello)
# 二进制,静态库,动态库安装,TARGETS
# ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
install(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
参数:
/**
* arg1 arg2 "arg3 DESTINATION arg4"xn
* @param arg1
* TARGETS,二进制,静态库,动态库安装
* FILES,单纯的文件复制,如头文件
* @param args2 要被复制文件
* @param arg3
* ARCHIVE,特指静态库
* LIBRARY,特指动态库
* RUNTIME,特指可执行目标二进制
* @param arg4 要被复制到的路径,前缀是 `-DCMAKE_INSTALL_PREFIX` 的值 (可设置)
*/
install(
TARGETS
hello hello_static
LIBRARY DESTINATION lib # 这里表示
ARCHIVE DESTINATION lib
)
注意:
安装的时候,指定一下路径,放到系统下:cmake -D CMAKE_INSTALL_PREFIX=/usr ..
(这里的-D后面空格可加可不加)
使用外部共享库和头文件
准备工作,新建一个目录来使用外部共享库和头文件
[root@MiWiFi-R4CM-srv cmake3]$ tree
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
main.cpp
#include <hello.h>
int main(){
HelloFunc();
}
总结
- install在 /usr/include/ 后,直接include就能找到头文件
- install在 /usr/lib/ (也可能是/usr/lib64/或/usr/lib32/) 后,直接在CMakeLists中加入
target_link_libraries(main libhello.a)
就能找到库文件
常见bug解决
解决:make后头文件找不到的问题
PS:include <hello/hello.h> 这样include是可以,这么做的话,就没啥好讲的了
关键字:INCLUDE_DIRECTORIES 这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割
在CMakeLists.txt中加入头文件搜索路径
include_directories(/usr/include/hello)
感谢:网友:zcc720的提醒
另一个解决方案是:
我们上面例子中使用了绝对路径 INCLUDE_DIRECTORIES(/usr/include/hello) 来指明include路径的位置
我们还可以使用另外一种方式,使用环境变量 export CMAKE_INCLUDE_PATH=/usr/include/hello
解决:找到引用的函数问题
报错信息:undefined reference to `HelloFunc()'
关键字:LINK_DIRECTORIES 添加非标准的共享库搜索路径
指定第三方库所在路径,LINK_DIRECTORIES(/home/myproject/libs)
关键字:TARGET_LINK_LIBRARIES 添加需要链接的共享库
TARGET_LINK_LIBRARIES的时候,只需要给出动态链接库的名字就行了。
在CMakeLists.txt中插入链接共享库,主要要插在executable的后面
查看main的链接情况
[root@MiWiFi-R4CM-srv bin]$ ldd main
linux-vdso.so.1 => (0x00007ffedfda4000)
libhello.so => /lib64/libhello.so (0x00007f41c0d8f000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f41c0874000)
libm.so.6 => /lib64/libm.so.6 (0x00007f41c0572000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f41c035c000)
libc.so.6 => /lib64/libc.so.6 (0x00007f41bff8e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f41c0b7c000)
链接静态库
TARGET_LINK_LIBRARIES(main libhello.a)
技巧
头文件递归包含
这里本质上是递归调用:include_directories
# 递归包含头文件的函数。本质上是递归调用:include_directories
function(include_sub_directories_recursively root_dir)
if (IS_DIRECTORY ${root_dir}) # 当前路径是一个目录吗,是的话就加入到包含目录
# if (${root_dir} MATCHES "include")
message("include dir: " ${root_dir})
include_directories(${root_dir})
# endif()
endif ()
file(GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*) # 获得当前目录下的所有文件,让如ALL_SUB列表中
foreach (sub ${ALL_SUB})
if (IS_DIRECTORY ${root_dir}/${sub})
include_sub_directories_recursively(${root_dir}/${sub}) # 对子目录递归调用,包含
endif ()
endforeach ()
endfunction()
# 将目录下的所有头文件包含进PROJECT_BINARY_DIR中
include_sub_directories_recursively(${PROJECT_SOURCE_DIR}/src)
源文件递归包含
这里本质上是将对应的cpp/c文件全部存储在一个变量里
# 将目录下的所有源文件包含进SRCS中
file(GLOB_RECURSE SRCS src/*.cpp src/*.c)
add_executable(${PROJECT_NAME} main.cpp ${SRCS})