对于生成项目的可执行文件的操作, 实际上不管是采用 GCC 还是 CMake 工具,都是类似的,也就是从项目的
mian()
函数出发,找到项目依赖的全部的头文件/库文件/源文件等, 打包生成可执行程序;
源文件在不同目录下用 gcc 编译多个.h .c 文件
.
├── include // include文件夹存放头文件
│ ├── hexCov.h // 主函数的头文件
│ ├── hex2bin.h // 十六进制转二进制的头文件
│ ├── hex2dec.h // 十六进制转十进制的头文件
│ └── hex2otc.h // 十六进制转八进制的头文件
└── src // 存放项目源码/.c文件的目录
├── hexCov.c // 项目的函数汇总,主要用于后续编译.so动态库/.a静态库
└── main.c // 项目的主函数文件
hexCov.h
文件,声明主要的函数,但是不实现它:#include
int hexCov();
hex2bin.h
文件, 声明并实现十六进制转二进制的方法:#include
#include
void hexToBinary(const char *hexStr, char binaryStr[])
{
long decimal = strtol(hexStr, NULL, 16);
int i = 0;
while (decimal > 0)
{
binaryStr[i] = (decimal % 2) + '0';
decimal /= 2;
i++;
}
binaryStr[i] = '\0';
// 翻转字符串
int start = 0;
int end = i - 1;
while (start < end)
{
char temp = binaryStr[start];
binaryStr[start] = binaryStr[end];
binaryStr[end] = temp;
start++;
end--;
}
}
hex2dec.h
文件, 声明并实现十六进制转十进制的方法:#include
long hexToDec(const char *hexStr)
{
return strtol(hexStr, NULL, 16);
}
hex2otc.h
文件, 声明并实现十六进制转八进制的方法:#include
void hexToOct(char *hexStr, char octalStr[])
{
long decimal = strtol(hexStr, NULL, 16);
sprintf(octalStr, "%lo", decimal);
}
hexCov.c
文件, 实现函数文件, 这个文件很有意思的在于,main.c
文件如果想实现功能,那么它必须需要包括这个这个文件:#include "hex2bin.h"
#include "hex2dec.h"
#include "hex2otc.h"
void decToBinary(const char *hexStr, char binaryStr[]);
long hexToDec(const char *hexStr);
void decToOct(long decimal, char octalStr[]);
int hexCov()
{
char hexStr[100];
printf("Enter a hexadecimal number: ");
scanf("%s", hexStr);
// 将十六进制字符串转换为十进制整数
long decimal = hexToDec(hexStr);
printf("Decimal: %ld\n", decimal);
// 将十六进制字符串转换为八进制字符串
char octalStr[100];
hexToOct(hexStr, octalStr);
printf("Octal: %s\n", octalStr);
// 将十六进字符串转换为二进制字符串
char binaryStr[100];
hexToBinary(hexStr, binaryStr);
printf("Binary: %s \n", binaryStr);
return 0;
}
main.c
文件, 项目主函数的入口:#include "hexCov.h"
int main()
{
hexCov();
return 0;
}
gcc ./src/main.c -I ./include/ -o test
include_directories
命令将头文件目录添加到构建过程中。include_directories
可以接受一个或多个参数,每个参数都是一个目录路径,指定要包含的头文件所在的目录.CMake 构建项目时,会在指定的目录中查找头文件, 使用include_directories
命令,可以确保在编译源文件时,编译器能够找到所需的头文件.
include_directories
用法:include_directories(dir1 dir2 ...)
, 其中,dir1
、dir2
等是要包含的头文件目录路径。
这个命令通常与target_link_libraries
命令一起使用,以确保在链接过程中能够找到所需的头文件.
那么这个时候的 CMakeLists.txt
脚本可以写成如下格式:
cmake_minimum_required(VERSION 3.0)
project(HEXCOV)
# 当前工作空间的目录和CMakeLists.txt文件位置一致
set(WORKSPACE_PATH ${CMAKE_SOURCE_DIR})
# 添加头文件的路径
include_directories(${WORKSPACE_PATH}/include)
# 设置可执行文件的输出位置
set(EXECUTABLE_OUTPUT_PATH ${WORKSPACE_PATH})
# 生成执行文件
add_executable(test ${WORKSPACE_PATH}/src/main.c ${WORKSPACE_PATH}/src/hexCov.c)
build
文件夹,输入指令cmake ..
, CMake 语法没有问题之后,也在build
文件夹下输入make
生成可执行文件.aux_source_directory
: 用于将指定目录中的源文件自动添加到一个变量中。它的使用格式如下:aux_source_directory(
; 其中:
: 是要扫描的目录路径;
: 是存储扫描到的源文件的变量名称。
aux_source_directory
命令会自动扫描指定目录下的源文件,并将它们的路径添加到指定的变量中.
aux_source_directory
只会将目录下的源文件添加到变量中,而不会包含子目录中的源文件.
那么 CMakeLists.txt 文件可以改写为这样:
cmake_minimum_required(VERSION 3.0)
project(HEXCOV)
# 当前工作空间的目录和CMakeLists.txt文件位置一致
set(WORKSPACE_PATH ${CMAKE_SOURCE_DIR})
# 添加头文件的路径
include_directories(${WORKSPACE_PATH}/include)
# 添加源文件
# file(GLOB SRCS ${WORKSPACE_PATH}/src/*.c)
# 上下两个指令都是可以的
aux_source_directory(${WORKSPACE_PATH}/src SRCS)
# 设置可执行文件的输出位置
set(EXECUTABLE_OUTPUT_PATH ${WORKSPACE_PATH})
# 生成执行文件
add_executable(test ${SRCS})
修改main.c
代码,添加math.h
和pthread.h
此时按照上面的 CMakeLists.txt 脚本运行,则会出错, 因为缺少数学库libm.so
和线程库libpthread.so
target_link_libraries
用于将目标与一个或多个库进行链接;它的使用格式如下: target_link_libraries(
是要链接库的目标(可执行文件、静态库或动态库)的名称;
是链接库的范围,可以是 PRIVATE、PUBLIC 或 INTERFACE;PRIVATE
表示该库仅在当前目标中使用;PUBLIC
表示该库在当前目标以及依赖于当前目标的其他目标中使用;INTERFACE
表示该库仅在依赖于当前目标的其他目标中使用; ...
是要链接的库的名称;target_link_libraries
将目标与编译时需要的库进行链接,包括静态库和动态库.
链接库时,可以指定库的名称,也可以使用变量来代替.
动态库的名称均是形如libxxx.so
由lib
开头, .so
结尾, 其中的xxx
便是动态库的名称.
静态库的名称均是形如libxxx.a
由lib
开头, .a
结尾, 其中的xxx
便是静态库的名称.
如果链接的是系统提供的库,只需指定库的名称即可,如 target_link_libraries(test pthread)
.
如果链接的是自定义的库(静态库或动态库),通常需要提供库的完整路径,如 target_link_libraries(test ${CMAKE_SOURCE_DIR}/libs/libmy)
.
静态库是在编译时被链接到最终可执行文件或其他目标中,而动态库是在运行时被动态加载到内存中.
那么此时的 CMakeLists.txt 文件可以书写为以下:
cmake_minimum_required(VERSION 3.0)
project(HEXCOV)
# 当前工作空间的目录和CMakeLists.txt文件位置一致
set(WORKSPACE_PATH ${CMAKE_SOURCE_DIR})
# 添加头文件的路径
include_directories(${WORKSPACE_PATH}/include)
# 添加源文件
# file(GLOB SRCS ${WORKSPACE_PATH}/src/*.c)
# 上下两个指令都是可以的
aux_source_directory(${WORKSPACE_PATH}/src SRCS)
# 设置可执行文件的输出位置
set(EXECUTABLE_OUTPUT_PATH ${WORKSPACE_PATH})
# 生成执行文件
add_executable(test ${SRCS})
# 连接动态库
target_link_libraries(test m pthread)
add_library
用于创建一个静态库或动态库, 它的语法:add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
[STATIC | SHARED | MODULE]
是可选参数,指定要创建的库的类型;STATIC
表示创建静态库,它会将源文件编译成目标文件,并将这些目标文件打包成一个静态库文件(如.a、.lib);SHARED
表示创建动态库,它会将源文件编译成目标文件,并将这些目标文件打包成一个动态库文件(如.so、.dll);MODULE
表示创建模块库,它类似于动态库,但在某些平台上有不同的加载方式;[EXCLUDE_FROM_ALL]
是可选参数,表示该库不会被默认构建;如果希望将该库作为可选项构建,可以使用该参数;source1 [source2 ...]
是要编译到库中的源文件列表;add_library 命令用于创建静态库或动态库,库的名称应该是唯一的,并且在后续的链接过程中要被引用.
指定库的输出位置:set(LIBRARY_OUTPUT_PATH
, 其中的LIBRARY_OUTPUT_PATH
作为宏定义的参数,
是指定的位置.
实际上,如果source1 [source2 ...]
能够生成目标文件,那么它也能生成库文件.
如果将最后的程序打包为动态库,则可以这样写 CMakeLists.txt
:
cmake_minimum_required(VERSION 3.0)
project(HEXCOV)
# 当前工作空间的目录和CMakeLists.txt文件位置一致
set(WORKSPACE_PATH ${CMAKE_SOURCE_DIR})
# 添加头文件的路径
include_directories(${WORKSPACE_PATH}/include)
# 添加源文件
# file(GLOB SRCS ${WORKSPACE_PATH}/src/*.c)
# 上下两个指令都是可以的
aux_source_directory(${WORKSPACE_PATH}/src SRCS)
# 设置可执行文件的输出位置
set(EXECUTABLE_OUTPUT_PATH ${WORKSPACE_PATH})
# 生成执行文件
add_executable(test ${SRCS})
# 连接动态库
target_link_libraries(test m pthread)
# 指定库的路径与CMakeLists.txt同一个层级
set(LIBRARY_OUTPUT_PATH ${WORKSPACE_PATH})
add_library(hexCov0 STATIC ${SRCS})
add_library(hexCov1 SHARED ${SRCS})
如果项目比较大的情况下,单个
CMakeLists.txt
对项目的管理就容易混乱,目前主流的方式,是采用树状模式,将复杂的项目由一个根CMakeLists.txt
进行管理,各个子项目分别用被它的子CMakeLists.txt
管理, 其中包括你的 main.c 文件的那个层级的文件夹也算作是子项目.
以本文的工程为例子,讨论根和子
CMakeLists.txt
的注意事项.
.
├── build
├── CMakeLists.txt
├── decCov
│ ├── build
│ ├── CMakeLists.txt
│ ├── include
│ │ ├── dec2bin.h
│ │ ├── dec2hex.h
│ │ ├── dec2oct.h
│ │ └── decCov.h
│ └── src
│ ├── decCov.c
│ └── main.c
├── hexCov
│ ├── build
│ ├── CMakeLists.txt
│ ├── include
│ │ ├── hex2bin.h
│ │ ├── hex2dec.h
│ │ ├── hex2otc.h
│ │ └── hexCov.h
│ └── src
│ ├── hexCov.c
│ ├── main.c
│ └── testLib.c
├── libs
│ ├── libdeccov.a
│ ├── libhexcov.a
│ └── liboctcov.a
├── octCov
│ ├── build
│ ├── CMakeLists.txt
│ ├── include
│ │ ├── oct2bin.h
│ │ ├── oct2dec.h
│ │ ├── oct2hex.h
│ │ └── octCov.h
│ └── src
│ ├── main.c
│ └── octCov.c
└── src
├── CMakeLists.txt
└── main.c
add_subdirectory
用于向当前项目添加一个子目录, 它的使用格式如下:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
source_dir
是要添加的子目录的路径;
binary_dir
是可选参数,用于指定构建子目录的二进制输出路径。如果不指定,则默认为${CMAKE_CURRENT_BINARY_DIR}/source_dir
,一般而言,都是不指定的;
EXCLUDE_FROM_ALL
是可选参数,表示将子目录排除在默认构建目标之外;
add_subdirectory
指令可以将其他目录中的 CMakeLists.txt
文件引入到当前项目中。这样可以将整个项目划分为多个子目录,每个子目录对应一个模块或子项目;在使用 add_subdirectory
指令时,需要确保指定的 source_dir
目录中包含有效的 CMakeLists.txt
文件;
子目录中的 CMakeLists.txt
文件会被自动加载和处理,将其包含的内容添加到当前项目中;子目录中的 CMakeLists.txt
文件可以使用和当前项目相同的变量、函数和其他 CMake
命令;
add_subdirectory
指令就是调用子项目的CMakeLists.txt
脚本, 父CMakeLists.txt
的变量可以被子CMakeLists.txt
读取到,反之不行.CMakeLists.txt
中的cmake
语法宏定义一些参数已经是其父节点CMakeLists.txt
的内容了,比方说CMAKE_SOURCE_DIR
就是始终指向父节点CMakeLists.txt
的位置.CMakeLists.txt
中的一些针对项目本身的设置参数,还是随着CMakeLists.txt
脚本而变化,例如父节点的PROJECT_SOURCE_DIR
指向的是父节点的CMakeLists.txt
路径,例如为./A
,例如 A 下面的 AA 文件夹的CMakeLists.txt
得到的PROJECT_SOURCE_DIR
就是./A/AA
include_directories
设置可以放在父节点上,这样子节点都能拿到这些库文件.libm
库内的pow
函数,只有声明,源码在libm.so
内CMakeLists.txt
可以这么写cmake_minimum_required(VERSION 3.0)
project(BCDCOV)
# 设置工程的路径,也就是和CMakeLists.txt同一个层级的目录,同时声明为可以传递给子cmake的全局变量.
set(PROPATH ${CMAKE_SOURCE_DIR})
message(">>> I am parents directory, CMAKE_SOURCE_DIR is: " ${PROPATH})
# 设置库文件输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs)
message(">>> I am parents directory, PROJECT_SOURCE_DIR is: " ${PROJECT_SOURCE_DIR})
# 设置可执行文件的输出位置
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR})
# 设置头文件的位置,为了最后的main.c能够找到头文件.
include_directories(${PROJECT_SOURCE_DIR}/decCov/include)
include_directories(${PROJECT_SOURCE_DIR}/hexCov/include)
include_directories(${PROJECT_SOURCE_DIR}/octCov/include)
# 添加子目录
add_subdirectory(${PROPATH}/decCov)
add_subdirectory(${PROPATH}/hexCov)
add_subdirectory(${PROPATH}/octCov)
add_subdirectory(${PROPATH}/src)
那么decCov
, hexCov
, octCov
三个子文件夹分别构建出libdeccov.a
, libhexcov.a
, liboctcov.a
到最外层的 lib 目录, 他们的CMakeLists.txt
可以这么写:
decCov
子项目CMakeLists.txt
可以这样写:
cmake_minimum_required(VERSION 3.0)
project(DECCOV)
# 输出一段话,观察add_subdirectory的顺序.
message(">>> Into decCov CMakeLists.txt")
# 查看父CMakeLists.txt的变量传递到子文件中的例子
message("<<< Parent path: " ${PROPATH})
# 此时的cmake语法中的宏变量是针对于父CMakeLists.txt的变量了.
message("<<< In decCov PROJECT_SOURCE_DIR is: " ${CMAKE_SOURCE_DIR})
# 此时针对项目的变量依旧是指向当前CMakeLists.txt的值
message("<<< In decCov PROJECT_SOURCE_DIR is: " ${PROJECT_SOURCE_DIR})
# 设置当前的工作空间
set(CUR_DIR ${CMAKE_CURRENT_SOURCE_DIR})
# 生成库文件
# 源文件已经在父CMakeLists.txt添加完成不需要再添加源文件.
# 添加源文件,注意,此时不添加main.c进去,否则会出现多个mian函数入口!
add_library(deccov STATIC ${CUR_DIR}/src/decCov.c)
hecCov
子项目CMakeLists.txt
可以这样写:cmake_minimum_required(VERSION 3.0)
project(HEXCOV)
# 输出一段话,可以显示CMake的进度.
message(">>> Into hexCov CMakeLists.txt")
# 设置当前的工作空间
set(CUR_DIR ${CMAKE_CURRENT_SOURCE_DIR})
# 生成库文件
# 源文件已经在父CMakeLists.txt添加完成不需要再添加源文件.
# 添加源文件,注意,此时不添加main.c进去,否则会出现多个mian函数入口!
add_library(hexcov STATIC ${CUR_DIR}/src/hexCov.c)
octCov
子项目CMakeLists.txt
可以这样写:cmake_minimum_required(VERSION 3.0)
project(OCTCOV)
# 输出一段话,可以显示CMake的进度.
message(">>> Into octCov CMakeLists.txt")
# 设置当前的工作空间
set(CUR_DIR ${CMAKE_CURRENT_SOURCE_DIR})
# 生成库文件
# 源文件已经在父CMakeLists.txt添加完成不需要再添加源文件.
# 添加源文件,注意,此时不添加main.c进去,否则会出现多个mian函数入口!
add_library(octcov STATIC ${CUR_DIR}/src/octCov.c)
src
目录下有一个main.c
它就需要引用了decCov
, hexCov
, octCov
中的头文件和libdeccov.a
, libhexcov.a
, liboctcov.a
,构建出一个test.elf
文件cmake_minimum_required(VERSION 3.0)
project(BCDCORE)
# 可执行文件
add_executable(test.elf main.c)
# 可执行文件依赖的库文件包括一个数学库math也就是m(-lm)
target_link_libraries(test.elf deccov hexcov octcov m)
cmake
指令的输出:什么是交叉编译:在一个平台上生成另一个平台上的可执行代码。例如,在 x86_64 Linux
平台上,可以使用交叉编译工具aarch64-linux-gnu-
链生成针对 ARM
体系结构的 Linux
可执行文件.
交叉编译包括什么:编译器、链接器和其他必要的工具和依赖.
这里以主机(也就是我现在的PC电脑)为x86_64 Linux
交叉编译一个aarch64
架构的可执行文件为例, 首先需要安装交叉编译工具:交叉编译工具链–aarch64安装流程,例子的话可以参考:ARM平台搭建Python环境
CMake
中,使用set(CMAKE_C_COMPILER )
和set(CMAKE_CXX_COMPILER )
命令设置用于交叉编译aarch64
架构的交叉编译工具# 设置C代码的交叉编译工具绝对路径位置
set(CMAKE_C_COMPILER )
# 设置CPP代码的交叉编译工具绝对路径位置
set(CMAKE_CXX_COMPILER )
可以通过
whereis
命令找出工具为位置:pldz@pldz-pc:~/share/Others/CMake_Tutorial/code/simpleProject$ whereis aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc: /home/pldz/aarch64/bin/aarch64-linux-gnu-gcc
set()
命令设置CMAKE_SYSTEM_PROCESSOR
变量来指定目标架构,CMAKE_SYSTEM_NAME
变量来指定目标系统,例如这里的aarch64
架构的Linux
系统:# 设置目标系统的架构
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# 设置目标系统的类型
set(CMAKE_SYSTEM_NAME Linux)
# 设置交叉编译工具的root目录
set(CMAKE_FIND_ROOT_PATH )
# 设置交叉编译工具的lib目录
set(CMAKE_LIBRARY_PATH )
CMAKE_C_FLAGS
和CMAKE_EXE_LINKER_FLAGS
等变量,例如:# 设置连接标志armv8表示arm64
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/path/to/aarch64-linux-libs")
CMake
的项目,交叉编译的选项应该放在项目project
前进行设置,否则可能陷入检查变量被修改而陷入cmake循环:# 假设进行交叉编译
# 设置目标机器类型.
set(CMAKE_SYSTEM_NAME Linux)
# 设置目标系统架构.
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# 设置交叉编译的工具链位置.
set(CROSS_CHAIN_PATH /home/pldz/aarch64)
# 设置交叉编译的工具.
set(CMAKE_C_COMPILER ${CROSS_CHAIN_PATH}/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER ${CROSS_CHAIN_PATH}/bin/aarch64-linux-gnu-g++)
project(BCDCOV)
上诉的项目则可以修改它的主CMakeLists.txt
文件为:
cmake_minimum_required(VERSION 3.0)
# 假设进行交叉编译,在project设置之前设置交叉编译的工具设置!
# 设置目标机器类型.
set(CMAKE_SYSTEM_NAME Linux)
# 设置目标系统架构.
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# 设置交叉编译的工具链位置.
set(AARCH64_CHAIN_PATH /home/pldz/aarch64/bin)
# 设置交叉编译的工具.
set(CMAKE_C_COMPILER ${AARCH64_CHAIN_PATH}/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER ${AARCH64_CHAIN_PATH}/aarch64-linux-gnu-g++)
project(BCDCOV)
# 设置工程的路径,也就是和CMakeLists.txt同一个层级的目录,同时声明为可以传递给子cmake的全局变量.
set(PROPATH ${CMAKE_SOURCE_DIR})
message(">>> I am parents directory, CMAKE_SOURCE_DIR is: " ${PROPATH})
# 设置库文件输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs)
message(">>> I am parents directory, PROJECT_SOURCE_DIR is: " ${PROJECT_SOURCE_DIR})
# 设置可执行文件的输出位置
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR})
# 设置头文件的位置,为了最后的main.c能够找到头文件.
include_directories(${PROJECT_SOURCE_DIR}/decCov/include)
include_directories(${PROJECT_SOURCE_DIR}/hexCov/include)
include_directories(${PROJECT_SOURCE_DIR}/octCov/include)
# 添加子目录
add_subdirectory(${PROPATH}/decCov)
add_subdirectory(${PROPATH}/hexCov)
add_subdirectory(${PROPATH}/octCov)
add_subdirectory(${PROPATH}/src)