当前位置: 首页 > news >正文

中山哪里可以做网站wordpress seo是什么

中山哪里可以做网站,wordpress seo是什么,专做电器的网站,wordpress插件地图标记目录cmake简介cmake的下载cmake 的使用方法示例一#xff1a;单个源文件(cmake生成的中间文件以及可执行文件都放在build目录下)示例二#xff1a;多个源文件示例三#xff1a;生成库文件(动态库和静态库、修改库文件名字、最低版本要求)示例四#xff1a;将源文件放到不同… 目录cmake简介cmake的下载cmake 的使用方法示例一单个源文件(cmake生成的中间文件以及可执行文件都放在build目录下)示例二多个源文件示例三生成库文件(动态库和静态库、修改库文件名字、最低版本要求)示例四将源文件放到不同的目录(需添加多个CMakeLists.txt并指明头文件)示例五将生成的可执行文件和库文件放到不同的目录下CMakeLists.txt 语法规则简单的语法介绍常用命令常用变量PROJECT_SOURCE_DIR、PROJECT_BINARY_DIR等CMAKE_VERSION、PROJECT_VERSION等改变行为的变量描述系统的变量控制编译的变量双引号的作用条件判断循环语句数学运算math定义函数宏定义文件操作设置交叉编译下载高版本的cmake变量的作用域属性cmake简介 开放源代码。我们可以直接从cmake官网https://cmake.org/下载到它的源代码 跨平台。它允许开发者编写一种与平台无关跨平台的CMakeLists.txt 文件来制定整个工程的编译流程cmake 工具会解析CMakeLists.txt 文件语法规则再根据当前的编译平台生成本地化的Makefile 和工程文件。 语法规则简单。Makefile 语法规则比较复杂。 cmake的下载 cmake 就是一个工具命令在Ubuntu 系统下通过apt-get 命令可以在线安装如下所示 sudo apt-get install cmake查看cmake的版本号 cmake 官方也给大家提供相应教程链接地址如下所示 https://cmake.org/documentation/ //文档总链接地址 https://cmake.org/cmake/help/latest/guide/tutorial/index.html //培训教程 cmake 的使用方法 示例一单个源文件(cmake生成的中间文件以及可执行文件都放在build目录下) //main.c #include stdio.h int main() {printf(Hello World!\n);return 0; }新建一个CMakeLists.txt 文件在文件中写入如下内容 project(HELLO) #设置工程名称 add_executable(hello ./main.c) #生成一个名为hello 的可执行文件所需源文件为当前目录下的main.c写入完成之后保存退出当前工程目录结构如下所示 ├──CMakeLists.txt └──main.c在工程目录下直接执行cmake 命令如下所示 cmake ./cmake 后面携带的路径指定了CMakeLists.txt 文件的所在路径执行结果如下所示 执行完cmake 之后除了源文件main.c 和CMakeLists.txt 之外生成了很多其它的文件或文件夹包括CMakeCache.txt、CmakeFiles、cmake_install.cmake、Makefile。 有了Makefile 之后接着我们使用make 工具编译我们的工程如下所示 在Ubuntu 下运行即可如下所示 CMakeLists.txt 文件解析 ⚫ 第一行project(HELLO) project 命令用于设置工程的名称通常需要我们提供参数多个参数使用空格分隔。 设置工程名称并不是强制性的但是最好加上。 ⚫ 第二行add_executable(hello ./main.c) add_executable(hello ./main.c)表示需要生成一个名为hello 的可执行文件所需源文件为当前目录下的main.c。 使用out-of-source 方式构建(cmake生成的中间文件以及可执行文件都放在build目录下) 在上面的例子中cmake 生成的文件以及最终的可执行文件hello与工程的源码文件main.c 混在了一起这使得工程看起来非常乱使用out-of-source 方式构建。 将cmake 编译生成的文件清理下然后在工程目录下创建一个build 目录如下所示 ├──build ├──CMakeLists.txt └──main.c然后进入到build 目录下执行cmake cd build/ cmake ../ make这样cmake 生成的中间文件以及make 编译生成的可执行文件就全部在build目录下了如果要清理工程直接删除build 目录即可。 示例二多个源文件 ⚫ hello.h 文件内容 #ifndef __TEST_HELLO_ #define __TEST_HELLO_void hello(const char *name);#endif //__TEST_HELLO_⚫ hello.c 文件内容 #include stdio.h #include hello.hvoid hello(const char *name) {printf(Hello %s!\n, name); }⚫ main.c 文件内容 #include hello.hint main(void) {hello(World);return 0; }⚫ 然后准备好CMakeLists.txt 文件 project(HELLO) #设置工程名称 set(SRC_LIST main.c hello.c) #设置变量 add_executable(hello ${SRC_LIST}) #生成一个名为hello 的可执行文件所需源文件为当前目录下的main.c工程目录结构如下所示 ├──build //文件夹 ├──CMakeLists.txt ├──hello.c ├──hello.h └──main.c同样进入到build 目录下执行cmake、再执行make 编译工程最终就会得到可执行文件hello。 在本例子中set 命令用于设置变量如果变量不存在则创建该变量并设置它我们定义了一个SRC_LIST 变量记录main.c 和hello.c在add_executable 命令引用了该变量。 当然我们也可以不去定义SRC_LIST 变量直接将源文件列表写在add_executable 命令中如下 add_executable(hello main.c hello.c)示例三生成库文件(动态库和静态库、修改库文件名字、最低版本要求) 在本例中除了生成可执行文件hello 之外我们还需要将hello.c 编译为静态库文件或者动态库文件在示例二的基础上对CMakeLists.txt 文件进行修改如下所示 project(HELLO) add_library(libhello hello.c) #生成静态库文件 add_executable(hello main.c) target_link_libraries(hello libhello) #目标指定依赖库进入到build 目录下执行cmake、再执行make 编译工程编译完成之后在build 目录下就会生成可执行文件hello 和库文件如下所示 目录结构如下所示 ├──build │ ├──hello │ └──liblibhello.a ├──CMakeLists.txt ├──hello.c ├──hello.h └──main.cCMakeLists.txt 文件解释 add_library 命令用于生成库文件在本例中我们传入了两个参数 第一个参数表示库文件的名字需要注意的是这个名字是不包含前缀和后缀的名字(掐头去尾)在Linux 系统中库文件的前缀是lib动态库文件的后缀是.so而静态库文件的后缀是.a所以意味着最终生成的库文件对应的名字会自动添加上前缀和后缀第二个参数表示库文件对应的源文件。 本例中add_library 命令生成了一个静态库文件liblibhello.a如果要生成动态库文件可以这样做 add_library(libhello SHARED hello.c) #生成动态库文件 add_library(libhello STATIC hello.c) #生成静态库文件target_link_libraries 命令为目标指定依赖库在本例中hello.c 被编译为库文件并将其链接进hello 程序。 修改生成的库文件名字 本例生成的库为liblibhello.a名字非常不好看想生成libhello.a直接修改add_library 命令的参数可以吗 add_library(hello hello.c)答案是不行的因为hello 这个目标已经存在了add_executable(hello main.c)目标名对于整个工程来说是唯一的不可出现相同名字的目标。 实际上我们只需要在CMakeLists.txt文件中添加下面这条命令即可 set_target_properties(libhello PROPERTIES OUTPUT_NAME hello)set_target_properties 用于设置目标的属性这里通过set_target_properties 命令对libhello 目标的OUTPUT_NAME 属性进行了设置将其设置为hello。 我们进行实验此时CMakeLists.txt 文件中的内容如下所示 cmake_minimum_required(VERSION 3.5) project(HELLO) add_library(libhello SHARED hello.c) set_target_properties(libhello PROPERTIES OUTPUT_NAME hello) add_executable(hello main.c) target_link_libraries(hello libhello)除了添加set_target_properties 命令之外我们还加入了 cmake_minimum_required 命令该命令用于设置当前工程的cmake 最低版本号要求这个并不是强制性的。进入到build 目录下使用cmake make 编译整个工程编译完成之后会发现生成的库文件为libhello.so而不是liblibhello.so。 ├──build │ ├──hello │ └──libhello.so ├──CMakeLists.txt ├──hello.c ├──hello.h └──main.c示例四将源文件放到不同的目录(需添加多个CMakeLists.txt并指明头文件) 上面的示例源文件都是放在同一个目录下将这些源文件按照类型、功能、模块给它们放置到不同的目录下如下所示 ├──build #build 目录 ├──CMakeLists.txt ├──libhello │ ├──CMakeLists.txt │ ├──hello.c │ └──hello.h └──src├──CMakeLists.txt└──main.cCMakeLists.txt 文件的数量从1 个一下变成了3 个: ⚫ 顶层CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO) add_subdirectory(libhello) add_subdirectory(src)顶层CMakeLists.txt 中使用了add_subdirectory 命令该命令告诉cmake 去子目录中寻找新的CMakeLists.txt 文件并解析它 ⚫ src 目录下的CMakeLists.txt include_directories(${PROJECT_SOURCE_DIR}/libhello) add_executable(hello main.c) target_link_libraries(hello libhello)src 的CMakeList.txt 文件中新增加了include_directories 命令用来指明头文件所在的路径并且使用到了PROJECT_SOURCE_DIR 变量该变量指向了一个路径从命名上可知该变量表示工程源码的目录。 ⚫ libhello 目录下的CMakeLists.txt add_library(libhello hello.c) set_target_properties(libhello PROPERTIES OUTPUT_NAME hello)和前面一样进入到build 目录下进行构建、编译最终会得到可执行文件hellobuild/src/hello和库文件libhello.abuild/libhello/libhello.a ├──build │ ├──libhello │ │ └──libhello.a │ └──src │ └──hello ├──CMakeLists.txt ├──libhello │ ├──CMakeLists.txt │ ├──hello.c │ └──hello.h └──src├──CMakeLists.txt└──main.c示例五将生成的可执行文件和库文件放到不同的目录下 如果我想让可执行文件单独放置在bin目录下而库文件单独放置在lib目录下就像下面这样 ├──build├──lib│ └──libhello.a└──bin└──hello可以通过两个变量来实现将src 目录下的CMakeList.txt 文件进行修改如下所示 include_directories(${PROJECT_SOURCE_DIR}/libhello) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) add_executable(hello main.c) target_link_libraries(hello libhello)然后再对libhello 目录下的CMakeList.txt 文件进行修改如下所示 set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) add_library(libhello hello.c) set_target_properties(libhello PROPERTIES OUTPUT_NAME hello)EXECUTABLE_OUTPUT_PATH 变量控制可执行文件的输出路径 LIBRARY_OUTPUT_PATH 变量控制库文件的输出路径。 修改完成之后再次对工程进行构建、编译此时可执行文件hello 放置在build/bin 目录下、库文件libhello.a 放置在build/lib 目录下 ├──build │ ├──bin │ │ └──hello │ └──lib │ └──libhello.a ├──CMakeLists.txt ├──libhello │ ├──CMakeLists.txt │ ├──hello.c │ └──hello.h └──src├──CMakeLists.txt└──main.cCMakeLists.txt 语法规则 cmake 的使用方法其实还是非常简单的并没有Makefile的语法规则那么复杂难以理解。本小节我们来学习CMakeLists.txt 的语法规则。 简单的语法介绍 ⚫ 注释 使用“#”号进行单行注释 # # 这是注释信息 # cmake_minimum_required(VERSION 3.5) project(HELLO) 大多数脚本语言都是使用“#”号进行注释。⚫ 命令command 通常在CMakeLists.txt 文件中使用最多的是命令譬如上例中的cmake_minimum_required、project 都是命令命令的使用方式有点类似于C 语言中的函数因为命令后面需要提供一对括号并且通常需要我们提供参数多个参数使用空格分隔而不是逗号“,”这是与函数不同的地方。命令的语法格式如下所示 command(参数1 参数2 参数3 ...)参数可以分为必要参数和可选参数必要参数使用参数表示可选参数使用[参数]表示譬如set 命令 set(variable value... [PARENT_SCOPE])set 命令用于设置变量第一个参数和第二个参数是必要参数在参数列表…表示参数个数没有限制的最后可以添加一个可选参数PARENT_SCOPEPARENT_SCOPE 选项根据实际使用情况确定是否需要添加。 在CMakeLists.txt 中命令名不区分大小写 project(HELLO) #小写 PROJECT(HELLO) #大写⚫ 变量variable 使用set 命令可以对变量进行设置譬如 # 设置变量MY_VAL set(MY_VAL Hello World!)上例中通过set 命令对变量MY_VAL 进行设置将其内容设置为Hello World! 通过${MY_VAL}方式来引用变量如下所示 #设置变量MY_VAL set(MY_VAL Hello World!)#引用变量MY_VAL message(${MY_VAL})变量可以分为cmake 内置变量以及自定义变量上例中所定义的MY_VAL 是一个自定义变量在上小节中所使用的LIBRARY_OUTPUT_PATH 和EXECUTABLE_OUTPUT_PATH 变量是cmake 的内置变量每一个内置变量都有自己的含义像这样的内置变量还有很多。 常用命令 这个链接https://cmake.org/cmake/help/v3.5/manual/cmake-commands.7.html 可以查询到所有的命令及其相应的介绍 介绍一些基本的命令如下表所示 command说明add_executable可执行程序目标add_library库文件目标add_subdirectory去指定目录中寻找新的CMakeLists.txt 文件aux_source_directory收集目录中的文件名并赋值给变量cmake_minimum_required设置cmake 的最低版本号要求get_target_property获取目标的属性include_directories设置所有目标头文件的搜索路径相当于gcc 的-I 选项link_directories设置所有目标库文件的搜索路径相当于gcc 的-L 选项link_libraries设置所有目标需要链接的库list列表相关的操作message用于打印、输出信息project设置工程名字set设置变量set_target_properties设置目标属性target_include_directories设置指定目标头文件的搜索路径target_link_libraries设置指定目标库文件的搜索路径target_sources设置指定目标所需的源文件 常用变量 变量可以提供某种信息通常只需要读取变量即可不需要对变量进行修改 变量说明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_VERSIONcmake 的主版本号CMAKE_MINOR_VERSIONcmake 的次版本号CMAKE_VERSIONcmake 的版本号主次修订PROJECT_VERSION_MAJOR工程的主版本号PROJECT_VERSION_MINOR工程的次版本号PROJECT_VERSION工程的版本号CMAKE_PROJECT_NAME工程的名字PROJECT_NAME工程名与CMAKE_PROJECT_NAME 等价 PROJECT_SOURCE_DIR、PROJECT_BINARY_DIR等 PROJECT_SOURCE_DIR 变量表示工程的顶级目录也就是顶层CMakeLists.txt 文件所在目录PROJECT_BINARY_DIR 变量表示工程的BINARY_DIR 也就是顶层CMakeLists.txt 源码对应的BINARY_DIR输出文件目录。 譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt └──main.cCMakeLists.txt 文件内容如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO)message(${PROJECT_SOURCE_DIR}) message(${PROJECT_BINARY_DIR})CMakeLists.txt 中我们打印了PROJECT_SOURCE_DIR 和PROJECT_BINARY_DIR 变量进入到build目录下执行cmake 从打印信息可知PROJECT_SOURCE_DIR 指的就是工程的顶层CMakeLists.txt 源码所在路径而PROJECT_BINARY_DIR 指的是我们执行cmake 命令的所在目录也是顶层CMakeLists.txt 源码的BINARY_DIR。 ➢ CMAKE_SOURCE_DIR 和CMAKE_BINARY_DIR与上面两个等价 ➢ CMAKE_CURRENT_SOURCE_DIR 和MAKE_CURRENT_BINARY_DIR 指的是当前源码的路径以及当前源码的BINARY_DIR通过示例来看看譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt ├──main.c └──src└──CMakeLists.txt顶层CMakeLists.txt 文件通过add_subdirectory 加载子目录src 下的CMakeLists.txt src 目录下CMakeLists.txt 文件内容如下所示 # src 下的CMakeLists.txt message(${PROJECT_SOURCE_DIR}) message(${PROJECT_BINARY_DIR}) message(${CMAKE_CURRENT_SOURCE_DIR}) message(${CMAKE_CURRENT_BINARY_DIR})通过message 将这些变量打印出来对比看看进入到build 目录下执行cmake CMAKE_VERSION、PROJECT_VERSION等 ➢ CMAKE_VERSION、CMAKE_MAJOR_VERSION 和CMAKE_MINOR_VERSION 记录cmake 的版本号如下 # CMakeLists.txt message(${CMAKE_VERSION}) message(${CMAKE_MAJOR_VERSION}) message(${CMAKE_MINOR_VERSION})打印信息如下 ➢ PROJECT_VERSION、PROJECT_VERSION_MAJOR 和PROJECT_VERSION_MINOR 记录工程的版本号其实可以给工程设置一个版本号通过project()命令进行设置如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0) #设置工程版本号为1.1.0# 打印 message(${PROJECT_VERSION}) message(${PROJECT_VERSION_MAJOR}) message(${PROJECT_VERSION_MINOR})打印信息如下 ➢ CMAKE_PROJECT_NAME 和PROJECT_NAME 这俩是等价的记录了工程的名字 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0) #设置工程版本号为1.1.0# 打印工程名字 message(${CMAKE_PROJECT_NAME}) message(${PROJECT_NAME})打印信息如下 改变行为的变量 这些变量可以改变某些行为 变量说明BUILD_SHARED_LIBS控制cmake 是否生成动态库CMAKE_BUILD_TYPE指定工程的构建类型release 或debugCMAKE_SYSROOT对应编译器的在–sysroot 选项CMAKE_IGNORE_PATH设置被find_xxx 命令忽略的目录列表CMAKE_INCLUDE_PATH为find_file()和find_path()命令指定搜索路径的目录列表CMAKE_INCLUDE_DIRECTORIES_BEFORE用于控制include_directories()命令的行为CMAKE_LIBRARY_PATH指定find_library()命令的搜索路径的目录列表CMAKE_MODULE_PATH指定要由include()或find_package()命令加载的CMake 模块的搜索路径的目录列表CMAKE_PROGRAM_PATH指定find_program()命令的搜索路径的目录列表 ➢ BUILD_SHARED_LIB 对于add_library()命令当没有显式指定生成动态库时SHARED 选项默认生成的是静态库其实我们可以通过BUILD_SHARED_LIBS 变量来控制add_library()命令的行为当将变量设置为on 时表示使能动态库则add_library()默认生成的便是动态库文件当变量设置为off 或未设置时add_library()默认生成的便是静态库文件。测试如下 譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt ├──hello │ └──hello.c └──world└──world.c顶层CMakeLists.txt 文件如下所示 # 顶层CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0)set(BUILD_SHARED_LIBS on) add_library(hello hello/hello.c) add_library(world world/world.c)进入到build 目录下执行cmake、make 进行构建、编译将会生成动态库文件libhello.so、libworld.so。 ➢ CMAKE_BUILD_TYPE 设置编译类型Debug 或者Release。debug 版会生成相关调试信息可以使用GDB 进行调试release 不会生成调试信息 # Debug 版本 set(CMAKE_BUILD_TYPE Debug)# Release 版本 set(CMAKE_BUILD_TYPE Release)➢ CMAKE_SYSROOT cmake 会将该变量传递给编译器–sysroot 选项通常我们在设置交叉编译时会使用到。 ➢ CMAKE_INCLUDE_PATH 分别用于查找文件、路径我们需要传入一个文件名find_file()命令会将该文件的全路径返回给我们而find_path()命令则会将文件的所在目录返回给我们。 这两个命令去哪找文件呢也就是通过CMAKE_INCLUDE_PATH 变量来进行指定CMAKE_INCLUDE_PATH 指定了一个目录列表find_file()、find_path()会去这个目录列表中查找文件。接下来我们进行测试。 譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt └──src└──hello.c顶层CMakeLists.txt 文件内容如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0) #设置工程版本号为1.1.0find_file(P_VAR hello.c) message(${P_VAR})通过find_file 命令查找hello.c 文件将路径信息记录在P_VAR 变量中现在我们没有设置CMAKE_INCLUDE_PATH 变量看看能不能找到hello.c 文件cmake 打印信息如下 很明显提示没有找到现在我们对CMAKE_INCLUDE_PATH 变量进行设置如下所示 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0) #设置工程版本号为1.1.0# 设置CMAKE_INCLUDE_PATH 变量 set(CMAKE_INCLUDE_PATH ${PROJECT_SOURCE_DIR}/src)# 查找文件 find_file(P_VAR hello.c) message(${P_VAR})此时打印信息为 这次就成功找到了hello.c 文件并将文件的全路径返回给我们。 ➢ CMAKE_LIBRARY_PATH 指定find_library()命令的搜索路径的目录列表。find_library()命令用于搜索库文件find_library()将会从CMAKE_LIBRARY_PATH 变量设置的目录列表中进行搜索。 ➢ CMAKE_MODULE_PATH 指定要由include()或find_package()命令加载的CMake 模块的搜索路径的目录列表。 ➢ CMAKE_INCLUDE_DIRECTORIES_BEFORE 改变include_directories()命令的行为。include_directories()命令默认情况下会将目录添加到列表的后面如果将CMAKE_INCLUDE_DIRECTORIES_BEFORE 设置为on则 include_directories()命令会将目录添加到列表前面同理若将CMAKE_INCLUDE_DIRECTORIES_BEFORE 设置为off 或未设置该变量include_directories()会将目录添加到列表后面。 ➢ CMAKE_IGNORE_PATH 要被find_program()、find_library()、find_file()和find_path()命令忽略的目录列表。表示这些命令不会去CMAKE_IGNORE_PATH 变量指定的目录列表中搜索。 描述系统的变量 这些变量描述了系统相关的一些信息 变量说明CMAKE_HOST_SYSTEM_NAME运行cmake 的操作系统的名称其实就是uname -sCMAKE_HOST_SYSTEM_PROCESSOR运行cmake 的操作系统的处理器名称uname -pCMAKE_HOST_SYSTEM运行cmake 的操作系统复合信息CMAKE_HOST_SYSTEM_VERSION运行cmake 的操作系统的版本号uname -rCMAKE_HOST_UNIX如果运行cmake 的操作系统是UNIX 和类UNIX则该变量为true否则是空值CMAKE_HOST_WIN32如果运行cmake 的操作系统是Windows则该变量为true否则是空值CMAKE_SYSTEM_NAME目标主机操作系统的名称CMAKE_SYSTEM_PROCESSOR目标主机的处理器名称CMAKE_SYSTEM目标主机的操作系统复合信息CMAKE_SYSTEM_VERSION目标主机操作系统的版本号ENV用于访问环境变量UNIX与CMAKE_HOST_UNIX 等价WIN32与CMAKE_HOST_WIN32 等价 ➢ CMAKE_HOST_SYSTEM_NAME 、CMAKE_HOST_SYSTEM_PROCESSOR 、 CMAKE_HOST_SYSTEM 和CMAKE_HOST_SYSTEM_VERSION 这四个变量描述的是运行cmake 的主机相关的信息我们直接打印出来看看即可 # 打印信息 message(${CMAKE_HOST_SYSTEM_NAME}) message(${CMAKE_HOST_SYSTEM_PROCESSOR}) message(${CMAKE_HOST_SYSTEM}) message(${CMAKE_HOST_SYSTEM_VERSION})对应的打印信息如下 ➢ CMAKE_SYSTEM_NAME 、CMAKE_SYSTEM_PROCESSOR 、CMAKE_SYSTEM 和CMAKE_SYSTEM_VERSION 用于描述目标主机相关的信息目标主机指的是可执行文件运行的主机譬如我们的ARM 开发板。 # 打印信息 message(${CMAKE_SYSTEM_NAME}) message(${CMAKE_SYSTEM_PROCESSOR}) message(${CMAKE_SYSTEM}) message(${CMAKE_SYSTEM_VERSION})cmake 打印信息如下 因为我们并没有对cmake 配置交叉编译默认会使用Ubuntu 系统运行cmake 的主机本身的编译工具所以生成的目标文件可执行文件或库文件只能运行在Ubuntu 系统中所以这4 个变量记录的依然是Ubuntu 主机的信息。 ➢ ENV 这个变量可用于访问环境变量用法很简单$ENV{VAR} # 访问环境变量 message($ENV{XXX})通过$ENV{XXX}访问XXX 环境变量我们来测试一下首先在Ubuntu 系统下使用export 命令导出XXX 环境变量 export XXXHello World! cd build/ cmake ..打印信息如下所示 从打印信息可知ENV 变量确实可以访问到Linux 系统的环境变量。 控制编译的变量 这些变量可以控制编译过程具体如下所示 变量说明EXECUTABLE_OUTPUT_PATH可执行程序的输出路径LIBRARY_OUTPUT_PATH库文件的输出路径 用来设置可执行文件的输出目录以及库文件的输出目录接下来我们进行简单地测试。 譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt ├──hello │ ├──hello.c │ └──hello.h └──main.chello.c 会被编译成动态库文件libhello.so而main.c 会被编译成可执行程序main.c 源码中调用了hello.c提供的函数顶层CMakeLists.txt 文件内容如下所示 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) #设置工程版本号为1.1.0 project(HELLO VERSION 1.1.0) # 设置可执行文件和库文件输出路径 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)# 头文件包含 include_directories(hello)# 动态库目标 add_library(hello SHARED hello/hello.c)# 可执行程序目标 add_executable(main main.c) target_link_libraries(main PRIVATE hello) #链接库进入到build目录下执行cmake、make进行构建、编译最终会生成可执行文件main 和库文件libhello.so目录结构如下所示 ├──build │ ├──bin │ │ └──main │ ├──lib │ └──libhello.so ├──CMakeLists.txt ├──hello │ ├──hello.c │ └──hello.h └──main.c这样使得生成的可执行程序在build/bin 目录下、生成的库文件在build/lib 目录下如果把这两行给注释掉那么生成的文件在build 目录中因为默认最终的目标文件的输出目录就是源码的BINARY_DIR。 双引号的作用 CMake 中双引号的作用我们可以从两个方面进行介绍命令参数和引用变量。 命令参数 调用命令时参数可以使用双引号譬如 project(HELLO)也可以不使用双引号譬如 project(HELLO)在本例中是没有区别的命令中多个参数之间使用空格进行分隔而cmake 会将双引号引起来的内容作为一个整体当它当成一个参数假如你的参数中有空格那么就可以使用双引号如下所示 message(Hello World) message(Hello World)在这个例子中第一个message 命令传入了两个参数而第二个message 命令只传入一个参数 在第一个message 命令中会将两个独立的字符串Hello 和World 都打印出来而且World 会紧跟在Hello 之后如下 HelloWorld而第二个message 命令只有一个参数所以打印信息如下 Hello World引用变量 我们先来看个例子如下所示 # CMakeLists.txt set(MY_LIST Hello World China) message(${MY_LIST})这个例子的打印信息如下 HelloWorldChina在这个例子中MY_LIST 是一个列表该列表包含了3 个元素分别是Hello、World、China。但这个message 命令打印时却将这三个元素全部打印出来并且各个元素之间没有任何分隔。此时我们可以在引用变量${MY_LIST}时加上双引号如下所示 # CMakeLists.txt set(MY_LIST Hello World China) message(${MY_LIST})此时message 打印信息如下 Hello;World;China因为此时$ {MY_LIST}是一个列表我们用${MY_LIST}这种形式的时候表示要让CMake 把这个数组的所有元素当成一个整体而不是分散的个体。于是为了保持数组的含义又提供一个整体的表达方式CMake 就会用分号“;”把这数组的多个元素连接起来。 而如果不加双引号时CMake 不会数组当成一个整体看待而是会将数组中的各个元素提取出进行打印输出。 条件判断 在cmake 中可以使用条件判断条件判断形式如下 if(expression)# then section.command1(args ...)command2(args ...)... elseif(expression2)# elseif section.command1(args ...)command2(args ...)... else(expression)# else section.command1(args ...)command2(args ...)... endif(expression)else 和endif 括号中的expression可写可不写如果写了就必须和if 中的expression一致。 expression 就是一个进行判断的表达式表达式对照表如下 表达式truefalse说明 constant 如果constant 为1、ON、YES、TRUE、Y 或非零数则为真如果constant 为0、OFF 、NO 、FALSE 、N 、IGNORE 、NOTFOUND、空字符串或以后缀-NOTFOUND 结尾则为False。布尔值大小写不敏感如果与这些常量都不匹配则将其视为变量或字符串 variable/string 已经定义并且不是false 的变量未定义或者是false 的变量变量就是字符串NOT expression expression 为falseexpression 为true expr1 AND expr2 expr1 和expr2 同时为trueexpr1 和expr2 至少有一个为false expr1 OR expr2 expr1 和expr2 至少有一个为trueexpr1 和expr2 都是falseCOMMAND namename 是一个已经定义的命令、宏或者函数name 未定义TARGET namename 是add_executable() 、add_library() 或add_custom_target() 定义的目标name 未定义TEST namename 是由add_test()命令创建的现有测试名称name 未创建EXISTS pathpath 指定的文件或目录存在path 指定的文件或目录不存在仅适用于完整路径IS_DIRECTORY pathpath 指定的路径为目录path 指定的路径不为目录仅适用于完整路径IS_SYMLINK pathpath 为符号链接path 不是符号链接仅适用于完整路径IS_ABSOLUTE pathpath 为绝对路径path 不是绝对路径variable/string MATCHES regexvariable 与正则表达式regex 匹配成功variable 与正则表达式regex 匹配失败variable/string IN_LIST 右边列表中包含左边的元素右边列表中不含左边的元素DEFINED 如果给定的变量已定义则为真。如果给定的变量未定义只要变量已经被设置它是真还是假并不重要。注意宏不是变量。variable/string LESS variablestring如果给定的字符串或变量的值是有效数字且小于右侧的数字则为真。左侧的数字大于或等于右侧的数字variable/string GREATER variable/string如果给定的字符串或变量的值是有效数字且大于右侧的数字则为真。左侧的数字小于或等于右侧的数字variable/string EQUAL variable/string如果给定的字符串或变量的值是有效数字并且等于右侧的值则为真左侧的数字不等于右侧的数字 上表中只是列出其中一部分表达式还有其它一些表达式这里并未列出大家可以通过https://cmake.org/cmake/help/v3.5/command/if.html 这个链接地址进行查看现在我们对上表中的表达式进行详解。 ⚫ constant 在if(constant)条件判断中如果constant 是1、ON、YES、TRUE、Y 或非零数字那么这个if 条件就是true如果constant 是0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀-NOTFOUND结尾那么这个条件判断的结果就是false。 在cmake 中可以把1、ON、YES、TRUE、Y 或非零数字以及0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀-NOTFOUND 结尾这些理解为常量类似于布尔值而且它们不区分大小写如果参数不是这些特定常量之一则将其视为变量或字符串并使用除 constant 之外的表达式。 if(ON)message(true) else()message(false) endif()输出为true if(YES)message(true) else()message(false) endif()输出为true if(true)message(true) else()message(false) endif()输出为true if(100)message(true) else()message(false) endif()输出为true if(0)message(true) else()message(false) endif()输出为false if(N)message(true) else()message(false) endif()输出为false if(NO)message(true) else()message(false) endif()输出为false ⚫ variable/string 在if(variable/string)条件判断中如果变量已经定义并且它的值是一个非假常量则条件为真否则为假注意宏参数不是变量在cmake 中也可以使用宏这个后面再给大家介绍。 set(GG Hello) if(GG)message(true) else()message(false) endif()输出为true set(GG NO) if(GG)message(true) else()message(false) endif()输出为false if(GG)message(true) else()message(false) endif()输出为false ⚫ NOT expression NOT 其实就类似于C 语言中的取反在if(NOT )条件判断中如果表达式expression 为真则条件判断为假如果表达式expression 为假则条件判断为真。 if(NOT GG)message(true) else()message(false) endif()输出为true 因为GG 变量没有定义所以GG 表达式为假但因为前面有NOT 关键字进行取反操作整个if 条件判断为真。 if(NOT YES)message(true) else()message(false) endif()输出为false if(NOT 0)message(true) else()message(false) endif()输出为true ⚫ expr1 AND expr2 这个就类似于C 语言中的逻辑与只有expr1 和expr2 同时为真时条件判断才为真否则条件判断为假。 if(yes AND on)message(true) else()message(false) endif()输出为true if(yes AND no)message(true) else()message(false) endif()输出为false if(false AND no)message(true) else()message(false) endif()输出为false ⚫ expr1 OR expr2 类似于C 语言中的逻辑或||当expr1 或expr2 至少有一个为真时条件判断为真否则为假。 if(false OR no)message(true) else()message(false) endif()输出为false if(yes OR no)message(true) else()message(false) endif()输出为true if(ON OR yes)message(true) else()message(false) endif()输出为true ⚫ COMMAND command-name 如果command-name 是一个已经定义的命令、宏或函数时条件判断为真否则为假。 除了宏之外在cmake 中还可以定义函数这个我们也会在后面向大家介绍。 if(COMMAND yyds)message(true) else()message(false) endif()输出为false if(COMMAND project)message(true) else()message(false) endif()输出为true ⚫ TARGET target-name 如果target-name 是add_executable()、add_library()或add_custom_target()定义的目标这些目标在整个工程中必须是唯一的不可出现两个名字相同的目标则条件判断为真否则为假。 if(TARGET hello)message(true) else()message(false) endif()输出为false add_library(hello hello.c)if(TARGET hello)message(true) else()message(false) endif()输出为true ⚫ EXISTS path 如果path 指定的文件或目录存在则条件判断为真否则为假。需要注意的是path 必须是文件或目录的全路径也就是绝对路径。 譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt ├──hello │ ├──hello.c │ └──hello.h └──main.c在顶层CMakeLists.txt 文件中使用if(EXISTS path)进行判断 if(EXISTS ${PROJECT_BINARY_DIR})message(true) else()message(false) endif()输出为true if(EXISTS ${PROJECT_BINARY_DIR}/hello)message(true) else()message(false) endif()输出为true if(EXISTS ${PROJECT_BINARY_DIR}/world)message(true) else()message(false) endif()输出为false if(EXISTS ${PROJECT_BINARY_DIR}/hello/hello.c)message(true) else()message(false) endif()输出为true ⚫ IS_DIRECTORY path 如果path 指定的路径是一个目录则条件判断为真否则为假同样path 也必须是一个绝对路径。 还是以上例中的工程目录结构为例 if(IS_DIRECTORY ${PROJECT_BINARY_DIR}/hello)message(true) else()message(false) endif()输出为true if(IS_DIRECTORY ${PROJECT_BINARY_DIR}/hello/hello.c)message(true) else()message(false) endif()⚫ IS_ABSOLUTE path 如果给定的路径path 是一个绝对路径则条件判断为真否则为假。 if(IS_ABSOLUTE ${PROJECT_BINARY_DIR})message(true) else()message(false) endif()输出为true if(IS_ABSOLUTE ./hello)message(true) else()message(false) endif()输出为false ⚫ variable|string MATCHES regex 这个表达式用的比较多可以用来匹配字符串可以使用正则表达式进行匹配。 如果给定的字符串或变量的值与给定的正则表达式匹配则为真否则为假。 set(MY_STR Hello World)if(MY_STR MATCHES Hello World)message(true) else()message(false) endif()输出为true 其实也可以引用变量 set(MY_STR Hello World)if(${MY_STR} MATCHES Hello World)message(true) else()message(false) endif()输出为true set(MY_STR Hello World)if(Hello World MATCHES Hello World)message(true) else()message(false) endif()输出为true ⚫ variable|string IN_LIST 如果左边给定的变量或字符串是右边列表中的某个元素相同则条件判断为真否则为假。 set(MY_LIST Hello World China)if(Hello IN_LIST MY_LIST)message(true) else()message(false) endif()输出为true set(MY_LIST Hello World China) set(Hello China)if(Hello IN_LIST MY_LIST)message(true) else()message(false) endif()输出为true ⚫ DEFINED 如果给定的变量已经定义则条件判断为真否则为假只要变量已经被设置定义if 条件判断就是真至于变量的值是真还是假并不重要。 if(DEFINED yyds)message(true) else()message(false) endif()输出为false set(yyds YYDS)if(DEFINED yyds)message(true) else()message(false) endif()输出为true ⚫ variable|string LESS variable|string 如果左边给定的字符串或变量的值是有效数字并且小于右侧的值则为真。否则为假。 测试如下 if(100 LESS 20)message(true) else()message(false) endif()输出为false if(20 LESS 100)message(true) else()message(false) endif()输出为true ⚫ variable|string GREATER variable|string 如果左边给定的字符串或变量的值是有效数字并且大于右侧的值则为真。否则为假。 测试如下 if(20 GREATER 100)message(true) else()message(false) endif()输出为false if(100 GREATER 20)message(true) else()message(false) endif()输出为true ⚫ variable|string EQUAL variable|string 如果左边给定的字符串或变量的值是有效数字并且等于右侧的值则为真。否则为假。 测试如下 if(100 EQUAL 20)message(true) else()message(false) endif()输出为false if(100 EQUAL 100)message(true) else()message(false) endif()输出为true ⚫ elseif 分支 可以使用elseif 组成多个不同的分支 set(MY_LIST Hello World China)if(Hello IN_LIST MY_LIST)message(Hello) elseif(World IN_LIST MY_LIST)message(World) elseif(China IN_LIST MY_LIST)message(China) else()message(false) endif()循环语句 cmake 中除了if 条件判断之外还支持循环语句包括foreach()循环、while()循环。 一、foreach 循环 ①、foreach 基本用法 foreach 循环的基本用法如下所示 foreach(loop_var arg1 arg2 ...)command1(args ...)command2(args ...)... endforeach(loop_var)endforeach 括号中的loop_var可写可不写如果写了就必须和foreach 中的loop_var一致。 参数loop_var 是一个循环变量循环过程中会将参数列表中的变量依次赋值给他类似于C 语言for 循环中经常使用的变量i。 # foreach 循环测试 foreach(loop_var A B C D)message(${loop_var}) endforeach()打印信息为 A B C D使用foreach 可以编译一个列表中的所有元素如下所示 # foreach 循环测试 set(my_list hello world china)foreach(loop_var ${my_list})message(${loop_var}) endforeach()打印信息如下 ②、foreach 循环之RANGE 关键字 用法如下所示 foreach(loop_var RANGE stop) foreach(loop_var RANGE start stop [step])对于第一种方式循环会从0 到指定的数字stop包含stopstop 不能为负数。 而对于第二种循环从指定的数字start 开始到stop 结束步长为step不过step 参数是一个可选参数如果不指定默认step1三个参数都不能为负数而且stop 不能比start 小。 接下来我们进行测试测试一 # foreach 循环测试 foreach(loop_var RANGE 4)message(${loop_var}) endforeach()打印信息如下 测试二 # foreach 循环测试 foreach(loop_var RANGE 1 4 1)message(${loop_var}) endforeach()打印信息如下 ③、foreach 循环之IN 关键字 用法如下 foreach(loop_var IN [LISTS [list1 [...]]][ITEMS [item1 [...]]])循环列表中的每一个元素或者直接指定元素。 接下来进行测试测试一 # foreach 循环测试 set(my_list A B C D)foreach(loop_var IN LISTS my_list)message(${loop_var}) endforeach()打印信息如下 测试二 # foreach 循环测试 foreach(loop_var IN ITEMS A B C D)message(${loop_var}) endforeach()打印信息同上。 二、while 循环 while 循环用法如下 while(condition)command1(args ...)command1(args ...)... endwhile(condition)endwhile 括号中的condition 可写可不写如果写了就必须和while 中的condition 一致。 cmake 中while 循环的含义与C 语言中while 循环的含义相同但条件condition 为真时执行循环体中的命令而条件condition 的语法形式与if 条件判断中的语法形式相同。 # while 循环测试 set(loop_var 4)while(loop_var GREATER 0)message(${loop_var})math(EXPR loop_var ${loop_var} - 1) endwhile()输出结果如下 上例中while 循环的条件是(loop_var GREATER 0)等价于(loop_var 0)当loop_var 变量的有效数值大于0 时执行while 循环体在while 循环体中使用到了cmake 中的数学运算命令math()关于数学运算下小节会向大家介绍。 在while 循环体中打印loop_var之后将loop_var 减一。 三、break、continue cmake 中也可以在循环体中使用类似于C 语言中的break 和continue 语句。 ①、break break()命令用于跳出循环和在C 语言中的作用是一样的测试如下 # while...break 测试 set(loop_var 10)while(loop_var GREATER 0) #loop_var0 时执行循环体message(${loop_var})if(loop_var LESS 6) #当loop_var 小于6 时message(break)break() #跳出循环endif()math(EXPR loop_var ${loop_var} - 1)#loop_var-- endwhile()打印信息如下 整个代码笔者就不再解释了注释已经写得很清楚了 ②、continue continue()命令用于结束本次循环执行下一次循环测试如下 # while...continue 测试 # 打印所有偶数 set(loop_var 10)while(loop_var GREATER 0) #loop_var0 时执行循环体math(EXPR var ${loop_var} % 2) #求余if(var EQUAL 0) #如果var0,表示它是偶数message(${loop_var}) #打印这个偶数math(EXPR loop_var ${loop_var} - 1)#loop_var--continue() # 执行下一次循环endif()math(EXPR loop_var ${loop_var} - 1)#loop_var-- endwhile()这段cmake 代码是求0 到10 之间的偶数左闭右开并将偶数打印出来使用到了continue()命令代码不再解释注释已经写得很清楚了。 打印结果如下 关于break()和continue()命令的使用就介绍到这里了。 数学运算math 在cmake 中如何使用数学运算呢其实cmake 提供了一个命令用于实现数学运算功能这个命令就是math()如下所示 math(EXPR output variable math expression)math 命令中第一个参数是一个固定的关键字EXPR第二个参数是一个返回参数将数学运算结果存放在这个变量中而第三个参数则是一个数学运算表达式支持的运算符包括加、-减、*乘、/除、%求余、|按位或、按位与、^按位异或、~按位取反、左移、右移以及这些运算符的组合运算它们的含义与C 语言中相同。 譬如 math(EXPR out_var 11) #计算11 math(EXPR out_var 100 * 2) ##计算100x2 math(EXPR out_var 10 20) #计算10 20我们进行测试 # math()命令测试 math(EXPR out_var 100 100) message(${out_var})math(EXPR out_var 100 - 50) message(${out_var})math(EXPR out_var 100 * 100) message(${out_var})math(EXPR out_var 100 / 50) message(${out_var})math(EXPR out_var (100 100) * 50 - 2) message(${out_var})测试结果如下 定义函数 在cmake 中我们也可以定义函数cmake 提供了function()命令用于定义一个函数使用方法如下所示 function(name [arg1 [arg2 [arg3 ...]]])command1(args ...)command2(args ...)... endfunction(name)endfunction 括号中的可写可不写如果写了就必须和function 括号中的一致。 ①、基本使用方法 第一个参数name 表示函数的名字arg1、arg2…表示传递给函数的参数。调用函数的方法其实就跟使用命令一样一个简单地示例如下所示 # function 函数测试 # 函数名: xyz function(xyz arg1 arg2)message(${arg1} ${arg2}) endfunction()# 调用函数 xyz(Hello World)打印信息如下 ②、使用return()命令 在function()函数中也可以使用C 语言中的return 语句退出函数如下所示 # function 函数测试 # 函数名: xyz function(xyz)message(Hello)return() # 退出函数message(World) endfunction()# 调用函数 xyz()执行结果如下 只打印了Hello并没有打印World说明return()命令是生效的执行return()命令之后就已经退出当前函数了所以并不会打印World。但是需要注意的是return 并不可以用于返回参数那函数中如何返回参数给调用者呢关于这个问题后续再给大家讲解因为这里涉及到其它一些问题本小节暂时先不去理会这个问题。 ③、可变参函数 在cmake 中调用函数时实际传入的参数个数不需要等于函数定义的参数个数甚至函数定义时参数个数为0但是实际传入的参数个数必须大于或等于函数定义的参数个数如下所示 # function 函数测试 # 函数名: xyz function(xyz arg1)message(${arg1}) endfunction()# 调用函数 xyz(Hello World China)函数xyz 定义时只有一个参数但是实际调用时我们传入了3 个参数注意这并不会报错是符合function()语法规则的会正常执行打印信息如下 从打印信息可知message()命令打印出了调用者传入的第一个参数也就是Hello。 这种设计有什么用途呢正如我们的标题所言这种设计可用于实现可变参函数与C 语言中的可变参数函数概念相同但是有个问题就如上例中所示用户传入了3 个参数但是函数定义时并没有定义这些形参函数中如何引用到第二个参数World 以及第三个参数China 呢其实cmake 早就为大家考虑到了并给出了相应的解决方案就是接下来向大家介绍的内部变量。 ④、函数的内部变量 function()函数中可以使用内部变量所谓函数的内部变量指的就是在函数内部使用的内置变量这些内部变量如下所示 我们可以进行测试 # function 函数测试 # 函数名: xyz function(xyz arg1 arg2)message(ARGC: ${ARGC})message(ARGV: ${ARGV})message(ARGN: ${ARGN})message(ARGV0: ${ARGV0})message(ARGV1: ${ARGV1})# 循环打印出各个参数set(i 0)foreach(loop ${ARGV})message(arg${i}: ${loop})math(EXPR i ${i} 1)endforeach() endfunction()# 调用函数 xyz(A B C D E F G)源码执行结果如下 这个大家自己去对照一下就知道了。 ⑤、函数的作用域 在cmake 中通过function()命令定义的函数类似于一个自定义命令实际上并不是当然事实上cmake 提供了自定义命令的方式譬如通过add_custom_command()来实现如果大家有兴趣可以自己去学习下笔者便不再进行介绍了。 使用function()定义的函数我们需要对它的使用范围进行一个简单地了解譬如有如下工程目录结构 ├──build ├──CMakeLists.txt ├──hello├──CMakeLists.txt我们在顶层目录下定义了一个函数xyz顶层CMakeLists.txt 源码内容如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0)# 函数名: xyz function(xyz)message(Hello World!) endfunction()# 加载子源码 add_subdirectory(hello)接着我们在子源码中调用xyz()函数hello 目录下的CMakeLists.txt 如下所示 # hello 目录下的CMakeLists.txt message(这是子源码) xyz() # 调用xyz()函数大家觉得这样子可以调用成功吗事实上这是没问题的父源码中定义的函数、在子源码中是可以调用的打印信息如下 那反过来子源码中定义的函数在父源码中可以使用吗我们来进行测试顶层CMakeLists.txt 源码内容如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO VERSION 1.1.0) #设置工程版本号为1.1.0# 加载子源码 add_subdirectory(hello) message(这是父源码) xyz()在父源码中调用xyz()函数在子源码中定义xyz()函数如下所示 message(这是子源码)# 函数名: xyz function(xyz)message(Hello World!) endfunction()进入到build 目录执行cmake如下所示 事实证明这样也是可以的说明通过function()定义的函数它的使用范围是全局的并不局限于当前源码、可以在其子源码或者父源码中被使用。 宏定义 cmake 提供了定义宏的方法cmake 中函数function 和宏定义macro 在某种程度上来说是一样的都是创建一段有名字的代码可以在后面被调用还可以传参数。通过macro()命令定义宏如下所示 macro(name [arg1 [arg2 [arg3 ...]]])COMMAND1(ARGS ...)COMMAND2(ARGS ...)... endmacro(name)endmacro 括号中的可写可不写如果写了就必须和macro 括号中的一致。参数name表示宏定义的名字在宏定义中也可以使用前面给大家介绍的ARGVXX 是一个数字、ARGC、ARGV、ARGN 这些变量所以这些也是宏定义的内部变量如下所示 # macro 宏定义测试 macro(XYZ arg1 arg2)message(ARGC: ${ARGC})message(ARGV: ${ARGV})message(ARGN: ${ARGN})message(ARGV0: ${ARGV0})message(ARGV1: ${ARGV1})# 循环打印出各个参数set(i 0)foreach(loop ${ARGV})message(arg${i}: ${loop})math(EXPR i ${i} 1)endforeach() endmacro()# 使用宏 XYZ(A B C D E)源码打印信息如下 从定义上看他们貌似一模一样宏和函数确实差不多但还是有区别的譬如宏的参数和诸如ARGV、ARGC、ARGN 之类的值不是通常CMake 意义上的变量它们是字符串替换就像C 语言预处理器对宏所做的一样因此您将无法使用以下命令 if(ARGV1) # ARGV1 is not a variable if(DEFINED ARGV2) # ARGV2 is not a variable if(ARGC GREATER 2) # ARGC is not a variable foreach(loop_var IN LISTS ARGN) # ARGN is not a variable因为在宏定义中宏的参数和诸如ARGC、ARGV、ARGN 等这些值并不是变量它们是字符串替换也就是说当cmake 执行宏定义时会先将宏的参数和ARGC、ARGV、ARGN 等这些值进行字符串替换然后再去执行这段宏其实就像是C 语言中的预处理步骤这是与函数不同的地方。 我们来进行测试 # macro 宏 macro(abc arg1 arg2)if(DEFINED ARGC)message(true)else()message(false)endif() endmacro()# function 函数 function(xyz arg1 arg2)if(DEFINED ARGC)message(true)else()message(false)endif() endfunction()# 调用宏 abc(A B C D)# 调用函数 xyz(A B C D)上面的代码中我们定义了一个宏abc 和一个函数xyz它们俩的代码是一样的都是在内部使用if()判断ARGC 是不是一个变量如果是打印true如果不是打印false下面会分别调用宏abc 和函数xyz打印信息如下所示 所以从打印信息可知在宏定义中ARGC 确实不是变量其实在执行宏之前会将ARGC 进行替换如下所示 if(DEFINED 4)message(true) else()message(false) endif()把ARGC 替换为4因为我们实际传入了4 个参数。 当然除此之外cmake 中函数和宏定义还有其它的区别譬如函数有自己的作用域、而宏定义是没有作用域的概念。 文件操作 cmake 提供了file()命令可对文件进行一系列操作譬如读写文件、删除文件、文件重命名、拷贝文件、创建目录等等本小节我们一起来学习这个功能强大的file()命令。 ①、写文件写、追加内容 使用file()命令写文件使用方式如下所示 file(WRITE filename content...) file(APPEND filename content...)将写入名为的文件中。如果文件不存在它将被创建如果文件已经存在WRITE 模式将覆盖它APPEND 模式将内容追加到文件末尾。 测试代码如下 # file()写文件测试 file(WRITE wtest.txt Hello World!) #给定内容生成wtest.txt 文件 file(APPEND wtest.txt China) #给定内容追加到wtest.txt 文件末尾注意文件可以使用绝对路径或相对路径指定相对路径被解释为相对于当前源码路径。 执行CMakeLists.txt 代码之后会在当前源码目录下生成一个名为wtest.txt 的文件如下所示 接着查看wtest.txt 文件中内容如下所示 ②、写文件由内容生成文件 由内容生成文件的命令为 file(GENERATE OUTPUT output-fileINPUT input-file|CONTENT content[CONDITION expression])output-file指定输出文件名可以带路径绝对路径或相对路径 INPUT input-file指定输入文件通过输入文件的内容来生成输出文件 CONTENT content指定内容直接指定内容来生成输出文件 CONDITION expression如果表达式expression 条件判断为真则生成文件、否则不生成文件。 同样指定文件既可以使用相对路径、也可使用绝对路径不过在这里相对路径被解释为相对于当前源码的BINARY_DIR 路径而不是当前源码路径。 测试代码如下 # 由前面生成的wtest.txt 中的内容去生成out1.txt 文件 file(GENERATE OUTPUT out1.txt INPUT ${PROJECT_SOURCE_DIR}/wtest.txt)# 由指定的内容生成out2.txt file(GENERATE OUTPUT out2.txt CONTENT This is the out2.txt file)# 由指定的内容生成out3.txt加上条件控制用户可根据实际情况 # 用表达式判断是否需要生成文件这里只是演示直接是1 file(GENERATE OUTPUT out3.txt CONTENT This is the out3.txt file CONDITION 1)进入到build 目录下执行cmake 执行完cmake 之后会在build 目录也就是顶层源码的BINARY_DIR下生成了out1.txt、out2.txt 和 out3.txt 三个文件内容如下 ③、读文件字节读取 file()读文件命令格式如下 file(READ filename variable[OFFSET offset] [LIMIT max-in] [HEX])从名为的文件中读取内容并将其存储在中。 可选择从给定的开始最多读取字节。HEX 选项使数据转换为十六进制表示对二进制数据有用。 同样指定文件既可以使用相对路径、也可使用绝对路径相对路径被解释为相对于当前源码路径。 测试代码如下 # file()读文件测试 file(READ ${PROJECT_SOURCE_DIR}/wtest.txt out_var) #读取前面生成的wtest.txt message(${out_var}) # 打印输出# 读取wtest.txt 文件限定起始字节和大小 file(READ ${PROJECT_SOURCE_DIR}/wtest.txt out_var OFFSET 0 LIMIT 10) message(${out_var})# 读取wtest.txt 文件以二进制形式读取限定起始字节和大小 file(READ ${PROJECT_SOURCE_DIR}/wtest.txt out_var OFFSET 0 LIMIT 5 HEX) message(${out_var})打印信息如下所示 ④、以字符串形式读取 命令格式如下所示 file(STRINGS filename variable [options...])从文件中解析ASCII 字符串列表并将其存储在中。这个命令专用于读取字符串会将文件中的二进制数据将被忽略回车符(\r, CR)字符被忽略。 filename指定需要读取的文件可使用绝对路径、也可使用相对路径相对路径被解释为相对于当前源码路径。 variable存放字符串的变量。 options可选的参数可选择0 个、1 个或多个选项这些选项包括 ➢ LENGTH_MAXIMUM 读取的字符串的最大长度 ➢ LENGTH_MINIMUM 读取的字符串的最小长度 ➢ LIMIT_COUNT 读取的行数 ➢ LIMIT_INPUT 读取的字节数 ➢ LIMIT_OUTPUT 存储到变量的限制字节数 ➢ NEWLINE_CONSUME把换行符也考虑进去 ➢ NO_HEX_CONVERSION除非提供此选项否则Intel Hex 和Motorola S-record 文件在读取时会自动转换为二进制文件。 ➢ REGEX 只读取符合正则表达式的行 ➢ ENCODING 指定输入文件的编码格式目前支持的编码有UTF-8、UTF-16LE、 UTF-16BE、UTF-32LE、UTF-32BE。如果未提供ENCODING 选项并且文件具有字节顺序标记则ENCODING 选项将默认为尊重字节顺序标记。 测试代码如下 # 从input.txt 文件读取字符串 file(STRINGS ${PROJECT_SOURCE_DIR}/input.txt out_var) message(${out_var})# 限定读取字符串的最大长度 file(STRINGS ${PROJECT_SOURCE_DIR}/input.txt out_var LENGTH_MAXIMUM 5) message(${out_var})# 限定读取字符串的最小长度 file(STRINGS ${PROJECT_SOURCE_DIR}/input.txt out_var LENGTH_MINIMUM 4) message(${out_var})# 限定读取行数 file(STRINGS ${PROJECT_SOURCE_DIR}/input.txt out_var LIMIT_COUNT 3) message(${out_var})从input.txt 文件读取字符串input.txt 文件的内容如下所示 上述代码执行的结果如下所示 大家自己去对比就知道这些选项具体是什么意思了这里便不再多说 ⑤、计算文件的hash 值 file()命令可以计算指定文件内容的加密散列hash 值并将其存储在变量中。命令格式如下所示 file(MD5|SHA1|SHA224|SHA256|SHA384|SHA512 filename variable)MD5|SHA1|SHA224|SHA256|SHA384|SHA512 表示不同的计算hash 的算法必须要指定其中之一filename 指定文件可使用绝对路径、也可使用相对路径相对路径被解释为相对于当前源码的 BINARY_DIR将计算结果存储在variable 变量中。 测试代码如下 # 计算文件的hash 值 file(SHA256 ${PROJECT_SOURCE_DIR}/input.txt out_var) message(${out_var})这里我们还是用上面创建的input.txt 文件使用SHA256 算法进行计算结果如下 ⑥、文件重命名 使用file()命令可以对文件进行重命名操作命令格式如下 file(RENAME oldname newname)oldname 指的是原文件newname 指的是重命名后的新文件文件既可以使用绝对路径指定也可以使用相对路径指定相对路径被解释为相对于当前源码路径。 测试代码 # 文件重命名 file(RENAME ${PROJECT_SOURCE_DIR}/input.txt ${PROJECT_SOURCE_DIR}/output.txt)测试结果如下 ⑦、删除文件 使用file()命令可以删除文件命令格式如下 file(REMOVE [files...]) file(REMOVE_RECURSE [files...])REMOVE 选项将删除给定的文件但不可以删除目录而REMOVE_RECURSE 选项将删除给定的文件或目录、以及非空目录。指定文件或目录既可以使用绝对路径、也可以使用相对路径相对路径被解释为相对于当前源码路径。 测试代码 # file 删除文件或目录测试 file(REMOVE ${PROJECT_SOURCE_DIR}/out1.txt) file(REMOVE_RECURSE ${PROJECT_SOURCE_DIR}/out2.txt ${PROJECT_SOURCE_DIR}/empty-dir ${PROJECT_SOURCE_DIR}/Non_empty-dir)out1.txt 和out2.txt 是普通文件empty-dir 是一个空目录而Non_empty-dir 是一个非空目录如下所示 进入到build 目录下执行cmake 执行完cmake 命令之后这些文件以及文件夹都被删除了。 总结 关于file()命令就给大家介绍这么多了其实file()命令的功能很强大除了以上给大家介绍的基本功能外还支持文件下载、文件锁等功能大家有兴趣可以自己去了解。 设置交叉编译 默认情况下cmake 会使用主机系统运行cmake 命令的操作系统的编译器来编译我们的工程那么得到的可执行文件或库文件只能在Ubuntu 系统运行如果我们需要使得编译得到的可执行文件或库文件能够在我们的开发板ARM 平台上运行则需要配置交叉编译。 前面我们已经安装了阿尔法I.MX6U 硬件平台对应的交叉编译工具 我们使用的交叉编译器如下 arm-poky-linux-gnueabi-gcc #C 编译器 arm-poky-linux-gnueabi-g #C编译器其实配置交叉编译非常简单只需要设置几个变量即可如下所示 # 配置ARM 交叉编译 set(CMAKE_SYSTEM_NAME Linux) #设置目标系统名字 set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构# 指定编译器的sysroot 路径 set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots) set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)# 指定交叉编译器arm-gcc 和arm-g set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g)# 为编译器添加编译选项 set(CMAKE_C_FLAGS -marcharmv7ve -mfpuneon -mfloat-abihard -mcpucortex-a7) set(CMAKE_CXX_FLAGS -marcharmv7ve -mfpuneon -mfloat-abihard -mcpucortex-a7)set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)CMAKE_SYSTEM_NAME 变量在前面给大家介绍过表示目标主机譬如ARM 开发板的操作系统名称Linux表示目标操作系统是Linux 系统。 CMAKE_SYSTEM_PROCESSOR 变量表示目标架构名称。 CMAKE_SYSROOT该变量的值会传递给gcc 编译器的–sysroot 选项也就是sysroot${CMAKE_SYSROOT}–sysroot 选项指定了编译器的sysroot 目录也就是编译器的系统根目录编译过程中需要链接的库、头文件等就会去该目录下寻找譬如标准C 库、标准C 头文件这些。 CMAKE_C_COMPILER 变量指定了C 语言编译器gcc由于是交叉编译所以应该指定为arm-gcc。 CMAKE_CXX_COMPILER 变量指定了C语言编译器g由于是交叉编译所以应该指定为arm-g。 CMAKE_C_FLAGS 变量为gcc 编译器添加编译选项。 CMAKE_CXX_FLAGS 变量为g编译器添加编译选项。 CMAKE_FIND_ROOT_PATH_MODE_INCLUDE 变量控制CMAKE_SYSROOT 中的路径是否被find_file()和find_path()使用。如果设置为ONLY则只会搜索CMAKE_SYSROOT 中的路径如果设置为NEVER则CMAKE_SYSROOT 中的路径将被忽略并且仅使用主机系统路径。如果设置为BOTH则将搜索主机系统路径和CMAKE_SYSROOT 中的路径。 同理CMAKE_FIND_ROOT_PATH_MODE_LIBRARY 变量控制CMAKE_SYSROOT 中的路径是否被find_library()使用如果设置为ONLY则只会搜索CMAKE_SYSROOT 中的路径如果设置为NEVER则CMAKE_SYSROOT 中的路径将被忽略并且仅使用主机系统路径。如果设置为BOTH则将搜索主机系统路径和CMAKE_SYSROOT 中的路径。 CMAKE_SYSROOT、CMAKE_C_COMPILER、CMAKE_CXX_COMPILER 这些变量涉及到交叉编译工具的安装路径(R项目板子上设置了环境变零不用输入路径了)需要根据自己的实际安装路径来确定。 接着我们进行测试譬如工程目录结构如下所示 ├──build ├──CMakeLists.txt └──main.cmain.c 源文件中调用了printf()函数打印了“Hello World!”字符串CMakeLists.txt 文件内容如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5)################################## # 配置ARM 交叉编译 ################################# set(CMAKE_SYSTEM_NAME Linux) #设置目标系统名字 set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构# 指定编译器的sysroot 路径 set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots) set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)# 指定交叉编译器arm-linux-gcc 和arm-linux-g set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g)# 为编译器添加编译选项 set(CMAKE_C_FLAGS -marcharmv7ve -mfpuneon -mfloat-abihard -mcpucortex-a7) set(CMAKE_CXX_FLAGS -marcharmv7ve -mfpuneon -mfloat-abihard -mcpucortex-a7)set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) ################################# # end ##################################project(HELLO) #设置工程名称 add_executable(main main.c)这里要注意配置ARM 交叉编译的这些代码需要放置在project()命令之前否则不会生效。 接着进入到build 目录下然后执行cmake发现执行cmake 会报错 下载高版本的cmake 因为用的是Ubuntu 系统自带的cmake 工具它的版本是3.5.1当前最新cmake 版本已经更新到了3.22 了所以3.5.1 这个版本可能确实是太旧了建议大家去下载一个高版本的cmake不然会报错。 那怎么去下载高版本的cmake其实非常简单我们首先进入到cmake 的GitHub 链接地址 https://github.com/Kitware/CMake/releases笔者并没有使用最新的cmake而是使用了3.16.0为了保持一致也建议大家下载这个版本往后翻页找到这个版本如下所示 这里我们下载cmake-3.16.0-Linux-x86_64.tar.gz 压缩包文件这个不是cmake 的源码工程而是可以在x86-64 的Linux 系统下运行的可执行程序其中就包括了cmake 工具所以我们下载这个即可非常方便都不用自己编译 下载成功之后将其拷贝到Ubuntu 系统的用户家目录下并将其解压到某个目录解压之后生成cmake-3.16.0-Linux-x86_64 文件夹这里笔者选择将其解压到家目录下的tools 目录中如下所示 cmake 工具就在cmake-3.16.0-Linux-x86_64/bin 目录下。 现在重新进入到我们的工程目录下进入到build 目录执行cmake如下所示 接着执行make 命令编译 编译生成的main 可执行文件通过file 命令查看可知它是一个ARM 架构的可执行程序可以把它拷贝到开发板上去运行肯定是没有问题的这里就不再演示了。 上例中的这种交叉编译配置方式自然是没有问题的但是不规范通常的做法是将这些配置项也就是变量的设置单独拿出来写在一个单独的配置文件中而不直接写入到CMakeLists.txt 源码中然后在执行cmake 命令时指定配置文件给cmake让它去配置交叉编译环境。 如何指定配置文件呢通过如下方式 cmake -DCMAKE_TOOLCHAIN_FILEcfg_file_path ..通过-DCMAKE_TOOLCHAIN_FILE 选项指定配置文件-D 是cmake 命令提供的一个选项通过该选项可以创建一个缓存变量缓存变量就是全局变量在整个工程中都是生效的会覆盖CMakeLists.txt 源码中定义的同名变量所以-DCMAKE_TOOLCHAIN_FILE 其实就是设置了缓存变量CMAKE_TOOLCHAIN_FILE它的值就是“”号后面的内容cmake 会执行CMAKE_TOOLCHAIN_FILE变量所指定的源文件对交叉编译进行设置现在我们进行测试在工程源码目录下创建一个配置文件arm-linux-setup.cmake内容如下 ################################## # 配置ARM 交叉编译 ################################# set(CMAKE_SYSTEM_NAME Linux) #设置目标系统名字 set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构# 指定编译器的sysroot 路径 set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots) set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)# 指定交叉编译器arm-linux-gcc 和arm-linux-g set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g)# 为编译器添加编译选项 set(CMAKE_C_FLAGS -marcharmv7ve -mfpuneon -mfloat-abihard -mcpucortex-a7) set(CMAKE_CXX_FLAGS -marcharmv7ve -mfpuneon -mfloat-abihard -mcpucortex-a7)set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) ################################# # end ##################################此时CMakeLists.txt 文件内容如下剔除了交叉编译的配置项 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(HELLO) add_executable(main main.c)此时工程目录结构如下所示 ├──arm-linux-setup.cmake ├──build ├──CMakeLists.txt └──main.c进入到build 目录下执行cmake 接着执行make 编译 所以这种方式也是没有问题的推荐使用这种方式配置交叉编译而不是直接写入到CMakeLists.txt 源码中。 变量的作用域 如同C 语言一样在cmake 中变量也有作用域的概念本小节我们就来聊一聊关于cmake 中变量作用域的问题。 本小节从三个方面进行介绍函数作用域、目录作用域以及全局作用域。 一、函数作用域function scope 我把这个作用域叫做函数作用域当在函数内通过set 将变量var 与当前函数作用域绑定时变量var 仅在函数作用域内有效出了这个作用域如果这个作用域外也有同名的变量var那么使用的将是域外同名变量varfunc1()内部调用func2()嵌套调用的函数func2()内部如果也引用变量var那么该变量var 应该是func1()内部定义的变量如果有的话如果func1()内部没有绑定变量var那么就会使用func1()作用域外定义的变量var依次向外搜索。 以上这段话大家可能不好理解我们通过几个示例来看看函数作用域。 ①、函数内部引用函数外部定义的变量 示例代码如下所示 # 函数xyz function(xyz)message(${ABC}) #引用变量ABC endfunction()set(ABC Hello World) #定义变量ABCxyz() # 调用函数ABC 是函数外部定义的一个变量在函数xyz 中引用了该变量打印信息如下 所以可知函数内可以引用函数外部定义的变量。 ②、函数内定义的变量是否可以被外部引用 示例代码如下所示 # 函数xyz function(xyz)set(ABC Hello World)#定义变量ABC endfunction()xyz() # 调用函数if(DEFINED ABC)message(true)message(${ABC}) #引用函数内定义的变量ABC else()message(false) endif()函数内定义了变量ABC外部调用函数之后通过if(DEFINED ABC)来判断变量ABC 是否有定义如果定义了该变量打印true 并将变量打印出来如果没有定义该变量则打印false。测试结果如下 所以可知函数内部定义的变量仅在函数内部可使用出了函数之后便无效了这其实跟C 语言中差不多函数中定义的变量可以认为是局部变量外部自然是无法去引用的。 ③、函数内定义与外部同名的变量 测试代码如下所示 # 函数xyz function(xyz)message(函数内部)message(${ABC})set(ABC Hello China!)#设置变量ABCmessage(${ABC}) endfunction()set(ABC Hello World!)#定义变量ABC xyz() # 调用函数 message(函数外部) message(${ABC})在这段代码中我们在函数外定义了变量ABC“Hello World!”在函数内去设置变量ABC“Hello China!”函数执行完之后在外部调用message()打印变量ABC。如果按照C 语言中的理解那么函数外部打印ABC 变量的值应该等于Hello China!大家不要去关注变量的定义是否需要放在函数定义之前这种解释性脚本语言是没有类似于C 语言中申明这种概念的函数虽然定义了但是调用函数是在定义变量之后的但事实是不是这样呢我们来看看打印信息 从打印信息可知事实并非我们上面所假设那样函数内调用set 去设置变量ABC并不是设置了外部变量ABC 的值而是在函数新创建了一个变量ABC这个与C 语言是不一样的跟Python 很像如果大家学过Python 的话应该就知道。 所以函数内部的代码中调用set 之前引用了变量ABC此时它会搜索函数内是否定义了该变量如果没有它会向外搜索结果就找到了外部定义的变量ABC所以函数内部的第一条打印信息是Hello World!“调用set 之后函数内也创建了一个变量ABC此时再次引用ABC 将使用函数内定义的变量而非是外部定义的变量所以第二条打印信息是Hello China!”。 ④、函数内如何设置外部定义的变量 那如果需要在函数内修改外部定义的变量该如何做呢譬如下面这段代码 # 函数xyz function(xyz)set(ABC Hello China!) endfunction()set(ABC Hello World!) xyz() # 调用函数 message(${ABC})通过前面的介绍可知xyz()函数内通过set 只是创建了一个在函数内部使用的变量ABC而并非是去修改外部定义的变量ABC那如何能使得函数内可以去修改外部定义的变量呢其实也非常简单set 命令提供了一个可选选项PARENT_SCOPE只需在调用set 命令时在参数列表末尾加上PARENT_SCOPE 关键字即可如下所示 # 函数xyz function(xyz)set(ABC Hello China! PARENT_SCOPE) #加上PARENT_SCOPE endfunction()set(ABC Hello World!) xyz() # 调用函数 message(${ABC})再来看看打印信息 打印信息证明加上PARENT_SCOPE 之后确实可以那PARENT_SCOPE 选项究竟是什么 官方给出的解释是这样的如果添加了PARENT_SCOPE 选项则变量将设置在当前作用域范围之上的作用域范围内每个目录在这里“目录”指的是包含了CMakeLists.txt 的目录或函数都会创建一个新作用域此命令会将变量的值设置到父目录或上层调用函数中函数嵌套的情况下。 这是什么意思呢其实就是说如果set 命令添加了PARENT_SCOPE 选项那就意味着并不是在当前作用域set 命令所在作用域内设置这个变量而是在当前作用域的上一层作用域父作用域中设置该变量当前作用域的上一层作用域该怎么理解呢这个根据具体的情况而定下面举几个例子进行说明。 示例代码1 # 函数xyz function(xyz)set(ABC Hello China! PARENT_SCOPE) #加上PARENT_SCOPE endfunction()set(ABC Hello World!) xyz() # 调用函数 message(${ABC})在这个例子中函数xyz 中调用set 时添加了PARENT_SCOPE 选项意味着会在函数xyz 的上一层作用域中设置ABC 变量函数的上一层作用域也就是调用xyz()函数时所在的作用域也就是当前源码对应的作用域当前目录作用域。 示例代码2 # 函数func2 function(func2)set(ABC Hello People! PARENT_SCOPE) endfunction()# 函数func1 function(func1)set(ABC Hello China!)func2() endfunction()set(ABC Hello World!) func1() message(${ABC})在这个示例中函数func1 中调用了func2那么函数func2 的上一层作用域就是func1 函数对应的作用域。 示例代码3 有如下工程目录结构 ├──build ├──CMakeLists.txt └──src└──CMakeLists.txt顶层CMakeLists.txt 文件内容如下 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(TEST)add_subdirectory(src) xyz() message(${ABC})顶层源码调用src 目录下的子源码子源码下定义了一个函数xyz如下所示 # src 下的CMakeLists.txt function(xyz)set(ABC Hello World! PARENT_SCOPE) endfunction()在这种情况下函数xyz的上一层作用域便是顶层目录作用域顶层源码作用域关键是看“谁”调用该函数。 同理下面这种情况也是如此 顶层CMakeLists.txt 文件 # CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(TEST)add_subdirectory(src) message(${ABC})src 目录下的CMakeLists.txt 文件 # src 下的CMakeLists.txt set(ABC Hello World! PARENT_SCOPE)变量ABC 会在顶层源码中被设置而不是set 命令所在的作用域中。 ⑤、函数的返回值如何实现 前面给大家介绍函数的时候提到过cmake 中函数也可以有返回值但是不能通过return()命令来实现由于当时没介绍PARENT_SCOPE 所以没法给大家讲解如何去实返回值现在我们已经知道了 PARENT_SCOPE 选项的作用其实就是通过这个选项来实现函数的返回值功能。 先来看个示例 # 顶层CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(TEST)# 定义一个函数xyz # 实现两个数相加并将结果通过out 参数返回给调用者 function(xyz out var1 var2)math(EXPR temp ${var1} ${var2})set(${out} ${temp} PARENT_SCOPE) endfunction()xyz(out_var 5 10) message(${out_var})打印结果如下 看到这里不知道大家明白了没其实很简单调用xyz()函数时传入的out_var 是作为一个参数传入进去的而不是变量名但现在需要将其变成一个变量名怎么做呢那就是在函数中获取参数out 的值将参数out 的值作为变量名然后用set 创建该变量并添加了PARENT_SCOPE 选项。所以通过message 便可以打印出该变量因为这个变量在源码中定义了。 二、目录作用域Directory Scope 我把这个作用域叫做目录作用域。子目录会将父目录的所有变量拷贝到当前CMakeLists.txt 源码中当前CMakeLists.txt 中的变量的作用域仅在当前目录有效。 目录作用域有两个特点向下有效上层作用域中定义的变量在下层作用域中是有效的值拷贝。举个栗子来进一步阐述 譬如目录结构如下所示 ├──CMakeLists.txt └──sub_dir└──CMakeLists.txt父目录CMakeLists.txt 文件内容如下 # 父源码 cmake_minimum_required(VERSION 3.5) project(TEST)set(parent_var Hello parent) message(parent-parent_var: ${parent_var}) add_subdirectory(sub_dir) message(parent-parent_var: ${parent_var})在父源码中我们定义了一个变量parent_var并将其设置为Hello parent。 子源码CMakeLists.txt 内容 message(subdir-parent_var: ${parent_var}) set(parent_var Hello child) message(变量修改之后) message(subdir-parent_var: ${parent_var})在子源码中第1 行打印了parent_var 变量这个变量是由父源码所创建的由于变量向下有效所以在子源码中也可以使用第2 行我们去修改parent_var 变量将其设置为Hello child但这是子源码新建的一个变量并没改变父源码中的parent_var 变量也就是说这里的set 并不影响父源码中的parent_var变量仅仅只是改变了子源码中的parent_var 变量这就是值拷贝的含义子源码从父源码中拷贝了一份变量副本。 执行结果如下 三、全局作用域Persistent Cache 持久缓存、缓存变量 缓存变量在整个cmake 工程的编译生命周期内都有效所以这些变量的作用域是全局范围的工程内的其他任意目录都可以访问缓存变量注意cmake 是从上到下来解析CMakeLists.txt 文件的。 缓存变量可以通过set 命令来定义使用set 命令时添加CACHE 选项来实现除此之外还有其它多种方式可以定义缓存变量譬如前面给大家介绍的cmake -D 选项是经常用来定义缓存变量的方法cmake -DXXX就表示创建了一个名为XXX 的全局变量关于缓存变量笔者就不过多的介绍了有兴趣的读者可以自己去研究下。 属性 本小节简单地向大家介绍一下cmake 中的属性相关的概念。 属性大概可以分为多种全局属性、目录属性源码属性、目标属性以及其它一些分类。在 https://cmake.org/cmake/help/v3.5/manual/cmake-properties.7.html 中有详细介绍。如下 属性会影响到一些行为这里重点给大家介绍下目录属性和目标属性其它的大家自己去看。 一、目录属性 目录属性其实就是CMakeLists.txt 源码的属性来看看有哪些 这里我们随便挑几个来讲解 CACHE_VARIABLES 当前目录中可用的缓存变量列表。 CLEAN_NO_CUSTOM 如果设置为true 以告诉Makefile Generators 在make clean 操作期间不要删除此目录的自定义命令的输出文件。如何获取或设置属性稍后再给大家介绍。 INCLUDE_DIRECTORIES 此属性是目录的头文件搜索路径列表其实就是include_directories() 命令所添加的目录 include_directories() 命令会将指定的目录添加到INCLUDE_DIRECTORIES 属性中所以 INCLUDE_DIRECTORIES 属性其实就是一个头文件搜索路径列表。 测试代码如下 # 父源码 cmake_minimum_required(VERSION 3.5) project(TEST)#获取目录的INCLUDE_DIRECTORIES 属性 get_directory_property(out_var INCLUDE_DIRECTORIES) message(${out_var})#调用include_directories 添加头文件搜索目录 include_directories(include)#再次获取INCLUDE_DIRECTORIES 属性 get_directory_property(out_var INCLUDE_DIRECTORIES) message(${out_var})#再次调用include_directories将目录放在列表前面 include_directories(BEFORE hello)#再次获取INCLUDE_DIRECTORIES 属性 get_directory_property(out_var INCLUDE_DIRECTORIES) message(${out_var})本例中使用了get_directory_property()命令该命令用于获取目录的属性使用方法如下 get_directory_property(variable [DIRECTORY dir] prop-name)将属性的值存储在variable 变量中第二个参数是一个可选参数可指定一个目录如果不指定则默认是当前源码所在目录第三个参数prop-name 表示对应的属性名称。 上述代码的打印信息如下所示 第一个message 打印的是空信息说明此时INCLUDE_DIRECTORIES 是空的没有添加任何目录。 include_directories()命令默认将目录添加到INCLUDE_DIRECTORIES 列表的末尾可显式指定BEFORE 或AFTER 将目录添加到列表的前面或后面在前面给大家介绍过。 既然如此那是不是可以直接去设置INCLUDE_DIRECTORIES 属性来添加头文件搜索目录而不使用include_directories()命令来添加这样当然是可以的可以使用set_directory_properties()命令设置目录属性如下所示 set_directory_properties(PROPERTIES prop1 value1 prop2 value2)接下来进行测试假设工程目录结构如下所示 ├──build ├──CMakeLists.txt ├──include │ └──hello.h └──main.c源文件main.c 中包含了hello.h 头文件hello.h 头文件在include 目录下CMakeLists.txt 如下 # 父源码 cmake_minimum_required(VERSION 3.5) project(TEST)set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES /home/dt/vscode_ws/cmake_test/include) get_directory_property(out_var INCLUDE_DIRECTORIES) message(${out_var})add_executable(main main.c)进入到build 目录下执行cmake、make 构建、编译 编译成功说明这种做法是没有问题的需要注意的是调用set_directory_properties()命令设置属性时需要使用绝对路径。 父目录的INCLUDE_DIRECTORIES 属性可以初始化、填充子目录的INCLUDE_DIRECTORIES 属性测试如下 譬如工程目录结构如下 ├──CMakeLists.txt └──subdir└──CMakeLists.txt父源码内容如下所示 # 父源码 cmake_minimum_required(VERSION 3.5) project(TEST)#调用include_directories 添加2 个目录 include_directories(include hello) get_directory_property(p_list INCLUDE_DIRECTORIES) message(${p_list})#调用子源码 add_subdirectory(subdir)子源码内容 #子源码 get_directory_property(c_list INCLUDE_DIRECTORIES) message(${c_list})打印信息如下 LINK_DIRECTORIES 此属性是目录的库文件搜索路径列表其实就是link_directories()命令所添加的目录link_directories 命令会将指定的目录添加到LINK_DIRECTORIES 属性中所以LINK_DIRECTORIES 属性其实就是一个库文件搜索路径列表。 测试如下 # 父源码 cmake_minimum_required(VERSION 3.5) project(TEST)#获取目录的LINK_DIRECTORIES 属性 get_directory_property(out_var LINK_DIRECTORIES) message(${out_var})#添加库文件搜索目录 link_directories(include hello) get_directory_property(out_var LINK_DIRECTORIES) message(${out_var})打印信息如下 同样父目录的LINK_DIRECTORIES 属性可以初始化、填充子目录的LINK_DIRECTORIES 属性。也直接去设置LINK_DIRECTORIES 属性来添加库文件搜索目录而不使用link_directories()命令来添加大家可以去测试一下这里不再演示了 MACROS 当期目录中可用的宏命令列表。 PARENT_DIRECTORY 加载当前子目录的源目录其实就是说当前源码的父源码所在路径对于顶级目录该值为空字符串。此属性只读、不可修改。 VARIABLES 当前目录中定义的变量列表。 只读属性、不可修改 二、目标属性 目标属性顾名思义就是目标对应的属性如下 目标属性可通过get_target_property、set_target_property 命令获取或设置。 目标属性比较多这里挑几个给大家介绍下 BINARY_DIR 只读属性定义目标的目录中CMAKE_CURRENT_BINARY_DIR 变量的值。 SOURCE_DIR 只读属性定义目标的目录中CMAKE_CURRENT_SOURCE_DIR 变量的值。 INCLUDE_DIRECTORIES 目标的头文件搜索路径列表target_include_directories()命令会将目录添加到INCLUDE_DIRECTORIES 列表中INCLUDE_DIRECTORIES 会拷贝目录属性中的INCLUDE_DIRECTORIES 属性作为初始值。 INTERFACE_INCLUDE_DIRECTORIES target_include_directories()命令使用PUBLIC 和INTERFACE 关键字的值填充此属性。 INTERFACE_LINK_LIBRARIES target_link_libraries()命令使用PUBLIC 和INTERFACE 关键字的值填充此属性。 LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_NAME LINK_LIBRARIES 目标的链接依赖库列表。 OUTPUT_NAME 目标文件的输出名称。 TYPE 目标的类型它将是STATIC_LIBRARY 、MODULE_LIBRARY 、SHARED_LIBRARY 、 INTERFACE_LIBRARY、EXECUTABLE 之一或内部目标类型之一。
http://wiki.neutronadmin.com/news/193667/

