CMake简单学习

发布时间:2024年01月19日

一、什么是CMake

简单的说:

CMake是一个跨平台的构建系统,用于管理软件项目的构建过程。能够自动生成适用于不同平台和编译器的构建文件。

详细的说:

CMake是一个跨平台的开源构建系统,用于管理软件项目中的构建过程。它使用一种名为“CMakeLists.txt”的特殊脚本语言来描述构建过程,并能够自动生成各种不同平台和编译器所需的构建文件。CMake支持多种编程语言,包括C、C++、Java、Python等。

使用CMake可以将构建过程从特定的编译器和操作系统中解耦出来,使得同一个项目可以在多个平台上进行构建。与传统的构建系统相比,CMake具有更为灵活的配置选项和更强大的模块化能力,方便用户自定义和扩展构建规则。

在ROS中,CMake是ROS的默认构建系统。每个ROS包都包含一个名为CMakeLists.txt的文件,其中包含了编译该包所需的构建规则和依赖关系。通过使用CMake,用户可以轻松创建、编译和运行ROS软件包,实现机器人应用程序的开发和部署。

二、以实战代学

(1)我们先写一个hello world来暖暖场

我们需要在一个空的文件夹中写两个文件hello.cpp和CMakeLists.txt

注意:在CMake中,默认情况下,CMakeLists.txt文件的名字是固定的,必须以这个名称命名。CMake会自动查找当前目录下的CMakeLists.txt文件并加载它。

hello.cpp代码

#include <iostream>
using namespace std;

int main()
{
    cout << "hello world" << endl;
    return 0;
}

CMakeLists.txt代码

project(HELLO)
set(SRC_LIST hello.cpp)
message(STATUS "This is a BINARY dir" ${HELLO_BINARY_DIR})
message(STATUS "This is a SOURCE dir" ${HELLO_SOURCE_DIR})
add_executable(hello ${SRC_LIST})

代码分析

project(HELLO)
  • projec()是用来设置项目名称的,一般简单设置就行。
  • project(HELLO) 指定了?程的名字,并且?持所有语?建议
  • project(HELLO CXX) 指定了?程的名字,并且?持语?是C++
  • project(HELLO C CXX) 指定了?程的名字,并且?持语?是CC++
  • project(HELLO)隐式定义了两个CMAKE的变量HELLO_BINARY_DIR和HELLO_SOURCE_DIR
问题:如果改了?程名,HELLO_BINARY_DIR和HELLO_SOURCE_DIR 这两个变量名也会改变
解决:?定义两个预定义变量: PROJECT_BINARY_DIR PROJECT_SOURCE_DIR ,这两个变量和 HELLO_BINARY_DIR, HELLO_SOURCE_DIR 是?致的。所以改了?程名也没有关系
set(SRC_LIST hello.cpp)

set(SRC_LIST hello.cpp)命令用于将hello.cpp文件的名称存储在变量中,以便在后续的命令中引用它。

message(STATUS "This is a BINARY dir" ${HELLO_BINARY_DIR})
message(STATUS "This is a SOURCE dir" ${HELLO_SOURCE_DIR})
message命令用于输出消息到CMake的输出窗口,这里将输出当前二进制目录和源码目录的路径。
message命令 主要包含三种信息:
SEND_ERROR ,产?错误,?成过程被跳过。
SATUS ,输出前缀为 的信息。
FATAL_ERROR ,?即终?所有 cmake 过程
add_executable(hello ${SRC_LIST})

add_executable命令将源代码文件${SRC_LIST}编译为一个可执行文件hello

${SRC_LIST}是取值的意思

这里的${SRC_LIST}就是与上面的代码set(SRC_LIST hello.cpp)有关系

---------------------------------------------------代码分析完毕-----------------------------------------------------------

使用cmake .(注意:cmake .中的.不要忘记了)

问题:为什么要使用cmake

解答:在终端输入命令cmake的作用是运行CMake,使用CMakeLists.txt文件中的指令来生成对应的构建系统文件。

具体地说,当你在终端中运行cmake命令时,CMake将读取当前目录中的CMakeLists.txt文件,并根据其中的指令生成对应的构建系统脚本(如Makefile、Visual Studio项目文件等),以便编译和构建你的项目。

图1 hello文件的初始状态?

图2?使用cmake .?

cmake . 后生成的大多数文件可以不予理会

需要注意的是Makefile文件

生成的Makefile文件是一种构建系统文件,用于指导make工具进行项目的编译和构建。

Makefile中包含了一系列规则和命令,用于描述项目中源文件的依赖关系以及如何生成最终的可执行文件或库文件。通过读取Makefile文件,make工具可以自动地根据这些规则和命令来确定需要重新编译的文件,并执行相应的编译和链接操作。

make命令编译

在终端输入make命令时,它会根据当前目录中的Makefile文件中所定义的规则和依赖关系,自动编译生成指定的目标文件。

通常情况下,我们在Makefile文件中会定义多个目标,比如可执行文件、静态库或动态库等。在执行make命令时,如果没有指定目标,则默认会使用Makefile文件中第一个定义的目标作为默认目标进行编译。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图3?使用make编译?

