C++构建系统

当C++系统依赖模块多了之后,构建系统本身的工程质量就成为了重要的瓶颈,这方面开源项目里做的最好的是Chromium项目。

全局视角

构建系统有很多东西,从全局视角来说,这是一个目录

  • 理解 C/C++ 代码的基本编写
    • 理解 C99
    • 理解 C++ 11/14/17/20 标准
    • 理解 C++ 现代多线程支持
    • 掌握 Google 编码规范
  • 理解 build-system 最终是通过 gcc/g++ 执行命令的,因此需要理解 gcc 的构建流程
    • 理解 GCC 的预处理,编译,链接,汇编
      • 理解静态链接,动态链接,理解静态库和动态库
      • 理解导出符号和导入符号
    • 理解 GCC 的编译参数
    • 理解 GCC 的警告选项
    • 理解 GCC 的编译和链接优化选项
  • 理解 build-system, meta-build-system, package manager
    • 最基本的,要熟悉 build-system 的 Makefile
      • 作为对比,应该熟悉 Ninja
    • 在这个基础上,理解 meta-build-system: CMake
      • 作为对比,应该熟悉 GN
    • 在这个基础上,理解 pacakge-manager : Conan
  • 理解 单元测试,例如 google test
    • 进而理解 测试覆盖率,gcov, lcov 工具
  • 只有 build-system 还是不够的,常常需要 Python编写一个 build 套装,熟悉并理解 Python
    • 如何用 Python 执行命令
    • 如何用 Python 做文件操作
    • 如何用 Python 做环境变量变更

前置依赖

理解 GCC 的 Warning 选项

GCC-Warning-Options
gcc警告选项汇总

  • -Werror: 把警告当作错误
    • 相反的是: -Wno-error
  • -Wfatal-errors: 警告当作错误,遇到第一个Warning编译器就当作出错退出
    • 相反的是: -Wno-fatal-errors
  • -Wall: 打开除了 extra 的所有警告
  • -Wextra: 打开 extra 的所有警告
  • -Werror=xxx: 将xxx警告视为错误

-Wall 打开的警告包含这些:

-Waddress
-Warray-bounds=1 (only with -O2)
-Warray-compare
-Warray-parameter=2 (C and Objective-C only)
-Wbool-compare
-Wbool-operation
-Wc++11-compat  -Wc++14-compat
-Wcatch-value (C++ and Objective-C++ only)
-Wchar-subscripts
-Wcomment
-Wdangling-pointer=2
-Wduplicate-decl-specifier (C and Objective-C only)
-Wenum-compare (in C/ObjC; this is on by default in C++)
-Wenum-int-mismatch (C and Objective-C only)
-Wformat
-Wformat-overflow
-Wformat-truncation
-Wint-in-bool-context
-Wimplicit (C and Objective-C only)
-Wimplicit-int (C and Objective-C only)
-Wimplicit-function-declaration (C and Objective-C only)
-Winit-self (only for C++)
-Wlogical-not-parentheses
-Wmain (only for C/ObjC and unless -ffreestanding)
-Wmaybe-uninitialized
-Wmemset-elt-size
-Wmemset-transposed-args
-Wmisleading-indentation (only for C/C++)
-Wmismatched-dealloc
-Wmismatched-new-delete (only for C/C++)
-Wmissing-attributes
-Wmissing-braces (only for C/ObjC)
-Wmultistatement-macros
-Wnarrowing (only for C++)
-Wnonnull
-Wnonnull-compare
-Wopenmp-simd
-Wparentheses
-Wpessimizing-move (only for C++)
-Wpointer-sign
-Wrange-loop-construct (only for C++)
-Wreorder
-Wrestrict
-Wreturn-type
-Wself-move (only for C++)
-Wsequence-point
-Wsign-compare (only in C++)
-Wsizeof-array-div
-Wsizeof-pointer-div
-Wsizeof-pointer-memaccess
-Wstrict-aliasing
-Wstrict-overflow=1
-Wswitch
-Wtautological-compare
-Wtrigraphs
-Wuninitialized
-Wunknown-pragmas
-Wunused-function
-Wunused-label
-Wunused-value
-Wunused-variable
-Wuse-after-free=2
-Wvla-parameter (C and Objective-C only)
-Wvolatile-register-var
-Wzero-length-bounds

GCC 编译和链接问题

收集C++构建系统(2023):

C++ 的构建系统,分为3层:Build-System, Meta-Build-System, Package-Manager

Makefile 的FAQ

阮一峰:Make 命令教程
makefiletutorial
Makefile 里执行 Shell 命令要点

  1. shell 脚本在 target 里执行才有效,其他地方都被忽略
  2. make 把每一行 Shell 脚本当作一个独立的单元,在单独的进程中运行,因此如果要想配置变量后执行,必须放在同一行。
  3. make 在调用 Shell 之前进行预处理,既展开所有的 Makefile 的变量和函数,这些变量和函数都以 $ 开头。
  4. make 预处理时,所有以 $ 开头都,都会被展开,因此 Shell 语句要引用自己的变量,就应该以 $$ 开头,此外 Shell 变量时不需要括号的,在.sh文件里用$xx,在Makefile里就得写成 $$xx

