Lely CANopen 交叉构建说明:为什么执行 make install DESTDIR="${PWD}/stage",以及 lib/pkgconfig 的作用
@[toc]
结论
在 Lely CANopen / lely-core 的交叉编译流程中:
1 | make -j"$(nproc)" |
这两条命令不是重复动作。它们分别解决两个问题:
make:把源码编译、链接成目标平台可用的库和工具。make install DESTDIR="${PWD}/stage":把已构建产物按最终安装目录布局复制到 staging 目录,便于检查、打包、部署和给后续应用交叉编译使用。
本次构建还安装了 lib/pkgconfig/*.pc 文件。它们不是库本体,而是给 pkg-config 查询的构建元数据,用来自动输出依赖 Lely 库时需要的 -I、-L、-l 等编译和链接参数。
先看完整流程
1 | flowchart TD |
对应关系可以简化成:
| 阶段 | 命令 | 主要作用 | 输出位置 |
|---|---|---|---|
| SDK 环境 | source <YOCTO_SDK>/environment-setup-armv8a-poky-linux |
设置交叉编译器、sysroot、CFLAGS、LDFLAGS 等 | 当前 shell 环境 |
| 配置 | ../configure --host=... --prefix=... |
生成适合目标平台和功能裁剪的 Makefile | <BUILD_DIR> |
| 构建 | make -j"$(nproc)" |
编译对象文件并链接库/工具 | <BUILD_DIR>/src、<BUILD_DIR>/tools 等 |
| 安装到 staging | make install DESTDIR="${PWD}/stage" |
把构建产物复制成最终安装布局 | <LELY_STAGE><INSTALL_PREFIX> |
| 查询依赖参数 | pkg-config --cflags/--libs liblely-coapp |
给后续应用编译输出头文件路径和链接参数 | 标准输出 |
为什么要执行 make install DESTDIR="${PWD}/stage"
DESTDIR 是 staging 根目录,不是最终安装前缀
GNU Automake 文档把 DESTDIR 定义为 staged installation。包仍然按最终路径配置,例如 --prefix=/usr;执行 make install DESTDIR=/tmp/stage 时,安装动作会被重定向到 /tmp/stage/usr/...,便于检查安装结果、生成文件清单或打包部署。
本次流程可以抽象成:
1 | DESTDIR = <LELY_STAGE> |
例如:
1 | <LELY_STAGE><INSTALL_PREFIX>/include |
1 | flowchart LR |
这一步的价值是:
- 不把 AArch64 目标库安装到 x86_64 构建主机的系统目录。
- 可以直接检查最终会安装哪些文件。
- 可以把
<LELY_STAGE>打包进 SDK 或 rootfs 生成流程。 - 可以让后续应用把
<LELY_STAGE>当成 Lely 的临时 sysroot/staging root。 - 可以保留
--prefix的最终部署路径语义。
DESTDIR 和 prefix 不要混用
二者职责不同:
| 项目 | 作用 | 是否进入 .pc / .la 等安装元数据 |
典型值 |
|---|---|---|---|
--prefix |
最终安装前缀 | 是 | /usr、/usr/local、<INSTALL_PREFIX> |
DESTDIR |
临时安装根目录 | 否,通常只用于安装时重定向 | <BUILD_DIR>/stage |
如果目标板最终运行时也使用 <INSTALL_PREFIX>,当前配置是匹配的。
如果最终要部署到目标板的 /usr,更常见的配置应改成:
1 | ../configure \ |
这样安装树会是:
1 | stage/usr/include |
而不是:
1 | stage/<INSTALL_PREFIX>/include |
make 构建了什么
日志显示 make -j"$(nproc)" 进入了 doc、include、src、python、pkgconfig 和 tools 等目录。真正发生大量编译和链接的是 src 和 tools。
1 | graph TD |
主要库产物
| 构建产物 | 类型 | 作用 |
|---|---|---|
liblely-libc.la |
兼容层库 | 提供 C11、POSIX 以及部分 C++ 兼容能力,是多个 Lely 库的基础。 |
liblely-tap.la |
测试辅助库 | TAP 测试协议实现,主要服务测试套件。 |
liblely-util.la |
工具基础库 | 提供数据结构、错误处理、诊断、内存/文件缓冲等公共功能。 |
liblely-can.la |
CAN 基础库 | 提供 CAN 帧、CAN 网络对象和帧处理辅助接口。 |
liblely-co.la |
CANopen 协议栈库 | 提供对象字典、NMT、PDO、SDO、SYNC、EMCY、LSS 等 CANopen 服务。 |
liblely-io.la |
旧 I/O 库 | 旧版 I/O 抽象。Lely 文档中该库属于 deprecated 方向。 |
liblely-ev.la |
事件库 | 提供事件循环、future、task、strand、fiber executor 等异步基础设施。 |
liblely-io2.la |
新 I/O 库 | 提供异步 I/O,重点服务 timer、signal handler 和 CAN device。 |
liblely-coapp.la |
C++ CANopen 应用库 | C++ 层 CANopen 应用封装,后续 C++ master/slave 应用通常优先链接它。 |
主要工具产物
| 工具 | 作用 |
|---|---|
can2udp |
CAN 与 UDP 转发/隧道相关工具。 |
coctl |
CANopen control tool,可用于命令行控制 CANopen master/slave 过程。 |
cocat |
CANopen cat 工具。 |
cocatd |
CANopen cat daemon。 |
dcf2c |
把 EDS/DCF 转成 C 静态设备描述,适合不想在运行时解析 DCF 的场景。 |
日志中的关键字是什么意思
make all-recursive
1 | make all-recursive |
表示 Automake 生成的顶层 Makefile 正在递归进入子目录执行 all 目标。doc、include、pkgconfig 等目录里可能没有需要编译的内容,所以会看到:
1 | Nothing to be done for 'all'. |
这不是错误,只表示该目录在当前目标下没有新增工作。
CC / CXX / CCLD / CXXLD
| 日志前缀 | 含义 |
|---|---|
CC |
使用 C 编译器编译 C 源文件。 |
CXX |
使用 C++ 编译器编译 C++ 源文件。 |
CCLD |
使用 C 链接器驱动进行链接。 |
CXXLD |
使用 C++ 链接器驱动进行链接。 |
示例:
1 | CC liblely_co_la-nmt.lo |
含义是:先编译 CANopen NMT、PDO 等模块,再链接出 liblely-co.la 这个 libtool library。
.lo、.la、.so、.a 的区别
| 后缀 | 含义 | 是否最终部署重点 |
|---|---|---|
.lo |
libtool object,构建中间文件 | 否 |
.la |
libtool archive,描述库依赖、版本和安装路径的元数据 | 通常会安装,但目标运行时不直接加载它 |
.so.* |
动态库实体 | 是 |
.so |
开发链接用符号链接 | 开发包需要 |
.a |
静态库 | 静态链接或 SDK 场景需要 |
copying selected object files to avoid basename conflicts...
这通常出现在 io2 目录,因为不同子目录中可能存在同名源文件或对象文件。libtool/Makefile 为避免对象文件 basename 冲突,会复制选定对象文件。这不是错误。
libtool: warning: relinking ...
安装 libtool 库时,可能出现:
1 | libtool: warning: relinking 'liblely-xxx.la' |
含义是安装阶段根据最终 -rpath、依赖库路径和 staging 前缀重新链接库,使安装后的库记录正确的安装目录语义。GNU Libtool 文档说明,使用临时 staging area 时,libtool --mode=install 会根据安装目标路径和构建时的安装路径进行处理,必要时会触发 relink。
在这类交叉构建中,只要后续没有 error、No such file、undefined reference 或安装命令失败,单独的 relinking warning 通常不是失败信号。
libtool: warning: remember to run 'libtool --finish ...'
示例:
1 | libtool: warning: remember to run 'libtool --finish <INSTALL_PREFIX>/lib' |
含义是 libtool 提示最终安装到真实运行目录后,可能还需要让系统动态链接器识别库路径,例如更新动态链接器缓存或设置运行时库搜索路径。
在 staging 场景下,这条 warning 需要按部署方式判断:
| 部署方式 | 是否需要手动处理 |
|---|---|
打包进 rootfs 的标准库目录,如 /usr/lib |
通常由 rootfs/package 机制处理 |
部署到非标准目录,如 <INSTALL_PREFIX>/lib |
需要确保目标板运行时能找到 .so |
| 只用于交叉编译应用,不在构建主机运行目标程序 | 构建主机上通常不需要执行 libtool --finish |
目标板运行时可能需要:
1 | export LD_LIBRARY_PATH=<INSTALL_PREFIX>/lib:${LD_LIBRARY_PATH} |
或者把库路径加入目标系统的动态链接器配置。实际做法取决于 rootfs、发行版和部署策略。
make install DESTDIR 安装了什么
日志显示安装阶段主要复制了以下内容:
1 | graph TD |
目标安装树
1 | <LELY_STAGE><INSTALL_PREFIX>/ |
其中 lib/pkgconfig 是后续应用交叉编译时最容易被忽略、但实际很关键的一部分。
lib/pkgconfig 是什么
它保存的是 .pc 元数据文件
lib/pkgconfig 目录用于存放 pkg-config 的 .pc 文件。.pc 文件描述一个库的:
- 头文件目录。
- 库文件目录。
- 链接库名。
- 版本号。
- 依赖库关系。
- 静态链接时可能额外需要的依赖。
pkg-config 的核心用途就是从这些 .pc 文件中读取元数据,然后输出编译器和链接器需要的参数。
一个典型 .pc 文件结构如下:
1 | prefix=<INSTALL_PREFIX> |
实际字段以 lely-core 生成结果为准。这里重点是理解字段用途:
| 字段 | 作用 |
|---|---|
prefix |
最终安装前缀,来自 ../configure --prefix=...。 |
libdir |
库搜索目录,通常是 ${prefix}/lib。 |
includedir |
头文件搜索目录,通常是 ${prefix}/include。 |
Requires |
依赖的其他 pkg-config module。 |
Libs |
链接当前库需要的 -L 和 -l 参数。 |
Cflags |
编译时需要的 -I 等参数。 |
为什么 Lely 会安装多个 .pc 文件
日志显示安装了这些 .pc 文件:
1 | liblely-can.pc |
这是因为 lely-core 不是单个库,而是一组分层库。每个库都有自己的 .pc 文件,后续应用只需要声明最上层依赖,例如 liblely-coapp,pkg-config 会根据 .pc 中的依赖关系输出它需要的其他 Lely 库。
1 | flowchart TD |
pkg-config --cflags 和 pkg-config --libs 分别解决什么问题
| 命令 | 输出内容 | 用在编译/链接的哪一步 |
|---|---|---|
pkg-config --cflags liblely-coapp |
头文件路径等编译参数,例如 -I.../include |
编译 .c/.cpp 时 |
pkg-config --libs liblely-coapp |
库目录和库名,例如 -L.../lib -llely-coapp ... |
链接可执行程序时 |
pkg-config --libs --static liblely-coapp |
静态链接所需的更完整依赖 | 静态链接时 |
pkg-config --modversion liblely-coapp |
版本号 | 配置检查或诊断时 |
pkg-config --exists liblely-coapp |
只检查是否存在 | 脚本判断时 |
后续应用可以这样使用:
1 | ${CXX} main.cpp -o canopen_app \ |
如果是 Makefile:
1 | CXXFLAGS += $(shell pkg-config --cflags liblely-coapp) |
如果使用 Autotools,也可以通过 PKG_CHECK_MODULES 让配置阶段自动检查并填充 <PREFIX>_CFLAGS 和 <PREFIX>_LIBS。
加入本次 pkg-config 环境变量说明
你补充的操作可以脱敏整理为:
1 | source <YOCTO_SDK>/environment-setup-armv8a-poky-linux |
source environment-setup-armv8a-poky-linux 做什么
这一步加载 Yocto SDK 的交叉编译环境,通常会设置:
| 变量 | 常见作用 |
|---|---|
CC / CXX |
指向目标平台交叉编译器。 |
AR / RANLIB / STRIP |
指向目标平台 binutils 工具。 |
SDKTARGETSYSROOT |
指向 Yocto SDK 自带目标 sysroot。 |
CFLAGS / CXXFLAGS |
附带目标架构、sysroot、安全编译参数等。 |
LDFLAGS |
附带目标链接参数。 |
PKG_CONFIG_* |
有些 SDK 会设置默认 pkg-config 交叉环境。 |
加载 SDK 后再设置 Lely 相关 PKG_CONFIG_*,目的是让后续应用既能找到 Lely 自己的 .pc 文件,也能继续找到 Yocto sysroot 里的系统库 .pc 文件。
PKG_CONFIG_SYSROOT_DIR=${LELY_STAGE} 做什么
PKG_CONFIG_SYSROOT_DIR 用于交叉编译 sysroot 前缀处理。对 .pc 文件里的 -I${includedir}、-L${libdir} 这类路径,pkg-config 会把 sysroot 加到路径前面。
假设 liblely-coapp.pc 中写的是:
1 | prefix=<INSTALL_PREFIX> |
设置:
1 | export PKG_CONFIG_SYSROOT_DIR=<LELY_STAGE> |
查询结果就会变成:
1 | -I<LELY_STAGE><INSTALL_PREFIX>/include |
这正好指向 staging 目录中的目标头文件和目标库,而不是构建主机上的路径。
PKG_CONFIG_LIBDIR=... 做什么
PKG_CONFIG_LIBDIR 指定 .pc 文件搜索目录。交叉编译时更推荐明确设置它,而不是只追加 PKG_CONFIG_PATH,因为要避免 pkg-config 意外搜索到构建主机上的 x86_64 .pc 文件。
本次设置可以拆开看:
1 | PKG_CONFIG_LIBDIR= |
| 路径 | 作用 |
|---|---|
${LELY_STAGE}<INSTALL_PREFIX>/lib/pkgconfig |
找到本次 staged install 生成的 liblely-*.pc。 |
${SDKTARGETSYSROOT}/usr/lib/pkgconfig |
找到 Yocto 目标 sysroot 中库相关 .pc 文件。 |
${SDKTARGETSYSROOT}/usr/share/pkgconfig |
找到 Yocto 目标 sysroot 中架构无关 .pc 文件。 |
这样配置后,pkg-config 查询 liblely-coapp 时会优先从 Lely staging 目录读取:
1 | <LELY_STAGE><INSTALL_PREFIX>/lib/pkgconfig/liblely-coapp.pc |
再根据它的 Requires、Libs、Cflags 输出完整参数。
本次查询输出说明
脱敏后的查询输出是:
1 | pkg-config --cflags liblely-coapp |
逐项解释:
| 输出片段 | 含义 |
|---|---|
-I<LELY_STAGE><INSTALL_PREFIX>/include |
编译阶段搜索 Lely 头文件,例如 <lely/coapp/master.hpp>。 |
-L<LELY_STAGE><INSTALL_PREFIX>/lib |
链接阶段搜索 Lely 库文件目录。 |
-llely-coapp |
链接 C++ CANopen application library。 |
-llely-io2 |
链接新 I/O 异步接口库。 |
-llely-ev |
链接事件库。 |
-llely-co |
链接 CANopen 协议栈库。 |
-llely-can |
链接 CAN 基础库。 |
-llely-util |
链接公共工具库。 |
-llely-libc |
链接 Lely 兼容层库。 |
-lrt |
链接 POSIX realtime 库。某些平台/工具链上 timer、clock 等接口需要它。 |
这说明 lib/pkgconfig/liblely-coapp.pc 和它依赖的 .pc 文件已经能被当前 shell 中的 pkg-config 正确找到。
推荐增加的检查命令
为了确认没有误用主机 .pc 文件,可以执行:
1 | pkg-config --path liblely-coapp |
期望 --path 输出在:
1 | <LELY_STAGE><INSTALL_PREFIX>/lib/pkgconfig/liblely-coapp.pc |
如果输出跑到 /usr/lib/pkgconfig、/usr/share/pkgconfig 或主机其他路径,说明交叉编译环境有污染风险。
后续应用如何使用 staged Lely
编译一个依赖 liblely-coapp 的 C++ 应用
1 | source <YOCTO_SDK>/environment-setup-armv8a-poky-linux |
如果你的应用还依赖 Yocto sysroot 中的其他库,例如 libsocketcan、glib-2.0 或其他目标库,也应通过同一个交叉 pkg-config 环境查询,避免混入主机依赖。
CMake 项目示例
1 | find_package(PkgConfig REQUIRED) |
执行 CMake 前仍需要在 shell 中准备好 PKG_CONFIG_SYSROOT_DIR 和 PKG_CONFIG_LIBDIR。
安装结果应该如何检查
检查 staging 安装树
1 | find <LELY_STAGE><INSTALL_PREFIX> -maxdepth 3 -type f | sort |
重点检查:
1 | <LELY_STAGE><INSTALL_PREFIX>/include/lely/coapp/master.hpp |
检查库架构
1 | file <LELY_STAGE><INSTALL_PREFIX>/lib/liblely-coapp.so.* |
期望结果应显示目标架构,例如 AArch64,而不是构建主机架构。
检查 pkg-config 输出
1 | pkg-config --path liblely-coapp |
如果 --cflags 和 --libs 都指向 <LELY_STAGE><INSTALL_PREFIX>,说明 staged Lely 对后续应用编译是可见的。
常见问题
为什么 make install 阶段还会 relink
因为 libtool 需要在安装阶段确认库的最终 rpath、依赖关系和安装目录语义。使用 DESTDIR 时,它会把临时 staging 根和最终 prefix 区分开。日志出现 relink warning 并不等于失败,关键看最终是否安装出 .so、.a、.la 和符号链接。
为什么 pkg-config --libs liblely-coapp 输出了一串 Lely 库
因为 liblely-coapp 是高层 C++ 应用库,它依赖 io2、ev、co、can、util、libc 等底层库。pkg-config 会根据 .pc 中的依赖关系输出链接所需库,避免你手工维护顺序。
为什么使用 PKG_CONFIG_LIBDIR,而不是只设置 PKG_CONFIG_PATH
PKG_CONFIG_PATH 通常是追加搜索路径;交叉编译时,如果默认搜索路径仍包含构建主机的 /usr/lib/pkgconfig,就可能混入主机库参数。PKG_CONFIG_LIBDIR 用于指定基础搜索路径,更适合隔离目标 sysroot 和 staged dependency。
PKG_CONFIG_SYSROOT_DIR 会影响所有路径吗
它主要影响 .pc 输出中的编译/链接路径,例如 -I、-L 后面的路径。对 pkg-config --variable 查询出来的普通变量,不一定自动加 sysroot。因此交叉编译脚本中不要假设所有变量都会被 sysroot 化。
lib/pkgconfig 目录需要部署到目标板吗
如果目标板只运行程序,通常不需要 .pc 文件;目标板运行时需要的是 .so、配置文件和可执行文件。
如果目标板本身还要进行本机编译,或者你要把该目录作为 SDK 给其他工程使用,就应保留 lib/pkgconfig/*.pc。
推荐的最终命令模板
下面是脱敏后的推荐命令模板:
1 | # 1. 加载 Yocto SDK 交叉编译环境 |
关键判断标准
构建和安装是否成功,可以按下面几项判断:
| 检查项 | 期望结果 |
|---|---|
make 结束 |
没有 Error,生成 liblely-*.la 和工具。 |
make install DESTDIR=... 结束 |
staging 中出现 include、lib、lib/pkgconfig、bin、etc。 |
file liblely-coapp.so.* |
显示目标架构,例如 AArch64。 |
pkg-config --path liblely-coapp |
指向 <LELY_STAGE><INSTALL_PREFIX>/lib/pkgconfig/liblely-coapp.pc。 |
pkg-config --cflags liblely-coapp |
输出 -I<LELY_STAGE><INSTALL_PREFIX>/include。 |
pkg-config --libs liblely-coapp |
输出 -L<LELY_STAGE><INSTALL_PREFIX>/lib 和 Lely 依赖库。 |
总结
make 负责“把源码变成库和工具”;make install DESTDIR="${PWD}/stage" 负责“把库和工具整理成可部署目录树”。
对本次 Lely CANopen 交叉构建来说,DESTDIR 的主要意义是安全地形成 staging 安装树,而 lib/pkgconfig/*.pc 的主要意义是让后续应用可以通过 pkg-config 自动拿到正确的交叉编译参数。
你补充的:
1 | export PKG_CONFIG_SYSROOT_DIR=${LELY_STAGE} |
是把 staged Lely 接入后续应用交叉编译流程的关键步骤。只要 pkg-config --path liblely-coapp 指向 staging 里的 .pc 文件,并且 --cflags、--libs 输出指向 <LELY_STAGE><INSTALL_PREFIX>,后续应用就可以用这套 Lely 头文件和 AArch64 库进行交叉编译。
参考资料
- GNU Automake Manual, “Building Binary Packages Using DESTDIR”: https://www.gnu.org/software/automake/manual/html_node/DESTDIR.html
- GNU Libtool Manual, “Install mode”: https://www.gnu.org/software/libtool/manual/html_node/Install-mode.html
- GNU Libtool Manual, “Installing libraries”: https://www.gnu.org/software/libtool/manual/html_node/Installing-libraries.html
- Lely CANopen Documentation, “Installation”: https://opensource.lely.com/canopen/docs/installation/
- Lely CANopen Documentation, “Library overview”: https://opensource.lely.com/canopen/docs/overview/
- pkg-config Guide: https://people.freedesktop.org/~dbn/pkg-config-guide.html
- pkgconf manual page: https://man.archlinux.org/man/pkgconf.1.en
- Autotools Mythbuster, “Supporting Cross-Compilation”: https://autotools.info/pkgconfig/cross-compiling.html









