当前位置:网站首页>C语言-Cmake-CMakeLists.txt教程
C语言-Cmake-CMakeLists.txt教程
2022-07-08 00:12:00 【胡安民】
介绍
CMakeLists.txt官网教程 , cmake命令配置等教程
CMakeLists.txt文件使用的是Cmake命令完成的,那么我们来了解下常用的命令使用, 这是必须要会的,否则静态库,动态库,都没法导入,而且项目文件管理等都靠这个文件, 就算你代码学的在好,这个不懂最后都不可能是一个完整的项目,只能在编译工具跑跑而已,不能发布出去
CMake是跨平台编译工具,比make更高级一些。其编译的主要工作是生成CMakeLists.txt文件,然后根据该文件生成Makefile,最后调用make来生成可执行程序或者动态库,具体流程如下:
以下教程都是基于Clion工具进行的, Clion极大地简化了我们构造C的难度,我们大部分情况只需要关注开发代码就行, 构建的部分在项目开始的时候配置一下就行, 下面就是讲解如何进行配置 , 前提需要将Clion-MinGW配置好具体教程: http://t.csdn.cn/FnTra
入门
cmake_minimum_required(VERSION 3.20) # 声明了需要使用的cmake的最低版本
project(example1 VERSION 1.0.0 LANGUAGES C) # 项目的名字 版本 编译语言
set(CMAKE_C_STANDARD 11) #c编译器的版本
add_executable(example1 main.c) # 通过源文件main.c生成可执行文件demo.exe
当我们运行编译后,在cmake-build-debug目录下就会出现example1.exe
可执行文件了 ,当然如果只是学习C这点默认配置其实就够了
但是如果是自己开发项目,因为文件多了,同时还需要引入外部的各种库文件,那么这点配置就根本不行了, 在学习配置CMakeLists.txt前我么需要先学习下cmake的一些语法,因为CMakeLists.txt里面内容就是使用cmake编写的
cmake常用的语法
因为我们又不用把CMake语法都学完,我们单纯的只是想打包项目而已,所以我们就将打包项目常用的语法说下(没用到的就不写了)
注释
行注释使用#
;块注释使用#[[xxxxx]]
。比如:
# Multi line comments follow
#[[
xxxxx
]]
变量
程序变量
CMake中程序变量使用set和unset命令设置或者取消设置变量。作用域只是在CMakeLists.txt里
设置的变量可以是字符串,数字或者列表 ,语法: set(变量名 变量值
) 比如:
# Set variable
set(AUTHOR_NAME Farmer)
set(AUTHOR "Farmer Li")
set(AUTHOR Farmer\ Li)
# Set list
set(SLOGAN_ARR To be)
set(SLOGAN_ARR To;be)
set(SLOGAN_ARR "To;be")
set(NUM 30) # Saved as string, but can compare with other number string
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_DIRCMAKE_SOURCE_DIR
与 PROJECT_SOURCE_DIR
等价CMAKE_BINARY_DIR
与 PROJECT_BINARY_DIR
等价CMAKE_CURRENT_SOURCE_DIR
当前源码所在路径CMAKE_CURRENT_BINARY_DIR
当前源码的 BINARY_DIRCMAKE_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 等价
消息打印
MESSAGE(打印内容)
MESSAGE( STATUS 打印内容)
执行系统命令
执行shell命令
execute_process(COMMAND <一句shell命令> WORKING_DIRECTORY <这句shell命令执行的工作目录>)
如果命令不依赖于某个目录,而是全局的话那么可以直接使用
execute_process(COMMAND echo "Hello")
执行shell脚本
execute_process(COMMAND sh test.sh WORKING_DIRECTORY <test.sh所在目录> )
查询文件
file(GLOB_RECURSE ALL_SRC
src/**.c
# module2/*.c
)
递归查询所有src下面所有后缀为.c
的文件,并且保存到ALL_SRC里(List方式)
创建目录
execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory 目录创建)
复制文件
execute_process( COMMAND ${CMAKE_COMMAND} -E copy 文件/文件列表 目标目录)
复制文件夹
execute_process( COMMAND ${CMAKE_COMMAND} -E copy_directory 目录/目录列表 目标地址)
列表
list(LENGTH <list> <output variable>)
list(GET <list> <element index> [<element index> ...] <output variable>)
list(APPEND <list> <element> [<element> ...])
list(FIND <list> <value> <output variable>)
list(INSERT <list> <element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value> [<value> ...])
list(REMOVE_AT <list> <index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
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的第一个成员。
循环
foreach(var ${list})
# xxxx
endforeach()
target_link_libraries
连接库(静态和动态)有好几种方式,
- link_directories
- find_library
- find_path
- find_package
- target_link_libraries
记住唯一一种就行target_link_libraries,万能连接,要写在add_executable之后 ,可以和find_path搭配或者循环
target_link_libraries (${PROJECT_NAME} 动态库/动态库列表) # 将当前项目连接动态库 , 绝对路径方式
项目编译CMakeLists.txt(静态动态库通用)
cmake_minimum_required(VERSION 3.20)# 声明了需要使用的cmake的最低版本 注意: 改为自己的cmake版本
project(demo1 C) # 项目名称 和 语言 注意: 项目名称需要改为你自己的
set(CMAKE_C_STANDARD 11) #改为自己C语言的编译版本
# 搜索.h文件
file(GLOB_RECURSE ALL_H
${PROJECT_SOURCE_DIR}/include_h/**.h
)
# 搜索include_h下所有包含.h的父级目录
foreach(file ${ALL_H})
# 获取父目录
string(REGEX REPLACE "/$" "" CURRENT_FOLDER_ABSOLUTE ${file})
string(REGEX REPLACE "(.*/)(.*)" "\\1" CURRENT_FOLDER ${CURRENT_FOLDER_ABSOLUTE})
list(APPEND include_h ${CURRENT_FOLDER})
endforeach()
# 目录去重
list(REMOVE_DUPLICATES include_h)
MESSAGE("include_directories" ${include_h})
# 指定.h头文件搜索路径
include_directories(${include_h})
# 递归查询文件
file(GLOB_RECURSE ALL_SRC
# 可以配置多个路径
${PROJECT_SOURCE_DIR}/src/**.c # 获取src下面的所有.c文件
# ${PROJECT_SOURCE_DIR}/module2/*.c
)
MESSAGE("add_executable" ${ALL_SRC})
# 手动添加ioc图标 ,等依赖文件
set(ICO ico/ico.o)
# 需要编译的文件 ,生成exe
add_executable(${PROJECT_NAME} ${ICO} ${ALL_SRC})
# 将环境变量路径下的所有库文件连接到项目里
if(DEFINED ENV{LIBRARY_CMAKE_PATH_FILES})
file(GLOB_RECURSE ALL_library
# 可以配置多个路径
$ENV{LIBRARY_CMAKE_PATH_FILES}/**.a # 获取lib下面的所有.a文件
$ENV{LIBRARY_CMAKE_PATH_FILES}/**.lib # 获取lib下面的所有.lib文件
$ENV{LIBRARY_CMAKE_PATH_FILES}/**.os # 获取lib下面的所有.lib文件
$ENV{LIBRARY_CMAKE_PATH_FILES}/**.dll # 获取lib下面的所有.lib文件
)
MESSAGE("target_link_libraries: " ${ALL_library})
# 链接静态库也可以链接动态库
target_link_libraries (${PROJECT_NAME} ${ALL_library})
endif()
# 将当前项目下lib所有的所有库文件(.os .dll .a .lib )连接到项目里
file(GLOB_RECURSE ALL_library
# 可以配置多个路径
${PROJECT_SOURCE_DIR}/lib/**.a # 获取lib下面的所有.a文件
${PROJECT_SOURCE_DIR}/lib/**.lib # 获取lib下面的所有.lib文件
${PROJECT_SOURCE_DIR}/lib/**.os # 获取lib下面的所有.lib文件
${PROJECT_SOURCE_DIR}/lib/**.dll # 获取lib下面的所有.lib文件
)
MESSAGE("target_link_libraries: " ${ALL_library})
# 链接静态库也可以链接动态库
target_link_libraries (${PROJECT_NAME} ${ALL_library})
如果有库文件在其他地方那么可以设置系统环境变量LIBRARY_CMAKE_PATH_FILES
, 来连接,默认会添加将当前项目lib下所有库文件
注意:
静态库的文件代码不能和源码的冲突 (同名文件内不能有相同名称的方法), 否则在编译的时候就会报错
如果同时使用静态库和动态库,并且他们之间有同名文件而且内部有同名的方法, 那么默认以静态库为准,因为静态库在打包的时候会将所需代码拷贝进包中,而动态库在运行期间才会去动态库里找对应的代码, 当调用方法的时候优先在当前应用内部找如果存在了那么就不会去动态库找了
静态库可以打断点, 动态库是没法打断点(都是汇编)
打包静态或动态库CMakeLists.txt(通用)
静态动态都可以使用下面写好的CMakeLists.txt,在文件中有说明,不同的库只需要改下就行
cmake_minimum_required(VERSION 3.20) # 声明了需要使用的cmake的最低版本
project(tool C) # 项目名称 和 语言
set(CMAKE_C_STANDARD 11) # C编译器版本
# 搜索全部的.c文件
file(GLOB_RECURSE ALL_SRC
${PROJECT_SOURCE_DIR}/src/**.c
${PROJECT_SOURCE_DIR}/*.c
)
MESSAGE("add_library" ${ALL_SRC})
# 静态打包库文件 (自行选择)
add_library(${PROJECT_NAME} ${ALL_SRC})
# 动态打包库文件 (自行选择)
# add_library(${PROJECT_NAME} SHARED ${ALL_SRC})
# 将库文件和头文件都复制到一个目录下
## 在项目根下创建 ${PROJECT_NAME} 文件夹
set( public_include ${PROJECT_SOURCE_DIR}/cmake-build-debug/${PROJECT_NAME})
execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${public_include})
## 拷贝头部文件,到到${PROJECT_NAME}文件夹
file(GLOB_RECURSE ALL_SRC
${PROJECT_SOURCE_DIR}/src/**.h
${PROJECT_SOURCE_DIR}/**.h
)
MESSAGE("copy" ${ALL_SRC})
execute_process( COMMAND ${CMAKE_COMMAND} -E copy ${ALL_SRC} ${public_include})
最后当运行打包后, 只需要将库文件和头文件夹,复制到需要的地方就行了
动态库导入无效报错问题
静态库在编译的时候就会被打包到exe中
当我们项目使用的是动态库打包后,是运行不起来的 ,因为动态库打包的项目是在运行时候才会去找依赖 ,默认会去找exe当前目录下或者系统的PATH环境变量里的路径, 如果都没有那么就会报错
解决办法
如果当前是在本地开发代码那么直接在Clion的环境变量中配置就行了
如果当前需要打包项目的话,而dll文件和exe文件不是在同级目录下,那么以下有二种解决方案:
- 那么将相关的dll文件都复制到exe同级目录下,然后压缩为zip文件,客户下载解压后双击exe那么也是能跑的(不推荐)
- 设置我的电脑的环境变量中的系统变量的PATH, 将动态库的绝对路径添加进去就行了(不推荐,自己玩可以这样弄)
- 将dll和项目放入安装程序中,让用户下载后自己安装(目前主流)
刷新CMakeLists.txt文件
我们很多时候导入一些文件,或者库的时候,发现编译错误了,但是实际上是没有错误的因为CMakeLists.txt(构建的有缓存,需要我们删除
边栏推荐
- Gnuradio3.9.4 create OOT module instances
- About snake equation (2)
- npm 内部拆分模块
- Version 2.0 de tapdata, Open Source Live Data Platform est maintenant disponible
- regular expression
- break net
- From starfish OS' continued deflationary consumption of SFO, the value of SFO in the long run
- About snake equation (1)
- Sword finger offer II 041 Average value of sliding window
- Codeforces Round #643 (Div. 2)——B. Young Explorers
猜你喜欢
从cmath文件看名字是怎样被添加到命名空间std中的
About snake equation (3)
Sword finger offer II 041 Average value of sliding window
写一个纯手写的qt的hello world
碳刷滑环在发电机中的作用
The solution of frame dropping problem in gnuradio OFDM operation
为什么更新了 DNS 记录不生效?
2、TD+Learning
Apache多个组件漏洞公开(CVE-2022-32533/CVE-2022-33980/CVE-2021-37839)
3、多智能体强化学习
随机推荐
如果时间是条河
从cmath文件看名字是怎样被添加到命名空间std中的
Version 2.0 of tapdata, the open source live data platform, has been released
鼠标事件-事件对象
Mat file usage
C语言-模块化-Clion(静态库,动态库)使用
Graphic network: uncover the principle behind TCP's four waves, combined with the example of boyfriend and girlfriend breaking up, which is easy to understand
About how USRP sets the sampling frequency below the minimum sampling frequency reached by the hardware
Common operations of numpy on two-dimensional array
QML fonts use pixelsize to adapt to the interface
Voice of users | winter goes and spring comes, waiting for flowers to bloom -- on gbase 8A learning comprehension
Guojingxin center "friendship and righteousness" - the meta universe based on friendship and friendship, and the parallel of "honguniverse"
Scalar / vector / matrix derivation method
剑指 Offer II 041. 滑动窗口的平均值
COMSOL----微阻梁模型的搭建---最终的温度分布和变形情况---材料的添加
uniapp一键复制功能效果demo(整理)
qt-使用自带的应用框架建立--hello world--使用min GW 32bit
Qml 字体使用pixelSize来自适应界面
Running OFDM in gnuradio_ RX error: gr:: Log: info: packet_ headerparser_ b0 - Detected an invalid packet at item ××
LeetCode 练习——剑指 Offer 36. 二叉搜索树与双向链表