Bundling FFMPEG using CMAKE and RPATH - c++

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

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

Related

libtool on windows requires absolute path, when I'm already giving it an absolute path

I'm trying to compile libjpeg on windows, which uses libtool as part of it's process.
Whenever i tried to make install, libtool would complain about absolute paths, despise the fact that I was giving it an absolute path
$ ../configure --enable-shared --enable-static --prefix D:/hernan/programacion/libjpeg/install
$ make install
(bunch of unrelated descriptions)
./libtool --mode=install /bin/install -c libjpeg.la D:/hernan/programacion/libjpeg/install/lib/libjpeg.la
libtool: install: `D:/hernan/programacion/libjpeg/install/lib' must be an absolute directory name
Try `libtool --help --mode=install' for more information.
I'm using mingw shell, since ./configure never works on cmd
Ok, so the solution for this is rather silly.
While the link and compile modes interpret correctly my path, the install mode that depends on libtool doesn't. You have to change the driver part of the prefix erasing the : and encasing it between /
So in my case it ended up being
$ ../configure --enable-static --enable-shared --prefix /d/hernan/programacion/libjpeg/install

How to integrate clang-tidy to CMake and GCC?

I want to integrate clang-tidy to our C and C++, CMake based project which is compiled using a custom GCC toolchain.
I've tried following this tutorial, setting CMAKE_CXX_CLANG_TIDY. I've also tried generating a compilation database by setting CMAKE_EXPORT_COMPILE_COMMANDS to ON and pointing run-clang-tidy.py to its directory.
In both cases, I've encountered (the same) few errors that are probably related to differences between Clang and GCC:
Some warning flags that are enabled in the CMake files are not supported in Clang but are supported in GCC (like -Wlogical-op). As the compiler is GCC, the file builds correctly, and the flag is written to the compilation database, but clang-tidy complains about it.
clang-tidy complains some defines and functions are unavailable, even though the code compiles just fine. As an example, the android-cloexec-open check suggested using O_CLOEXEC to improve security and force the closing of files, but trying to use this define leads to an undefined identifier error (even though our GCC compiles the code).
As an example to a function that is not found, there is clock_gettime.
Our code compiles with the C11 standard and C++14 standard, without GNU extensions:
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_EXTENSIONS OFF)
The custom toolchain is a cross-compilation toolchain which runs on Linux and compiles to FreeBSD.
Is there a way to disable the passing of some flags by CMake to clang-tidy? Am I using clang-tidy wrong?
I suspect this issue is related to disabling GNU extensions, using a cross-compilation toolchain, and some feature-test-macro which is not defined by default in Clang but is defined with GCC (e.g. _GNU_SOURCE/_POSIX_SOURCE). If this is the case, how can I check it? If not, should I use clang-tidy differently?
EDIT
As #pablo285 asked, here are 2 warnings I get for a single file, and then as I added --warnings-as-errors=*, the build stops:
error: unknown warning option '-Wlogical-op' ; did you mean '-Wlong-long'? [clang-diagnostic-error]
<file path>: error: use of undeclared identifier 'O_CLOEXEC' [clang-diagnostic-error]
O_WRONLY | O_CLOEXEC
^
I decided to write a python script that will replace clang-tidy, receive the commandline from CMake and edit it to fix various errors. Here are the modification to the commandline I tried:
Remove none clang compile flags
This helps with things like the first warning, as now I don't pass flags that clang doesn't know. It seems like I can't configure CMake to pass different set of flags to GCC and to clang-tidy, so if anyone is familiar with some solution to this problem, I'll be happy to hear!
I changed the include directories that are passed to clang-tidy
As mentioned in the post, I use a custom toolchain (which cross-compiles). I used this post and Python to extract the list of standard include directories, and added them to the flag list as a list of -isystem <dir>. I also added -nostdinc so that clang-tidy won't try to look on his own headers instead of mine
This helped with the issue above, as now various defines such as O_CLOEXEC is defined in the toolchain's headers, but as my toolchain is based on GCC, clang couldn't parse the <type_traits> header which includes calls to many compiler intrinsics
I'm not sure what's the best approach in this case
#shycha: Thanks for the tip, I'll try disabling this specific check and I'll edit this post again
Ok, I think that I have a solution. After a couple of evenings I was able to make it work.
In general I compile like this
rm -rf build
mkdir build
cd build
cmake -C ../cmake-scripts/clang-tidy-all.cmake .. && make
Where cmake-scripts directory contains:
clang-tidy-all.cmake
toolchain_arm_clang.cmake
The two important files are listed below.
But what is more important, is how you need to compile this.
First, toolchain_arm_clang.cmake is referenced directly from clang-tidy-all.cmake via set(CMAKE_TOOLCHAIN_FILE ...). It must be, however, referenced from the point of view of the building directory, so if you use multiple levels of build-dirs, e.g.: build/x86, build/arm, build/darwin, etc., then you must modify that path accordingly.
Second, the purpose of set(CONFIG_SCRIPT_PRELOADED ...) is to be sure that the config script was pre-loaded, i.e., cmake -C ../cmake-scripts/clang-tidy-all.cmake ..
Typically, you would want to have something like this somewhere in your CMakeLists.txt file:
message(STATUS "CONFIG_SCRIPT_PRELOADED: ${CONFIG_SCRIPT_PRELOADED}")
if(NOT CONFIG_SCRIPT_PRELOADED)
message(FATAL_ERROR "Run cmake -C /path/to/cmake.script to preload a config script!")
endif()
Third, there is /lib/ld-musl-armhf.so.1 hard-coded in set(CMAKE_LINKER_ARM_COMPAT_STATIC ...); on the development box that I use, it points to /lib/libc.so, so it might by OK to use /lib/libc.sh instead. I've never tried.
Fourth, using set(CMAKE_C_LINK_EXECUTABLE ...) and set(CMAKE_LINKER_ARM_COMPAT_STATIC ...) was because CMake was complaining about some linking problems during checking the compiler, i.e., before even running make.
Fifth, I was only compiling C++ code, so if you need to compile some C, then it might be required to also properly configure set(CMAKE_C_CREATE_SHARED_LIBRARY ...), but I have no idea whether there is such a config option.
General Advice
Do not integrate it immediately. First test some simple CMake project with one library (preferably a C++ one) and make it work, then add the second library, but in C, tweak it again. And only after that incorporate it into the code base.
Toolchain
I used a custom toolchain with GCC 8.3.0 and musl C library, so locations of some files might be different for other toolchains.
Custom CMake
Some variables, like (already mentioned) CONFIG_SCRIPT_PRELOADED, EXPORT_PACKAGE_TO_GLOBAL_REGISTRY, DO_NOT_BUILD_TESTS, or DO_NOT_BUILD_BENCHMARKS are not generic CMake options, i.e., I use them only in my CMakeLists.txt, so you can safely ignore them.
Variables that are unset at the end of each *.cmake file, e.g., build_test, extra_clang_tidy_unchecks_for_tests_only, don't need to be present in the project's main CMakeLists.txt.
Clang
$ clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git 4650b2f36949407ef25686440e3d65ac47709deb)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /opt/local/bin
Files
clang-tidy-all.cmake:
set(ALL_CXX_WARNING_FLAGS --all-warnings -Weverything -Wno-c++98-compat -Wno-c++98-c++11-compat -Wno-c++98-c++11-c++14-compat -Wno-padded -Wno-c++98-compat-pedantic)
set(CXX_COMPILE_OPTIONS "-std=c++17;-O3;${ALL_CXX_WARNING_FLAGS}" CACHE INTERNAL "description")
set(CMAKE_CROSSCOMPILING True)
set(CMAKE_TOOLCHAIN_FILE "../cmake-scripts/toolchain_arm_clang.cmake" CACHE FILEPATH "CMake toolchain file")
set(CONFIG_SCRIPT_PRELOADED true CACHE BOOL "Ensures that config script was preloaded")
set(build_test False)
if(build_test)
message(STATUS "Using test mode clang-tidy checks!")
set(extra_clang_tidy_unchecks_for_tests_only ",-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-special-member-functions")
endif()
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--enable-check-profile;--checks=-*,abseil-string-find-startswith,bugprone-*,cert-*,clang-analyzer-*,cppcoreguidelines-*,google-*,hicpp-*,llvm-*,misc-*,modernize-*,-modernize-use-trailing-return-type,performance-*,readability-*,-readability-static-definition-in-anonymous-namespace,-readability-simplify-boolean-expr,portability-*${extra_clang_tidy_unchecks_for_tests_only}" CACHE INTERNAL "clang-tidy")
message(STATUS "build_test: ${build_test}")
message(STATUS "extra_clang_tidy_unchecks_for_tests_only: ${extra_clang_tidy_unchecks_for_tests_only}")
message(STATUS "CMAKE_CXX_CLANG_TIDY: ${CMAKE_CXX_CLANG_TIDY}")
# We want to skip building tests when clang-tidy is run (it takes too much time and serves nothing)
if(DEFINED CMAKE_CXX_CLANG_TIDY AND NOT build_test)
set(DO_NOT_BUILD_TESTS true CACHE BOOL "Turns OFF building tests")
set(DO_NOT_BUILD_BENCHMARKS true CACHE BOOL "Turns OFF building benchmarks")
endif()
unset(build_test)
unset(extra_clang_tidy_unchecks_for_tests_only)
set(EXPORT_PACKAGE_TO_GLOBAL_REGISTRY "OFF" CACHE INTERNAL "We don't export clang-tidy-all version to global register")
toolchain_arm_clang.cmake:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 4.14.0)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(gcc_version 8.3.0)
set(x_tools "/opt/zynq/xtl")
set(CMAKE_C_COMPILER "clang" CACHE INTERNAL STRING)
set(CMAKE_CXX_COMPILER "clang++" CACHE INTERNAL STRING)
set(CMAKE_RANLIB "llvm-ranlib" CACHE INTERNAL STRING)
set(CMAKE_AR "llvm-ar" CACHE INTERNAL STRING)
set(CMAKE_AS "llvm-as" CACHE INTERNAL STRING)
set(CMAKE_LINKER "ld.lld" CACHE INTERNAL STRING)
execute_process(
COMMAND bash -c "dirname `whereis ${CMAKE_LINKER} | tr -s ' ' '\n' | grep ${CMAKE_LINKER}`"
OUTPUT_VARIABLE cmake_linker_dir
)
string(REGEX REPLACE "\n$" "" cmake_linker_dir "${cmake_linker_dir}")
set(cmake_linker_with_dir "${cmake_linker_dir}/${CMAKE_LINKER}" CACHE INTERNAL STRING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -iwithsysroot /include/c++/${gcc_version} -iwithsysroot /include/c++/${gcc_version}/arm-linux-musleabihf" CACHE INTERNAL STRING)
set(CMAKE_SYSROOT ${x_tools}/arm-linux-musleabihf)
set(CMAKE_FIND_ROOT_PATH ${x_tools}/arm-linux-musleabihf)
set(CMAKE_INSTALL_PREFIX ${x_tools}/arm-linux-musleabihf)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)
set(triple arm-linux-musleabihf)
set(CMAKE_LIBRARY_ARCHITECTURE ${triple})
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER_TARGET ${triple})
set(lib_path_arm ${x_tools}/arm-linux-musleabihf/lib)
## Bootstrap library stuff:
set(Scrt1_o ${lib_path_arm}/Scrt1.o)
set(crti_o ${lib_path_arm}/crti.o)
set(crtn_o ${lib_path_arm}/crtn.o)
set(lib_path_gcc ${x_tools}/lib/gcc/${triple}/${gcc_version})
set(crtbeginS_o ${lib_path_gcc}/crtbeginS.o)
set(crtendS_o ${lib_path_gcc}/crtendS.o)
# Clang as linker
# --no-pie disable position independent executable, which is required when building
# statically linked executables.
set(CMAKE_CXX_LINK_EXECUTABLE "clang++ --target=${triple} -Wl,--no-pie --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=${cmake_linker_with_dir} <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET> ")
set(CMAKE_CXX_CREATE_SHARED_LIBRARY "clang++ -Wl, --target=${triple} --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=${cmake_linker_with_dir} -shared <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET> ")
#
# Do not use CMAKE_CXX_CREATE_STATIC_LIBRARY -- it is created automatically
# by cmake using ar and ranlib
#
#set(CMAKE_CXX_CREATE_STATIC_LIBRARY "clang++ -Wl,--no-pie,--no-export-dynamic,-v -v --target=${triple} --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=ld.lld <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET> ")
## Linker as linker
set(CMAKE_LINKER_ARM_COMPAT_STATIC "-pie -EL -z relro -X --hash-style=gnu --eh-frame-hdr -m armelf_linux_eabi -dynamic-linker /lib/ld-musl-armhf.so.1 ${Scrt1_o} ${crti_o} ${crtbeginS_o} -lstdc++ -lm -lgcc_s -lgcc -lc ${crtendS_o} ${crtn_o}")
set(CMAKE_C_LINK_EXECUTABLE "${CMAKE_LINKER} ${CMAKE_LINKER_ARM_COMPAT_STATIC} <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET>")
# Debian bug 708744(?)
#include_directories("${CMAKE_SYSROOT}/usr/include/")
#include_directories("${CMAKE_SYSROOT}/usr/include/c++/${gcc_version}")
#include_directories("${CMAKE_SYSROOT}/usr/include/c++/${gcc_version}/${triple}")
## Clang workarounds:
set(toolchain_lib_dir_0 "${CMAKE_SYSROOT}/lib")
set(toolchain_lib_dir_1 "${CMAKE_SYSROOT}/../lib")
set(toolchain_lib_dir_2 "${CMAKE_SYSROOT}/../lib/gcc/${triple}/${gcc_version}")
set(CMAKE_TOOLCHAIN_LINK_FLAGS "-L${toolchain_lib_dir_0} -L${toolchain_lib_dir_1} -L${toolchain_lib_dir_2}")
## CMake workarounds
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "exe link flags")
set(CMAKE_MODULE_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "module link flags")
set(CMAKE_SHARED_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "shared link flags")
unset(cmake_linker_with_dir)
unset(cmake_linker_dir)
Maybe not exactly what you're looking for but I'm using this in CMakeLists.txt:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_custom_target(lint
COMMAND sh -c "run-clang-tidy -header-filter=.* -checks=`tr '\\n' , <${CMAKE_SOURCE_DIR}/checks.txt` >lint.out 2>lint.err"
COMMAND sh -c "grep warning: lint.out || true"
COMMAND ls -lh ${CMAKE_BINARY_DIR}/lint.out
VERBATIM
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
This creates a separate build target (make lint) for the clang-tidy check. clang-tidy takes a long time for my project so I don't want to run it during every build; make lint can be run manually if required, and it's also executed in a CI job after every push to the repo (in a way that makes the CI pipeline fail, blocking the merge, if there are any findings).
The output of make lint is the list of clang-tidy findings with as little context as possible. The full output, including context for findings, is in lint.out, and error messages are in lint.err, both of which I'm saving as CI artefacts.
checks.txt is a text file in the project root that defines which clang-tidy checks to activate, like so:
*
-altera-id-dependent-backward-branch
-altera-struct-pack-align
-altera-unroll-loops
-android-*
The first line enables all available checks, the other lines disable checks that I don't want.
Will only work in a Unix-like system of course.

Compiler error with yaml-cpp - undefined reference to `YAML::detail::node_data::convert_to_map`

Here's the complete log:
/tmp/ccCvErNZ.o: In function `YAML::detail::node& YAML::detail::node_data::get<std::string>(std::string const&, std::shared_ptr<YAML::detail::memory_holder>)':
cricket.cpp:(.text._ZN4YAML6detail9node_data3getISsEERNS0_4nodeERKT_St10shared_ptrINS0_13memory_holderEE[_ZN4YAML6detail9node_data3getISsEERNS0_4nodeERKT_St10shared_ptrINS0_13memory_holderEE]+0x94): undefined reference to `YAML::detail::node_data::convert_to_map(std::shared_ptr<YAML::detail::memory_holder>)'
collect2: error: ld returned 1 exit status
The code I'm trying to compile is simple
#include <iostream>
#include <yaml-cpp/yaml.h>
using namespace std;
int main() {
YAML::Node test = YAML::LoadFile("test.yaml");
if (test["date"]) {
cout << "HELLO";
}
return 0;
}
The YAML I'm using is the example from http://www.yaml.org/start.html
If I just try to load the YAML, it loads fine. But if I try to access any data I get the same error. So it's not a linking problem.
EDIT: I can do things like cout << test and cout << test.type() and other functions. I think the problem is in using a string based map for accessing internal nodes.
It seems that you are not properly linking the yaml-cpp library. Add the argument -lyaml-cpp when compiling. For example:
g++ -I/usr/local/include -L/usr/local/lib -lyaml-cpp -o test main.cpp
EDIT
Another option is to include this cmake to your CMakeLists.txt:
# attempt to find static library first if this is set
if(YAMLCPP_STATIC_LIBRARY)
set(YAMLCPP_STATIC libyaml-cpp.a)
endif()
# find the yaml-cpp include directory
find_path(YAMLCPP_INCLUDE_DIR yaml-cpp/yaml.h
PATH_SUFFIXES include
PATHS
~/Library/Frameworks/yaml-cpp/include/
/Library/Frameworks/yaml-cpp/include/
/usr/local/include/
/usr/include/
/sw/yaml-cpp/ # Fink
/opt/local/yaml-cpp/ # DarwinPorts
/opt/csw/yaml-cpp/ # Blastwave
/opt/yaml-cpp/
${YAMLCPP_DIR}/include/)
# find the yaml-cpp library
find_library(YAMLCPP_LIBRARY
NAMES ${YAMLCPP_STATIC} yaml-cpp
PATH_SUFFIXES lib64 lib
PATHS ~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw
/opt/local
/opt/csw
/opt
${YAMLCPP_DIR}/lib)
# handle the QUIETLY and REQUIRED arguments and set YAMLCPP_FOUND to TRUE if all listed variables are TRUE
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(YAMLCPP DEFAULT_MSG YAMLCPP_INCLUDE_DIR YAMLCPP_LIBRARY)
mark_as_advanced(YAMLCPP_INCLUDE_DIR YAMLCPP_LIBRARY)
I experienced something similar to this when trying to compile OpenXcom on a Debian system. It turned out that I was using a mix of testing and stable packages, the yaml package was from stable, and the combination somehow made linking fail if there were more than just a few initial functions.
If that is what you're experiencing (and you're using Debian too), try compiling a source package from stable with
apt-get --build source libyaml-cpp0.5/stable
This command will build .deb packages for libyaml, and you can then insert them with dpkg like this:
dpkg -i libyaml-cpp0.5*.deb
as root.
Compiling libyaml from source will make it link to the testing libraries you already have, instead of expecting stable libraries, and so should solve the problem above. It did for me, at least.
Even if you don't use Debian, compiling yaml-cpp from source (e.g. a tarball) might work for similar reasons.
Make sure these two files exist:
/usr/local/lib/libyaml-cpp.a
/usr/local/include/yaml-cpp/yaml.h
My CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 3.4)
FIND_PACKAGE(yaml-cpp REQUIRED)
ADD_EXECUTABLE(yaml_cpp_test yaml_cpp_test.cpp)
TARGET_LINK_LIBRARIES(yaml_cpp_test yaml-cpp)
The content of yaml_cpp_test.cpp is same as mentioned in the question.
I try to reappear the problem on vps (Ubuntu 14.04.1 LTS)
wget -c https://github.com/jbeder/yaml-cpp/archive/release-0.5.1.tar.gz
tar xvf release-0.5.1.tar.gz yaml-cpp-release-0.5.1/
cd yaml-cpp-release-0.5.1/
sudo apt-get install cmake
sudo apt-get install libboost-dev
cmake .
make
make install
after that, yaml-cpp install to /usr/local/lib and /usr/local/include
files in my working directory:
fqlgy#li407-86:~/yaml-cpp$ ll
total 12
-rw-r--r-- 1 fqlgy fqlgy 162 May 8 03:29 CMakeLists.txt
-rw-r--r-- 1 fqlgy fqlgy 10 May 8 03:11 test.yaml
-rw-r--r-- 1 fqlgy fqlgy 240 May 8 03:11 yaml_cpp_test.cpp
As i tried to run "cmake .", there is some error, so i delete the line CMakeFiles/CMakeOutput.log, the content of CMakeLists.txt is :
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
ADD_EXECUTABLE(yaml_cpp_test yaml_cpp_test.cpp)
TARGET_LINK_LIBRARIES(yaml_cpp_test yaml-cpp)
The following output is as expected:
fqlgy#li407-86:~/yaml-cpp$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/fqlgy/yaml-cpp
fqlgy#li407-86:~/yaml-cpp$ make
Scanning dependencies of target yaml_cpp_test
[100%] Building CXX object CMakeFiles/yaml_cpp_test.dir/yaml_cpp_test.cpp.o
Linking CXX executable yaml_cpp_test
[100%] Built target yaml_cpp_test
fqlgy#li407-86:~/yaml-cpp$ ./yaml_cpp_test
HELLO
I confirm that certain versions of yaml-cpp library contain this issue (and it is not about improper linking).
It is rather dirty, but I've resolved it by defining empty functions in my code, it looks like
YAML::BadConversion::~BadConversion()
{
}
void YAML::detail::node_data::convert_to_map(std::shared_ptr<YAML::detail::memory_holder>)
{
}
The approach is not perfect (e.g. because it causes duplicates if another library version is used).
In my case, I run the exact same code on two computer which only give error on one of them and that almost make me crazy. This is not a compilation error, linking error or something, I think the code is dirty or something else.
I tried all option:
regular building from source and installing using cmake .. => make => make install
same as 1, with CC=$(which gcc) CXX=$(which g++) cmake -DBUILD_SHARED_LIBS=ON ..
uninstalling the default package from apt (i use ubuntu 16.04)
all failed, until I find Ilya Golshtein's answer, then in my code which will use yaml-cpp I added this code jsut before YAML::LoadFIle Line
YAML::BadConversion::~BadConversion()
{
}
void YAML::detail::node_data::convert_to_map(std::shared_ptr<YAML::detail::memory_holder>)
{
}
This is the only solution that worked
I'm late to the party here and have no idea what I'm doing but for those of you on Windows, make sure your IDE likes your mingw-g++. Mine auto-defaulted to strawberry perl (?) and when I changed it to mingw-g++ everything started working. I suppose it has something to do with the versioning?
It bothered me, too. I met it when version of yaml-cpp was "0.7.0". I solved it by using "0.6.0" instead.

