C语言-Cmake-CMakeLists.txt教程

x33g5p2x  于2022-07-07 转载在 其他  
字(7.5k)|赞(0)|评价(0)|浏览(615)

介绍

CMakeLists.txt官网教程 , cmake命令配置等教程

CMakeLists.txt文件使用的是Cmake命令完成的,那么我们来了解下常用的命令使用, 这是必须要会的,否则静态库,动态库,都没法导入,而且项目文件管理等都靠这个文件, 就算你代码学的在好,这个不懂最后都不可能是一个完整的项目,只能在编译工具跑跑而已,不能发布出去

CMake是跨平台编译工具,比make更高级一些。其编译的主要工作是生成CMakeLists.txt文件,然后根据该文件生成Makefile,最后调用make来生成可执行程序或者动态库,具体流程如下:

以下教程都是基于Clion工具进行的, Clion极大地简化了我们构造C的难度,我们大部分情况只需要关注开发代码就行, 构建的部分在项目开始的时候配置一下就行, 下面就是讲解如何进行配置 , 前提需要将Clion-MinGW配置好具体教程: http://t.csdn.cn/FnTra

入门

  1. cmake_minimum_required(VERSION 3.20) # 声明了需要使用的cmake的最低版本
  2. project(example1 VERSION 1.0.0 LANGUAGES C) # 项目的名字 版本 编译语言
  3. set(CMAKE_C_STANDARD 11) #c编译器的版本
  4. add_executable(example1 main.c) # 通过源文件main.c生成可执行文件demo.exe

当我们运行编译后,在cmake-build-debug目录下就会出现example1.exe 可执行文件了 ,当然如果只是学习C这点默认配置其实就够了
但是如果是自己开发项目,因为文件多了,同时还需要引入外部的各种库文件,那么这点配置就根本不行了, 在学习配置CMakeLists.txt前我么需要先学习下cmake的一些语法,因为CMakeLists.txt里面内容就是使用cmake编写的

cmake常用的语法

因为我们又不用把CMake语法都学完,我们单纯的只是想打包项目而已,所以我们就将打包项目常用的语法说下(没用到的就不写了)

注释

行注释使用#;块注释使用#[[xxxxx]]。比如:

  1. # Multi line comments follow
  2. #[[
  3. xxxxx
  4. ]]

变量

程序变量

CMake中程序变量使用set和unset命令设置或者取消设置变量。作用域只是在CMakeLists.txt里

设置的变量可以是字符串,数字或者列表 ,语法: set(变量名 变量值) 比如:

  1. # Set variable
  2. set(AUTHOR_NAME Farmer)
  3. set(AUTHOR "Farmer Li")
  4. set(AUTHOR Farmer\ Li)
  5. # Set list
  6. set(SLOGAN_ARR To be)
  7. set(SLOGAN_ARR To;be)
  8. set(SLOGAN_ARR "To;be")
  9. set(NUM 30) # Saved as string, but can compare with other number string
  10. set(FLAG ON) # Bool value

主要有以下要点:

  • 如果要设置的变量值包含空格,则需要使用双引号或者使用""转义,否则可以省略双引号;
  • 如果设置多个值或者字符串值的中间有";“,则保存成list,同样是以”;"分割的字符串;
  • 变量可以被list命令操作,单个值的变量相当于只有一个元素的列表;
  • 引用变量:${variable},在if()条件判断中可以简化为只用变量名。
  • unset(variable) 删除变量

环境变量

CMake允许设置环境变量,环境变量通过特殊的形式$ENV{varName}获取,通过SET(ENV{变量名} "变量值")设置环境变量

注意: 设置环境变量作用只是在CMakeLists.txt编译结束后就没了, 所以一般我们就来获取环境变量而已,和判断是否存在

共有变量

提供信息的变量可以提供某种信息,通常只需要读取变量即可,而不需要对变量进行修改。

