使用 CMAKE 和 RPATH 捆绑 FFMPEG
正如标题所解释的,我正在尝试将 FFMPEG 与我的可执行应用程序捆绑在一起。
但是,我似乎无法弄清楚 FFMPEG 共享库的运行时加载。我不确定什么是不正确的:
- 主项目和 FFMPEG 库的
RPATH/RUNPATH
。 - CMake 安装步骤。
- 或者只是我的整个方法(最有可能)。
这是一个 CMake 项目,希望最终找到一个“标准解决方案”,我将包含重现此问题所需的所有内容。
CMakeLists.txt:
cmake_minimum_required(VERSION 3.21) # Required for the install(RUNTIME_DEPENDENCY_SET...) subcommand
project(cmake-demo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Setup CMAKE_PREFIX_PATH for `find_package`
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/vendor/install)
find_package(PkgConfig REQUIRED)
pkg_check_modules(ffmpeg REQUIRED IMPORTED_TARGET libavdevice libavfilter libavformat libavcodec
libswresample libswscale libavutil)
# Setup variable representing the vendor install directory
set(VENDOR_PATH ${CMAKE_CURRENT_LIST_DIR}/vendor/install)
# Set RPATH to the dependency install tree
set(CMAKE_INSTALL_RPATH $ORIGIN/../deps)
# Add our executable target
add_executable(demo main.cpp)
target_include_directories(demo PRIVATE ${VENDOR_PATH}/include)
target_link_libraries(demo PRIVATE PkgConfig::ffmpeg)
include(GNUInstallDirs)
install(TARGETS demo RUNTIME_DEPENDENCY_SET runtime_deps)
install(RUNTIME_DEPENDENCY_SET runtime_deps
DESTINATION ${CMAKE_INSTALL_PREFIX}/deps
# DIRECTORIES ${VENDOR_PATH}/lib
POST_EXCLUDE_REGEXES "^/lib" "^/usr" "^/bin")
main.cpp:
extern "C"{
#include <libavcodec/avcodec.h>
}
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "FFMPEG AVCODEC VERSION: " << avcodec_version() << std::endl;
return 0;
}
说明:
设置 FFMPEG(这可能需要一段时间...):
git clone https://github.com/FFmpeg/FFmpeg.git --recurse-submodules --shallow-submodules vendor/src/ffmpeg
git checkout release/5.0
export INSTALL_PATH=$PWD/vendor/install
cd vendor/src/ffmpeg
./configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
# WITH RPATH (HARDCODED TO VENDOR install directory)
# ./configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldflags=-Wl,-rpath,$INSTALL_PATH/lib --extra-ldexeflags=-pie
make -j16 V=1
make install
cd ../../..
CMake 命令:
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build -j16 #-v
cmake --install build
步骤之间的清理:
# Run from top-level dir of project
rm -rf build install
rm -rf vendor/install # ONLY if you need to modify the FFMPEG step.
调查:
- 按照 <一个href="https://stackoverflow.com/questions/38058041/ Correct-usage-of-rpath-relative-vs-absolute/38058216#38058216">这篇帖子我设置了
RPATH/RUNPATH< /code> 到
$ORIGIN/../deps
的可执行文件,并且 FFMPEG 库上没有 RPATH。
- 当我这样做时,cmake 安装步骤失败,抱怨
libavutil.so
无法在install(RUNTIME_DEPENDENCY_SET...)
函数中解析。这确实有意义,因为在build
目录中生成的cmake_install.cmake
脚本正在将演示可执行文件移动到安装目录并执行file(RPATH_CHANGE...)
在进行运行时依赖集分析之前对可执行文件进行分析。根据 CMake 文档此处并使用readelf -d demo 可执行文件的RUNPATH
是$ORIGIN/../deps
并且只有文档中的情况 #1 适用于此处,因此图书馆不是根本就没有找到。
- 从 1 中引用的 CMake 文档中,
install(RUNTIME_DEPENDENCY_SET...)
函数有一个可用作搜索路径的DIRECTORIES
参数。组合 1 中的设置时,在CMakeLists.txt
中取消注释并重新运行所有 CMake 命令,安装就会成功(尽管如文档所述,CMake 确实会针对使用DIRECTORIES 找到的所有依赖项发出警告) )。但是,运行可执行文件(即
./install/bin/demo
)会失败,因为运行时加载程序无法找到libswresample.so
。对此进行进一步调试,我运行了export LD_DEBUG=libs
并尝试重新启动可执行文件。这表明运行时加载程序正在使用demo
中的$ORIGIN/../deps
RUNPATH 查找libavcodec.so
。然而,当搜索传递依赖项时(即libavcodec.so
对libswresample.so
的依赖),的
没有被搜索,而是加载器回退到系统路径并且找不到库。RUNPATH
demo - 通过在 FFMPEG 库上设置 RPATH,而不是在 CMake
install(RUNTIME_DEPENDENCY_SET...)
子命令中使用DIRECTORIES
参数,我能够满足 CMake 和加载程序的要求。 CMake 运行时没有任何警告,并且可执行文件按预期加载/运行。但是,这不是一个可行的解决方案,因为使用 ldd 检查 install/deps 文件夹中的库时,libswresample.so 和其他库指向vendor/install
目录,该目录在部署时不会出现在捆绑包中。
有谁知道标准方法是什么或者我上面缺少什么?我对任何和所有建议/工具持开放态度,但我这样做是为了尝试了解什么是“标准实践”,并希望将其扩展到跨平台解决方案(即 Windows/macOS)。因此,我想避免弄乱系统级加载器配置(ldconfig
)。
谢谢!
As the title explains, I am trying to bundle FFMPEG with my executable application.
However, I can't seem to figure out the runtime loading of the FFMPEG shared libraries. I'm not sure what is incorrect:
RPATH/RUNPATH
of main project and FFMPEG libraries.- The CMake install steps.
- Or just my entire approach (most likely).
This is a CMake project and in hope of finally finding a "standard solution", I am going to include everything needed to reproduce this issue.
CMakeLists.txt:
cmake_minimum_required(VERSION 3.21) # Required for the install(RUNTIME_DEPENDENCY_SET...) subcommand
project(cmake-demo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Setup CMAKE_PREFIX_PATH for `find_package`
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/vendor/install)
find_package(PkgConfig REQUIRED)
pkg_check_modules(ffmpeg REQUIRED IMPORTED_TARGET libavdevice libavfilter libavformat libavcodec
libswresample libswscale libavutil)
# Setup variable representing the vendor install directory
set(VENDOR_PATH ${CMAKE_CURRENT_LIST_DIR}/vendor/install)
# Set RPATH to the dependency install tree
set(CMAKE_INSTALL_RPATH $ORIGIN/../deps)
# Add our executable target
add_executable(demo main.cpp)
target_include_directories(demo PRIVATE ${VENDOR_PATH}/include)
target_link_libraries(demo PRIVATE PkgConfig::ffmpeg)
include(GNUInstallDirs)
install(TARGETS demo RUNTIME_DEPENDENCY_SET runtime_deps)
install(RUNTIME_DEPENDENCY_SET runtime_deps
DESTINATION ${CMAKE_INSTALL_PREFIX}/deps
# DIRECTORIES ${VENDOR_PATH}/lib
POST_EXCLUDE_REGEXES "^/lib" "^/usr" "^/bin")
main.cpp:
extern "C"{
#include <libavcodec/avcodec.h>
}
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "FFMPEG AVCODEC VERSION: " << avcodec_version() << std::endl;
return 0;
}
Instructions:
Setup FFMPEG (this can take awhile...):
git clone https://github.com/FFmpeg/FFmpeg.git --recurse-submodules --shallow-submodules vendor/src/ffmpeg
git checkout release/5.0
export INSTALL_PATH=$PWD/vendor/install
cd vendor/src/ffmpeg
./configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie
# WITH RPATH (HARDCODED TO VENDOR install directory)
# ./configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldflags=-Wl,-rpath,$INSTALL_PATH/lib --extra-ldexeflags=-pie
make -j16 V=1
make install
cd ../../..
CMake Commands:
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build -j16 #-v
cmake --install build
Cleanup between steps:
# Run from top-level dir of project
rm -rf build install
rm -rf vendor/install # ONLY if you need to modify the FFMPEG step.
Investigation:
- Following the direction of this post I set the
RPATH/RUNPATH
of the executable to$ORIGIN/../deps
and no RPATH on the FFMPEG libraries.
- When I do this, the cmake install step fails complaining that
libavutil.so
cannot be resolved in theinstall(RUNTIME_DEPENDENCY_SET...)
function. This does make sense because the generatedcmake_install.cmake
script in thebuild
directory is moving the demo executable to the install directory and performingfile(RPATH_CHANGE...)
on the executable prior to doing the runtime dependency set analysis. According to the CMake documentation here and usingreadelf -d demo
theRUNPATH
of the executable is$ORIGIN/../deps
and only case #1 from the docs apply here so the library isn't found at all.
- From the CMake documentation referenced in 1, there is a
DIRECTORIES
argument for theinstall(RUNTIME_DEPENDENCY_SET...)
function that can be used as a search path. When combining the setup in 1, uncommenting this inCMakeLists.txt
and rerunning all the CMake commands, the installation is successful (although as documented, CMake does emit warnings for all the dependencies found usingDIRECTORIES
). However, running the executable (i.e../install/bin/demo
) is unsuccessful because the runtime loader cannot locatelibswresample.so
. Debugging into this a little further, I ranexport LD_DEBUG=libs
and tried to relaunch the executable. This shows that the runtime loader is findinglibavcodec.so
using the$ORIGIN/../deps
RUNPATH fromdemo
. However, when searching for the transitive dependency (i.e.libavcodec.so
's dep. onlibswresample.so
), theRUNPATH
ofdemo
is NOT searched, instead the loader falls back to the system path and the library is not found. - I was able to satisfy CMake and the loader by setting the RPATH on the FFMPEG libraries and not using the
DIRECTORIES
argument with the CMakeinstall(RUNTIME_DEPENDENCY_SET...)
sub-command. CMake runs without any warnings and the executable loads/runs as expected. However, this is NOT a viable solution because when inspecting the libraries in theinstall/deps
folder withldd
, thelibswresample.so
and other libraries are pointing back to thevendor/install
directory which is not going to be present in the bundle when deployed.
Does anyone know what the standard approach is or what I am missing above? I am open to any and all suggestions/tools but I am doing this in an attempt to learn what is "standard practice" and am hoping to extend this to a cross-platform solution (i.e Windows/macOS). As a result, I would like to avoid messing with the system level loader configuration (ldconfig
).
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
重现问题
为了尝试重现您的问题,我实际上放弃了所有 CMake 内容,只是从命令行编译了
main.cpp
。我想您会同意问题的根源在于 ffmpeg 构建。输出如下所示:
您可以通过添加
-rpath-link
... 来消除此问题...但可执行文件失败并出现以下错误:
如果您使用
-rpath
而不是-rpath-link
,您会收到此错误:最后一个错误是因为
libavcodec.so
引用了libwresample.so
,并且没有它在它的 RPATH 中。main
成功找到libavcodec.so
,但随后动态链接器卡住了。解决问题
据我所知,ffmpeg
configure
脚本会清理通过--extra-ldflags
传入的参数及其变体。无论您如何努力,我认为它都不会允许您以这种方式传递$
符号。要传递未经清理的参数,您应该在调用configure
时在环境中设置LDFLAGS
(及其变体)。最终目标是将字符串-Wl,-rpath,'$ORIGIN'
放入命令中以创建每个库,这意味着我们希望将该值分配给LDSOFLAGS
。让我们首先直接传递它:在 src/build/ffbuild/config.mak 中,我们看到以下行:
在本例中,单引号由运行
configure 的 shell 解释
命令,因此它们不会出现在生成的 makefile 中。但是,如果我们对单引号进行转义,那么 shell 将扩展 $ORIGIN 环境变量(在本例中为空),并生成以下行:
如果将“真正的”单引号放在转义的内部然而,
它会产生正确的行:
但是现在,存在
make
也 使用$
符号的问题。通常,我们可以通过用$$
替换它来转义$
符号。 但是,您可以在src/ffbuild/library.mak
中看到LDSOFLAGS
在define RULES
内部使用,然后将其与行$(eval $(RULES))
一起应用。这将立即扩展 LDSOFLAGS 及其内部的任何变量,这意味着在最终版本中$$
将被替换为单个$
配方,所以,我们需要使用四个$
符号才能在eval
中的扩展中生存下来执行配方时的扩展。为了完整起见,我们还定义 LDEXEFLAGS ,以便 ffmpeg 可执行文件正常工作。请注意,
LDEXEFLAGS
仅需要两个$
符号(您必须查看 makefile 才能知道这一点)。构建和安装后,最后要做的就是确保
main
也具有正确的RPATH
:结果如下:
Reproducing the Issue
To try to reproduce your problem, I actually discarded all CMake stuff and just compiled
main.cpp
from the command line. I think you'll agree that the root of the issue is in theffmpeg
build.The output looked like this:
You can make this go away by adding
-rpath-link
...but the executable fails with this error:
If you use
-rpath
instead of-rpath-link
, you get this error instead:This last error is because
libavcodec.so
referenceslibwresample.so
, and doesn't have it in its RPATH.main
findslibavcodec.so
successfully, but then the dynamic linker gets stuck.Solving the Issue
From what I can tell, the ffmpeg
configure
script sanitizes the arguments passed in via--extra-ldflags
and it's variants. No matter how hard you try, I don't think it will allow you to pass in a$
sign in this way. To pass in un-sanitized arguments, you should setLDFLAGS
(and its variants) in the environment when you callconfigure
. The end goal is to get the string-Wl,-rpath,'$ORIGIN'
into the command to create each library, which means we want that value assigned toLDSOFLAGS
. Let's start by passing it in straight:Looking in
src/build/ffbuild/config.mak
we see the following line:In this case, the single quotes were interpreted by the shell running the
configure
command, so they don't appear in the generated makefile.If we escape the single quotes, however, then the shell will expand the
$ORIGIN
environment variable (empty, in this case), and produce this line:If you put "real" single quotes inside the escaped ones, however...
it produces the correct line:
Now, however, there is the problem that
make
also uses$
signs. Typically we could escape the$
sign by replacing it with$$
. However, you can see insrc/ffbuild/library.mak
thatLDSOFLAGS
is used inside ofdefine RULES
, which is then applied with the line$(eval $(RULES))
. This will expandLDSOFLAGS
and any variables inside of it immediately, which means the$$
will be replaced with a single$
in the final version of the recipe, so, we need to use four$
signs to survive the expansion in theeval
as well as the expansion when the recipe is executed.For completeness, let's also define
LDEXEFLAGS
, so that theffmpeg
executable works properly. Notice thatLDEXEFLAGS
only requires two$
signs (you would have to look in the makefiles to know this).After building and installing, the last thing to do is to make sure
main
also has the correctRPATH
:And here's the result: