CMake、CMakeLists.txt 基础语法

发布时间:2023-03-26 18:09:18 作者:yexindonglai@163.com 阅读(1979)

前言

代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。CMake是最常用的构建工具,诞生于1977年,主要用于C语言的项目。但是实际上 ,任何只要某个文件有变化,就要重新构建的项目,都可以用CMake构建

cmake是一个根据指定的Shell命令进行构建的工具。它的规则很简单,你规定要构建哪个文件、它依赖哪些源文件,当那些文件有变动时,如何重新构建它。

cmake 构建命令如下

  1. cmake .. //或者cmake.这里两个点表示在上层文件夹下寻找cmakelists生成makefile和相关文件
  2. make //执行makefile
  3. make install //(optional)安装
  4. make clean // 清理工程

1、创建项目

括号中的字符不需要用双引号括起来

  1. project(name)

2、set() 添加、修改变量

添加变量和修改变量用的同一个指令;

  1. # 添加常亮
  2. set(NAME_1 yexindong)
  3. # 添加字符串变量
  4. set(NAME_2 "yexindong")
  5. # 添加数值变量
  6. set(NAME_3 15)
  7. # 设置变量result1的值为"abc",并将该变量的作用域扩展至父目录。PARENT_SCOPE 变量的作用域升级到父目录,即在当前目录的父目录中也可以使用该变量
  8. set(result1 "abc" PARENT_SCOPE)
  9. # 设置全局变量,设置一个名为EXAMPLE_VAR的全局变量,并将其值设置为example_value。INTERNAL 变量定义只在当前的CMakeLists.txt文件中有效,不会传递给子目录
  10. set(EXAMPLE_VAR "example_value" CACHE INTERNAL "")

2.1 清除变量 unset()

unset会清除变量内的值

  1. unset(var CACHE)

3、常用系统变量

  1. // 常用系统变量
  2. ${PROJECT_SOURCE_DIR} // 指向项目路径,也就是src路径,如果没有就是项目路径
  3. ${PROJECT_BINSARY_DIR} // 指向编译路径,也就是build文件夹,如果没有则默认跟项目路径相同
  4. set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINSARY_DIR}/bin) // 修改常用系统变量路径EXECUTABLE_OUTPUT_PATH,也就是可执行文件输出路径
  5. set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) // 修改常用系统变量路径LIBRARY_OUTPUT_PATH,也就是库文件输出路径
  6. set(CMAKE_INSTALL_PREFFIX=/usr) // 修改cmake安装的默认前缀路径,默认是/usr/local,也就是默认是针对本用户/usr/local,可以改为针对所有用户/usr

4、打印信息

  1. # 打印字符串
  2. message(STATUS "yes")
  3. # 打印变量的值
  4. message(STATUS ${name_1})

5、add_difinitions() 增加编译选项

如果要增加c++11的相关特性,比如nullptr,则增加该句,放在add_executable()前面:

  1. add_definitions(-std=c++11)

6、指定子src目录

如果要单独一个src子文件夹放置.cpp和.h文件,则需要在根路径的cmakelists增加这句,同时src文件夹下增加额外cmakelists文件。
参考Teris源码。此时,主目录下的cmakelists主要用来包含src文件夹,并没有生成可执行文件,而src里边的cmakelists则会有生成可执行文件。

  1. add_subdirectory(src)

7、include_directories() 添加头文件路径

有两个语句都可以实现添加头文件路径,一个是include_directories()这是需要放在生成可执行文件之前。
另一个是target_include_directories()这是需要放在在生成可执行文件之后。

  1. include_directories(./common)
  2. target_include_directories(main ./common)

实测发现默认的.so路径只有一个/usr/lib,也就是库文件如果在这个路径下则不需要欧手动链接。
但如果不是在这个路径下,或者是在这个路径下的文件夹中,则需要手动链接,即添加

  1. link_libraries(“lib_path.so”)
8.1 动态库文件路径第一种方式

有两个语句都可以实现链接库文件,一个是link_libraries(path)这是在生成可执行文件之前就指定库文件路径(必须放在add_executable()的前边)。
另一个语句也可以实现链接库文件,即用target_link_libraries(main path)这是在生成target可执行文件之后链接(必须放在add_executable()的后边)
两者的区别就是在定义链接文件时实在可执行文件生成之前还是之后,都可以实现功能

8.2 动态库文件路径第二种方式

