CMake 入门与进阶

目录

  • 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.h"void hello(const char *name)
{printf("Hello %s!\n", name);
}

⚫ main.c 文件内容

#include "hello.h"int 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.c

CMakeLists.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.c

CMakeLists.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 目录下进行构建、编译,最终会得到可执行文件hello(build/src/hello)和库文件libhello.a(build/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.c

CMakeLists.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_SCOPE(PARENT_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_DIR
CMAKE_SOURCE_DIR与PROJECT_SOURCE_DIR 等价
CMAKE_BINARY_DIR与PROJECT_BINARY_DIR 等价
CMAKE_CURRENT_SOURCE_DIR当前源码所在路径
CMAKE_CURRENT_BINARY_DIR当前源码的BINARY_DIR
CMAKE_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.c

CMakeLists.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 或debug
CMAKE_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 -s)
CMAKE_HOST_SYSTEM_PROCESSOR运行cmake 的操作系统的处理器名称(uname -p)
CMAKE_HOST_SYSTEM运行cmake 的操作系统(复合信息)
CMAKE_HOST_SYSTEM_VERSION运行cmake 的操作系统的版本号(uname -r)
CMAKE_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 XXX="Hello World!"
cd build/
cmake ..

打印信息如下所示:
在这里插入图片描述
从打印信息可知,ENV 变量确实可以访问到Linux 系统的环境变量。

控制编译的变量

这些变量可以控制编译过程,具体如下所示:

变量说明
EXECUTABLE_OUTPUT_PATH可执行程序的输出路径
LIBRARY_OUTPUT_PATH库文件的输出路径

用来设置可执行文件的输出目录以及库文件的输出目录,接下来我们进行简单地测试。

譬如工程目录结构如下所示:

├──build
├──CMakeLists.txt
├──hello
│	├──hello.c
│	└──hello.h
└──main.c

hello.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 都是false
COMMAND 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,包含stop,stop 不能为负数。
而对于第二种,循环从指定的数字start 开始到stop 结束,步长为step,不过step 参数是一个可选参数,如果不指定,默认step=1;三个参数都不能为负数,而且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_var>0 时执行循环体message("${loop_var}")if(loop_var LESS 6) #当loop_var 小于6message("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_var>0 时执行循环体math(EXPR var "${loop_var} % 2") #求余if(var EQUAL 0) #如果var=0,表示它是偶数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 "1+1") #计算1+1
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表示宏定义的名字,在宏定义中也可以使用前面给大家介绍的ARGVX(X 是一个数字)、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-file<INPUT 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 "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-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.c

main.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 "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-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_FILE=cfg_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 "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-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,那么使用的将是域外同名变量var;func1()内部调用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://www.mzph.cn/news/264975.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux开发板网线直连电脑配置方法/vmware虚拟机与本机的网络ping通

目录Linux开发板网线直连电脑配置方法vmware虚拟机与本机的网络ping通Linux开发板网线直连电脑配置方法 参考&#xff1a;https://www.bilibili.com/video/BV1n541197rk?spm_id_from333.999.0.0 一般情况&#xff0c;开发板连路由器&#xff0c;电脑也连路由器&#xff0c;路由…

ubuntu网站做图像外链

http://paste.ubuntu.org.cn 转载于:https://www.cnblogs.com/yuliyang/p/3658964.html

Markdown 基础学习

Markdown是什么&#xff1f; Markdwon是一种轻量级标记语言&#xff0c;它以纯文本形式&#xff08;易读、易写、易更改&#xff09;编写文档&#xff0c;并最终以HTLM格式发布。Markdown也可以理解为将以 MARKDOWN语法编写的语言转换成HTML内容的工具。 为什么要使用Markdown?…

状态机模型

参考&#xff1a;什么是状态机&#xff1f;用C语言实现进程5状态模型 参考&#xff1a;设计模式&#xff1a;一目了然的状态机图 案例&#xff1a;状态模式(C语言实现)——MP3播放、暂停案例 STM32按键消抖——入门状态机思维&#xff08;常用的switch-case形式&#xff0c;实现…

yii开启gii功能

如果不想面对黑白界面&#xff0c;那么yii框架&#xff0c;给我们提供了一个模块gii 在配置文件中main.php 再通过访问模块的方式访问gii转载于:https://www.cnblogs.com/xiashuo-he/p/3659334.html

2、基于wsgiref模块DIY一个web框架

一 web框架 Web框架(Web framework)是一种开发框架&#xff0c;用来支持动态网站、网络应用和网络服务的开发。这大多数的web框架提供了一套开发和部署网站的方式&#xff0c;也为web行为提供了一套通用的方法。web框架已经实现了很多功能&#xff0c;开发人员使用框架提供的方…

C标准时间与时间戳的相互转换

什么是时间戳&#xff1f; 时间戳是指格林威治时间自1970年1月1日&#xff08;00:00:00 GTM&#xff09;至当前时间的总秒数。它也被称为Unix时间戳&#xff08;Unix Timestamp&#xff09;。时间戳是能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据&#xff0…

Linux系统信息与系统资源

目录系统信息系统标识unamesysinfo 函数gethostname 函数sysconf()函数时间、日期GMT 时间UTC 时间UTC 时间格式时区实时时钟RTC获取时间time/gettimeofday时间转换函数设置时间settimeofday总结进程时间times 函数clock 函数产生随机数休眠(延时)秒级休眠: sleep微秒级休眠: u…

简单的一个用javascript做的'省市区'三级联动效果

2019独角兽企业重金招聘Python工程师标准>>> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head><title>javascript简单三级联动效果</title…

线程与线程同步

目录线程概述线程概念并发和并行线程ID创建线程终止线程回收线程取消线程取消一个线程取消状态以及类型取消点线程可取消性的检测分离线程注册线程清理处理函数线程属性线程栈属性分离状态属性线程安全线程栈可重入函数线程安全函数一次性初始化线程特有数据线程局部存储更多细…

CAS证书分析(2)

CAS的核心就是其Ticket&#xff0c;及其在Ticket之上的一系列处理操作。CAS的主要票据有TGT、ST、PGT、PGTIOU、PT&#xff0c;其中TGT、ST是CAS1.0协议中就有的票据&#xff0c;PGT、PGTIOU、PT是CAS2.0协议中有的票据。一 名词解释TGT&#xff08;Ticket Grangting Ticket&am…

Google Logos

All Googles logos.... 转载于:https://www.cnblogs.com/WuCountry/archive/2006/01/20/320689.html

〈转贴〉如何解决 Windows XP 中的硬件和软件驱动程序问题

如何解决 Windows XP 中的硬件和软件驱动程序问题 察看本文应用于的产品文章编号:322205最后修改:2004年3月25日修订:1.0本页 症状原因解决方案 检查第三方软件或驱动程序 检查新硬件这篇文章中的信息适用于:症状 在安装新硬件设备或新软件后&#xff0c;您的计算机可能自动开始…

MYSQL配置关键

2019独角兽企业重金招聘Python工程师标准>>> 在启动管理init.d里关于mysql的命令有 sudo /etc/init.d/mysql start|stop|restart|reload|force-reload|status sudo apt-get install mysql-server GRANT ALL PRIVILEGES ON *.* TO rootlocalhost IDENTIFIED BY &quo…

百度正式发布PaddlePaddle深度强化学习框架PARL

去年&#xff0c;斯坦福大学神经生物实验室与 EPFL 联合举办了一场强化学习赛事——人工智能假肢挑战赛&#xff08;AI for Prosthetics Challenge&#xff09;&#xff0c;希望将强化学习应用到人体腿部骨骼仿真模拟模型的训练。 经过激烈的角逐&#xff0c;最终来自百度大脑的…

关于lvalue and rvalue

2019独角兽企业重金招聘Python工程师标准>>> lvalue &#xff1a;An object is a region of storage that can be examined and stored into.An lvalue does not necessarily permit modification of the object it designates&#xff1a; eg An array type An inc…

gitlab 使用教程

视频教程&#xff1a;叮&#xff5e;&#xff0c;你收到一份最全的gitlab使用说明 地址&#xff1a;https://www.bilibili.com/video/BV11E411x7Uv?spm_id_from333.337.search-card.all.click 目录简介1、注册、登录2、创建项目3、添加项目成员4、分支权限设置5、下载安装git6…

mssql 分页

为什么80%的码农都做不了架构师&#xff1f;>>> http://www.cnblogs.com/ddlink/archive/2013/03/30/2991007.html 分页问题修正 http://blog.csdn.net/wangkadm/article/details/12708005 转载于:https://my.oschina.net/macleo/blog/223782

今天体育课受伤

中午轮滑体育课&#xff0c;由于技术不到家&#xff08;基本上还不会&#xff09;&#xff0c;一不小心就扭下去了&#xff0c;虽然从开始下倒到坐在地上不到1秒的时间&#xff0c;不过那一瞬间的记忆却特别的漫长&#xff1a; 先是向左倒&#xff0c;膝关节处感觉到了咔嚓的两…

OSAL操作系统分析(添加自定义任务)

目录事件驱动型OSAL操作系统原理分析OSAL消息收发过程向OSAL系统添加自定义任务事件驱动型OSAL操作系统原理分析 任务就是一个函数&#xff0c;每一个任务都要有一个函数&#xff0c;形成函数列表&#xff08;函数指针数组&#xff09; 以上就是任务处理函数&#xff0c;都是任…