概述
kind = "bin" 的 host tool 如果依赖一个 kind = "shared" 的包,构建出的可执行文件带着一条没人满足的 DT_NEEDED:mcpp 不会把那个 .so 放到工具旁边,子构建的 bin/ 里只有可执行文件本身。工具随后被 build.mcpp 调用时直接起不来。
实测环境:mcpp 2026.8.27.2。
复现
一个 host tool 包(freedesktop.wayland-scanner,mcpplibs/mcpp-index#290):
[targets.wayland-scanner]
kind = "bin"
main = "../../upstream/src/scanner.c"
[dependencies]
compat.expat = "2.7.1" # kind = "shared", soname = libexpat.so.1
另一个包用 tools = ["wayland-scanner"] 请求它,并在自己的 build.mcpp 里执行。
结果
Building host tool wayland-scanner:wayland-scanner from wayland-scanner v1.26.0
error: dependency 'wayland': build.mcpp exited with 1 (build aborted):
/home/runner/.mcpp/build-cache/v1/tool/freedesktop/wayland-scanner@1.26.0/…/bin/wayland-scanner:
error while loading shared libraries: libexpat.so.1: cannot open shared object file: No such file or directory
子构建的产物
$ ls <build-cache>/v1/tool/freedesktop/wayland-scanner@1.26.0/<hash>/bin/
wayland-scanner # 只有它
$ find <build-cache>/v1/tool/freedesktop/wayland-scanner@1.26.0/<hash> -name 'libexpat*'
(无)
工具确实链了它:
$ readelf -d wayland-scanner | grep -E 'NEEDED|RPATH'
(NEEDED) Shared library: [libexpat.so.1]
(NEEDED) Shared library: [libc.so.6]
(RPATH) Library rpath: [<store>/xim-x-glibc/2.44/lib64:<store>/xim-x-gcc/16.1.0/lib64:$ORIGIN:<registry>/subos/default/lib]
RPATH 里有 $ORIGIN,但那个目录里没有 .so;依赖包建出来的 libexpat.so.1 没有被搬过去。
更糟的一面:在开发机上它「成功」
RPATH 的最后一项是 <registry>/subos/default/lib。装了图形栈的机器上那里正好有一个 libexpat.so.1 —— 是 xim:expat(Mesa 的传递依赖)的那一份。于是工具照常启动,只不过链的是另一个库的另一个版本:
$ ld.so --list wayland-scanner | grep expat
libexpat.so.1 => <registry>/subos/default/lib/libexpat.so.1 # xim:expat 2.6.2,不是 compat.expat 2.7.1
所以:开发机绿、干净 runner 红,而且开发机上那次"成功"用的根本不是声明的那个依赖。这比直接失败更难发现。
影响
任何"自己构建、在 build.mcpp 里运行"的代码生成器都会踩到 —— 而这正是 host tool 存在的意义。目前只能靠让被依赖的包用 kind = "lib" 绕开(mcpplibs/mcpp-index#291 就是这么做的),但那把一个本该由消费场景决定的形态,变成了包作者必须提前替所有消费者做的决定:同一个包既要能被 host tool 静态链、又可能需要作为进程里唯一的 .so 被别人共享。
[build] dependency_linkage / per-dependency linkage(#519)看起来正是这一根轴,但它在 2026.8.27.2 之后才有,而索引的 floor 就是 2026.8.27.2。
建议
host tool 子构建把它的 shared 依赖产物 stage 到工具旁边($ORIGIN 已经在 RPATH 里,所以放过去就够),或者在 tool 的 RPATH 里指向那些 .so 的实际产出目录。
另外值得考虑:让 <registry>/subos/default/lib 不要出现在 host tool 的 RPATH 里,否则一个缺失的依赖会被 subos 里同名的库悄悄顶上 —— 上面那个「开发机成功」就是这么来的。
相关
概述
kind = "bin"的 host tool 如果依赖一个kind = "shared"的包,构建出的可执行文件带着一条没人满足的 DT_NEEDED:mcpp 不会把那个.so放到工具旁边,子构建的bin/里只有可执行文件本身。工具随后被build.mcpp调用时直接起不来。实测环境:mcpp
2026.8.27.2。复现
一个 host tool 包(
freedesktop.wayland-scanner,mcpplibs/mcpp-index#290):另一个包用
tools = ["wayland-scanner"]请求它,并在自己的build.mcpp里执行。结果
子构建的产物
工具确实链了它:
RPATH 里有
$ORIGIN,但那个目录里没有.so;依赖包建出来的libexpat.so.1没有被搬过去。更糟的一面:在开发机上它「成功」
RPATH 的最后一项是
<registry>/subos/default/lib。装了图形栈的机器上那里正好有一个libexpat.so.1—— 是xim:expat(Mesa 的传递依赖)的那一份。于是工具照常启动,只不过链的是另一个库的另一个版本:所以:开发机绿、干净 runner 红,而且开发机上那次"成功"用的根本不是声明的那个依赖。这比直接失败更难发现。
影响
任何"自己构建、在 build.mcpp 里运行"的代码生成器都会踩到 —— 而这正是 host tool 存在的意义。目前只能靠让被依赖的包用
kind = "lib"绕开(mcpplibs/mcpp-index#291 就是这么做的),但那把一个本该由消费场景决定的形态,变成了包作者必须提前替所有消费者做的决定:同一个包既要能被 host tool 静态链、又可能需要作为进程里唯一的.so被别人共享。[build] dependency_linkage/ per-dependencylinkage(#519)看起来正是这一根轴,但它在 2026.8.27.2 之后才有,而索引的 floor 就是 2026.8.27.2。建议
host tool 子构建把它的 shared 依赖产物 stage 到工具旁边(
$ORIGIN已经在 RPATH 里,所以放过去就够),或者在 tool 的 RPATH 里指向那些.so的实际产出目录。另外值得考虑:让
<registry>/subos/default/lib不要出现在 host tool 的 RPATH 里,否则一个缺失的依赖会被 subos 里同名的库悄悄顶上 —— 上面那个「开发机成功」就是这么来的。相关