使用 CMAKE 和 RPATH 捆绑 FFMPEG

发布于 2025-01-13 10:57:58 字数 5005 浏览 0 评论 0原文

正如标题所解释的,我正在尝试将 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.

调查:

  1. 按照 <一个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. 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.solibswresample.so的依赖),RUNPATH demo 没有被搜索,而是加载器回退到系统路径并且找不到库。
  2. 通过在 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:

  1. 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 the install(RUNTIME_DEPENDENCY_SET...) function. This does make sense because the generated cmake_install.cmake script in the build directory is moving the demo executable to the install directory and performing file(RPATH_CHANGE...) on the executable prior to doing the runtime dependency set analysis. According to the CMake documentation here and using readelf -d demo the RUNPATH of the executable is $ORIGIN/../deps and only case #1 from the docs apply here so the library isn't found at all.
  1. From the CMake documentation referenced in 1, there is a DIRECTORIES argument for the install(RUNTIME_DEPENDENCY_SET...) function that can be used as a search path. When combining the setup in 1, uncommenting this in CMakeLists.txt and rerunning all the CMake commands, the installation is successful (although as documented, CMake does emit warnings for all the dependencies found using DIRECTORIES). However, running the executable (i.e. ./install/bin/demo) is unsuccessful because the runtime loader cannot locate libswresample.so. Debugging into this a little further, I ran export LD_DEBUG=libs and tried to relaunch the executable. This shows that the runtime loader is finding libavcodec.so using the $ORIGIN/../deps RUNPATH from demo. However, when searching for the transitive dependency (i.e. libavcodec.so's dep. on libswresample.so), the RUNPATH of demo is NOT searched, instead the loader falls back to the system path and the library is not found.
  2. 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 CMake install(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 the install/deps folder with ldd, the libswresample.so and other libraries are pointing back to the vendor/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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

念三年u 2025-01-20 10:57:58

重现问题

为了尝试重现您的问题,我实际上放弃了所有 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 传入的参数及其变体。无论您如何努力,我认为它都不会允许您以这种方式传递 $ 符号。要传递未经清理的参数,您应该在调用 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 解释 命令,因此它们不会出现在生成的 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中看到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 仅需要两个 $ 符号(您必须查看 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

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 the ffmpeg build.

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

The output looked like this:

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

You can make this go away by adding -rpath-link...

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

but the executable fails with this error:

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

If you use -rpath instead of -rpath-link, you get this error instead:

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

This last error is because libavcodec.so references libwresample.so, and doesn't have it in its RPATH. main finds libavcodec.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 set LDFLAGS (and its variants) in the environment when you call configure. 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 to LDSOFLAGS. Let's start by passing it in straight:

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

Looking in src/build/ffbuild/config.mak we see the following line:

LDSOFLAGS=-Wl,-rpath,$ORIGIN

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:

LDSOFLAGS=-Wl,-rpath,''

If you put "real" single quotes inside the escaped ones, however...

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

it produces the correct line:

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

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 in src/ffbuild/library.mak that LDSOFLAGS is used inside of define RULES, which is then applied with the line $(eval $(RULES)). This will expand LDSOFLAGS 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 the eval as well as the expansion when the recipe is executed.

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

For completeness, let's also define LDEXEFLAGS, so that the ffmpeg executable works properly. Notice that LDEXEFLAGS only requires two $ signs (you would have to look in the makefiles to know this).

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

After building and installing, the last thing to do is to make sure main also has the correct RPATH:

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

And here's the result:

$ ./main
FFMPEG AVCODEC VERSION: 3871332
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文