零基础CMake入门(基于ROS)

cmake实践

实战示例项目:

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

基础知识铺垫:

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

C++ 有多种标准:
C++98
C++11(又名 C++0x)
C++14(又名 C++1y)
C++17(又名 C++1z)
C++2a(2020 年计划的下一个标准)

' 对于 6.1 之前的 GCC 版本,默认模式是 C++98,对于 GCC 6.1 及更高版本,默认模式是 C++14 '

'您可以使用命令行标志-std显式指定 C++ 标准。例如:'
-std=c++98, 或-std=gnu++98(带有 GNU 扩展的 C++98)
-std=c++11, 或-std=gnu++11(带有 GNU 扩展的 C++11)
-std=c++14,或-std=gnu++14(带有 GNU 扩展的 C++14),GCC 6.1 及更高版本的默认模式
-std=c++17,或-std=gnu++17(带有 GNU 扩展的 C++17),实验性的
-std=c++2a,或-std=gnu++2a(带有 GNU 扩展的 C++2a),实验性的

常用的 GCC 编译器选项:

g++ -Wall -g -o Hello.exe Hello.cpp
    -o:     指定输出的可执行文件名
    -Wall: 打印“ all”警告信息
    -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 控制语句中是直接使用变量名

        有关双引号:

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

3.内部构建和外部构建

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

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

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

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

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

5.CMake的预定义变量

PROJECT_NAME									'工程名' -->当前'CMakeList.txt里'设置的project_name
PROJECT_SOURCE_DIR 							    工程的根目录
PROJECT_BINARY_DIR 							    运行cmake的目录,通常是${PROJECT_SOURCE_DIR}/build

CMAKE_CURRENT_SOURCE_DIR: 					    '当前正处理'的源码路径  --> 当前'CMakeLists.txt' 所在的路径
CMAKE_CURRENT_BINARY_DIR: 						当前正在处理的'构建目录'
备注: 每个由'add_subdirectory添加'的目录将会在'构建树(Build Tree)'中创建一个'构建目录'
补充: 对于直接在'源码目录中编译'的情况,当前正在处理的构建目录就是'当前源码'所在的目录-->'基本不会'
CMAKE_CURRENT_LIST_DIR: 						当前处理的'cmake文件'所在的目录
备注:这里的cmake文件可能是'.camke'文件或'CMakeListst.txt'文
CMAKE_CURRENT_LIST_FILE: 						输出调用这个变量的 CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE: 						输出这个变量所在的行-->'定位报错'
CMAKE_PROJECT_NAME: 							'整个项目'配置的project_name

'LIBRARY'_OUTPUT_DIR、'BINARY'_OUTPUT_DIR:		'库'和'可执行'的最终存放目录
CMAKE_MODULE_PATH							    用来定义自己的 cmake 模块所在的路径

6.CMake常用指令

基本指令:

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

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

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

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

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

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

SET()

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

set(ENV{} [])                                            设置环境变量

$ENV:环境变量的调用

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

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

主要包含三种信息:

SEND_ERROR               产生错误,成功过程被跳过
SATUS                    "输出前缀为--的信息,常用"
FATAL_ERROR              立即终止所有 cmake 过程

例子:MESSAGE(SATUS "\n #### I love China! #### \n")