可见到生成了可以运行的hello编译文件

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图4?运行hello结果?

(2)hello word外部构建

上述例?是内部构建,他?产的临时?件特别多,不?便清理。
外部构建,就会 把?成的临时?件放在build?录下 ,不会对源?件有任何影响强烈建议使?外部构建?式

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 图5?创建文件夹build

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图6?cmake ..

进? build ,运? cmake .. 当然 .. 表示上?级?录,你可以写 CMakeLists.txt 所在的绝对路径,?产的?件都在 build?录下了?
然后在build目录下make

(3)以项目工程的格式来构建hello world

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图7 项目工程初始形态

build?录:把?成的临时?件放在build?录下

src目录:用来放置工程的源代码

问题:为什么src和工程目录下都要有CMakeList.txt

解答:

  1. 工程目录下的CMakeLists.txt: 工程目录下的CMakeLists.txt文件主要用于设置整个项目的全局配置和管理。它可以包含多个子目录,并指导CMake如何构建和组织整个项目。这个CMakeLists.txt文件通常用于包含整个项目的编译选项、链接选项、依赖库的查找和添加、生成的可执行文件或库文件的输出位置等。它是整个项目的入口点,负责协调各个子目录的构建过程

  2. 源代码src目录下的CMakeLists.txt: 源代码目录下的CMakeLists.txt文件主要用于描述该目录下的源代码文件,指导CMake如何编译和构建当前目录下的源文件。这个CMakeLists.txt文件通常用于定义当前目录下的编译目标、源文件的编译选项、依赖库的链接等。它是每个源代码目录的入口点,负责管理当前目录下的源代码的构建过程。

工程目录下的CMakeLists.txt文件提供了整个项目的全局配置和管理,而源代码src目录下的CMakeLists.txt文件则主要用于描述该目录下的源代码文件,指导CMake如何编译和构建当前目录下的源文件。

工程目录下的CMakeLists.txt的代码

project(HELLO)
add_subdirectory(src bin)

代码分析

add_subdirectory(src bin)
src ??录 加??程
并指定 编译输出 ( 包含编译中间结果 ) 路径为 bin ?录
如果不进? bin ?录的指定,那么编译结果 ( 包括中间结果 ) 都将存放在 build/src ?录
源代码src目录下的CMakeLists.txt的代码
add_executable(hello hello.cpp)

add_executable命令将源代码文件${SRC_LIST}编译为一个可执行文件hello

编译

进? build ,运? cmake .. 当然 .. 表示上?级?录,你可以写 CMakeLists.txt 所在的绝对路径,?产的?件都在 build?录下了?
然后在build目录下make
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图8?编译结果

buid目录中生成了bin目录,可执行文件hello就在bin目录中

(4)静态库和动态库的构建

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图9?项目工程初始形态

hello.cpp的代码

#include "hello.h"
#include <iostream>
using namespace std;
void HelloFunc()
{
    cout << "Hello World" << endl;
}

hello.h的代码

#ifndef HELLO_H
#define HELLO_H

void HelloFunc();

#endif

工程目录下的CMakeLists.txt的代码

project(HELLO)
add_subdirectory(lib bin)

源代码lib目录下的CMakeLists.txt的代码

set(LIBHELLO_SRC hello.cpp)

add_library(hello_static STATIC ${LIBHELLO_SRC})
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

add_library(hello SHARED ${LIBHELLO_SRC})
set_target_properties(hello PROPERTIES OUTPUT_NAME "hello")
set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)

INSTALL(FILES hello.h DESTINATION include/hello)
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

代码分析

add_library(hello_static STATIC ${LIBHELLO_SRC})
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

add_library(hello SHARED ${LIBHELLO_SRC})
set_target_properties(hello PROPERTIES OUTPUT_NAME "hello")
set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)

这个代码是为了同时构建名为hello的静态库和动态库。

为什么不能直接用

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
原因:因为直接用添加的静态库和动态库名字相同,cmake ..会报错
add_library(hello_static STATIC ${LIBHELLO_SRC})
编译生成一个名为hello_static的静态库目标文件,并将${LIBHELLO_SRC}中定义的源代码文件编译进去。
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
将hello_static库文件的名称改为hello。
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

设置hello_static库文件只有在执行make clean命令时才会被删除。

默认情况下,CMake会自动将所有生成的库文件和可执行文件都放在构建目录下的一个特定位置。如果你不设置CLEAN_DIRECT_OUTPUT属性或者将其设置为0,那么在执行make clean命令时,CMake会删除这个目录中的所有内容,包括生成的库文件和可执行文件。

但是,如果你将CLEAN_DIRECT_OUTPUT属性设置为1,那么在执行make clean命令时,CMake只会删除由该目标直接生成的文件,而不会删除任何其他文件。在这个例子中,它只会删除hello_static库文件,而不会删除hello动态库文件和其他文件。

这个属性通常用于帮助节省时间和资源。因为静态库文件只需要在第一次构建时生成一次,因此在后续构建中,如果不需要重新生成该文件,则可以通过设置CLEAN_DIRECT_OUTPUT属性来避免不必要的文件操作。