PROJECT_SOURCE_DIR工程顶层目录,也就是顶层 CMakeLists.txt 源码所在目录
PROJECT_BINARY_DIR 工 程 BINARY_DIR , 也 就 是 顶 层 CMakeLists.txt 源 码 的BINARY_DIR
CMAKE_SOURCE_DIRPROJECT_SOURCE_DIR 等价
CMAKE_BINARY_DIRPROJECT_BINARY_DIR 等价
CMAKE_CURRENT_SOURCE_DIR 当前源码所在路径
CMAKE_CURRENT_BINARY_DIR 当前源码的 BINARY_DIR
CMAKE_MAJOR_VERSION cmake 的主版本号
CMAKE_MINOR_VERSION cmake 的次版本号
CMAKE_VERSION cmake 的版本号(主+次+修订)
PROJECT_VERSION_MAJOR工程的主版本号
PROJECT_VERSION_MINOR 工程的次版本号
PROJECT_VERSION工程的版本号
CMAKE_PROJECT_NAME 工程的名字
PROJECT_NAME 工程名,与 CMAKE_PROJECT_NAME 等价

消息打印

  1. MESSAGE(打印内容)
  2. MESSAGE( STATUS 打印内容)

执行系统命令

执行shell命令

  1. execute_process(COMMAND <一句shell命令> WORKING_DIRECTORY <这句shell命令执行的工作目录>)

如果命令不依赖于某个目录,而是全局的话那么可以直接使用

  1. execute_process(COMMAND echo "Hello")

执行shell脚本

  1. execute_process(COMMAND sh test.sh WORKING_DIRECTORY <test.sh所在目录> )