另外一种方法实现链接库文件:就是把该库文件所在路径加入到CMAKEINCLUDE_PATH中然后用find_path()指令。
但注意:加入到CMAKE_INCLUDE_PATH并不代表他会把路径提供给编译器,还是需要自己用find_path找到该头文件。
这种方式的优点在于,只要加入到路径后,所有find
指令都可以使用

  1. export CMAKE_INCLUDE_PATH=/usr/local/include/test
  2. find_path(myHeader hello.h) // 把头文件找到,并赋值给变量myHeader
  3. include_directories(${myHeader}) // 把该路径包含进来
8.3 动态库文件路径第三种方式

第三种方法实现链接库文件:就是利用find_package()来查找cmake支持的模块,或者自定义的模块来获得对应头文件和库文件路径。
这种方法参考find_package()的使用。
find_library()和find_path()查找库文件和头文件
查找库文件需提供库文件名,用find_library(var name HINTS path PATH_SUFFIXES suff1 suff2), 即搜索名称为name的库文件(实际名称是libname.so),找到后存入var中,
并可以带多个关键参数,其中HINTS关键参数代表搜索路径,PATH_SUFFIXES代表路径后缀

  1. find_library(_NVINFER_LIB nvinfer HINTS ${TRT_LIB} PATH_SUFFIXES lib lib64)

9、find_path 查找文件所在的目录

CMake中的命令find_path用于查找指定的文件的目录,如果指定的文件在目录中找到,则结果将存储在 var中,除非清除var,否则不会重复搜索。如果没找到,结果将为var-NOTFOUND。

  1. unset(var CACHE) # 清除变量
  2. find_path(var head.hpp) # 查找文件所在的目录
  3. message(STATUS ${var})

打印结果

  1. /var/data/ # 若找到,返回文件所在的目录
  2. var-NOTFOUND # 若未找到,返回的信息

10、find_package() 查找第三方库的头文件和链接库文件路径

注意:采用find_package()命令cmake的模块查找顺序是:先在变量${CMAKE_MODULE_PATH}查找,然后在/usr/shared/cmake/Modules/里边查找。

  1. find_package(CUDA REQUIRED) # 查找某个第三方库的cmake module,比如CUDA代表的就是FindCUDA.cmake这个module
  2. find_package(OpenCV REQUIRED) # 多个库分别查找, 然后统一加到include_directories和link_libraries即可
  3. target_include_directories(tensorrt PUBLIC ${CUDA_INCLUDE_DIRS} ${TENSORRT_INCLUDE_DIR})
  4. target_link_libraries(tensorrt ${CUDA_LIBRARIES} ${TENSORRT_LIBRARY} ${CUDA_CUBLAS_LIBRARIES} ${CUDA_cudart_static_LIBRARY} ${OpenCV_LIBS})

11、file()对文件和文件夹的操作

