使用 CMAKE 和 RPATH 捆绑 FFMPEG



正如标题所解释的那样,我正在尝试将FFMPEG与我的可执行应用程序捆绑在一起。

但是,我似乎无法弄清楚 FFMPEG 共享库的运行时加载。我不确定什么是不正确的:

  • RPATH/RUNPATH主项目和FFMPEG库。
  • 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")

主.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.

调查:

  1. 按照这篇文章的方向,我将可执行文件的RPATH/RUNPATH设置为$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. 从 1 中引用的 CMake 文档中,有一个可用作搜索路径的install(RUNTIME_DEPENDENCY_SET...)函数的DIRECTORIES参数。当将安装程序组合在 1 中,在CMakeLists.txt中取消注释并重新运行所有 CMake 命令时,安装成功(尽管如文档所述,CMake 确实会针对使用DIRECTORIES找到的所有依赖项发出警告)。但是,运行可执行文件(即./install/bin/demo) 不成功,因为运行时加载程序找不到libswresample.so。进一步调试,我运行export LD_DEBUG=libs并尝试重新启动可执行文件。这表明运行时加载程序正在使用demo中的$ORIGIN/../depsRUNPATH 查找libavcodec.so。但是,当搜索传递依赖关系(即libavcodec.solibswresample.so上 ),不搜索demoRUNPATH,而是加载器回退到系统路径,找不到库。
  2. 我能够通过在 FFMPEG 库上设置 RPATH 来满足 CMake 和加载程序,而不是将DIRECTORIES参数与 CMakeinstall(RUNTIME_DEPENDENCY_SET...)子命令一起使用。CMake 在没有任何警告的情况下运行,可执行文件按预期加载/运行。但是,这不是一个可行的解决方案,因为当使用ldd检查install/deps文件夹中的库时,libswresample.so和其他库会指向vendor/install目录,该目录在部署时不会出现在捆绑包中。

有谁知道标准方法是什么或我在上面缺少什么?我对任何和所有建议/工具持开放态度,但我这样做是为了学习什么是"标准实践",并希望将其扩展到跨平台解决方案(即 Windows/macOS)。因此,我想避免弄乱系统级加载程序配置(ldconfig)。

谢谢!

重现

问题

为了重现您的问题,我实际上丢弃了所有 CMake 内容,只是从命令行编译main.cpp。我想你会同意问题的根源在于ffmpeg构建。

g++ -I vendor/install/include -c -o main.o main.cpp
g++ -L vendor/install/lib -o main main.o -lavcodec

输出如下所示:

usr/bin/ld: warning: libswresample.so.4, needed by vendor/install/lib/libavcodec.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: warning: libavutil.so.57, needed by vendor/install/lib/libavcodec.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: vendor/install/lib/libavcodec.so: undefined reference to `av_opt_set_int@LIBAVUTIL_57'
/usr/bin/ld: vendor/install/lib/libavcodec.so: undefined reference to `av_get_picture_type_char@LIBAVUTIL_57'
# many more lines of undefined references

您可以通过添加-rpath-link来消除这种情况...

g++ -Wl,-rpath-link,vendor/install/lib -L vendor/install/lib -o main main.o -lavcodec

但可执行文件失败并显示此错误:

./main: error while loading shared libraries: libavcodec.so.59: cannot open shared object file: No such file or directory

如果使用-rpath而不是-rpath-link,则会收到此错误:

./main: error while loading shared libraries: libswresample.so.4: cannot open shared object file: No such file or directory

最后一个错误是因为libavcodec.so引用了libwresample.so,并且在其 RPATH 中没有它。main成功找到libavcodec.so,但随后动态链接器卡住了。

解决问题

据我所知,ffmpegconfigure脚本清理了通过--extra-ldflags及其变体传入的参数。无论你多么努力,我认为它都不会让你以这种方式通过$标志。要传入未经净化的参数,您应该在调用configure时在环境中设置LDFLAGS(及其变体)。最终目标是将字符串-Wl,-rpath,'$ORIGIN'到命令中创建每个库,这意味着我们希望将该值分配给LDSOFLAGS。让我们从直接传递它开始:

LDSOFLAGS=-Wl,-rpath,'$ORIGIN' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie

src/build/ffbuild/config.mak中,我们看到以下行:

LDSOFLAGS=-Wl,-rpath,$ORIGIN

在这种情况下,单引号由运行configure命令的 shell 解释,因此它们不会出现在生成的生成文件中。

但是,如果我们转义单引号,那么 shell 将扩展$ORIGIN环境变量(在本例中为空),并生成以下行:

LDSOFLAGS=-Wl,-rpath,''

但是,如果您将"真正的"单引号放在转义的引号中......

LDSOFLAGS=-Wl,-rpath,''$ORIGIN'' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie

它产生正确的行:

LDSOFLAGS=-Wl,-rpath,'$ORIGIN'

然而,现在存在一个问题,即make使用$符号。通常,我们可以通过将$符号替换为$$来转义它。但是,您可以在src/ffbuild/library.mak中看到LDSOFLAGSdefine RULES内部使用,然后与行$(eval $(RULES))一起应用。这将立即扩展LDSOFLAGS及其内部的任何变量,这意味着$$将在配方的最终版本中替换为单个$因此,我们需要使用四个$符号来生存eval中的扩展以及执行配方时的扩展。

LDSOFLAGS=-Wl,-rpath,''$$$$ORIGIN'' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie

为了完整起见,我们还定义LDEXEFLAGS,以便ffmpeg可执行文件正常工作。请注意,LDEXEFLAGS只需要两个$符号(您必须查看生成文件才能知道这一点)。

LDEXEFLAGS=-Wl,-rpath,''$$ORIGIN/../lib'' LDSOFLAGS=-Wl,-rpath,''$$$$ORIGIN'' ../configure --prefix=$INSTALL_PATH --enable-shared --disable-static --enable-pic --enable-lto --extra-cflags=-fPIC --extra-ldexeflags=-pie

构建和安装后,最后要做的是确保main也具有正确的RPATH

g++ -Wl,-rpath,'$ORIGIN/vendor/install/lib' -L vendor/install/lib -o main main.o -lavcodec

结果如下:

$ ./main
FFMPEG AVCODEC VERSION: 3871332

最新更新