使用 CMAKE 和 RPATH 捆绑 FFMPEG

Bundling FFMPEG using CMAKE and RPATH

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

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

这是一个 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.

调查:

  1. 按照 post 的指示,我将可执行文件的 RPATH/RUNPATH 设置为 $ORIGIN/../deps 并且在 FFMPEG 库上没有 RPATH。
  1. 根据 1 中引用的 CMake 文档,install(RUNTIME_DEPENDENCY_SET...) 函数有一个 DIRECTORIES 参数可用作搜索路径。当结合 1 中的设置,在 CMakeLists.txt 中取消注释并重新 运行 所有 CMake 命令时,安装成功(尽管如文档所述,CMake 确实为使用 [=26= 找到的所有依赖项发出警告]).但是,运行 可执行文件(即 ./install/bin/demo)不成功,因为运行时加载程序无法找到 libswresample.so。进一步调试,我 运行 export LD_DEBUG=libs 并尝试重新启动可执行文件。这表明运行时加载程序正在使用来自 demo$ORIGIN/../deps RUNPATH 查找 libavcodec.so。然而,当搜索 t运行sitive 依赖时(即 libavcodec.solibswresample.so 的依赖),demoRUNPATH 不会被搜索,而是加载程序回退到系统路径,找不到库。
  2. 我能够通过在 FFMPEG 库上设置 RPATH 并且不使用带有 CMake install(RUNTIME_DEPENDENCY_SET...) sub-command 的 DIRECTORIES 参数来满足 CMake 和加载器的要求。 CMake 运行时没有任何警告,并且可执行文件 loads/runs 符合预期。但是,这不是一个可行的解决方案,因为当使用 ldd 检查 install/deps 文件夹中的库时,libswresample.so 和其他库指向 vendor/install 目录部署时不会出现在捆绑包中。

有谁知道标准方法是什么或者我在上面遗漏了什么?我对所有 suggestions/tools 持开放态度,但我这样做是为了尝试了解什么是“标准做法”,并希望将其扩展到 cross-platform 解决方案(即 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,但是动态链接器卡住了。

解决问题

据我所知,ffmpeg configure 脚本清理了通过 --extra-ldflags 传入的参数及其变体。无论您多么努力,我认为它都不会让您以这种方式传递 $ 标志。要传递 un-sanitized 参数,您应该在调用 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

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

但是,如果我们转义单引号,那么 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中看到LDSOFLAGS被用在define RULES的内部,然后应用到行$(eval $(RULES))。这将立即扩展 LDSOFLAGS 和其中的任何变量,这意味着 $$ 将在最终版本的配方中被替换为单个 $so,我们需要使用四个 $ 标志来在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 只需要两个 $ 符号(您必须查看 makefile 才能知道这一点)。

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