GCC & binutils build - C compiler cannot create executables

I'm trying to build gcc-5.3 and binutils-2.26. I've done it like this:
mkdir gcc; cd gcc
wget http://path/to/gcc-5.3.0.tar.bz2
wget http://path/to/binutils-2.26.tar.bz2
tar xf gcc-5.3.0.tar.bz2
tar xf binutils-2.26.tar.bz2
cd gcc-5.3.0
contrib/download_prerequisites
for file in ../binutils-2.26/*; do ln -s "${file}"; done
cd ..
mkdir build
mkdir dist
cd build
../gcc-5.3.0/configure --prefix=/home/teamcity/gcc/dist --disable-multilib --with-system-zlib --enable-languages=c,c++,fortran --program-suffix=-mine
make
This appears to build the first stage executables okay; prev-gas, prev-gcc, prev-ld are all present with plausible-looking executables in them. But the next stage fails:
Configuring stage 2 in ./intl
configure: loading cache ./config.cache
checking whether make sets $(MAKE)... yes
checking for a BSD-compatible install... /usr/bin/install -c
checking whether NLS is requested... yes
checking for msgfmt... /usr/bin/msgfmt
checking for gmsgfmt... /usr/bin/msgfmt
checking for xgettext... /usr/bin/xgettext
checking for msgmerge... /usr/bin/msgmerge
checking for x86_64-unknown-linux-gnu-gcc... /home/teamcity/gcc/build/./prev-gcc/xgcc -B/home/teamcity/gcc/build/./prev-gcc/ -B/home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/bin/ -B/home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/bin/ -B/home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/lib/ -isystem /home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/include -isystem /home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/sys-include -L/home/teamcity/gcc/build/./ld
checking for C compiler default output file name...
configure: error: in `/home/teamcity/gcc/build/intl':
configure: error: C compiler cannot create executables
See `config.log' for more details.
The relevant bit of config.log appears to be this:
configure:2978: checking for C compiler default output file name
configure:3000: /home/teamcity/gcc/build/./prev-gcc/xgcc -B/home/teamcity/gcc/build/./prev-gcc/ -B/home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/bin/ -B/home/teamcity/gcc/dist/x86_64-unkn
own-linux-gnu/bin/ -B/home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/lib/ -isystem /home/teamcity/gcc/dist/x86_64-unknown-linux-gnu/include -isystem /home/teamcity/gcc/dist/x86_64-unknown-l
inux-gnu/sys-include -L/home/teamcity/gcc/build/./ld -g -O2 -gtoggle -static-libstdc++ -static-libgcc conftest.c >&5
/home/teamcity/gcc/build/./prev-gcc/as: 106: exec: /home/teamcity/gcc/build/./gas/as-new: not found
This looks like prev-gcc's as is expecting to find gas/as-new, when actually it's prev-gas/as-new.
Is there some workaround for this? Is it safe to just ln -s prev-gas gas? I'd sort of expect that to cause problems later on. Is it not possible to build these two versions of gcc and binutils together?
This is my edited answer after trying it on my own on an Ubuntu 14.04 Azure VM.
The following approach worked for me:
Build and install binutils 2.26; for the purposes of this discussion,
let's say it's installed in /opt/gcc-5.3.0.
Configure gcc-5.3.0 in a build directory using /root/objdir/../gcc-5.3.0/configure --prefix=/opt/gcc-5.3.0
--enable-languages=c,c++ --disable-multilib --with-ld=/opt/gcc-5.3.0/bin/ld --with-as=/opt/gcc-5.3.0/bin/as assuming gcc-5.3.0 and the build directory, objdir, are at the same
level.
Do make followed by make install in the objdir build directory.
To verify that the ld used by the newly-built gcc is the one from the new binutils:
/opt/gcc-5.3.0/bin/gcc -print-prog-name=ld
The output should be, in this example:
/opt/gcc-5.3.0/bin/ld
Another test: rename the system ld, in my case /usr/bin/ld; the newly-built gcc should still work.
Of course, this applies to both ld and as.
Setting AS and LD environment variables to point to the newly-built binaries from the binutils package did not work: the -print-prog-name=... still showed default tools, and removing/renaming the default tools caused gcc to fail.
Thus the best way of accomplishing this is to build binutils first and then use the --with-ld and --with-as options to configure. If you also want to ensure that the newly-built binutils are used to build GCC, you may want to put them in the PATH before the system-provided tools, or you can even rename the system-provided tools to keep them out of the picture.
Thank you for checking with another mailing list to verify that building GCC and binutils together doesn't work unless they are pulled from the source control, I guess that option is not applicable when using downloaded tarballs. Overall this was an interesting exercise.

How can I set rpath on gcc binaries during bootstrap?

I am trying to build gcc 4.7.2 using a custom prefix $PREFIX
I have built and installed all the prerequisites into my prefix location, and then successfully configured, built and installed gcc.
The problem that I now have is that $PREFIX is not in the library search path, and therefore the shared libraries cannot be found.
$PREFIX/bin $ ./g++ ~/main.cpp
$PREFIX/libexec/gcc/x86_64-suse-linux/4.7.2/cc1plus: \
error while loading shared libraries: \
libcloog-isl.so.1: \
cannot open shared object file: No such file or directory
What works, but isn't ideal
If I export LD_LIBRARY_PATH=$PREFIX/lib then it works, but I'm looking for something which works without having to set environment variables.
If I use patchelf to set the RPATH on all the gcc binaries then it also works; however this involves searching out all elf binaries and iterating over them calling patchelf, I would rather have something more permanent.
What I think would be ideal for my purposes
So I'm hoping there is a way to have -Wl,-rpath,$PREFIX/lib passed to make during the build process.
Since I know the paths won't need to be changed this seems like the most robust solution, and can be also be used for when we build the next gcc version.
Is configuring the build process to hard code the RPATH possible?
What I have tried, but doesn't work
Setting LDFLAGS_FOR_TARGET prior to calling configure:
All of these fail:
export LDFLAGS_FOR_TARGET="-L$PREFIX/lib -R$PREFIX/lib"
export LDFLAGS_FOR_TARGET="-L$PREFIX/lib"
export LDFLAGS_FOR_TARGET="-L$PREFIX/lib -Wl,-rpath,$PREFIX/lib"
Setting LDFLAGS prior to calling configure:
export LDFLAGS="-L$PREFIX/lib -Wl,-rpath,$PREFIX/lib"
In any event I worry that these will override any of the LDFLAGS gcc would have had, so I'm not sure these are a viable option even if they could be made to work?
My configure line
For completeness here is the line I pass to configure:
./configure \
--prefix=$PREFIX \
--build=x86_64-suse-linux \
--with-pkgversion='SIG build 12/10/2012' \
--disable-multilib \
--enable-cloog-backend=isl \
--with-mpc=$PREFIX \
--with-mpfr=$PREFIX \
--with-gmp=$PREFIX \
--with-cloog=$PREFIX \
--with-ppl=$PREFIX \
--with-gxx-include-dir=$PREFIX/include/c++/4.7.2
I've found that copying the source directories for gmp, mpfr, mpc, isl, cloog, etc. into the top level gcc source directory (or using symbolic links with the same name) works everywhere. This is in fact the preferred way.
You need to copy (or link) to those source directory names without the version numbers for this to work.
The compilers do not need LD_LIBRARY_PATH (although running applications built with the compilers will need an LD_LIBRARY_PATH to the $PREFIX/lib64 or something like that - but that's different)
Start in a source directory where you'll keep all your sources.
In this source directory you have your gcc directory either by unpacking a tarball or svn...
I use subversion.
Also in this top level directory you have, say, the following source tarballs:
gmp-5.1.0.tar.bz2
mpfr-3.1.1.tar.bz2
mpc-1.0.1.tar.gz
isl-0.11.1.tar.bz2
cloog-0.18.0.tar.gz
I just download these and update to the latest tarballs periodically.
In script form:
# Either:
svn checkout svn://gcc.gnu.org/svn/gcc/trunk gcc_work
# Or:
bunzip -c gcc-4.8.0.tar.bz2 | tar -xvf -
mv gcc-4.8.0 gcc_work
# Uncompress sources.. (This will produce version numbered directories).
bunzip -c gmp-5.1.0.tar.bz2 | tar -xvf -
bunzip -c mpfr-3.1.1.tar.bz2 | tar -xvf -
gunzip -c mpc-1.0.1.tar.gz | tar -xvf -
bunzip -c isl-0.11.1.tar.bz2 | tar -xvf -
gunzip -c cloog-0.18.0.tar.gz | tar -xvf -
# Link outside source directories into the top level gcc directory.
cd gcc_work
ln -s ../gmp-5.1.0 gmp
ln -s ../mpfr-3.1.1 mpfr
ln -s ../mpc-1.0.1 mpc
ln -s ../isl-0.11.1 isl
ln -s ../cloog-0.18.0 cloog
# Get out of the gcc working directory and create a build directory. I call mine obj_work.
# I configure the gcc binary and other outputs to be bin_work in the top level directory. Your choice. But I have this:
# home/ed/projects
# home/ed/projects/gcc_work
# home/ed/projects/obj_work
# home/ed/projects/bin_work
# home/ed/projects/gmp-5.1.0
# home/ed/projects/mpfr-3.1.1
# home/ed/projects/mpc-1.0.1
# home/ed/projects/isl-0.11.1
# home/ed/projects/cloog-0.18.0
mkdir obj_work
cd obj_work
../gcc_work/configure --prefix=../bin_work <other options>
# Your <other options> shouldn't need to involve anything about gmp, mpfr, mpc, isl, cloog.
# The gcc build system will find the directories you linked,
# then configure and compile the needed libraries with the necessary flags and such.
# Good luck.
I've been using this configure option with gcc-4.8.0, on FreeBSD, after building and installing gmp, isl and cloog:
LD_LIBRARY_PATH=/path/to/isl/lib ./configure (lots of other options) \
--with-stage1-ldflags="-rpath /path/to/isl/lib -rpath /path/to/cloog/lib -rpath /path/to/gmp/lib"
and the resulting gcc binary does not need any LD_LIBRARY_PATH. The LD_LIBRARY_PATH for configure is needed because it compiles a test program to check for the ISL version, which would fail if it didn't find the ISL shared lib.
I tried it on Linux (Ubuntu) where it failed during configuring because the -rpath args were passed to gcc instead of ld. I could fix this by using
--with-stage1-ldflags="-Wl,-rpath,/path/to/isl/lib,-rpath,/path/to/cloog/lib,-rpath,/path/to/gmp/lib"
instead.
Just using configure --with-stage1-ldflags="-Wl,-rpath,/path/to/lib" was not enough for me to build gcc 4.9.2, bootstrap failed in stage 2. What works is to pass he flags directly to make via
make BOOT_LDFLAGS="-Wl,-rpath,/path/to/lib"
I got this from https://gcc.gnu.org/ml/gcc/2008-09/msg00214.html
While it still involves setting environment variables, what I do is that I define LD_RUN_PATH, which sets the rpath. That way the rest of the system can keep using the system provided libraries instead of using the ones that your gcc build generates.
I am going to make a suggestion that I believe solves your problem, although it definitely does not answer your question. Let's see how many downvotes I get.
Writing a generic wrapper script to set LD_LIBRARY_PATH and then to run the executable is easy; see https://stackoverflow.com/a/7101577/768469.
The idea is to pass something like --prefix=$PREFIX/install to configure, building an install tree that looks like this:
$PREFIX/
install/
lib/
libcloogXX.so
libgmpYY.so
...
bin/
gcc
emacs
...
bin/
.wrapper
gcc -> .wrapper
emacs -> .wrapper
.wrapper is a simple shell script:
#!/bin/sh
here="${0%/*}" # or use $(dirname "$0")
base="${0##*/}" # or use $(basename "$0")
libdir="$here"/../install/lib
if [ "$LD_LIBRARY_PATH"x = x ] ; then
LD_LIBRARY_PATH="$libdir"
else
LD_LIBRARY_PATH="$libdir":"$LD_LIBRARY_PATH"
fi
export LD_LIBRARY_PATH
exec "$here"/../install/bin/"$base" "$#"
This will forward all arguments correctly, handle spaces in arguments or directory names, and so forth. For practical purposes, it is indistinguishable from setting the rpath like you want.
Also, you can use this approach not only for gcc, but for your entire my-personal-$PREFIX tree. I do this all the time in environments where I want an up-to-date suite of GNU tools, but I do not have (or want to admit to have) root access.
Try to add your $PREFIX to /etc/ld.so.conf and then run ldconfig:
# echo $PREFIX >> /etc/ld.so.conf
# ldconfig
This will recreate cache that is used by runtime linker and it will pick up your libraries.
WARNING: This operation will cause ALL applications to use your newly compiled libraries in $PREFIX instead of default location