查询文件

  1. file(GLOB_RECURSE ALL_SRC
  2. src/**.c
  3. # module2/*.c
  4. )

递归查询所有src下面所有后缀为.c的文件,并且保存到ALL_SRC里(List方式)

创建目录

  1. execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory 目录创建)

复制文件

  1. execute_process( COMMAND ${CMAKE_COMMAND} -E copy 文件/文件列表 目标目录)

复制文件夹

  1. execute_process( COMMAND ${CMAKE_COMMAND} -E copy_directory 目录/目录列表 目标地址)

列表

  1. list(LENGTH <list> <output variable>)
  2. list(GET <list> <element index> [<element index> ...] <output variable>)
  3. list(APPEND <list> <element> [<element> ...])
  4. list(FIND <list> <value> <output variable>)
  5. list(INSERT <list> <element_index> <element> [<element> ...])
  6. list(REMOVE_ITEM <list> <value> [<value> ...])
  7. list(REMOVE_AT <list> <index> [<index> ...])
  8. list(REMOVE_DUPLICATES <list>)
  9. list(REVERSE <list>)
  10. list(SORT <list>)
  • 使用LENGTH选项时,该命令会返回给定list的长度。
  • 使用GET选项时,该命令返回list中所有被index索引的元素构成的list。
  • 使用APPEND选项时,该命令将会在该list之后追加若干元素。
  • 使用FIND选项时,该命令将返回list中指定的元素的索引;若果未找到,返回-1。
  • 使用INSERT选项时,该命令将在list中指定的位置插入若干元素。
  • 使用REMOVE_AT和REMOVE_ITEM选项将会从list中删除一些元素。它们之间的区别是:REMOVE_ITEM删除的是指定的项,而REMOVE_AT删除的是在指定索引处的项。
  • 使用REMOVE_DUPLICATES选项时,该命令将删除list中的重复项。
  • 使用REVERSE选项时,该命令将把list的内容就地前后倒换。
  • 使用SORT选项时,该命令将按字母序对list总的内容就地排序。

注意: 在CMake中,一个list是一个由封号;分割的一组字符串。使用set命令可以创建一个list。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e;而set(var “a b c d e”)命令创建的只是一个字符串,或者说是只有一个项的list。 当使用指定索引的命令格式时,如果是大于等于0的数,是从list第一个项开始的序号,list的第一项的索引是0。如果小于等于-1,这个索引是从结尾开始的逆向索引,其中-1表示的是list的最后一项。当使用负数索引时,注意它们不是从0开始!-0与0等价,它指向list的第一个成员。

循环

  1. foreach(var ${list})
  2. # xxxx
  3. endforeach()

连接库(静态和动态)有好几种方式,

  1. link_directories
  2. find_library
  3. find_path
  4. find_package
  5. target_link_libraries

记住唯一一种就行target_link_libraries,万能连接,要写在add_executable之后 ,可以和find_path搭配或者循环

  1. target_link_libraries (${PROJECT_NAME} 动态库/动态库列表) # 将当前项目连接动态库 , 绝对路径方式

项目编译CMakeLists.txt(静态动态库通用)

  1. cmake_minimum_required(VERSION 3.20)# 声明了需要使用的cmake的最低版本 注意: 改为自己的cmake版本
  2. project(demo1 C) # 项目名称 和 语言 注意: 项目名称需要改为你自己的
  3. set(CMAKE_C_STANDARD 11) #改为自己C语言的编译版本
  4. # 搜索.h文件
  5. file(GLOB_RECURSE ALL_H
  6. ${PROJECT_SOURCE_DIR}/include_h/**.h
  7. )
  8. # 搜索include_h下所有包含.h的父级目录
  9. foreach(file ${ALL_H})
  10. # 获取父目录
  11. string(REGEX REPLACE "/$" "" CURRENT_FOLDER_ABSOLUTE ${file})
  12. string(REGEX REPLACE "(.*/)(.*)" "\\1" CURRENT_FOLDER ${CURRENT_FOLDER_ABSOLUTE})
  13. list(APPEND include_h ${CURRENT_FOLDER})
  14. endforeach()
  15. # 目录去重
  16. list(REMOVE_DUPLICATES include_h)
  17. MESSAGE("include_directories" ${include_h})
  18. # 指定.h头文件搜索路径
  19. include_directories(${include_h})
  20. # 递归查询文件
  21. file(GLOB_RECURSE ALL_SRC
  22. # 可以配置多个路径
  23. ${PROJECT_SOURCE_DIR}/src/**.c # 获取src下面的所有.c文件
  24. # ${PROJECT_SOURCE_DIR}/module2/*.c
  25. )
  26. MESSAGE("add_executable" ${ALL_SRC})
  27. # 手动添加ioc图标 ,等依赖文件
  28. set(ICO ico/ico.o)
  29. # 需要编译的文件 ,生成exe
  30. add_executable(${PROJECT_NAME} ${ICO} ${ALL_SRC})
  31. # 将环境变量路径下的所有库文件连接到项目里
  32. if(DEFINED ENV{LIBRARY_CMAKE_PATH_FILES})
  33. file(GLOB_RECURSE ALL_library
  34. # 可以配置多个路径
  35. $ENV{LIBRARY_CMAKE_PATH_FILES}/**.a # 获取lib下面的所有.a文件
  36. $ENV{LIBRARY_CMAKE_PATH_FILES}/**.lib # 获取lib下面的所有.lib文件
  37. $ENV{LIBRARY_CMAKE_PATH_FILES}/**.os # 获取lib下面的所有.lib文件
  38. $ENV{LIBRARY_CMAKE_PATH_FILES}/**.dll # 获取lib下面的所有.lib文件
  39. )
  40. MESSAGE("target_link_libraries: " ${ALL_library})
  41. # 链接静态库也可以链接动态库
  42. target_link_libraries (${PROJECT_NAME} ${ALL_library})
  43. endif()
  44. # 将当前项目下lib所有的所有库文件(.os .dll .a .lib )连接到项目里
  45. file(GLOB_RECURSE ALL_library
  46. # 可以配置多个路径
  47. ${PROJECT_SOURCE_DIR}/lib/**.a # 获取lib下面的所有.a文件
  48. ${PROJECT_SOURCE_DIR}/lib/**.lib # 获取lib下面的所有.lib文件
  49. ${PROJECT_SOURCE_DIR}/lib/**.os # 获取lib下面的所有.lib文件
  50. ${PROJECT_SOURCE_DIR}/lib/**.dll # 获取lib下面的所有.lib文件
  51. )
  52. MESSAGE("target_link_libraries: " ${ALL_library})
  53. # 链接静态库也可以链接动态库
  54. target_link_libraries (${PROJECT_NAME} ${ALL_library})

如果有库文件在其他地方那么可以设置系统环境变量LIBRARY_CMAKE_PATH_FILES, 来连接,默认会添加将当前项目lib下所有库文件

注意:

  • 静态库的文件代码不能和源码的冲突 (同名文件内不能有相同名称的方法), 否则在编译的时候就会报错

  • 如果同时使用静态库和动态库,并且他们之间有同名文件而且内部有同名的方法, 那么默认以静态库为准,因为静态库在打包的时候会将所需代码拷贝进包中,而动态库在运行期间才会去动态库里找对应的代码, 当调用方法的时候优先在当前应用内部找如果存在了那么就不会去动态库找了
  • 静态库可以打断点, 动态库是没法打断点(都是汇编)

打包静态或动态库CMakeLists.txt(通用)

静态动态都可以使用下面写好的CMakeLists.txt,在文件中有说明,不同的库只需要改下就行

  1. cmake_minimum_required(VERSION 3.20) # 声明了需要使用的cmake的最低版本
  2. project(tool C) # 项目名称 和 语言
  3. set(CMAKE_C_STANDARD 11) # C编译器版本
  4. # 搜索全部的.c文件
  5. file(GLOB_RECURSE ALL_SRC
  6. ${PROJECT_SOURCE_DIR}/src/**.c
  7. ${PROJECT_SOURCE_DIR}/*.c
  8. )
  9. MESSAGE("add_library" ${ALL_SRC})
  10. # 静态打包库文件 (自行选择)
  11. add_library(${PROJECT_NAME} ${ALL_SRC})
  12. # 动态打包库文件 (自行选择)
  13. # add_library(${PROJECT_NAME} SHARED ${ALL_SRC})
  14. # 将库文件和头文件都复制到一个目录下
  15. ## 在项目根下创建 ${PROJECT_NAME} 文件夹
  16. set( public_include ${PROJECT_SOURCE_DIR}/cmake-build-debug/${PROJECT_NAME})
  17. execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${public_include})
  18. ## 拷贝头部文件,到到${PROJECT_NAME}文件夹
  19. file(GLOB_RECURSE ALL_SRC
  20. ${PROJECT_SOURCE_DIR}/src/**.h
  21. ${PROJECT_SOURCE_DIR}/**.h
  22. )
  23. MESSAGE("copy" ${ALL_SRC})
  24. execute_process( COMMAND ${CMAKE_COMMAND} -E copy ${ALL_SRC} ${public_include})

最后当运行打包后, 只需要将库文件和头文件夹,复制到需要的地方就行了

动态库导入无效,报错问题

静态库在编译的时候就会被打包到exe中

当我们项目使用的是动态库打包后,是运行不起来的 ,因为动态库打包的项目是在运行时候才会去找依赖 ,默认会去找exe当前目录下或者系统的PATH环境变量里的路径, 如果都没有那么就会报错

解决办法

如果当前是在本地开发代码那么直接在Clion的环境变量中配置就行了

如果当前需要打包项目的话,而dll文件和exe文件不是在同级目录下,那么以下有二种解决方案:

  1. 那么将相关的dll文件都复制到exe同级目录下,然后压缩为zip文件,客户下载解压后双击exe那么也是能跑的(不推荐)
  2. 设置我的电脑的环境变量中的系统变量的PATH, 将动态库的绝对路径添加进去就行了(不推荐,自己玩可以这样弄)
  3. 将dll和项目放入安装程序中,让用户下载后自己安装(目前主流)

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复感谢,配合,希望我的努力对你有帮助^_^

免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我。

相关文章