INSTALL(FILES hello.h DESTINATION include/hello)

安装包含在hello.h文件中的头文件到系统路径usr/include/hello下。

INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

安装生成的动态库和静态库到系统路径lib下。动态库被安装在lib目录下,静态库被安装在usr/lib/static目录下(因为静态库的输出名称已经被修改为hello)。

(5)使用外部共享库和头文件

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?图10 项目工程初始形态

hello.cpp的代码

#include "hello.h"
void HelloFunc()
{
    HelloFunc();
}

工程目录下的CMakeLists.txt的代码

project(HELLO)
add_subdirectory(src bin)

源代码src目录下的CMakeLists.txt的代码

include_directories(/usr/include/hello)

add_executable(hello hello.cpp)

target_link_libraries(hello libhello.so)

分析

include_directories(/usr/include/hello)

/usr/include/hello目录添加到包含文件的搜索路径中。这样,在编译时,编译器将在该目录中查找头文件.

target_link_libraries(hello libhello.so)

libhello.so 动态库链接到 hello 可执行文件中。这样,在运行 hello 可执行文件时,系统将自动加载并使用 libhello.so 动态库中的函数和符号。

问题:代码中libhello.so是哪里来的为什么不用指明路径

答:TARGET_LINK_LIBRARIES 添加需要链接的共享库。当使用target_link_libraries函数时,如果要链接的库文件是系统默认的库文件或已在默认的库搜索路径中(例如 /usr/lib),则可以直接使用库文件名而无需指定完整的路径。

补充:LINK_DIRECTORIES 添加?标准的共享库搜索路径。当使用 LINK_DIRECTORIES 指令时,你需要提供库文件的完整路径。

最后,在build目录中cmake ..和make就行了,就不细讲了

三、shell脚本中的代码转入CMakeList.txt中例子

shell脚本的代码

gcc -c linuxrec.c
g++ -g -std=c++11 -o ivw_demo ivw_demo.cpp linuxrec.o -L./libs/ -laikit -lpthread -ldl -Wl,-rpath=lib -lasound

转换成CMakeList.txt中的代码

在 CMakeLists.txt 中添加一个可执行文件目标

add_executable(ivw_demo
    src/ivw_demo.cpp
    src/linuxrec.o
)

然后,指定该可执行文件需要链接的库文件和编译选项

target_link_libraries(ivw_demo
    libaikit
    pthread
    dl
    asound
)
set_target_properties(ivw_demo PROPERTIES
    LINK_FLAGS "-Wl,-rpath=lib"
)

分析

set_target_properties(ivw_demo PROPERTIES
    LINK_FLAGS "-Wl,-rpath=lib"
)
  • 这行代码用于设置目标 ivw_demo 的链接属性。具体来说,它使用了 set_target_properties 函数来为目标设置属性,并且属性名称为 LINK_FLAGS
  • 在 CMake 中,PROPERTIES 是一个用于设置目标属性的关键字。通过使用 set_target_properties 函数,可以为指定的目标(如可执行文件、库文件等)设置不同的属性。
  • LINK_FLAGS 属性是一个连接器选项,它允许你在链接目标时传递额外的标志给连接器。在这个例子中,-Wl,-rpath=lib 是要传递给连接器的标志。
  • -Wl:这是 GCC/Clang 编译器的选项,用于将后面的参数传递给连接器。
  • -rpath=lib:这是连接器选项,用于指定运行时库搜索路径。在这里,它指定了名为?lib?的目录作为运行时库的搜索路径。
  • 因此,这行代码的作用是告诉连接器在链接 ivw_demo 目标时,将 lib 目录添加到运行时库搜索路径中。这样,当运行 ivw_demo 时,系统将在 lib 目录中查找并加载所需的运行时库。

最后,设置可执行文件的输出路径

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_LIST_DIR}/../bin_${PLATFORM})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH})

分析

  • EXECUTABLE_OUTPUT_PATH 被设置为 ${CMAKE_CURRENT_LIST_DIR}/../bin_${PLATFORM}${CMAKE_CURRENT_LIST_DIR} 是 CMakeLists.txt 文件所在的当前目录,而 ${PLATFORM} 是一个变量,表示平台名称。
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY 被设置为 EXECUTABLE_OUTPUT_PATH。这样就将可执行文件的输出路径设置为 EXECUTABLE_OUTPUT_PATH
  • 通过这些设置,你可以指定生成的可执行文件的输出路径。${CMAKE_CURRENT_LIST_DIR} 表示以 CMakeLists.txt 文件所在的目录为基准,../bin_${PLATFORM} 表示将可执行文件放置在 CMakeLists.txt 文件所在目录的上一级目录中的名为 bin_${PLATFORM} 的子目录中。

四、学习资源

推荐(若有侵权,告知必删):【从零开始详细介绍CMake】https://www.bilibili.com/video/BV1vR4y1u77h?p=10&vd_source=8a8939fa34f1535a58f4146e906da94a

文章来源:https://blog.csdn.net/weixin_73690807/article/details/135675826
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。