Makefile 常用字符串函数

  • wildcard 通配符查找文件
  • substr 替换字符串
  • patsubstr 通配符替换字符串
  • filter 从用空格分割的字符串列表里保留含有通配符的条目
  • filter-out 从用空格分割的字符串列表里过滤掉含有通配符的条目

CMake FAQ

成为 CMake 熟练工,孰能生巧。

  • 基本变量
  • 基本函数
    • add_subdirectory : 进来用子目录+子目录/CMakeLists.txt 来层次化的组织子模块和子模块构建
      • add_executable
      • add_library
      • include_directories
      • target_include_directories
      • link_libraries
      • target_link_libraries

配置的环境问题

  • 构建系统依赖的环境非常复杂
    • 操作系统的环境变量
    • docker 的环境变量
    • 命令行选项
    • 从配置文件里读取的配置
  • 构建系统对环境的使用也很复杂
    • 有些是依赖命令输入参数
    • 有些是依赖默认参数
    • 有些是依赖操作系统/docker的环境变量
  • 典型例子:
    • 一个构建系统,同时有 make/cmake/conan包管理,三者对变量的使用像意大利面条一样

Conan 包管理

  • 以conan包管理为例子,这些都会是构建系统的卡点
    • 是否要下载二进制包
    • 是否要远端构建后下载(远端算力问题,没算力要等待)
    • 拉源代码的耗时
    • 本地编译的耗时
  • conan commands 文档: https://docs.conan.io/1/reference/commands.html

构建时间成本

  • build 的时间 = compile的时间 + link的时间 + 除错的时间 + 测试执行的时间
    • compile 的时间 = O(源代码数量)
    • link 的时间 = O(每个模块export的符号数)
      • 因此要控制每个模块 export 的符号数,非必要不导出
    • 除错的时间=除代码错误的时间+除构建脚本错误的时间
    • 除构建脚本的时间 = 除Makefile错误的时间 + 除CMake错误的时间+除conan包配置错误的时间
    • 除CMake错误的时间 = include错误 + link 错误
      • CMake 基本变量,option, 函数不熟悉的时间
      • include 错误 = include_directories 错误 + target_include_directories 错误
      • link 错误 = link_libraries 错误 + target_link_libraries 错误
      • compile 错误 = compile options 错误 + compile flag 错误
        • compile flag 错误 = CMAKE_C_FLAGS 错误 + CMAKE_CXX_FLAGS 错误
    • 测试执行的时间 = 除测试依赖错误的时间 + 除测试耗时未知模块的时间
      • 除测试依赖错误的时间 = 除 LD_LIBRARY_PATH 缺失的时间 + 除数据依赖缺失的时间
  • make -j n 可以指定同时参与 build 的 cpu 核心数
    • 加钱可以解决的问题,就加钱,加机器配置
  • Chromium 的 Ninja 就是为了加快编译速度的动机搞出来的。

Chromium 项目的构建系统

单元测试 & 测试覆盖分析工具

  • google test
  • gcov
  • lcov
    • lcov-result-merger
      • 使用gpt轻松转换成了python版本
    • 写了一个python给lcov输出的多项目html子文件夹合成顶级index.html的代码, todo: 上链接

顶级软件的测试

收集C++静态/动态代码分析软件(2023)

程序崩溃分析

Python作为构建系统脚本的常见问题

  • 使用Python编写的时候,容易没有模块化设计,一堆散装脚本放在一起,没有统一的入口
    • 最佳实践:提供统一的main.py 入口,使用统一的路由命令行设计,例如,使用 pyouter: https://github.com/fanfeilong/pyouter
    • 每个子命令,应该是一个Action子模块
    • 可以有公共的 common 基础库
  • 对于工具链整合,很容易全部用 os.system, subprocess.popen, subprocess.run 执行系统命令来完成一系列子命令的组合执行
    • 核心问题是状态的控制是非常规的:
      • 命令的执行应该是同步的还是异步的
      • 命令执行的输出日志需要重定向到 stdout,stdpipe,还是日志文件
      • 命令的执行进度条是否要回显
      • 子命令执行的结果,错误码如何正确处理
      • 如何并发执行一组相同的可并行操作
    • 另外一个问题是碎片化导致的系统可观察性低,可诊断性差的问题,解决办法请看:Python执行命令的正确做法
  • 对于文件和文件夹的操作
    • Python本身也有对文件系统的直接操作的库,如何保持一致或者确定边界。
      • os.copy, os.rmdir, os.mkdir, os.makedirs
      • shutil.copy, shutil.copy2, shutil.copytree, shutil.copytree2, shutil.rmtree, shutil.tar
    • 使用 os.system("rm -rf xxx") 也能操作,优劣?
    • 典型模式:
      • 【生成/拷贝/移动】【文件/文件夹】前,判断是否已存在,做已存在处理,典型做法是删除

参考资料

posted @ 2023-08-24 14:42  ffl  阅读(140)  评论(0编辑  收藏  举报