Linux 与 Cmake (一)——了解CMake
我使用的是Ubuntu20.04.1进行学习。一、那就先使用Linux编写一个C开始吧:Hello World!(1)在Terminal中编写C语言程序,进行编译调试。首先先熟悉一下Linux的基本操作命令吧:Linux命令的名称作用mkdir新建文件夹cd更改当前的工作目录ls列出文件夹下包含的文件信息pwd查看当前工作目录cp拷贝文件rm删除文件或文件夹mv移动文件cat查
我使用的是Ubuntu20.04.1进行学习。
一、那就先使用Linux编写一个C开始吧:Hello World!
(1)在Terminal中编写C语言程序,进行编译调试。首先先熟悉一下Linux的基本操作命令吧:
Linux命令的名称 | 作用 |
---|---|
mkdir | 新建文件夹 |
cd | 更改当前的工作目录 |
ls | 列出文件夹下包含的文件信息 |
pwd | 查看当前工作目录 |
cp | 拷贝文件 |
rm | 删除文件或文件夹 |
mv | 移动文件 |
cat | 查看文件内容 |
touch | 创建文件或更新文件时间 |
(2)熟悉vi编辑器
vi编辑器分为一般模式、插入模式、底行指令模式:
一般模式:进入vi时,当前为插入模式下按ESC即可到一般模式。
插入模式:按下A,I,O进入(编辑模式,AIO都是有区别的,看你的效率选择)
底行指令模式:使用: , / ?进行访问
插入模式用于编辑代码的内容,一般模式用来复制、粘贴、删除等操作,底行模式主要用来保存文件、退出、查找文本内容等。
对于一般模式下:
[N]dd | 剪切行内容 |
---|---|
[N]yy | 复制行内容 |
[N]x | 剪切从光标开始处的N个字符 |
p或P | 粘贴当前行内容,小写p是向下粘贴,大写P是向上粘贴 |
u | 撤销上一步的操作 |
h,,j,k,l | 分别表示向左、向下、向上、向右 |
1G或G | 分别指光标移动到首行和最末行 |
y0,y$ | 表示复制光标到行首部分的内容和光标到行尾部的内容 |
d0,d$ | 表示剪切同上 |
其中[N]是表示光标处向下N行的内容。
对底行指令模式下:
:w | 保存内容 |
---|---|
:q! | 强制退出 |
:q | 退出未修改的文件 |
:x | 保存并退出(也可以用:wq) |
:w 文件名 | 另存为文件名的文件夹 |
:r 文件名 | 读入文件呢诶人插入到当前光标位置 |
:N | 光标移动到第N行 |
:set nu | 显示行号(:set nonu不显示行号) |
/string | 查找字符串 |
:s/string1/string2 | 将字符串string1替换为string2 可以是起始行,结束行:s/string1/string2来将某一范围内的字符串进行替换,在末尾加/g表示所有找到的字符都替换 |
:!+Linux | 可执行Linux命令,执行后可再次回到编辑界面 |
对于复制粘贴,可以使用起始行,结束行 + y(或者d)的方式对某一部分进行复制(剪切)。
-
新建文件目录编写程序
mkdir ~/Ctest
-
进入文件目录进行编写C
cd Ctest
-
使用vi编辑
vi HelloWold.c
-
保存并退出
按下ESC键输入:wq退出。
-
编译并运行
对于gcc-c,是将后缀为.c的文件编译为.o的文件。对于Windows系统中的编译器来说,目标文件后缀为.obj,对于GCC编译器,目标文件的后缀名为.o。
对于gcc-c helloworld.c -o test是生成文件的名字是test.o,-o表示自定义目标文件的意思。
gcc-c helloworld.c -o test
./helloworld.c #运行
以上是gcc编译和链接分开的方式。下面看看gcc的编译和链接一次性过程:
$ cd Ctest #源文件所在目录
$ touch test.c #新建空白的源文件
$ gedit test.c #编辑源文件
$ gcc test.c 2022-03-13 21-21-12 的屏幕截图#生成可执行程序
$ ./a.out #运行可执行程序
最后总结一下gcc的编译过程:
Gcc是GNU编译器套件,包括C、C++、Objective-C、Fortran、Java、Ada和Go语言的前端等。
-
预处理,生成,i文件
-
预处理后的文件转换成汇编语言,生成.s文件
-
汇编变为目标代码(机器代码),生成.o的文件
-
链接目标代码,生成可执行程序a.out
二、回到Cmake
cmake官方网站:www.cmake.org
-
为什么要使用Cmake?
为了管理和组织大型的项目代码,使项目代码更清晰可读,也容易维护。决定代码的组织方式及其编译方式,也是程序设计的一部分。
-
那为什么不使用makefile?
makefile依赖于当前的编译平台,而且makefile的工作量比较大,解决依赖关系时也容易出错。因此对于大多数的项目,应当考虑使用一些更自动化一些的cmake或者autotools来生成makefile,而不是自己去动手编写。对于大多数的IDE,比如Delphi的make,Visual C++的nmake,Linux下GNU的make等等很多make工具,他们都是带着不同的规范和标准,所执行的makefile格式也是千差万别的。那么回到最初,如果软件想跨平台,必须要保证能够在不同的平台上面编译,那就需要cmake来解决这个问题,它允许开发者编写一种平台无关的CMakeList.txt文件来定制整个编译流程,然后根据目标用户的平台进一步生成所需的本地化Makefile和工程文件,如Unix的Makefile或Windows的Visual Studio工程。实现“write once,run everywhere”。
-
Cmake的主要特点
-
开发源码
-
跨平台
-
能够管理大型项目
-
简化编译构建过程和编译过程。Cmake的工具链非常简单:cmake+make。
-
可扩展,可编写特定功能的模块。
-
cmake下载:Download | CMake
-
安装Cmake:
sudo apt install cmake
- 查看Cmake版本:
cmake -version
- 解压从cmake官网下下来的资料
tar -zxvf cmake-3.23.0-rc3.tar.gz -C ./
-C表示指定目录,./表示当前目录。
-
进入解压目录
cd cmake-3.23.0-rc3
- 执行命令安装:
./bootstrap && make && make install
那继续从helloworld开始吧:
按照上面创建helloworld的流程一样,在Hello文件中创建一个helloworld.c的文档。
之后创建一个CMakeLists.txt,他就是Cmake所处理的代码。
在CMakeListtxt中加入这几行:
#规定运行此配置文件所需的CMake的最低版本
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
#参数值是HELLO 表示项目的名称为HELLO
PROJECT(HELLO)
#将名为helloworld.c的源文件编译成一个名称为
ADD_EXECUTABLE(hello helloworld.c)
整个hello项目就已经构建完毕了,下一步进行编译。
在hello目录下新建一个目录build,用于存放生成的所有中间文件和可执行文件。整体项目架构为:
hello/
|-CMakeLists.txt
|-build /
|-helloworld.c
在Windows下的cmake提供了图形界面,设定hello为source目录,build为二进制目录,然后点击configure即可以开始构建,之后进入build目录运行make命令编译。
在Linux下,首先进入目录build,然后运行命令。该命令会使cmake检测编译环境,并生成相应的makefile。接着,运行命令make进行编译。编译后,生成的所有中间文件和可执行文件都会在build目录下:
打开要编译文件所在的文件夹,输入:
cmake .
就可以看到成功生成了Makefile,还有一些cmake运行时自动生成的文件。这个时候生成了Makefile还有可执行文件hello。
然后在终端make并回车就会生成这句:[100%] Built target hello
之后运行hello:
三、对存在错误的Cmake进行改进,深入了解Cmake
这次要更改的是关于lightssdp的一个文件,其中存在有Makefiles以及CMakeList.txt,其中项目的源程序是没有错误的,CMakeList.txt是有很多错误的,那么就开始吧!
使用以上的命令进行编译:
这是一份关于SSDP的程序源码,他要使用Cmake进行编译,先好奇的学一下SSDP吧:
-
关于SSDP
-
了解CMakeList.txt
-
以上只是关于只有一个源文件的做法,如果有多个源文件,在CMakeList.txt中需要这么使用:
-
#CMake最低版本号要求
cmake_minimum_required(VERSION 2.8)
#项目信息
project(Demo2)
#指定生成目标
add_executable(Demo main.c MathFuntions.c)
以上程序对应的框架为:
./Demo2
|-main.c
|-MathFunctions.c
那么如果我们一个项目里面他有许多.c文件怎么办?使用aux_source_directory( <dir> <variable>)命令,这个命令会去查找指定目录下的所有源文件,然后将其结果存进指定变量名。
那么对于CMakeList.txt的修改即可如下:
# CMake最低版本号要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
#项目信息
PROJECT(DEMO 2)
#查找当前目录下的所有源文件
#并将其名称保存到DIR_SRCS变量
AUX_SOURCE_DIRECTORY(.DIR_SRCS)
#指定生成目标
ADD_EXECUTABLE(DEMO ${DIR_SRCS})
-
那如果有多个目录,多个源文件的话,需要怎么做?
./DEMO 3
|-MAIN.C
|-MATH/
|-MATHFUNCTIONS.C
|-MATHFUNCTIONS.H
在多个目录的情况下,要在每一个目录各编写一个CMakeList.txt文件。
使用ADD_SUBDIRECTORY指明项目包含一个子目录,这样子目录中的CmakeList.txt和源代码也会进行处理。
使用TARGET_LINK_LIBRARIES指明可执行文件main需要链接一个名为MATHFUNCTIONS的链接库。
那么对于根目录的CMakeList.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(DEMO3)
AUX_SOURCE_DIRECTORY(.DIR_SRCS)
#添加MATH子目录
ADD_SUBDIRECTORY(MATH)
ADD_EXECUTABLE(DEMO MAIN.C)
#添加链接库
TARGET_LINK_LIBRARIES(DEMO MATHFUNCTIONS)
对于子目录的CMakeList.txt:
# 查找当前目录下的所有源文件
# 并将名称保存到DIR_LIB_SRCS变量
AUX_SOURCE_DIRECTORY(.DIR_LIB_SRCS)
# 生成链接库
ADD_LIBRARY(MATHFUNCTIONS ${DIR_LIBZ_SRCS})
-
自定义编译选项
CMake允许为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。比如说,我们需要调用一个可选的数学库MATHFUNCTIONS.C,那么就要将该选项定义为ON,否则就调用标准库中的数学函数库。
使用configure_file命令用于加入一个配置头文件config.h,这个文件由Cmake从config.h生成,通过这样的机制,可以通过预定义一些参数和变量来控制代码的生成。
使用option命令添加一个USE_MYMATH,并且默认值为ON。
使用USE_MYMATH变量的值来决定是否使用我们自己编写的MathFunctions库。
那么对应的CMakeList.txt可以这么修改:
# CMake 最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 项目信息
PRJECT(DEMO 4)
# 加入一个配置头文件,用于处理CMake对源码的设置
CONFIGURE_FILE(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的MathFunctions库
OPTION(USE_MYMATH
"Use provided math implemengtation" 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})
之后修改所在的main.c函数,让其根据USE_MYMATH的预定义来决定是否调用标准库还是MathFunctions库:
#include <stdio.h>
#include <stdlib.h>
#include <config.h>
#ifdef USE_MYMATH
#include "math/MathFunctions.h"
#else
#include <math.h>
#endif
int main(int argc,char *argv[])
{
...
#ifdef USE_MYMATH
printf("Now we use our own Math library.\n");
...
#else
printf("Now we use the standard library.\n");
...
#endif
...
return 0;
}
config.h.in文件到底在干什么?
configure.h文件预定义了USE_MYMATH的值,但是我们并不直接编写这个文件。我们的操作是编写一个config.h.in文件,使configure.h方便预定义:
在configure.h.in中编写:
#cmakedefine USE_MYMATH
编译项目
ccmake 或者是 cmake -i(会提供一个交互式配置界面)
交互式界面可以更改变量USE_MYMATH的值,当如果我们设置这个值为ON时:
在config.h的值为:
#define USE_MYMATH
当设置这个值为OFF时:
在config.h的值为:
/* #undef USE_MYMATH */
-
安装与测试
CMake也可以指定安装规则,以及添加测试。这两个功能分别可以通过在产生Makefile后使用make install 和make test来执行。
定制安装规则:
在math/CMakeLists.txt的文件中添加:
#指定MathFunctions库的安装路径
install(TARGETS MathFunctions DESTNATION bin)
install(FILES MathFunctions.h DESTNATION include)
要指明MathFunctions库的安装路径。之后同样修改根目录的CMakeLists.txt文件,在末尾添加:
# 指定安装路径
install (TARGETS Demo DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
DESTINATION include)
生成的Demo文件和MathFunctions函数库libMathFunctions.o文件会被复制到/usr/local/bin中,而MathFunctions.h和生成的config.h文件则会被复制到/usr/local/include中。其中的/usr/local是默认安装到根目录。如果需要指定文件应该拷贝到哪个根目录下的话,可以使用CMAKE_INSTALL_PREFIX变量。
添加测试:
CMake提供了一个称为CTest的测试工具。只需要在项目的根目录的CMakeLists文件中调用一系列的ADD_TEST命令。
如果需要了解了解关于CTest更详细的用法可以在终端中输入:
man 1 ctest
使用test_run用来测试程序是否成功并返回0值。
使用PASS_REGULAR_EXPRESSION用来测试输出是否包含后面跟着的字符串。
# 启用测试
enable_testing()
# 测试程序是否成功运行
add_test(test_run Demo 5 2)
# 测试帮助信息是否可以正常提示
add_test(test_usage Demo)
set_tests_properties(test_usage
PROPERTIES PASS_REGULAR_EXPRESSION " Usage : .* base exponent")
# 测试5的平方
add_test(test_5_2 Demo 5 2)
set_tests_properties(test_5_2
PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")
如果要测试很多数据的话,就需要一个一个往里面添加,很繁琐,可以封装为下面这个函数:
# 定义一个宏,用来简化测试工作
macro(do_test arg1 arg2 result)
add_test(test_${arg1}_${arg2} Demo ${arg1} ${arg2})
set_tests_properties(test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro(do_test)
#使用一系列宏进行一系列的数据测试
do_test(5 2 "is 25")
-
支持gdb
让CMake支持gdb的设置只需要指定Debug模式下开启-g选项。
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -00 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -03 -Wall")
-
添加环境检查
适用于检查系统环境或者是使用一个平台相关特效的时候。比如会去检查系统是否自带我们需要的pow函数,有则调用,无则使用我们定义的pow。
添加CHECKFUNCTIONSEXISTS宏:
首先在顶层CMakeLists文件中添加CheckFuntionExists.cmake宏,并调用check_function_exists命令测试链接器是否能够在链接阶段找到pow函数。
# 检查系统是否支持pow函数
include (${CMAKE_ROOT}/Modules/CheckFuntionExists.cmake)
check_function_exists(pow HAVE_POW)
要把上面这段代码放在configure_file命令前。
预定义相关宏变量:
修改config.h.in文件,预定义相关的宏变量:
#cmakedefine HAVE_POW
在代码中使用宏和函数:
#ifdef HAVE_POW
printf("Now we use the standard library .\n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library .\n");
double result = power(base,exponent);
#endif
-
添加版本号
给项目添加和维护版本号有利于用户了解每个版本的维护情况,并及时了解当前所用的版本是否过时,还是说会出现可能不兼容的情况。
首先修改顶层CMakeLists文件,在project命令之后加入如下两行:
set(Demo_VERSION_MAJOR 1)
set(Demo_VERSION_MINOR 0)
分别指定当前的项目的主版本号和副版本号。
为了之后在代码中获取版本信息,我们可以修改config.h.in文件,添加两个预定义的变量:
#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@
这样就可以直接在代码中打印版本信息了:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#include "math/MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
// print version info
printf("%s Version %d.%d\n",
argv[0],
Demo_VERSION_MAJOR,
Demo_VERSION_MINOR);
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#if defined (HAVE_POW)
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
-
生成安装包
安装包包括二进制安装包和源码安装包,使用CPack,他也是CMake提供的一个工具,专门用于打包。
需要在CMakeLists.txt文件尾部添加下面几行:
# 构建一个CPack安装包
# 导入InstallRequiredSystemLibraries模块,以便之后导入CPack
include (InstallRequiredSystemLibraries)
#设置一些CPack相关变量,包括版权信息和版本信息
set(CPACK_RESOURCE_FILE_LICENSE
"${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
#导入CPack模块
include (CPack)
之后使用命令就可以了:
生成二进制安装包:
cpack -C CPackConfig.cmake
生成源码安装包
cpack -C CPackSourceConfig.cmake
之后执行:
cpack -C CPackConfig.cmake
就可以得到三个二进制文件,文件的内容都是一样的,可以执行其中一个,就会出现由CPack自动生成的交互式安装界面。
关于CPack的更详细用法可以通过man 1 cpack参考CPack的文档。
引用文档:CMake 入门实战 | HaHack
更多推荐
所有评论(0)