官方教程地址
官方代码地址
文件目录
[root@VM-4-6-centos staticLib]# tree .
.
|-- include
| `-- swap.h
|-- main
|-- main.cpp
`-- src
`-- swap.cpp
2 directories, 4 files
swap.cpp
void swap(int *a, int *b) {
int t = 0;
t = *a; *a = *b; *b = t;
}
swap.h
void swap(int *a, int *b);
main.cpp
#include
#include
#include "swap.h"
int main() {
int a = 1, b = 2;
std::cout << a << " " << b << std::endl;
swap(&a,&b);
std::cout << a << " " << b << std::endl;
}
编译指令
$ g++ main.cpp src/swap.cpp -o main -Iinclude
.
|-- include
| `-- swap.h
|-- main
|-- main.cpp
|-- src
| |-- libSwap.a
| |-- swap.cpp
| `-- swap.o
`-- staticmain
2 directories, 7 files
$ cd src/
$ g++ swap.cpp -c -I../include
$ ar rs libSwap.a swap.o
ar: creating libSwap.a
$ cd ..
$ g++ main.cpp -Iinclude -Lsrc -lSwap -o staticmain
$ ./staticmain
1 2
2 1
-I
:说明头文件在哪里-L
:说明链接库的位置-l
:需要链接的文件名称-o
: 生成可执行文件的名称*.o
目标文件,$ g++ swap.cpp -c -I../include
。ar
指令意为, ar - create, modify, and extract from archives
归档,可以通过man ar
来查看它后面可以跟的指令,ar rs lib*.a *.o
意思是把目标文件归档到静态库中。g++ main.cpp -Iinclude -Lsrc -lSwap -o staticmain
编译参数如上所示。ps:直接写上静态库的也可以编译,如g++ main.cpp -Iinclude src/libSwap.a
。还可以看到静态库里面有什么目标文件
[root@VM-4-6-centos src]# ar t libSwap.a
swap.o
[root@VM-4-6-centos src]$ g++ swap.cpp -I../include -fPIC -shared -o libSwap.so
[root@VM-4-6-centos src]$ ls
libSwap.a libSwap.so swap.cpp swap.o
# 等价于
# g++ swap.cpp -I../include -c -fPIC
# g++ -shared -o libSwap.so swap.o
链接写法同静态库
$ g++ main.cpp -Iinclude -Lsrc -lSwap -o dynamicmain
$ g++ main.cpp -Iinclude src/libSwap.so
静态库和动态库都存在时,优先使用动态库(这里是用这种-L -l的方式),如果是直接将库写在后面则是看哪个库靠前。
不明白的问题
## 这里不可以直接执行
[root@VM-4-6-centos staticLib]# ./dynamicmain
./dynamicmain: error while loading shared libraries: libSwap.so: cannot open shared object file: No such file or directory
## a.out 和 dynamicmain 大小一样
[root@VM-4-6-centos staticLib]# ll
total 48
-rwxr-xr-x 1 root root 9064 Dec 6 23:04 a.out
-rwxr-xr-x 1 root root 9064 Dec 6 23:08 dynamicmain
drwxr-xr-x 2 root root 4096 Dec 6 20:56 include
-rwxr-xr-x 1 root root 9096 Dec 6 21:06 main
-rw-r--r-- 1 root root 200 Dec 6 21:06 main.cpp
drwxr-xr-x 2 root root 4096 Dec 6 22:59 src
[root@VM-4-6-centos staticLib]# g++ main.cpp -Iinclude src/libSwap.so
[root@VM-4-6-centos staticLib]# ./a.out
1 2
2 1
## 为什么这里可以直接执行
## PAth字母打错了
[root@VM-4-6-centos staticLib]# LD_LIBRARY_PAth=src ./dynamicmain
./dynamicmain: error while loading shared libraries: libSwap.so: cannot open shared object file: No such file or directory
[root@VM-4-6-centos staticLib]# tree .
.
|-- a.out
|-- dynamicmain
|-- include
| `-- swap.h
|-- main
|-- main.cpp
`-- src
|-- libSwap.a
|-- libSwap.so
|-- swap.cpp
`-- swap.o
2 directories, 9 files
[root@VM-4-6-centos staticLib]# LD_LIBRARY_PATH=src ./dynamicmain
1 2
2 1
首先是最基础的三个参数
必须首先使用指定最低 CMake 版本cmake_minimum_required()
命令,我们使用project()
命令设置项目名称,add_executable()
命令告诉 CMake 使用指定的源代码文件创建可执行文件。set()
函数,给变量赋值。
# TODO 1: Set the minimum required version of CMake to be 3.10
cmake_minimum_required(VERSION 3.10)
# TODO 2: Create a project named Tutorial
project(Tutorial VERSION 1.7)
# TODO 7: Set the project version number as 1.0 in the above project command
# TODO 7 project的第二个参数会为项目设置版本号 在“1.7”中 1 代表 Tutorial_VERSION_MAJOR
# 7 代表 Tutorial_VERSION_MINOR
# 用于我们在h.in文件中替换配置使用
# TODO 6: Set the variable CMAKE_CXX_STANDARD to 11
# and the variable CMAKE_CXX_STANDARD_REQUIRED to True
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# TODO 8: Use configure_file to configure and copy TutorialConfig.h.in to
# TutorialConfig.h
configure_file(TutorialConfig.h.in TutorialConfig.h)
# TODO 3: Add an executable called Tutorial to the project
# Hint: Be sure to specify the source file as tutorial.cxx
add_executable(Tutorial tutorial.cxx)
# TODO 9: Use target_include_directories to include ${PROJECT_BINARY_DIR}
# 添加头文件的位置,因为产生的 TutorialConfig.h 文件在这里
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
# 关于这两个路径是什么,可以使用如下命令打印出来
message(STATUS "PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
# 结果是
# -- PROJECT_BINARY_DIR: /home/maheWork/learnCmake/cmake-3.25.1-tutorial-source/Step1_build
# -- PROJECT_SOURCE_DIR: /home/maheWork/learnCmake/cmake-3.25.1-tutorial-source/Step1
# 实践发现 这两个参数指的是 顶层CMakelists的绝对地址和产生二进制文件的目录的地址
configure_file([input][output])
,根据输入文件的替换规则,会读取编译时的一些信息替换给自己设置的宏定义,结合上面注释中的TODO 7和以下文件信息能够理解。
TutorialConfig.h.in
// the configured options and settings for Tutorial
// TODO 10: Define Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
TutorialConfig.h
// the configured options and settings for Tutorial
// TODO 10: Define Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 7
学习如何创建库、添加库
.
|-- CMakeLists.txt
|-- MathFunctions
| |-- CMakeLists.txt
| |-- MathFunctions.h
| `-- mysqrt.cxx
|-- TutorialConfig.h.in
`-- tutorial.cxx
生成库文件: add_library( )
在MathFunctions/CMakeLists.txt
将cpp实现文件添加入库
添加子目录: add_subdirectory( )、target_link_libraries( )、target_include_directories( )
既然要用其他目录下的文件,就要把该目录添加入工程,将可执行文件需要链接的库写出,需要的头文件写出。
根据编译条件自动添加库
cmake_minimum_required(VERSION 3.10)
project(Tutorial VERSION 1.0)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# configure_file(TutorialConfig.h.in TutorialConfig.h)
# 之前的话 USE_MYMATH 获取不到默认值 ON
# OPTION 中只有 ON 和 OFF
option(USE_MYMATH "Use tutorial provided math implementation" ON)
configure_file(TutorialConfig.h.in TutorialConfig.h)
# 条件判断是否要加入库,list中除了APPEND以外还有很多操作
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
#cmakedefine 用于h.in
文件中,只有当CMakeLists.txt中的同名变量为真时才会在生成的头文件中定义,区别于#define无论何时都会定义。这个定义是为了编译时 h.in
生成的h
文件中带有定义,使得cpp
中可以判断导入哪个头文件、使用哪些函数等。
额外问题
如果Configure文件配置语句在OPTION之前会有什么问题?
那么配置文件的部分值就只能通过自己的输入,获取不了option的默认值。
为库添加使用要求
target_compile_definitions()
target_compile_options()
target_include_directories()
target_link_directories()
target_link_options()
target_precompile_headers()
target_sources()
这一节让顶层list文件可以不用再包含子目录的头文件。
MathFunctions/CMakelists.txt
add_library(MathFunctions mysqrt.cxx)
# TODO 1: State that anybody linking to MathFunctions needs to include the
# current source directory, while MathFunctions itself doesn't.
# Hint: Use target_include_directories with the INTERFACE keyword
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "current source dir ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "current bin dir ${CMAKE_CURRENT_BINARY_DIR}")
message(STATUS "source dir ${CMAKE_SOURCE_DIR}")
message(STATUS "bin dir ${CMAKE_BINARY_DIR}")
可以看一下这些参数都代表什么位置
-- current source dir /home/maheWork/learnCmake/cmake-3.25.1-tutorial-source/Step3/MathFunctions
-- current bin dir /home/maheWork/learnCmake/cmake-3.25.1-tutorial-source/Step3_build/MathFunctions
-- source dir /home/maheWork/learnCmake/cmake-3.25.1-tutorial-source/Step3
-- bin dir /home/maheWork/learnCmake/cmake-3.25.1-tutorial-source/Step3_build
CURRENT 代表当前目录。
INTERFACE 的官网解释是 Remember INTERFACE
means things that consumers require but the producer doesn’t,意味自己不需要这个头文件但是如果其他其他调用需要这个头文件。
使用接口库设置 C++ 标准,使用生成器表达式添加编译器警告标志
target_compile_features
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# 这样的话就可以把后两句注释掉了
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
target_compile_features()
用来设置编译器版本
set(gcc_like_cxx "$")
set(msvc_cxx "$")
target_compile_options(tutorial_compiler_flags
INTERFACE
"$<${gcc_like_cxx}:$>"
"$<${msvc_cxx}:$>"
)
安装
在make
完之后执行make install
指令可以按照makefile
的内容将可执行文件、头文件、库文件安装到某个地方,默认是usr/local
为前缀,然后就可以直接在命令行通过文件名直接调用了
教程中出现三种情况:
install(TARGETS 库名 DESTINATION lib)
install(FILES (头文件名)MathFunctions.h DESTINATION include)
install(TARGETS (可执行文件名)Tutorial DESTINATION bin)
测试
在make
完之后执行make test
指令
# Enable
enable_testing()
# 测试名称和参数,以及成功的条件
add_test(NAME Runs COMMAND Tutorial 25)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# 用函数的形式
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction()
# 使用示例
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
# 以上都是写在CMakelists中的