• 零基础CMake入门(基于ROS)


    cmake实践

    实战示例项目:

            实战示例项目1:简单项目实战示例项目2:复杂项目,附源码

    基础知识铺垫:

            GNU C 和 C++ 编译器分别称为gcc和g++,GCC 是所谓的“ GNU 工具链”的关键组件,用于开发应用程序和编写操作系统。

    1. C++ 有多种标准:
    2. C++98
    3. C++11(又名 C++0x)
    4. C++14(又名 C++1y)
    5. C++17(又名 C++1z)
    6. C++2a(2020 年计划的下一个标准)
    7. ' 对于 6.1 之前的 GCC 版本,默认模式是 C++98,对于 GCC 6.1 及更高版本,默认模式是 C++14 '
    8. '您可以使用命令行标志-std显式指定 C++ 标准。例如:'
    9. -std=c++98, 或-std=gnu++98(带有 GNU 扩展的 C++98)
    10. -std=c++11, 或-std=gnu++11(带有 GNU 扩展的 C++11)
    11. -std=c++14,或-std=gnu++14(带有 GNU 扩展的 C++14),GCC 6.1 及更高版本的默认模式
    12. -std=c++17,或-std=gnu++17(带有 GNU 扩展的 C++17),实验性的
    13. -std=c++2a,或-std=gnu++2a(带有 GNU 扩展的 C++2a),实验性的

    常用的 GCC 编译器选项:

    1. g++ -Wall -g -o Hello.exe Hello.cpp
    2. -o: 指定输出的可执行文件名
    3. -Wall: 打印“ all”警告信息
    4. -g: 生成附加的符号调试信息以供gdb调试器使用

    C/C++源文件的编译步骤为:

            1.预处理: 宏定义展开, 头文件展开, 条件编译

            2.编译: 检查语法, 生成编译文件

            3.汇编: 将汇编文件生成目标文件二进制文件

            4.链接: 将目标文件链接成目标程序,此阶段由于动态链接的优势,GCC 默认链接到共享库

    1.CMake作用:

            上面的编译步骤针对个别的几个文件还能应付,如果源文件太多,一个一个编译就会特别麻烦,为什么不批处理编译源文件呢,于是就有了make工具,它是一个自动化编译工具,可以使用一条命令实现完全编译。还可以指定文件编译的顺序。但是使用make编译源码,需要编写一个规则文件,make依据它来批处理编译,这个文件就是makefile,所以编写makefile文件也是一个程序员所必备的技能。

            对于一个大工程,编写makefile实在是件复杂的事,于是人们又想,为什么不设计一个工具,读入所有源文件之后,自动生成makefile呢,于是就出现了cmake工具,它能够输出各种各样的makefile或者project文件,从而帮助程序员减轻负担。但是随之而来也就是编写cmakelist文件,它是cmake所依据的规则。

            总体的顺序就是:原文件--camkelist —cmake —makefile —make —生成可执行文件

            所以CMake是一个高级编译配置工具,并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces):若没有CMake,就需要使用gcc或者g++对文件进行逐个编译,有了CMake,所有的编译操作都可以通过编译配置CMakeList.txt这个文件完成。

    2.基本语法规则

            CMake 指令不分大小写,变量区分

            变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名

            有关双引号:

    1. " SET(SRC_LIST main.c)也可以写成 SET(SRC_LIST “main.c”) 是没有区别的,"
    2. 但是假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。这时候就必须使用双引号,
    3. " 如果写成了SET(SRC_LIST fu nc.c),就会出现错误"提示你找不到 fu 文件和 nc.c 文件。
    4. 这种情况,就必须写成:SET(SRC_LIST “fu nc.c”)

    3.内部构建和外部构建

            内部构建:产生的各种中间文件都位于源文件的同级目录下,产生很多中间的垃圾文件,干扰源文件的工作目录。

            而外部构建产生的中间文件对原有的工程结构没有任何影响,所有动作全部发生在编译目录。

    4.基于ROS的CMakeList.txt文件的配置顺序

            CMakeLists.txt的文件配置顺序很重要,否则可能无法正确编译,示例请看:项目实战示例2或者CMake语法调用顺序。

    1. cmake_minimum_required() Required CMake Version
    2. project() Package Name
    3. find_package() Find other CMake/Catkin packages needed for build
    4. catkin_python_setup() Enable Python module support
    5. add_message_files(), add_service_files(), add_action_files() Message/Service/Action Generators
    6. generate_messages() message/service/action generation
    7. catkin_package() Specify package build info export
    8. add_library()/add_executable()/target_link_libraries() Libraries/Executables to build
    9. install()
    10. catkin_add_gtest(), catkin_add_nosetests() , add_rostest() , add_rostest_gtest() Tests to build

    5.CMake的预定义变量

    1. PROJECT_NAME '工程名' -->当前'CMakeList.txt里'设置的project_name
    2. PROJECT_SOURCE_DIR 工程的根目录
    3. PROJECT_BINARY_DIR 运行cmake的目录,通常是${PROJECT_SOURCE_DIR}/build
    4. CMAKE_CURRENT_SOURCE_DIR: '当前正处理'的源码路径 --> 当前'CMakeLists.txt' 所在的路径
    5. CMAKE_CURRENT_BINARY_DIR: 当前正在处理的'构建目录'
    6. 备注: 每个由'add_subdirectory添加'的目录将会在'构建树(Build Tree)'中创建一个'构建目录'
    7. 补充: 对于直接在'源码目录中编译'的情况,当前正在处理的构建目录就是'当前源码'所在的目录-->'基本不会'
    8. CMAKE_CURRENT_LIST_DIR: 当前处理的'cmake文件'所在的目录
    9. 备注:这里的cmake文件可能是'.camke'文件或'CMakeListst.txt'
    10. CMAKE_CURRENT_LIST_FILE: 输出调用这个变量的 CMakeLists.txt 的完整路径
    11. CMAKE_CURRENT_LIST_LINE: 输出这个变量所在的行-->'定位报错'
    12. CMAKE_PROJECT_NAME: '整个项目'配置的project_name
    13. 'LIBRARY'_OUTPUT_DIR、'BINARY'_OUTPUT_DIR: '库''可执行'的最终存放目录
    14. CMAKE_MODULE_PATH 用来定义自己的 cmake 模块所在的路径

    6.CMake常用指令

    基本指令:

    PROJECT (HELLO):指定工程的名字HELLO,且支持所有语言

    1. PROJECT(HELLO CXX) 指定了工程的名字,并且支持语是C++,同时将项目名称存储在变量 PROJECT_NAME中
    2. PROJECT(HELLO C CXX) 指定了工程的名字,并且支持语是C和C++  想要支持Java就再往后追加

            该指令隐式定义了两个CMAKE的变量:

                    _BINARY_DIR,本例中是 HELLO_BINARY_DIR                       二进制文件保存路径

                    _SOURCE_DIR,本例中是 HELLO_SOURCE_DIR                   源代码路径

            又定义两个预定义变量:PROJECT_BINARY_DIRPROJECT_SOURCE_DIR,这两个变量和HELLO_BINARY_DIRHELLO_SOURCE_DIR是一致的,所以改了工程名也没有关系。

    SET()

    1. SET(SRC_LIST main.cpp t1.cpp t2.cpp) SRC_LIST变量就包含了main.cpp t1.cpp t2.cpp
    2. SET(EXECUTABLE_OUTPUT_PATH${PROJECT_BINARY_DIR}/bin)     更改生成的可执行文件路径
    3. SET(LIBRARY_OUTPUT_PATH${PROJECT_SOURCE_DIR}/lib) 设置库文件输出到代码源路径下的lib文件夹中
    4. SET(CMAKE_CXX_STANDARD 11)                               设置C++标准为C++11
    5. set(ENV{} []) 设置环境变量

    $ENV:环境变量的调用

    1. $ENV{HOME}:获取当前系统的home目录,并在编译时打印出这个路径
    2. MESSAGE(STATUS "HOME Directory: $ENV{HOME}") 使用$ENV{}就可以调用系统的环境变量了

    MESSAGE():向终端输出用户自定义的信息(在编译的过程中会输出)

    1. 主要包含三种信息:
    2. SEND_ERROR               产生错误,成功过程被跳过
    3. SATUS                    "输出前缀为--的信息,常用"
    4. FATAL_ERROR              立即终止所有 cmake 过程
    5. 例子:MESSAGE(SATUS "\n #### I love China! #### \n")

    find_package( [version] [EXACT] [QUIET] [REQUIRED])

            version: 版本合适

            EXACT: 版本必须一致

            两者的区别:version和EXACT: 都是可选的,version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。

            QUIET: 没找到包也不会报错,既如果查找失败,不会在屏幕进行输出(但是如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)

            REQUIRED: 必须找到该包,否则立即停掉整个cmake。而如果不指定REQUIRED则cmake会继续执行。

            COMPONENTS:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行。

            OPTIONAL_COMPONENTS和components:可选的模块,找不到也不会让cmake停止执行。[引用]

    1. 例子:
    2. find_package(OpenCV 4 REQUIRED)
    3. include_directories(${OpenCV_INCLUDE_DIRS})
    4. add_executable(main src/main.cpp)
    5. target_link_libraries(main ${OpenCV_LIBRARIES})
    6. find_package从目录中寻找OpenCV,找到后将头文件目录设置为${OpenCV_INCLUDE_DIRS}
    7. 库文件设为${OpenCV_LIBRARIES},然后在工程中包含OpenCV头文件目录,生成可执行文件,
    8. 最后链接OpenCV库。

    INCLUDE_DIRECTORIES()

            向工程添加多个指定头文件的搜索路径,路径之间用空格分隔,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:

            1. CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面。

            2. 通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前。

            如果使用了find_package(x)去查找到了某一个库,那么就可以使用这个命令去包含这个库的头文件

    1. 用法:
    2. include_directories(“/opt/MATLAB/R2012a/extern/include”)
    3. include_directories(include ${catkin_INCLUDE_DIRS})
    4. include_directories(${Boost_INCLUDE_DIRS})

    catkin_package():其他功能包使用本功能包要用这个

    1. catkin_package(
    2. INCLUDE_DIRS include   导出包的include路径'(声明给其它包的include路径)'
    3. LIBRARIES my_dep_app       导出项目中的库'(声明给其它包的库)'
    4. CATKIN_DEPENDS my_dep     '该项目'依赖的其他catkin项目
    5. DEPENDS system_lib         '该项目'所依赖的非catkin CMake项目,如普通的opencv库
    6. CFG_EXTRAS                 其他配置参数
    7. )

            对于DEPENDS依赖项,会将DEPENDS的头文件路径和库添加到本功能包下的include路径和库。假设当前我的DEPENS包含了Boost库,当其他功能包调用这个功能包也需要Boost库的时候,就可以不需要再find_package(Boost),但是如果功能包明确需要使用库的话,仍然建议显式寻找库find_package(Boost),而不是通过包含其他功能包隐式寻找,避免其他功能包修改依赖项导致功能包编译失败。

    find_library():在指定目录下查找指定库,并把库的绝对路径存放到变量里

    1. find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)
    2. 第一个参数是变量名称,第二个参数是库名称,第三个参数是HINTS,第4个参数是路径
    3. 使用find_library的好处是在执行cmake ..时就会去查找库是否存在,这样可以提前发现错误,不用等到链接时

            注意:find_library(TESTFUNC_LIB testFunc ...默认是查找动态库,如果想直接指定使用动态库还是静态库,可以写成find_library(TESTFUNC_LIB libtestFunc.so ...或者find_library(TESTFUNC_LIB libtestFunc.a ...

    ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}):生成库文件

            hello:就是正常的库名,生成的名字前面会加上lib, 最终产生的文件是libhello.so

            SHARED:生成动态库,STATIC:生成静态库  如果不写,默认是静态库

            ${LIBHELLO_SRC}:源文件

    1. 例子:
    2. ADD_LIBRARY (hello SHARED ${LIBHELLO_SRC})         # 添加动态库
    3. ADD_LIBRARY (hello STATIC ${LIBHELLO_SRC})         # 添加静态库
    4. '仍然用hello作为target名时,是不能成功创建所需的静态库的,因为hello作为一个target是不能重名的,
    5. 故把上面的hello修改为hello_static,同理不需要写全libhello_static.a 只需要填写hello_static即可,
    6. cmake系统会自动为你生成 libhello_static.a '
    7. ADD_LIBRARY (hello_static STATIC ${LIBHELLO_SRC})
    8. '按照一般的习惯,静态库名字跟动态库名字应该是一致的,只是扩展名不同;即:静态库名为 libhello.a;
    9. 动态库名为libhello.so;所以,希望 "hello_static" 在输出时,不是"hello_static",而是以"hello"
    10. 的名字显示,故设置如下:'
    11. SET_TARGET_PROPERTIES (hello_static PROPERTIES OUTPUT_NAME "hello")
    12. GET_TARGET_PROPERTY (OUTPUT_VALUE hello_static OUTPUT_NAME)
    13. ${
    14. 生成默认库的开关选项:BUILD_SHARED_LIBS
    15. 这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,
    16. 默认编译生成的库都是静态库,如果 SET BUILD_SHARED_LIBS ON后,默认生成的为动态
    17. }

    add_dependencies():添加依赖项

    在使用ROS的message、service、action时注意添加,如下:

            添加对其它package的依赖,前提是已经通过find_package()引入了这个package

            添加对本package的依赖 add_dependencies(my_target ${${PROJECT_NAME}_EXPORTED_TARGETS})

    ${catkin_EXPORTED_TARGETS} 和 ${${PROJECT_NAME}_EXPORTED_TARGETS}的使用时机:

            catkin_EXPORTED_TARGETS:如果有一个目标(甚至是过渡性的)依赖于需要建立消息/服务/动作的其他目标,需要在目标catkin_EXPORTED_TARGETS上添加显式依赖项,以使它们按照正确的顺序编译。这种情况几乎总是适用,除非你的软件包真的不使用ROS的任何部分。不幸的是,这种依赖不能自动传播。

            ${PROJECT_NAME}_EXPORTED_TARGETS:如果有编译消息和/或服务的软件包以及使用这些软件的可执行文件,则需要在自动生成的消息目标上创建明确的依赖关系,以便它们按正确的顺序编译。

    1. 理解:
    2. 假设我们需要生成一个可执行文件,该文件生成需要链接a.so b.so c.so d.so四个动态库,
    3. 正常来讲我们只需要以下两条指令即可:
    4. ADD_EXECUTABLE(main main.cpp)
    5. TARGET_LINK_LIBRARIES(main a.so b.so c.so d.so)
    6. '但是编译的时候报错,一些符号的定义找不到,而这些符号恰恰就在这几个库中,假设在a.so 和
    7. b.so中,在上述两条指令之间加上一条指令即可编译通过:'
    8. ADD_DEPENDENCIES(main a.so b.so)
    9. '原因很简单,生成main需要依赖a.so和b.so中的符号定义,然而a.so和b.so库的生成是在main
    10. 编译生成之后的,添加这条语句就是提醒编译器需要先生成main的依赖(a.so,b.so),然后再去生成main。'

    LINK_DIRECTORIES:添加需要链接的库文件目录

            link_directories(directory1 directory2 …)

    TARGET_LINK_LIBRARIES:添加要链接的库的名称

    1. TARGET_LINK_LIBRARIES(targetlibrary1 library2 …)
    2. 用法:用于链接库文件。根据调库原则,一是要有库的头文件,二是要有库生成的共享或者静态链接库,
    3. include_directories命令已经包含了头文件,所以需要用target_link_libraries包含库文件,
    4. '第一个参数表示的是可执行文件的名称,这个文件执行需要链接那些库,就全写在后面。'

    ADD_EXECUTABLE(目标可执行文件名  生成该可执行文件的源文件)

    1. ADD_EXECUTABLE(hello ${SRC_LIST})
    2. 生成可执行文件名是hello,源文件读取变量SRC_LIST中的内容,同样可以传入多个源文件

    ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

            作用:将指定的文件夹加到build任务列表中,这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置,

            source_dir:必选参数,该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。

            binary_dir:可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir,例子:add_subdirectory(src)

            EXCLUDE_FROM_ALL:是将写的目录从编译中排除,如程序中的example

    1. add_subdirectory(src)
    2. ' 这里指定src目录下存放了源文件,当执行cmake时,就会进入src目录去找CMakeLists.txt,
    3. 所以在src目录下也建立一个CMakeLists.txt '
    4. ADD_SUBDIRECTORY(src bin):
    5. ' 将src子目录加入工程并指定编译输出(包含编译中间结果)路径为bin 目录(bin目录自动创建)
    6. 如果不进行bin目录的指定,那么编译结果(包括中间结果)都将存放在build/src目录 '

    STREQUAL关键字

            用于比较字符串,相同返回 true

    SUBDIRS():构建多个子目录

            SUBDIRS(dir1 dir2 ...[EXCLUDE_FROM_ALL exclude_dir1 exclude_dir2 ...] [PREORDER] )

            SUBDIRS()和ADD_SUBDIRECTORY()的区别:ADD_SUBDIRECTORY()在调用子目录时处理子目录,而SUBDIRS()将目录推送到在当前CMakeLists文件末尾处理的列表上,这是旧行为,已被弃用

    SET_TARGET_PROPERTIES()

            用来设置输出的名称,对于动态库,还可以用来指定动态库版本API 版本

    1. SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
    2. ' VERSION 指代动态库版本,SOVERSION 指代 API 版本 '
    3. 在 build/lib 目录会生成:
    4. libhello.so.1.2
    5. libhello.so.1->libhello.so.1.2
    6. libhello.so -> libhello.so.1

    add_compile_options(-std=c++11 -Wall):添加编译选项

            在cmake脚本中,设置编译选项可以通过add_compile_options命令,也可以通过set命令修改CMAKE_CXX_FLAGS或CMAKE_C_FLAGS。使用这两种方式在有的情况下效果是一样的,但请注意它们还是有区别的:add_compile_options命令添加的编译选项是针对所有编译器的(包括c和c++编译器),而set命令设置CMAKE_C_FLAGS或CMAKE_CXX_FLAGS变量则是分别只针对c和c++编译器的。

    1. if(CMAKE_COMPILER_IS_GNUCXX)      #判断编译器类型,如果是gcc编译器,则在编译选项中加入c++11支持
    2.     add_compile_options(-std=c++11)
    3.     message(STATUS "optional:-std=c++11")  
    4. endif(CMAKE_COMPILER_IS_GNUCXX)

      使用add_compile_options添加-std=c++11选项,是想在编译c++代码时加上c++11支持选项。但是因为add_compile_options是针对所有类型编译器的,所以在编译c代码时,就会产生如下warning:

    1. [ 50%] Building C object libb64/CMakeFiles/b64.dir/libb64-1.2.1/src/cdecode.c.obj
    2. cc1.exe: warning: command line option ‘-std=c++11’ is valid for C++/ObjC++ but not for C
    3. [100%] Building C object libb64/CMakeFiles/b64.dir/libb64-1.2.1/src/cencode.c.obj
    4. cc1.exe: warning: command line option ‘-std=c++11’ is valid for C++/ObjC++ but not for C
    5. Linking C static library libb64.a
    6. [100%] Built target b64

      要消除这个warning,就不能使用add_compile_options,而是只针对c++编译器添加这个option。所以如下修改代码,则警告消除。

    1. if(CMAKE_COMPILER_IS_GNUCXX)
    2.     set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
    3.     message(STATUS "optional:-std=c++11")  
    4. endif(CMAKE_COMPILER_IS_GNUCXX)
    5. 同样:add_definitions这个命令也是针对所有编译器

    option(MYDEBUG "enable debug compilation" OFF):添加控制选项

    第一个参数是这个option的名字,第二个参数是字符串,用来描述这个option是来干嘛的,第三个是option的值,ON或OFF,也可以不写,不写就是默认OFF。

    1. if(MYDEBUG)    add_executable(main2 main2.c)
    2. else()     message(STATUS "Currently is not in debug mode")   
    3. endif()
    4. '使用了if-else来根据option来决定是否编译main2.c'

    get_filename_component()

    1. get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" ABSOLUTE)
    2. '原封不动取目录或包含路径的文件 如果不填写路径会自动 添加上自己项目路径,也就是CMAKE_CURRENT_LIST_DIR和它拼接'
    3. get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" NAME)
    4. '取文件名,不包含路径'
    5. get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" EXT)
    6. '取扩展名'
    7. get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" REALPATH)
    8. '和ABSOLUTE 一样'
    9. get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" PATH)
    10. '只保留路径(如果第二个参数传入的是路径,那么取该路径的上一层路径)'

    AUX_SOURCE_DIRECTORY( ):搜寻目录下的所有源文件

            收集指定目录中所有源文件的名称,并将列表存储在提供的变量中

    FILE()

    1. file(WRITE filename "message to write"... )
    2. 'WRITE :向文件 filename 中写入一个信息. 存在则覆盖,不存在则创建'
    3. file(APPEND filename "message to write"... )
    4. 'APPEND :同 WRITE, 但会加到文件末尾'
    5. file(REMOVE [file1 ...])
    6. 'REMOVE :删除给定文件,包括子路径'
    7. file(REMOVE_RECURSE [file1 ...])
    8. 'REMOVE_RECURSE : 删除给定文件和路径,包括非空路径'
    9. file(RELATIVE_PATH variable directory file)
    10. 'RELATIVE_PATH : 确定路径到确定文件的相对路径'

    INSTALL():把文件安装到某个位置

    1. install(TARGETS MyLib
    2.         EXPORT MyLibTargets
    3.         LIBRARY DESTINATION lib  '动态库安装路径'
    4.         ARCHIVE DESTINATION lib  '静态库安装路径'
    5.         RUNTIME DESTINATION bin  '可执行文件安装路径'
    6.         PUBLIC_HEADER DESTINATION include  '头文件安装路径'
    7.         )
    8. '执行完cmake后,执行命令make install来执行cmake里install的动作'
    例子:
    1. #STEP:安装cmake模块文件:这行代码会将cmake_module/目录下的所有内容(假设这是一个包含自定义CMake模块的目录)安装到工作空间编译后的共享位置。
    2. # ${CATKIN_PACKAGE_SHARE_DESTINATION}通常指向一个类似于devel/share//cmake的路径,在那里其他项目可以通过find_package命令找到并使用你的CMake模块。
    3. #NOTE:DIRECTORY: 指定要安装的目录及其内容。例如,INSTALL(DIRECTORY src/ DESTINATION include) 将指定的“src”目录下的所有文件复制到安装路径下的“include”目录。
    4. #NOTE:DESTINATION: 定义了安装的目标位置。它是相对于系统安装根目录(通常是 /usr/local 或者通过 -DCMAKE_INSTALL_PREFIX 设置的位置)的一个路径。例如,DESTINATION bin 表示将文件安装到系统的 bin 目录下。
    5. install(DIRECTORY cmake_module/
    6. DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
    7. )
    8. #STEP:安装头文件:这个指令会将include/目录下所有的头文件安装到目标系统的相应头文件搜索路径。
    9. # ${CATKIN_PACKAGE_INCLUDE_DESTINATION}通常指向类似include/的位置,这样其他项目在编译时能够通过#include 的方式引用你的头文件。
    10. install(DIRECTORY include/
    11. DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
    12. )
    13. #STEP:安装库和可执行文件:这段代码指定了项目的主目标(通常是库或可执行文件),将其分别按静态库、动态库和可执行文件类型安装到相应的目录。
    14. #静态库和动态库会被安装到${CATKIN_PACKAGE_LIB_DESTINATION},这通常是一个像lib//这样的路径;
    15. #而可执行文件会被安装到${CATKIN_PACKAGE_BIN_DESTINATION},如bin目录下。
    16. #NOTE:RUNTIME: 在install(TARGETS ...)语境下,RUNTIME指的是可执行文件或者动态链接库(.dll, .dylib, .so等)
    17. #NOTE:LIBRARY: 在install(TARGETS ...)语境下,LIBRARY指向的是需要安装的库文件,通常指动态库
    18. #NOTE:ARCHIVE: 在install(TARGETS ...)语境下,ARCHIVE特指静态库文件(.a或.lib)
    19. install(TARGETS ${PROJECT_NAME}
    20. ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} #static lib
    21. LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} #dynamic lib
    22. RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} #executable file
    23. )
    24. #STEP:单独安装特定的可执行文件:这行代码专门针对名为charge_node的目标进行安装,并且仅安装其运行时组件(即可执行文件),同样安装到${CATKIN_PACKAGE_BIN_DESTINATION}目录下。
    25. install(TARGETS charge_node
    26. RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
    27. )
    28. #STEP:安装其他资源文件:这里指定将launch、cfg和scripts三个目录中的所有文件和子目录安装到${CATKIN_PACKAGE_SHARE_DESTINATION},
    29. #这些通常包括启动文件、配置文件以及脚本文件等,便于其他节点在运行时访问和使用。这个路径通常为share/
    30. install (DIRECTORY launch cfg scripts
    31. DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
    32. )

    add_custom_command():用于添加自定义命令,实现某些操作。

            使用方式一:单独使用,在生成目标文件(使用 add_executable() add_library() 命令生成的文件)时自动执行 add_custom_command 指定的命令。

    1. add_custom_command(TARGET
    2.                 PRE_BUILD | PRE_LINK | POST_BUILD
    3.                     COMMAND command1 [ARGS] [args1...]
    4.                     [COMMAND command2 [ARGS] [args2...] ...]
    5.                     [BYPRODUCTS [files...]]
    6.                     [WORKING_DIRECTORY dir]
    7.                     [COMMENT comment]
    8.                     [VERBATIM] [USES_TERMINAL]
    9.                     [COMMAND_EXPAND_LISTS])
    10. 'TARGET:由 add_executable 或 add_library 生成的目标文件名称'
    11. 'PRE_BUILD | PRE_LINK | POST_BUILD:分别表示编译之前执行命令,链接之前执行命令,生成目标文件后执行命令;
    12. 具体的pre_build就是在cmake 命令之后, make命令之前。 post_build是在make 出可执行文件后执行'
    13. 'COMMAND:需要执行的命令'
    1. 例子:
    2. cmake_minimum_required(VERSION 3.5)
    3.  ​ project(test)
    4.  ​ add_executable(${PROJECT_NAME} main.c)
    5.  ​ add_custom_command(TARGET ${PROJECT_NAME}
    6.                     POST_BUILD
    7.                     COMMAND ${CMAKE_COMMAND} -E echo compile finish
    8.                     VERBATIM )
    9. ${使用-E标志可以以与操作系统无关的方式,运行许多公共操作}
    10. '该工程会生成可执行程序:test,生成 test 后,会在终端输出 compile finish。如下图:'

     参考1参考2

    系统信息:

    1. CMAKE_MAJOR_VERSION,CMAKE 主版本号,比如 2.4.6 中的 2
    2. CMAKE_MINOR_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4
    3. CMAKE_PATCH_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6
    4. CMAKE_SYSTEM 系统名称,比如 Linux-2.6.22
    5. CMAKE_SYSTEM_NAME 不包含版本的系统名,比如 Linux
    6. CMAKE_SYSTEM_VERSION 系统版本,比如 2.6.22
    7. CMAKE_SYSTEM_PROCESSOR 处理器名称,比如 i686.
    8. UNIX 在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
    9. WIN32 在所有的 win32 平台为 TRUE,包括 cygwin

            补充:gcc编译链中i686和x86-64有什么区别:

             图中有很多种交叉编译器,只需关注红色方框中的两个,这个编译链带的i686或者x86_64和Linux开发板没关系,和宿主的Ubuntu是64还是32有关系。

    ADD_DEFINITIONS()

            向 C/C++编译器添加-D 定义,比如: ADD_DEFINITIONS(-DENABLE_DEBUG),多个参数时,用空格分割。 如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变量设置。

    INCLUDE()

            用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块. INCLUDE(file1 [OPTIONAL]) INCLUDE(module [OPTIONAL]) OPTIONAL 参数的作用是文件不存在也不会产生错误。 你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH 中搜索这个模块并载入。 载入的内容将在处理到 INCLUDE 语句是直接执行。

    控制指令:

    开关选项:

    1. CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS '用来控制 IF ELSE 语句的书写方式'
    2. BUILD_SHARED_LIBS
    3. '这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,
    4. 默认编译生成的库都是静态库,如果 SET BUILD_SHARED_LIBS ON后,默认生成的为动态'
    5. CMAKE_C_FLAGS '设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS添加'
    6. CMAKE_CXX_FLAGS '设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS添加'

    IF()

    1. IF(expression)
    2. COMMAND1(ARGS ...)
    3. COMMAND2(ARGS ...)
    4. ...
    5. ELSE(expression)
    6. COMMAND1(ARGS ...)
    7. COMMAND2(ARGS ...)
    8. ...
    9. ENDIF(expression)

            另外一个指令是 ELSEIF,总体把握一个原则,凡是出现 IF 的地方一定要有对应的ENDIF,出现 ELSEIF 的地方,ENDIF 是可选的。

    1. #一个小例子,用来判断平台差异:
    2. IF (WIN32)
    3.     MESSAGE(STATUS “This is windows.”)
    4. ELSE (WIN32)
    5.     MESSAGE(STATUS “This is not windows”)
    6. ENDIF (WIN32)

            上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,ELSE(WIN32)之类的语句很容易引起歧义,可以SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON),这时候就可以写成如下代码形式:

    1. IF (WIN32)
    2. ELSE ()
    3. ENDIF ()
    4. "配合ELSEIF使用,可能的写法是这样:"
    5. IF (WIN32)
    6.     #do something related to WIN32
    7. ELSEIF (UNIX)
    8.     #do something related to UNIX
    9. ELSEIF(APPLE)
    10.     #do something related to APPLE
    11. ENDIF (WIN32)

    WHILE()

    1. WHILE(condition) "判断逻辑同if"
    2.     COMMAND1(ARGS ...)
    3.     COMMAND2(ARGS ...)
    4. ...
    5. ENDWHILE(condition)

    总参考:

    链接1链接2 

            暂时就先这样,以后还有什么命令再来补充。

  • 相关阅读:
    day41 jdk8新特性Stream流 数据库安装
    doris docker环境编译部署
    C#操作Zip压缩的内部文件
    uniapp 点击 富文本元素 图片 可以预览(非nvue)
    leetcode:剑指 Offer 17. 打印从1到最大的n位数(python3解法)
    从中间件团队窃取了这个组件,见识到了编码能力的天花板!!
    金山办公:订阅为王?
    本地安装GDAL3.0以上版本
    AUTOSAR中的Crypto Stack(一)--概述
    程序打包教程
  • 原文地址:https://blog.csdn.net/qq_34761779/article/details/127345399