asherikov/ccws
GitHub: asherikov/ccws
一个基于 Make 和 Shell 的 ROS 开发环境,集成交叉编译、测试、静态检查、文档生成与 Debian 二进制包部署功能。
Stars: 18 | Forks: 2
- [简介](#introduction)
- [功能](#features)
- [构建 profiles](#build-profiles)
- [执行 profiles](#execution-profiles)
- [依赖](#dependencies)
- [用法](#usage)
- [初始设置](#initial-setup)
- [编译](#compilation)
- [运行](#running)
- [测试](#testing)
- [文档](#documentation)
- [Debian 包生成](#debian-package-generation)
- [交叉编译](#cross-compilation)
- [高级用法](#advanced-usage)
- [`CCWS` docker 镜像](#ccws-docker-image)
- [CI 中的 `CCWS`](#ccws-in-ci)
- [扩展 `CCWS`](#extending-ccws)
- [编程代理](#coding-agents)
- [已知问题](#known-issues)
- [相关软件](#related-software)
- [TODO](#todo)
- [书签(不再提供
支持)](#bookmarks-not-going-to-be-supported)
# 简介
`CCWS` 是一个用于 ROS 的开发环境,它集成了传统的 `catkin` 工作区和 CI pipeline 的功能,以便于(交叉)编译、测试、linting、文档生成和二进制包生成。它既可用作 CI/CD 的骨干,也可用作开发人员的工作环境。请注意,`CCWS` 并不打算成为一个完整的解决方案,而是作为开发特定供应商工作流的基础。`CCWS` 与 ROS 版本无关,在大多数情况下应该同时适用于 ROS1 和 ROS2。
## 功能
- 构建 profiles —— 一组用于构建过程的配置,例如 cmake 工具链、colcon 配置、环境变量等。各个 profiles 之间互不冲突,可以并行使用,而无需克隆单独的工作区和包。
- 执行 profiles —— 简单的 shell mixins,旨在修改运行时环境,例如在 `valgrind` 中执行节点、改变节点崩溃处理方式等。
- 通过构建 profiles 实现的许多功能:
- 针对几个常见平台的交叉编译。
- 使用 `doxygen` 为整个工作区或选定的包生成文档,类似于 。
- 使用 `clang-tidy` 和 `scan_build` 进行 Linting。
- 各种静态检查,如 所示,特别是:
- `cppcheck`
- `catkin_lint`
- `yamllint`
- `shellcheck`
- 二进制 Debian 包生成。
- 演示如何使用其中部分功能的包模板。
- 可以根据可用的 RAM 而不是 CPU 核心数来选择并行作业的数量,因为 RAM 很可能是限制因素。
- 完全基于 `make` 和 shell 脚本。所有脚本和配置都保存在工作区中,便于根据特定需求进行调整。
- 与 AI 编程代理(`qwen`)集成。
## 构建 profiles
Profile 配置位于 `ccws/profiles/build` 中,`common` 子目录包含默认参数,这些参数可以被特定的 profiles 覆盖:
- \[默认\] `reldebug` —— 默认编译器,cmake 构建类型为 `RelWithDebInfo`。
- `release` —— 默认编译器,cmake 构建类型为 `Release`,测试被禁用。
- `scan_build` —— 使用 `scan_build` 和 `clang-tidy` 通过 `clang` 进行编译,以进行静态检查。`clang-tidy` 参数在 cmake 工具链中定义,必须如包模板 `CMakeLists` 中所示在包中启用。
- `clang_tidy` —— 没有 `clang` 和 `scan_build` 的 `scan_build` 简化版本。
- `thread_sanitizer` —— 使用线程 sanitizer 进行编译。
- `addr_undef_sanitizers` —— 使用地址和未定义行为 sanitizer 进行编译。
- `static_checks` —— 静态检查器及其配置。
- `doxygen` —— doxygen 及其配置。
- `cross_raspberry_pi` —— 针对 Raspberry Pi 的交叉编译。
- `cross_arm64` —— 针对 arm64 的交叉编译(使用 docker 容器)。
- `clangd` —— 从另一个 profile 收集编译命令,并在工作区根目录生成 clangd 配置文件。
- `deb` —— Debian 包生成(见下文)。
旧版:
- `cross_jetson_xavier` —— 针对 Jetson Xavier 的交叉编译。
- `cross_jetson_nano` —— 针对 Jetson Nano 的交叉编译。
## 执行 profiles
执行 profiles 设置可在启动脚本中使用的环境变量,以更改运行时行为,如 `ccws/pkg_template/catkin/launch/bringup.launch` 所示,当前可用的 profiles 有:
- `common` —— 一组通用的 ROS 参数,例如 `ROS_HOME`,它会自动包含在二进制包中。
- `test` —— 设置 `CCWS_NODE_CRASH_ACTION` 变量,使遵循该变量的节点变为 `required`(必需的),即此类节点的终止将导致测试脚本崩溃,从而可以很容易地被检测到。
- `valgrind` —— 将 `CCWS_NODE_LAUNCH_PREFIX` 设置为 `valgrind`,并设置一些控制 `valgrind` 行为的变量。
- `core_pattern` —— 设置 core pattern 以将 core 文件保存在 artifacts 目录中。
- `address_sanitizer` —— `addr_undef_sanitizers` profile 的辅助工具。
执行 profiles 对构建过程没有影响,仅在 `*test*` 目标或 Debian 包中生效。在测试中始终使用 `test` 执行 profile,并且可以通过 `EXEC_PROFILE=" "` 提供额外的 profiles。这些目标使用位于 `CCWS` 根文件夹中的 `setup.bash` 脚本加载 profiles,该脚本也可手动使用,例如 `source setup.bash [ [ ...]]`。
请注意,设置脚本始终包含 `common` profile,并且如果未指定其他执行 profiles,则使用 `test` 执行 profile。
## 依赖
可以使用 `make bp_install_build BUILD_PROFILE=[,...]` 安装依赖,这将安装以下工具和特定于 profile 的依赖:
- `colcon`
- `yq` —— 依赖
- `cmake`
- `ccache` —— 可以在 cmake 工具链中禁用
- `wget`
# 用法
有关命令使用提示,请参见 `.ccws/test_main.mk`。
## 初始设置
- 通过将开发者和供应商特定的参数添加到 `make/config.mk` 来覆盖它们,可用参数可以在 `Makefile` 的顶部找到。
- 使用 `make bp_install_build BUILD_PROFILE=` 目标安装 profile 依赖,交叉编译 profiles 需要一些额外的步骤,如下所述。在某些极简环境中,您可能需要在使用 `bp_install_build` 目标之前运行 `./ccws/tools/bin/bootstrap.sh` 来安装 `make` 和其他实用工具。
- 将包克隆到 `src` 子目录中,或使用 `make new PKG=` 创建新包。
- 使用 `make dep_install` 安装包依赖。可以通过使用 `PKG` 参数提供包名和/或使用 `CCWS_DEP_TYPE` (build\|exec\|test) 提供依赖类型来选择性地安装依赖。
## 编译
- `make build PKG=""`,其中 `` 是一个或多个以空格分隔的包名。
- `make ` —— `make build` 的快捷方式,但 `` 可以是包名的子字符串。所有匹配该子字符串的包都将被构建。
- 可以使用 `JOBS=X` 参数覆盖作业数。
- `make build PKG= BUILD_PROFILE=scan_build` 覆盖默认 profile。
## 运行
- 执行 `source setup.bash ` 以便能够使用包。也可以直接使用由 `colcon` 生成的设置脚本,例如 `install//local_setup.sh`,但在这种情况下,某些 `CCWS` 功能将不可用。
## 测试
- `make test PKG=` 使用 `colcon` 进行测试,或使用 `make wstest` 测试所有包。
- `make ctest PKG=` 绕过 `colcon` 并直接运行 `ctest`,或使用 `make wsctest` 测试所有包。
## 文档
- `make BUILD_PROFILE=doxygen`, `firefox artifacts/doxygen/index.html`
- 查看示例:
## Debian 包生成
### 概述
`CCWS` 采用了一种不同寻常的二进制包生成方法,这是传统 ROS(1 个包 = 1 个 deb)和 docker 容器之间的折中方案:在工作区中构建的所有包都被打包在一起,形成一个单一的 Debian“超级包”。与 `bloom` 不同,`CCWS` 直接生成二进制包,而不是先生成源包。
二进制包生成实现为构建 profile mixin,可以覆盖在任意构建 profile 之上:
`make BUILD_PROFILE=deb,reldebug`。
`CCWS` 的方法具有许多优点:
- 与传统的 ROS 方法相比,二进制兼容性问题被最小化:
- 无需担心多个独立二进制包之间的兼容性并执行 ABI 检查;
- 如果包含了基础的 ROS 包,还可以避免同一 ROS 版本的不同同步版本之间的二进制不兼容性(实际上这种情况确实会发生)。
- 在处理 tags、版本、git submodules 等方面,与 ROS 相比,包仓库的管理可以更加宽松,例如,不需要为所有包维护发布仓库。
- Debian“超级包”比独立包和 docker 容器都更容易处理,例如,开发人员可以从他们的工作分支生成它们,并轻松地复制和安装到目标机器上。
- Debian 包通常比 docker 容器具有一些优势:
- 执行期间零开销。
- 直接访问硬件。
- 轻松安装系统服务、udev 规则、配置等。
- 如果使用不同的 `VERSION` 参数构建,可以同时安装多种变体的二进制“超级包”。请注意,这会更改安装路径,因此某些在 `cmake` 中表现得过于“聪明”的工作区包,如果之前已经构建过,可能需要清理构建目录。
### 构建包
通常,为了在 `catkin` `cmake` 文件中获取所有正确的路径并正确安装系统文件,在编译期间需要将包安装到文件系统根目录。`CCWS` 使用 `proot` 来避免这一点,类似于交叉编译 profiles。
## 交叉编译
这里 `` 代表 `cross_raspberry_pi`、`cross_arm64`。
交叉编译的 make 目标可以在 `ccws/make/cross.mk` 和 `ccws/profiles//targets.mk` 中找到。
关于旧版 `cross_jetson_xavier` 和 `cross_jetson_nano` 的注意事项:这些 profiles 需要 Ubuntu 18.04 / ROS melodic 并安装 `nvcc`,你可能需要在容器中执行此操作。
下面记录了常规的工作流程,有关更多技术细节,请参见 `ccws/doc/cross-compilation.md` 以及 `.ccws/test_cross.mk` (ROS1) 和 `.ccws/test_cross_ros2.mk` (ROS2) 中的 `CCWS` CI 测试:
1. 使用 `make bp_install_build BUILD_PROFILE=` 安装 profile 依赖
2. 获取系统镜像:
- `cross_raspberry_pi` —— `bp_install_build` 目标会自动下载标准镜像;
- `cross_arm64` —— 使用 `docker_mountpoint` `make` 目标,该目标从 docker 镜像中提取根文件系统;
- `cross_jetson_xavier`、`cross_jetson_nano` —— `CCWS` 不会自动获取这些镜像,您必须手动将系统分区镜像复制到 `ccws/profiles/cross_jetson_xavier/system.img`。
3. 初始化源码仓库:
- `make wsinit REPOS="https://github.com/asherikov/staticoma.git"`
- \[构建所有 ROS1 包时(不适用于 ROS2)\] 将所有包的 ROS1 依赖项添加到工作区 `make dep_to_repolist ROS_DISTRO=melodic`,或特定包 `make dep_to_repolist PKG= ROS_DISTRO=melodic`;
- 获取所有包 `make wsupdate`。
4. 将工作区中包的系统依赖项安装到系统镜像中:
`make cross_install PKG=staticoma BUILD_PROFILE= ROS_DISTRO=`
5. 编译包:
- 使用 `make cross_mount BUILD_PROFILE=` 挂载 sysroot
- 构建包,例如 `make staticoma BUILD_PROFILE=` 或构建并生成 deb 包 `make PKG=staticoma BUILD_PROFILE=deb,`
- 完成后使用 `make cross_umount BUILD_PROFILE=` 卸载 sysroot
# 高级用法
## `CCWS` docker 镜像
预装了 `CCWS` 及依赖的 docker 镜像用于测试,但建议使用 `ccws/examples/Dockerfile` 作为示例构建定制化的镜像。
该镜像可以通过以下方式使用:
- `docker pull asherikov/ccws:noble` (或 `asherikov/ccws:jammy`)
- `mkdir tmp_ws` \# sources, build, install, cache 将会放在这里
- `docker run --rm -ti -v ./tmp_ws:/ccws/workspace asherikov/ccws:noble bash`
- `make wsinit REPOS="https://github.com/asherikov/qpmad.git"`
- `...`
## CI 中的 `CCWS`
有关示例,请参见 `ccws/examples/Jenkinsfile.example`、`.github/workflows/reusable_*` 以及 。
## 扩展 `CCWS`
可以通过多种方式扩展 `CCWS` 功能:
- 通过添加新的构建 profiles,例如
`make bp_new BUILD_PROFILE=vendor_static_checks,static_checks`,所有以 `vendor` 前缀开头的 profiles 都会被 git 忽略;
- 通过添加执行 profiles;
- 可以通过创建 `ccws/profiles/build/vendor/.mk` 文件来添加 `make` 目标;
- 可以将通用的 `cmake` 工具链后缀添加到 `ccws/profiles/build/vendor/toolchain_suffix.cmake`。
## 编程代理
提供了与 的基本集成,可以通过两种方式使用:
- `make qwen`:使用源码空间作为代理工作区,运行原始的 `ghcr.io/qwenlm/qwen-code` docker 容器。
- `make qwen_ccws`:运行自定义构建容器,参见 `ccws/examples/Dockerfile.qwen`(docker hub 上的 `asherikov/ccws_qwen:noble`),它同时包含 `CCWS` 和 `qwen-code`,允许代理在执行命令时使用 `CCWS`。源码、构建、安装和其他目录都作为卷挂载。
在这两种情况下,`qwen` 配置都存储在源码空间的 `.ccws/qwen` 目录中。更多细节请参见 `ccws/make/ai.mk`。
# 已知问题
- 在 docker 容器内部进行交叉编译或生成 Debian 包期间发生段错误(两者都需要 `proot`):这可能是由于 Linux 的 `seccomp` 特性导致的,可以使用 `--security-opt seccomp:unconfined` docker 参数将其禁用。对于 `proot` 使用 `PROOT_NO_SECCOMP=1` 禁用 `seccomp` 似乎是不必要的。
- 在 Ubuntu 22 的 arm64 架构上进行构建时(例如构建 Debian 包时),`proot` 发生段错误。必须使用更新版本的 `proot`,参见 。
- 使用 sanitizers(`addr_undef_sanitizers` 或 `thread_sanitizer` 构建 profiles)编译的程序在执行时输出 `2: AddressSanitizer:DEADLYSIGNAL` 或 `FATAL: ThreadSanitizer: unexpected memory mapping`:原因在于现代 Linux 内核通过 ASLR(地址空间布局随机化)加强了内存安全性,参见 。可以通过设置 `sudo sysctl vm.mmap_rnd_bits=28` 解决该问题。
- 由于 cmake 的误用,部分 ROS2 核心包无法使用 `CCWS` 构建,例如参见 。
- 工作区前缀是故意从调试信息中的路径裁剪掉的,您必须在 gdb 中设置路径替换才能正确解析它们,即 `set substitute-path / `。
- 交叉编译可能需要在构建主机上安装工作区依赖。
# 相关软件
- —— ROS 特定的 CI 脚本,非交互式、“一次性”设计,无 sanitizers,模拟交叉编译。
- —— 针对 ROS 和 ROS2 的模拟交叉编译。
- —— 以不同方式完成的针对 Raspberry Pi 的交叉编译。
- —— 覆盖了部分 `CCWS` 功能的 `github` action。
- 、 —— ROS 打包基础设施的核心。较为复杂,专门用于处理单个包而不是工作区,不适合快速的现场重新部署。
- 提供了类似于“超级包”的功能,允许将安装空间打包成单个归档文件。很自然地,它并不提供所有包功能,如依赖、安装脚本等。此外,它不依赖类 chroot 环境来确保路径正确。
- —— 基于 Python 的环境,也旨在实现工作区处理自动化。与主要作为构建/部署环境的 `CCWS` 不同,它具有更多 ROS 特有的功能,如设置 bringup 包。但是,它缺少 `CCWS` 所具有的一些高级构建功能。
- —— 特定于 VSCode 的工作区环境,提供了各种构建和静态分析选项,但不如 `CCWS` 的分析功能多。
# TODO
- 测试
- 模糊测试 、、、。
- 添加代码覆盖率 profile。
- cmake 3.21:`--output-junit = Output test results to JUnit XML file.`
- 静态检查
-
- `scan_build` 的潜在替代品 ,具有额外的检查和缓存。
- 动态检查
- 带 的执行 profile,以便在收到信号时自动启动调试器。
- 作为 `valgrind` 执行 profile 的替代方案 —— 尽管在一般情况下有些大材小用。
- 构建性能
- 用 替换 `ccache`。
- 或 可用于缓存 `clang-tidy` 运行。
- 使用 缓存 cmake 检查, 可能也有用。
- 使用 clang 进行构建时间分析 和 / 或 。
- 使用 支持分布式编译可能会很有用。
- 构建
- 可重现构建 。
- 打包
- 探索生成调试和开发包,特别是剥离静态库和头文件。
- —— 通用二进制包生成器,`dpkg-deb` 的潜在替代品。
- 容器和镜像
- 作为 OCI artifacts 的系统镜像 。
- 以分层的、类似容器的方式生成系统镜像 。
- 其他
- 高级依赖图生成 。
- 现代文档生成器 。
- 更好地与 GitHub 集成,例如 。
## 书签(不再提供支持)
- Shell 格式化工具 。
- —— C++ 静态分析工具。
- —— catkin 包的 linter。
- —— 感知 ROS 的静态分析,在非 `catkin_make` 构建环境中可能会有问题。
- 控制符号可见性并使用 进行验证。
- 集成
- 源代码拼写检查 。
- 不会使用 ,但可以集成其中的一些 linter。
- 添加 `CodeQL` profile ()。
- 使用 或 代替 loop 设备,以避免使用 sudo。不过,在 Ubuntu 中存在一些问题,错误 759725,参见 。`guestfs` 太慢了,不实用。
- 似乎不是很有用。
- 多用途 linter 包装器。
- 可能对检测不必要的头文件有用,但看起来已经停滞了。
- 似乎已被废弃,linter 并不是很有帮助 —— 主要是格式化的东西。这同样适用于 。
| CI status |
|
|---|
标签:Bash脚本, Cutter, Debian打包, LNA, ROS, 交叉编译, 合规性检查, 开发环境, 请求拦截