相关文章:

  • 酒店设计网站建设方案娱乐网站的代理怎么做
  • 文山建设5G网站万网建设网站
  • 图片 展示 网站模板百度一下就知道手机版
  • 移动电子商务网站建设网站建设策划书是有谁编写的
  • 哪个网站专门做邮轮旅游的中国建设银行官网站汽车卡
  • 建设网站出现400错误安卓市场官方版app下载
  • 多终端网站网站建设要学多少课程
  • 西安推荐企业网站制作平台北京网站建设的价格天
  • 本土建站工作室wordpress 整体搬家
  • 电子商务网站建设精英如何在手机上制作动画
  • 本地wordpress 跳转包头seo优化
  • 常州微网站建设网站模板开发
  • 太和县建设局网站网站首页模板下载
  • 斗鱼网站开发是用什么语言石家庄网站建设服务
  • 如何用七牛云做视频网站批量上传 wordpress
  • 中国免费网站申请许昌网站开发
  • 我怎么打不开建设银行的网站手工制作国庆节作品
  • co域名网站江苏常州武进区建设局网站
  • 长春网站建设长春网络推广培训班价格
  • 简单网站建设软件有哪些方面重庆品牌网站建设怎么样
  • 医院网站建设预算表在线制作文字
  • 电商网站制作教程wordpress 空间不足
  • 大厂县建设局网站太原网站开发培训
  • 什么是移动网站开发wordpress 招聘模板
  • 企业网站建设开始起步文章企业网查询是什么
  • 网站主机一般选哪种的大前端最新网站
  • 安徽建筑人才网淄博做网站跟优化
  • 建设安全员协会网站网站建设 长沙开福区
  • 模板网站建站步骤wordpress网站打开速度
  • 如何做单页网站impreza 4 wordpress