gcc & make & cmake
编译
安装 gcc (多版本共存)
阿里云镜像:http://mirrors.aliyun.com/gnu/gcc/
参考博客: Linux下编译安装GCC 4.9.4 - Caosiyang's Blog
关于头文件与库的搜索路径
为什么通常会需要配置 LD_LIBRARY_PATH
而不需要配置 C_INCLUDE_PATH
和 CPLUS_INCLUDE_PATH
, 例如:
安装 CUDA
安装 openCV
安装 tensorRT
关于 ldconfig
ldconfig
注:以下均为简单理解,并非准确理解
在 Linux 下,默认动态链接库的搜索路径保存在 /etc/ld.so.conf
中,默认动态链接库的访问使用缓存机制,存放在 /etc/ld.so.cache
(二进制格式),如果在默认路径下添加了动态链接库,则需要使用 ldconfig
更新默认路径里的动态链接库至 /etc/ld.so.cache
。具体来说,可能会遇到需要使用 ldconfig
命令的场景例如安装 mysql
时,默认会将 mysql 的库文件安装到 /usr/local/mysql/lib
,这个目录一般是在默认的动态链接库搜索路径下,因此由于缓存机制的存在,如果在不执行 ldconfig
时,在需要使用 mysql 相关的动态链接库时,会报找不到库的错误。
ldconfig -v
用于查看已经缓存的动态链接库。
另外,ldconfig 是系统层面的一些机制,在用户层面,也可以配置 LD_LIBRARY_PATH
来添加库目录
示例(杂录)
例子1
目录结构及文件内容
编译方法
单条指令编译
-I
选项用于增加include目录-o
选项用于指定编译输出的文件位置
先编译链接库,再利用链接库编译应用
-L
用于增加静态/动态链接库的搜索路径,-l
用于指定静态/动态链接库的具体名称(注意不含lib前缀及文件扩展名)。stackoverflow问答的解释可以使用 gcc 的组件 nm 命令查看 .so 文件中的符号。stackoverflow
编译步骤、动态链接库与静态链接库
编译步骤拆解为:
编译预处理(pre-processing):将
#include
处理好,宏展开等。使用-E
指定,生成文件后缀名习惯用.i
。编译(compiling):转换为汇编代码。使用
-S
指定,生成文件后缀名习惯用.s
。汇编(assembling):将汇编代码转换为目标文件。使用
-c
指定,生成文件后缀名习惯用.o
。链接(linking):将目标文件进行链接,最终生成可执行文件
可以把多个目标文件打包为一个作为函数库,这一过程可以借助
ar
命令来完成。函数库分为动态链接库与静态链接库
参考资料:
不确定好坏的资料:https://tldp.org/HOWTO/Program-Library-HOWTO/index.html
Make
CMake
资源:
项目结构与 cmake 基本命令
CMake 用于生成 Makefile (如果使用 make 的话), 典型的编译步骤如下
一种典型的项目组织如下
备注: 有些项目源文件和头文件不会分离, 而是放在同一级目录下, 也是常见的
CMakeLists.txt 语法
重要语法索引 (cheetsheet)
初学先概览, 回头再复习. TODO: 此部分内容来自 GPT, 回头再做确认(似乎不太靠谱, 很多出现在 CMake 官方 tutorial 中的命令这里没有, 许多这里出现的, CMake 官方 tutorial 中没有).
项目信息
cmake_minimum_required(VERSION X.Y)
: 指定 CMake 的最低版本要求。project(<project_name>)
: 定义项目的名称,可以可选地指定语言(C、C++等)。
源文件与目标
add_executable(<target> <source1> <source2> ...)
: 创建一个可执行目标。add_library(<target> <source1> <source2> ...)
: 创建一个库目标(静态库或共享库)。aux_source_directory(<directory> <variable>)
: 查找指定目录中的所有源文件,并将它们的名称存入变量。
依赖关系
target_link_libraries(<target> <library1> <library2> ...)
: 指定目标所依赖的库。add_subdirectory(<directory>)
: 添加子目录,通常用于包含其他 CMakeLists.txt 文件。
变量和选项
set(<variable> <value>)
: 定义或设置变量。option(<option_name> <description> <default>)
: 定义一个布尔选项,用户可以选择该选项。if(<condition>) ... endif()
: 条件语句,用于控制构建时的行为。foreach(<var> <items>) ... endforeach()
: 迭代语句,用于遍历列表中的每一项。
包含和查找模块
find_package(<package_name> [REQUIRED])
: 查找并加载指定的包。include_directories(<directory>)
: 添加包含目录。link_directories(<directory>)
: 添加链接目录。
生成规则和安装
install(TARGETS <target> DESTINATION <directory>)
: 指定安装目标及其安装位置。file(<command> <args>)
: 处理文件的多种操作,例如复制、删除、生成等。
配置和构建选项
set(CMAKE_BUILD_TYPE <type>)
: 设置构建类型,例如 Debug 或 Release。set(CMAKE_CXX_STANDARD <version>)
: 设置 C++ 标准。
脚本与宏
macro(<name> <args>) ... endmacro()
: 定义一个宏,可以多次调用。function(<name> <args>) ... endfunction()
: 定义一个函数,具有局部作用域。
自定义命令和目标
add_custom_command(...)
: 定义自定义构建命令。add_custom_target(...)
: 定义自定义构建目标。
其他
message(<message>)
: 打印消息到标准输出。include(<module>)
: 包含其他 CMake 文件。
add_library
, add_executable
分别表示创建一个库/可执行文件以及其所需要的源文件, 它们之后可能会跟上 target_link_libraries
, target_include_directories
, target_compile_definitions
, 分别表示为了生成这个库/可执行文件需要的其他库名,头文件目录,编译时宏定义.
这类 target_xxx
的命令还有:
target_compile_features
: 用于指定 C++ 标准, 例如:target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
target_compile_options
: 直接指定编译选项, 但需要手动解决跨平台问题. 例如:target_compile_options(tutorial_compiler_flags INTERFACE -Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused)
. 所谓跨平台, 指的是例如指定 C++ 标准, 使用gcc
编译器, 写法是gcc -std=c++11 ...
, 而MSVC
编译器的写法是cl /std:c++11
. 因此一般来说, 使用target_compile_options
时可能会见到下面的写法:
特殊变量: PROJECT_SOURCE_DIR
, PROJECT_BINARY_DIR
, CMAKE_CURRENT_SOURCE_DIR
官方 Tutorial 笔记
链接 (本文写作时的版本为cmake==3.31.0-rc2
): https://cmake.org/cmake/help/latest/guide/tutorial
Step1: A Basic Starting Point
主要介绍了单个源文件和头文件的情形, 但内容其实比较多
(1) 首先介绍了 project
, cmake_minimum_required
, add_executable
命令, 以及使用 cmake 命令行工具进行配置和构建
(2) 然后使用 set
命令配置 cmake 内置的变量 CMAKE_CXX_STANDARD
和 CMAKE_CXX_STANDARD_REQUIRED
, 从而实现对 C/C++标准 的管控
(3) 最后补充介绍了 project
中可以定义项目的版本号, 在这之后的 CMakeLists.txt
内容里就可以使用 <PROJECT-NAME>_VERSION_MAJOR
和 <PROJECT-NAME>_VERSION_MINOR
内置变量, 为了将这两个变量变得可以在源码中使用(源码中用宏来获取这两个变量), 可以采用如下方案: 配置 config.h.in
, 在这个模板文件中使用 @<PROJECT-NAME>_VERSION_MAJOR@
的语法用于宏值的定义, 并且在 CMakeLists.txt
中使用 configure_file
命令使得 cmake 在构建项目时生成真正的 config.h
文件, 最后使用 target_include_directories
让可执行文件的编译过程正确地 include 出于 PROJECT_BINARY_DIR
的 config.h
文件.
Step2: Adding a Library
主要介绍了多个源文件的情形, 且库代码位于单独的目录内, 且包含有自己的 CMakeLists.txt
文件.
(1) 内层 CMakeLists.txt
引入了 add_library
命令, 而外层的 CMakeLists.txt
为了使得最终的 main 程序能链接库, 需要使用 add_subdirectory
用于执行得到库, 使用 target_link_libraries
来让 main 链接到库, 使用 target_include_directories
使得 main 在编译时能识别到库的头文件
(2) 演示了如何利用 option
来为 cmake 在配置阶段增加 -D<OPTION_NAME>=<value>
来控制编译行为. 在官方的例子中, 场景是这样的, 我们的库是一个计算 sqrt
的函数, 而 main 程序希望实现如下功能: 如果希望使用我们自己实现的数学库, 那么就编译出该数学库, 并且 main 程序链接并使用其实现; 如果希望使用官方的数学库 cmath
, 那么就不编译我们自己的数学库. 具体实现方式如下: TODO
Step3: Adding Usage Requirements for a Library
主要是优化 Step1 和 Step2 中的一些写法: 在 Step1 中, C/C++标准 的管控是使用 set
命令全局管控 CMAKE_CXX_STANDARD
和 CMAKE_CXX_STANDARD_REQUIRED
来实现的, 这样就不能实现不同模块采用不同的 C/C++标准; 在 Step2 中, 外层的 main 不仅需要链接库, 还需要通过 target_include_directories
添加库文件目录.
Step4: Adding Generator Expressions
本部分感觉不是核心用法, Generator Expressions 是 CMakeLists.txt
的脚本特性
Step5: Installing and Testing
引入 cmake 项目安装, 以及单元测试, TODO: 此处代码移除
Step6: Adding Support for a Testing Dashboard
本部分感觉不是核心用法, 单元测试可以更花哨, 可以有个仪表盘来展示
Step7: Adding System Introspection
本部分与 Step6 都使用了 include
语法, 来引入 /path/to/cmake/Modules
中的其他“脚本”文件
Step8: Adding a Custom Command and Generated File
本部分实现了一个特殊功能: 首先编写一段 C 代码, 其执行后会生成一个头文件, 整个项目的构建过程是先得到这个头文件 (通过自定义命令来实现: add_custom_command
), 然后其他的库/可执行文件可能需要使用这个头文件
Step9: Packaging an Installer
本部分介绍了项目打包, 具体介绍了使用 cpack 的打包方式, 打包为 .tar.gz
文件(二进制安装), 操作比较简单, CMakeLists.txt
加几行配置即可. 其他的项目打包和发布方式待探索
Step10: Selecting Static or Shared Libraries
前序步骤所有的库都是使用的静态库(当然也可以在 add_library
是设定为动态库, 但前面没有明确提到), 本部分介绍怎么“优雅”地配置以生成动态链接库
Step11: Adding Export Configuration
TODO: 似乎是让本项目在其他项目中能使用, TODO
Step12: Packaging Debug and Release
TODO
单文件
引入: cmake_minimum_required
, project
, add_executable
, aux_source_directory
最简单的情况:
一个源文件和头文件, 用于编译得到一个可执行文件(引入
cmake_minimum_required
,project
,add_executable
)多个源文件和头文件在同一目录, 最终得到一个可执行文件(引入
aux_source_directory
)
库与链接
引入: add_library
, target_link_libraries
, target_include_directories
, add_subdirectory
注意: add_executable
用于指定可执行文件依赖的源文件, 而 target_link_libraries
用于指定可执行文件依赖的库文件. 并且 add_executable
必须在 target_link_libraries
之前. 虽然按照标准的 gcc 编译流程, 编译步骤确实是:
(1) 先编译并打包得到库文件 libMathFunctions.a
(2) 使用源文件 main.c
编译得到 main.o
(3) 使用 main.o
和 libMathFunctions.a
链接得到 Demo
但 CMakeLists.txt 的语法里不关心 main.o
这种中间产物, 它只关系为了生成 Demo
, 它需要哪些源文件 (对应于 add_executable
), 哪些库文件 (对应于 target_link_libraries
)
add_subdirectory
命令表示“执行”子目录的 CMakeLists.txt
: 父目录的 CMakeLists.txt
在 add_subdirectory
之前通过 set
定义的变量, 在子目录中的 CMakeLists.txt
是可见的, 并且在子目录 CMakeLists.txt
中定义的变量, 在父目录 add_subdirectory
之后, 对父目录的 CMakeLists.txt
也是可见的
configure_file
configure_file
: 根据模板文件生成宏定义头文件, 模板文件名通常是 config.h.in
, 而宏定义头文件名一般是 config.h
, 在使用 cmake 生成 Makefile 时, 会自动得到这个 config.h
(与编译选项无关). 在 C/C++ 源代码中可以去通过 #include "config.h"
从而确定 config.h
中的宏定义
CMakeLists.txt
:
demo.cxx
config.h.in
的写法如下:
自定义编译选项
TODO: 搞完整
option
+add_definitions
option
+config.h.in
+include "config.h"
引入: option
, add_definition
, if
前面已经看到过 cmake 像这样 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
的选项, 事实上, cmake 可以自定义编译选项. 涉及到的相关 cmake 语法有
option
: 增加一个 cmake 的编译选项, 也就是增加一个这种用法cmake -D<选项名>=<值>
add_definitions
: 将编译选项直接传递给编译器, 也就是利用 gcc 的-D<宏名>=<值>
参数, 例如:gcc -DDEBUG main.c -o main
为此需要在 CMakeLists.txt 中包含如下内容
C 代码中可以这样使用
安装与测试
CMake 官方 Tutorial-5
TODO: 基本已确认
关于 cmake 命令与 CMakeLists.txt 内的相对路径问题: CMakeLists.txt 的写法只需关注它本身与源码的相对路径, 而不必关心 cmake 命令行的参数. 这一点和 python 有区别 (python 代码里的 import 需要与 python 作为启动脚本时的当前目录需要相配)
CMakeLists.txt
的文件内容如下:
使用 cmake .
和 mkdir build && cd build && cmake ..
或者 cmake -B build -S .
均可以成功, 后两者的 Makefile 在 build/Makefile
CMAKE_CURRENT_SOURCE_DIR
总是指向 CMakeLists.txt
所在目录, 而 CMAKE_CURRENT_BINARY_DIR
总是指向生产的文件目录
CMAKE_CURRENT_SOURCE_DIR
总是指向 CMakeLists.txt
所在目录, 而 CMAKE_CURRENT_BINARY_DIR
总是指向生产的文件目录Last updated