find_package(<PackageName> [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停止执行。[引用]

例子:

find_package(OpenCV 4 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(main src/main.cpp)
target_link_libraries(main ${OpenCV_LIBRARIES})

find_package从目录中寻找OpenCV,找到后将头文件目录设置为${OpenCV_INCLUDE_DIRS},
库文件设为${OpenCV_LIBRARIES},然后在工程中包含OpenCV头文件目录,生成可执行文件,
最后链接OpenCV库。

INCLUDE_DIRECTORIES()

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

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

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

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

用法:
include_directories(“/opt/MATLAB/R2012a/extern/include”)
include_directories(include ${catkin_INCLUDE_DIRS})
include_directories(${Boost_INCLUDE_DIRS})

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

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

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

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

find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)
第一个参数是变量名称,第二个参数是库名称,第三个参数是HINTS,第4个参数是路径

使用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}:源文件

例子:

ADD_LIBRARY (hello SHARED ${LIBHELLO_SRC})         # 添加动态库
ADD_LIBRARY (hello STATIC ${LIBHELLO_SRC})         # 添加静态库

'仍然用hello作为target名时,是不能成功创建所需的静态库的,因为hello作为一个target是不能重名的,
故把上面的hello修改为hello_static,同理不需要写全libhello_static.a 只需要填写hello_static即可,
cmake系统会自动为你生成 libhello_static.a '

ADD_LIBRARY (hello_static STATIC ${LIBHELLO_SRC})

'按照一般的习惯,静态库名字跟动态库名字应该是一致的,只是扩展名不同;即:静态库名为 libhello.a;
动态库名为libhello.so;所以,希望 "hello_static" 在输出时,不是"hello_static",而是以"hello"
的名字显示,故设置如下:'

SET_TARGET_PROPERTIES (hello_static PROPERTIES OUTPUT_NAME "hello")
GET_TARGET_PROPERTY (OUTPUT_VALUE hello_static OUTPUT_NAME)

${

生成默认库的开关选项:BUILD_SHARED_LIBS
这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,
默认编译生成的库都是静态库,如果 SET BUILD_SHARED_LIBS ON后,默认生成的为动态

}

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

理解:

假设我们需要生成一个可执行文件,该文件生成需要链接a.so b.so c.so d.so四个动态库,
正常来讲我们只需要以下两条指令即可:

ADD_EXECUTABLE(main main.cpp)
TARGET_LINK_LIBRARIES(main a.so b.so c.so d.so)

'但是编译的时候报错,一些符号的定义找不到,而这些符号恰恰就在这几个库中,假设在a.so 和
b.so中,在上述两条指令之间加上一条指令即可编译通过:'

ADD_DEPENDENCIES(main a.so b.so)

'原因很简单,生成main需要依赖a.so和b.so中的符号定义,然而a.so和b.so库的生成是在main
编译生成之后的,添加这条语句就是提醒编译器需要先生成main的依赖(a.so,b.so),然后再去生成main。'

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

        link_directories(directory1 directory2 …)

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

TARGET_LINK_LIBRARIES(targetlibrary1 <debug | optimized> library2 …)

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

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

ADD_EXECUTABLE(hello ${SRC_LIST})
生成可执行文件名是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

add_subdirectory(src)
' 这里指定src目录下存放了源文件,当执行cmake时,就会进入src目录去找CMakeLists.txt,
  所以在src目录下也建立一个CMakeLists.txt '

ADD_SUBDIRECTORY(src bin):
' 将src子目录加入工程并指定编译输出(包含编译中间结果)路径为bin 目录(bin目录自动创建)
  如果不进行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 版本

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
' VERSION 指代动态库版本,SOVERSION 指代 API 版本 '

在 build/lib 目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
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++编译器的。

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

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

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

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

if(CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
    message(STATUS "optional:-std=c++11")  
endif(CMAKE_COMPILER_IS_GNUCXX)

同样:add_definitions这个命令也是针对所有编译器

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

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

if(MYDEBUG)    add_executable(main2 main2.c)
else()         message(STATUS "Currently is not in debug mode")   
endif()

'使用了if-else来根据option来决定是否编译main2.c'

get_filename_component()

get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" ABSOLUTE)
'原封不动取目录或包含路径的文件 如果不填写路径会自动 添加上自己项目路径,也就是CMAKE_CURRENT_LIST_DIR和它拼接'

get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" NAME)
'取文件名,不包含路径'

get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" EXT)
'取扩展名'

get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" REALPATH)
'和ABSOLUTE 一样'

get_filename_component(ANDROID_NDK_EXPECTED_PATH "d:/ahello.c" PATH)
'只保留路径(如果第二个参数传入的是路径,那么取该路径的上一层路径)'

AUX_SOURCE_DIRECTORY(<dir> <variable>):搜寻目录下的所有源文件

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

FILE()

file(WRITE filename "message to write"... )
'WRITE  :向文件 filename 中写入一个信息. 存在则覆盖,不存在则创建'

file(APPEND filename "message to write"... )
'APPEND :同 WRITE, 但会加到文件末尾'

file(REMOVE [file1 ...])
'REMOVE :删除给定文件,包括子路径'

file(REMOVE_RECURSE [file1 ...])
'REMOVE_RECURSE : 删除给定文件和路径,包括非空路径'

file(RELATIVE_PATH variable directory file)
'RELATIVE_PATH  : 确定路径到确定文件的相对路径'

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

install(TARGETS MyLib
        EXPORT MyLibTargets
        LIBRARY DESTINATION lib              '动态库安装路径'
        ARCHIVE DESTINATION lib              '静态库安装路径'
        RUNTIME DESTINATION bin              '可执行文件安装路径'
        PUBLIC_HEADER DESTINATION include    '头文件安装路径'
        )

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

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

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

add_custom_command(TARGET <target>
                    PRE_BUILD | PRE_LINK | POST_BUILD
                    COMMAND command1 [ARGS] [args1...]
                    [COMMAND command2 [ARGS] [args2...] ...]
                    [BYPRODUCTS [files...]]
                    [WORKING_DIRECTORY dir]
                    [COMMENT comment]
                    [VERBATIM] [USES_TERMINAL]
                    [COMMAND_EXPAND_LISTS])

'TARGET:由 add_executable 或 add_library 生成的目标文件名称'
'PRE_BUILD | PRE_LINK | POST_BUILD:分别表示编译之前执行命令,链接之前执行命令,生成目标文件后执行命令;
具体的pre_build就是在cmake 命令之后, make命令之前。 post_build是在make 出可执行文件后执行'
'COMMAND:需要执行的命令'
例子:

cmake_minimum_required(VERSION 3.5)

 ​ project(test)

 ​ add_executable(${PROJECT_NAME} main.c)

 ​ add_custom_command(TARGET ${PROJECT_NAME}
                    POST_BUILD
                    COMMAND ${CMAKE_COMMAND} -E echo compile finish
                    VERBATIM )

${使用-E标志可以以与操作系统无关的方式,运行许多公共操作}

'该工程会生成可执行程序:test,生成 test 后,会在终端输出 compile finish。如下图:'

 参考1参考2

系统信息:

CMAKE_MAJOR_VERSION,CMAKE     主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSION,CMAKE     次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSION,CMAKE     补丁等级,比如 2.4.6 中的 6
CMAKE_SYSTEM                   系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME              不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION           系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR         处理器名称,比如 i686.
UNIX                           在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
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 语句是直接执行。

控制指令:

开关选项:

CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS   '用来控制 IF ELSE 语句的书写方式'

BUILD_SHARED_LIBS
'这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,
默认编译生成的库都是静态库,如果 SET BUILD_SHARED_LIBS ON后,默认生成的为动态'

CMAKE_C_FLAGS                       '设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS添加'

CMAKE_CXX_FLAGS                     '设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS添加'

IF()

IF(expression)
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
ELSE(expression)
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
ENDIF(expression)

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

#一个小例子,用来判断平台差异:

IF (WIN32)
    MESSAGE(STATUS “This is windows.”)
ELSE (WIN32)
    MESSAGE(STATUS “This is not windows”)
ENDIF (WIN32)

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

IF (WIN32)
ELSE ()
ENDIF ()

"配合ELSEIF使用,可能的写法是这样:"

IF (WIN32)
    #do something related to WIN32
ELSEIF (UNIX)
    #do something related to UNIX
ELSEIF(APPLE)
    #do something related to APPLE
ENDIF (WIN32)

WHILE()

WHILE(condition)            "判断逻辑同if"
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
ENDWHILE(condition)

总参考:

链接1链接2 

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

  • 8
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值