如果要自动搜索所有支持文件,则可以考虑自动搜索命令

  1. file(GLOB Sources *.cpp) # 从当前目录查询所有的.cpp文件 ,将结果赋值到 Sources 变量中
  2. file(GLOB Includes *.h) # 从当前目录查询所有的.h 文件,将结果赋值到 Includes 变量中
  3. add_executable(Cars ${Sources} ${Includes}) # 生成可执行文件
  4. # 以下2行代码执行的结果是一样的
  5. file (GLOB files *.cpp */*.cpp) # 从当前目录和子目录查询所有的.cpp文件 ,将结果赋值到 files 变量中
  6. file(GLOB_RECURSE files *.c) # 添加当前目录及其子目录下的所有c文件列表到files变量中
  7. # 搜索指定目录以及子目录中所有的以.cpp结尾的文件,然后把它们保存到 native_srcs 变量中
  8. file(GLOB_RECURSE native_srcs src/main/cpp/*.cpp)
11.1 创建文件夹
  1. # 创建某个路径文件夹,MAKE_DIRECTORY 是固定的,path是你需要创建的文件夹的全路径地址
  2. file(MAKE_DIRECTORY path)

12、add_library() 生成动态或者静态链接库

可以选择生成SHARED动态库, STATIC静态库.
注意:库名称不需要写前缀lib,系统会自动在给出的库名称前面加lib.

  1. // 创建动态库
  2. add_library(algorithms SHARED ${src_path}) // 创建动态链接库:库名称libalgorithms, SHARED表示为.so动态链接库,src_path是.cpp文件所在路径
  3. // 创建静态库
  4. set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1) // 设置不清除同名动态库hello.so
  5. set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1) // 设置不清除同名静态库hello.a
  6. set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello") // 设置静态库的输出名称为hello,从而即使跟动态库重名也能实现

13、add_executable() 生成可执行文件

第一个参数生成后的文件名称,
第二个参数是需要生成的源文件路径,一般都是 .c、 .cpp 或者.h的文件

  1. add_executable(file_name path)

14、install() 安装生成的头文件和库文件:

cmake install是 CMake 的一个命令,用于安装构建的项目到指定的目录中。

在项目的 CMakeLists.txt 文件中,你可以使用 make install 命令来指定要安装的文件和目录。install 命令的语法如下:

  1. install(TARGETS <target1> <target2> ...
  2. DESTINATION <destination>
  3. [COMPONENT <component>]
  4. [PERMISSIONS permissions...]
  5. [CONFIGURATIONS [Debug|Release|...]]
  6. [OPTIONAL]
  7. )

其中,TARGETS 参数用于指定要安装的目标(可执行文件、库文件等),DESTINATION 参数用于指定安装目标的目录。

例如,假设你的项目生成了一个可执行文件 myapp,你可以使用以下命令将它安装到 /usr/local/bin 目录中:

  1. install(TARGETS myapp
  2. DESTINATION /usr/local/bin
  3. )

你还可以使用 install 命令安装其他类型的文件,比如库文件、头文件、配置文件等。例如,要安装一个库文件和相应的头文件,可以使用以下命令:

  1. install(TARGETS mylib
  2. DESTINATION /usr/local/lib
  3. )
  4. install(FILES mylib.h
  5. DESTINATION /usr/local/include
  6. )

在构建项目时,可以使用make install命令来执行安装操作。这将把构建的目标文件复制到指定的安装目录中。

请注意,安装目录可能需要管理员权限才能写入。如果没有足够的权限,你可能需要使用 sudo make install命令来以管理员身份执行安装。

15、function() 函数

  1. # 定义函数,函数名为 getDir,后面的param1和param2都是参数
  2. function(get_dir param1 param2)
  3. message(STATUS "123-" ${param1} "-" ${param2})
  4. endfunction()
  5. # 调用函数,两个参数之间用 空格隔开
  6. get_dir(1 2)

16、STRING() 用法

16.1、 FIND 在字符串中查询子子字符串是否存在

在CMake中,可以使用字符串操作函数来判断一个字符串中是否包含另一个字符串。以下是一些常用的函数:
在<string>中查找<substring>,如果找到则将其位置保存在<output_variable>中,否则将其赋值为-1。

  1. STRING(FIND <string> <substring> <output_variable>)

示例

  1. # 判断字符串是否包含 cmake-build-debug ,将结果输出到indexOfStr变量,若包含返回 > -1的值,若不包含返回-1
  2. STRING(FIND ${dir} cmake-build-debug indexOfStr)
16.2、 SUBSTRING 字符串截取

截取字符串str从索引start开始的长度为length的子字符串。

  1. string(SUBSTRING str start length)

示例

  1. # 截取字符串从索引1开始的长度为3的子字符串
  2. string(SUBSTRING "hello world" 1 3 result)
  3. message(STATUS "result: ${result}") # 输出 "ell"
16.3、 LENGTH 获取字符串长度
  1. #获取字符串str的长度为len。
  2. string(LENGTH str len)
  3. # 获取字符串的长度
  4. string(LENGTH "hello world" len)
  5. message(STATUS "len: ${len}") # 输出 "11"
16.4、 TOLOWER 将字符串str转换为小写字母。
  1. # 将字符串str转换为小写字母。
  2. string(TOLOWER str)
  3. # 将字符串转换为小写字母
  4. string(TOLOWER "HELLO WORLD" result)
  5. message(STATUS "result: ${result}") # 输出 "hello world"
16.5、TOUPPER 将字符串str转换为大写字母。
  1. # 将字符串str转换为大写字母。
  2. string(TOUPPER str)
  3. # 将字符串转换为大写字母
  4. string(TOUPPER "hello world" result)
  5. message(STATUS "result: ${result}") # 输出 "HELLO WORLD"
16.6、REPLACE:替换字符串中的子字符串
  1. string(REPLACE "world" "CMake" my_replaced_string "hello world")
  2. message("Replaced string is ${my_replaced_string}")
  3. # 输出:Replaced string is hello CMake
16.7、COMPARE:比较两个字符串
  1. string(COMPARE EQUAL "hello" "hello" my_equal)
  2. message("Strings are equal: ${my_equal}")
  3. string(COMPARE EQUAL "hello" "world" my_equal)
  4. message("Strings are equal: ${my_equal}")
  5. # 输出:
  6. Strings are equal: TRUE
  7. Strings are equal: FALSE
16.8、MATCH:匹配正则表达式
  1. string(MATCH "hello" "hello world" my_match)
  2. message("Matched string is ${my_match}")
  3. # 输出:
  4. Matched string is hello
16.9、REGEX:正则表达式替换
  1. string(REGEX REPLACE "world" "CMake" my_replaced_string "hello world")
  2. message("Replaced string is ${my_replaced_string}")
  3. # 输出:
  4. Replaced string is hello CMake
16.10、ASCII:获取字符的ASCII码值
  1. string(ASCII "a" my_ascii_value)
  2. message("ASCII value is ${my_ascii_value}")
  3. # 输出:
  4. ASCII value is 97
16.11、CONFIGURE:在字符串中进行配置
  1. set(my_string "Hello ${CMAKE_PROJECT_NAME}!")
  2. string(CONFIGURE "${my_string}" my_configured_string)
  3. message("Configured string is ${my_configured_string}")
  4. # 输出:
  5. Configured string is Hello <project-name>!
  6. 其中,<project-name>会被替换为实际的项目名称

17、while 循环

CMake 中 while 循环的语法为:

  1. while(<condition>)
  2. # 循环体
  3. # ...
  4. endwhile()

<condition> 是循环条件,可以是任何返回布尔值(即 true 或 false)的表达式或变量。当条件成立时,循环体会被执行,循环体可以包含任意 CMake 命令或函数。循环体执行完后,继续检查循环条件,如果条件仍然成立,则重复执行循环体,直到循环条件不成立。

下面是一些 while 循环的用法示例:

例子 1
  1. set(i 0)
  2. while(i LESS 5)
  3. message("i = ${i}")
  4. math(EXPR i "${i} + 1")
  5. endwhile()

输出:

  1. i = 0
  2. i = 1
  3. i = 2
  4. i = 3
  5. i = 4
例子 2
  1. set(j 10)
  2. while(j GREATER_EQUAL 0)
  3. message("j = ${j}")
  4. math(EXPR j "${j} - 2")
  5. endwhile()

输出:

  1. j = 10
  2. j = 8
  3. j = 6
  4. j = 4
  5. j = 2
  6. j = 0
例子 3
  1. set(k 3)
  2. while(k)
  3. message("Hello, world!")
  4. math(EXPR k "${k} - 1")
  5. endwhile()

输出:

  1. Hello, world!
  2. Hello, world!
  3. Hello, world!
例子 4
  1. set(str "abc")
  2. while(str MATCHES "a.*")
  3. message("${str}")
  4. string(REGEX REPLACE "^a" "" str "${str}")
  5. endwhile()

输出:

  1. abc
  2. bc
  3. c

18、aux_source_directory 添加指定目录中的源文件到变量

aux_source_directory 是一个 CMake 命令,用于将指定目录中的源文件自动添加到变量中。这个命令的语法如下:

  1. aux_source_directory(<dir> <variable>)

其中:

  • <dir> 是要搜索源文件的目录名称,
  • <variable> 是一个变量,用于存储找到的源文件列表。

注意.c.cpp 才是源文件,.h结尾的头文件不是源文件

aux_source_directory 命令会自动搜索指定目录下的所有源文件,并将它们的完整路径添加到 <variable> 变量中。这个命令会自动为每个源文件生成一个相对路径,并将它们存储在 <variable> 变量中。

aux_source_directory 命令通常用于自动发现源文件,特别是在构建一个大型项目时,手动列出每个源文件可能会很繁琐和容易出错。通过使用 aux_source_directory 命令,可以更方便地管理源文件列表。

以下是一个示例,演示了如何使用 aux_source_directory 命令:

  1. # 搜索 src 目录下的所有源文件,并将它们存储在 SOURCES 变量中
  2. aux_source_directory(src SOURCES)
  3. # 将 SOURCES 变量添加到一个可执行目标中
  4. add_executable(myapp ${SOURCES})

在上面的示例中,aux_source_directory 命令会搜索 src 目录下的所有源文件,并将它们的完整路径存储在 SOURCES 变量中。然后,SOURCES 变量被添加到 add_executable 命令中,作为构建可执行目标 myapp 的源文件列表。

需要注意的是,aux_source_directory 命令只会在第一次调用时将源文件添加到变量中,后续对同一目录调用该命令不会再次添加源文件。如果你在构建过程中添加了新的源文件,需要重新运行 CMake 来更新源文件列表。

关键字c++