Public flags are not applied to libraries (cmake) - c

With Cmake, I would like to apply some general flags to both executable and libraries.
Well, I thought I could use target_compile_options by using PUBLIC keyword. I tested on a small example with an executable and a static library, both having only one file (main.c & mylib.c), but that does not work as expected.
The root CMakeLists.txt looks like this:
cmake_minimum_required(VERSION 3.0)
# Add the library
add_subdirectory(mylib)
# Create the executable
add_executable(mytest main.c)
# Link the library
target_link_libraries(mytest mylib)
# Add public flags
target_compile_options(mytest PUBLIC -Wall)
And the library's CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
add_library(mylib STATIC mylib.c)
The flag -Wall is applied only on the main.c and not on the library file (mylib.c):
[ 25%] Building C object mylib/CMakeFiles/mylib.dir/mylib.c.o
cd /patsux/Programmation/Repositories/test-cmake-public/build/mylib && /usr/lib/hardening-wrapper/bin/cc -o CMakeFiles/mylib.dir/mylib.c.o -c /patsux/Programmation/Repositories/test-cmake-public/mylib/mylib.c
[ 50%] Linking C static library libmylib.a
[ 25%] Building C object CMakeFiles/mytest.dir/main.c.o
/usr/lib/hardening-wrapper/bin/cc -Wall -o CMakeFiles/mytest.dir/main.c.o -c /patsux/Programmation/Repositories/test-cmake-public/main.c
Now, If flags are applied on the library instead of the executable, that work.
# Add public flags on the library
target_compile_options(mylib PUBLIC -Wall)
I get:
[ 25%] Building C object mylib/CMakeFiles/mylib.dir/mylib.c.o
cd /patsux/Programmation/Repositories/test-cmake-public/build/mylib && /usr/lib/hardening-wrapper/bin/cc -Wall -o CMakeFiles/mylib.dir/mylib.c.o -c /patsux/Programmation/Repositories/test-cmake-public/mylib/mylib.c
[ 50%] Linking C static library libmylib.a
[ 75%] Building C object CMakeFiles/mytest.dir/main.c.o
/usr/lib/hardening-wrapper/bin/cc -Wall -o CMakeFiles/mytest.dir/main.c.o -c /patsux/Programmation/Repositories/test-cmake-public/main.c
[100%] Linking C executable mytest
That makes no sense to set general flags such as the type of target on a library.
How can I share general flags properly ? I know I can use add_definitions(). Is it the right way ?
I also tested:
set_target_properties(mytest PROPERTIES COMPILE_FLAGS -Wall)
But flags are not public.

You can add the flag like this in your root file:
add_compile_options(-Wall)
Or, if you were using a cmake version prior to 3.0:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

Related

cmake: how to set rpath in a shared library with only target_link_directories (without target_link_libraries)?

The goal of my project is as follows:
From my main executable, I want to load a library (libfoo.so) that loads a second library (libbar.so).
I want to not specify either relative or absolute paths in any filename arguments that I pass to dlopen: i.e. I'd like my code to read "dlopen("libfoo.so", RTLD_LAZY)" and not "/path/to/libfoo.so" or "../to/libfoo.so".
My understanding is that the way shared libraries are looked up (by libdl) is either 1) the value of the environment variable LD_LIBRARY_PATH, 2) "embedding" RPATH in binaries, 3) certain standard directories known to libdl.
My project's directory structure is like so:
.
├── CMakeLists.txt
├── build # this directory exists to perform an "out-of-source" build with "cmake .."
├── libs
│   ├── CMakeLists.txt
│   ├── bar.c
│   └── foo.c
└── main.c
main.c can successfully do dlopen("libfoo.so", RTLD_LAZY)
I did this by adding a target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs) statement in the CMakeLists.txt that compiles main.c.
This seems to have the effect of adding RPATH in the main executable, as desired:
$ objdump -x ./main | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
But foo.c is unsuccessful doing dlopen("libbar.so", RTLD_LAZY) even though I've added a target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) statement in its CMakeLists.txt.
I notice that in spite of having added the target_link_directories statement, libfoo.so does not have a RPATH:
$ objdump -x ./libs/libfoo.so | grep PATH
$
Things I've tried
Seemingly, RPATH is not added to shared libraries unless there is at least one target_link_libraries statement -- even if it's an "unnecessary" library.
I.e. if I link libfoo.so with libbar.so, then libfoo.so has the desired RPATH:
# Linking libbar works, but I'd prefer not to do this:
target_link_libraries(foo bar)
...results in:
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
...also if I link an "unnecessary" shared lib along with the target_link_directories statement, then also, libfoo.so has the desired RPATH:
# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)
# Linking an unnecessary library, then doing target_link_directories also works:
target_link_libraries(foo dl)
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
Questions
Do I understand CMake's behavior correctly: target_link_directories statements only result in a corresponding RPATH entry in the shared library if there is at least one target_link_library statement (even for an "unnecessary" lib) for the shared lib?
If that is correct, can someone please explain the rationale?
Is there another, "cleaner" way to add a RPATH to a shared lib (preferably with target_link_directories) without any "unnecessary" statements (like target_link_library to unnecessary libraries)?
The code/files:
// main.c
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
void* handle = dlopen("libfoo.so", RTLD_LAZY);
if (!handle) {
printf("dlopen error: %s\n", dlerror());
return EXIT_FAILURE;
}
{
void (*fptr)() = dlsym(handle, "func");
if (fptr) { fptr(); }
}
dlclose(handle);
return EXIT_SUCCESS;
}
// libs/foo.c
#include <dlfcn.h>
#include <stdio.h>
void func() {
void* handle = dlopen("libbar.so", RTLD_LAZY);
printf("here in libfoo!\n");
if (!handle) {
printf("dlopen error: %s\n", dlerror());
return;
}
{
void (*fptr)() = dlsym(handle, "func");
if (fptr) { fptr(); }
}
dlclose(handle);
}
// libs/bar.c
#include <stdio.h>
void func() {
printf("here in libbar!\n");
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my_prj)
add_subdirectory(libs)
add_executable(main main.c)
target_link_libraries(main dl)
target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
# libs/CMakeLists.txt
add_library(bar SHARED bar.c)
add_library(foo SHARED foo.c)
# This is what I want, but it doesn't work:
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)
# Linking an unnecessary library, then doing target_link_directories also works:
# target_link_libraries(foo dl)
# target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
Here's a working build. You just need to adjust the BUILD_RPATH property appropriately and, if you write install() rules, adjust your INSTALL_RPATH to be similar to what I wrote in this answer. The following build is robust and adjusts the BUILD_RPATH:
cmake_minimum_required(VERSION 3.23)
project(test)
add_library(bar SHARED libs/bar.c)
add_library(foo MODULE libs/foo.c)
target_link_libraries(foo PRIVATE bar)
add_executable(main main.c)
target_link_libraries(main PRIVATE ${CMAKE_DL_LIBS})
set_property(TARGET main APPEND PROPERTY BUILD_RPATH "$<TARGET_FILE_DIR:foo>")
The last two lines are important. You must link to CMAKE_DL_LIBS to portably call dlopen and friends. The second line makes sure the directory containing libfoo, which you know main will load, is in the RPATH.
Here's the console output:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
$ cmake --build build/ --verbose
[1/6] /usr/bin/cc -Dbar_EXPORTS -O2 -g -DNDEBUG -fPIC -MD -MT CMakeFiles/bar.dir/libs/bar.c.o -MF CMakeFiles/bar.dir/libs/bar.c.o.d -o CMakeFiles/bar.dir/libs/bar.c.o -c /home/alex/test/libs/bar.c
[2/6] /usr/bin/cc -Dfoo_EXPORTS -O2 -g -DNDEBUG -fPIC -MD -MT CMakeFiles/foo.dir/libs/foo.c.o -MF CMakeFiles/foo.dir/libs/foo.c.o.d -o CMakeFiles/foo.dir/libs/foo.c.o -c /home/alex/test/libs/foo.c
[3/6] /usr/bin/cc -O2 -g -DNDEBUG -MD -MT CMakeFiles/main.dir/main.c.o -MF CMakeFiles/main.dir/main.c.o.d -o CMakeFiles/main.dir/main.c.o -c /home/alex/test/main.c
[4/6] : && /usr/bin/cc -fPIC -O2 -g -DNDEBUG -shared -Wl,-soname,libbar.so -o libbar.so CMakeFiles/bar.dir/libs/bar.c.o && :
[5/6] : && /usr/bin/cc -O2 -g -DNDEBUG CMakeFiles/main.dir/main.c.o -o main -Wl,-rpath,/home/alex/test/build -ldl && :
[6/6] : && /usr/bin/cc -fPIC -O2 -g -DNDEBUG -shared -o libfoo.so CMakeFiles/foo.dir/libs/foo.c.o -Wl,-rpath,/home/alex/test/build libbar.so && :
$ ./build/main
here in libfoo!
here in libbar!
Answering some follow-up questions from the comments:
[W]hat is $<TARGET_FILE_DIR:foo>? Is TARGET_FILE_DIR a CMake variable (visible in CMakeLists.txts)?
It is not a variable, it is a generator expression. The values of these expressions are determined after the whole configuration step has been executed. In this way, we can be sure that this expression will expand to the actual directory containing libfoo.so, not merely the one we expect will contain it.
In general, I prefer to use generator expressions rather than variables whenever I can. They tend to lend CMake programming a more declarative rather than imperative feel and have fewer edge cases. For example, a user might set the value of CMAKE_RUNTIME_OUTPUT_DIRECTORY to something unexpected. This breaks your build if you compute the RPATH from CMAKE_CURRENT_BINARY_DIR or something.
[C]an you speak to the difference between target_link_libraries(main dl) (my version) and target_link_libraries(main PRIVATE ${CMAKE_DL_LIBS}) (your version)?
There are two differences here, both important:
Using target_link_libraries without a visibility specifier puts it into a weird limbo state that is sort of like PRIVATE, maybe, depending on the policy settings and whether or not any other calls to target_link_libraries have a visibility specifier. Both for clarity and to avoid these pitfalls, you should always specify one of PRIVATE, INTERFACE, or PUBLIC.
Using CMAKE_DL_LIBS is the correct way to link to whichever library contains the dlopen family of library functions. Did you know that HP-UX uses -ldld? Or that AIX uses -lld? Or that BSDs (including macOS) don't have a separate library for that? Well, passing -ldl is broken for those platforms.
Using target_link_libraries without a visibility specifier or passing it raw link flags are both serious code smells in the modern (post CMake ~3.5) era. Try to avoid them and ask questions when you don't think you can.

Correctly setting linker flags using CMake

I have a project with C and ASM (AT&T) source files that needs a linker script. My CMakeLists.txt looks something like this:
cmake_minimum_required(VERSION 2.8.4)
project(proj C ASM-ATT)
file(GLOB SOURCE_FILES *.c *.S)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -Wall -g -fno-stack-protector -pedantic")
add_executable(proj ${SOURCE_FILES})
set_target_properties(proj PROPERTIES LINK_FLAGS "-T${proj_SOURCE_DIR}/link.ld -melf_i386")
Strangely enough, building with make VERBOSE=1 reveals the following:
[ 14%] Linking C executable proj
(...)
/usr/bin/cc -m32 -Wall -g -fno-stack-protector -pedantic -T/path/to/link.ld -melf_i386 (all object files)
cc: error: unrecognized command line option '-melf_i386'
It seems that CMake is trying to use /usr/bin/cc as a C linker. I have played around with this and tried several different options (including setting CMAKE_LINKER and CMAKE_EXE_LINK_OPTIONS).
Also note that CMakeCache.txt contains a line saying
CMAKE_LINKER:FILEPATH=/usr/bin/ld
so it obviously is aware of ld and is simply using the C compiler to link the executable.
Any help would be greatly appreciated!
CMake by default invokes the linker indirectly through the compiler executable. The template command for linking executables is set up in the variable CMAKE_LANG_LINK_EXECUTABLE.
To have the compiler pass the flags on to the linker, use -Wl upon setting LINK_FLAGS, i.e.:
set_target_properties(
proj PROPERTIES LINK_FLAGS "-Wl,-T${proj_SOURCE_DIR}/link.ld,-melf_i386")

How to link my C program to a static/dynamic library?

I'm trying to link my C program to both a static and dynamic library to see the difference. How do I do that?
I've made my own Makefile:
# ------ executable rule -----------
app : app.o
-gcc -g app.o -o app
# ------ intermeditate object files rule (.o) -------
app.o : main.c
-gcc -g -c main.c -o app.o
I've only shown you some of my Makefile as I think the rest is unnecessary.
I've tried to write -L. lstatic after -gcc -g app.o -o app but it didn't work.
Read about invoking GCC. Order of arguments to gcc matters a lot!
You could use -static or -Bstatic
You can also explicitly link with static libraries, by giving some /usr/lib/libfoo.a (or some appropriate file path) argument at link time.
You'll better improve your Makefile to use existing builtin rules (try make -p) and conventional variables, e.g. like here. Read the documentation of GNU make.

Statically linking C++ libraries is failed

I am interested in including C++ language in my c based program. I have two systems (Host and Target) which target system is limited, especially in memory. So, in my host machine, I have to statically link some libraries which will be needed by the program on target side. However, in my makefile I have been manipulating gcc patch (using a variety of gcc flags, during both compiling and linking steps).
Everything is ok until using C++ language;
For example, By including iostream header file in .cpp file, there exist an error on the target system showing that : "can not resolve symbol 'wctob' "
I must indicate my makefile as bellow:
in the compiling step:
$(CXX) -O0 -I<Headers dir> $(LIBSC) -Wno-write-strings -Wno-narrowing -Wno-return-type -Wno-abi -Wno-unused-variable -DNDEBUG -Wa, -c -fmessage-length=0 -I<Headers dir> -march=4kec -EL -o "$#" "$<"
in the linking step:
$(CXX) -lpthread -Wl,-Map,output.map $(MyFlags) $(LIBS) $(STRIP) -muclibc -march=4kec -EL -o
"output.elf" $(OBJS) $(LIBS) $(MyFlags) -Wl,-rpath -Wl,<LIBDIR> -L<LIBDIR>
------some definitions
MyFlags = -lc -static-libstdc++ -static-libgcc;(such flags used in the linking step)
LIBDIR = is the address of needed libraries
LIBSC = -nostdinc++ ;(such flags used in the compiling step)
Headers dir = is the address of needed both c and c++ header files included by the programmer.
LIBS and OBJS are also the list of needed libraries and objects.
Also, I added LIBDIR to ld.so.conf and LD_LIBRARY_PATH, but the problem is not solved far away. (My g++ version is 4.7.3.)
Can anybody help me out?
You have to tell C++ compiler to use C naming and symbols. so :
extern "C" {
// your cpp codes
}

CMake: Static and dynamic linking based on BUILD_TYPE

I am developing a small simulation software that depends on two libraries, the GSL and the libconfig. As a build system, I use CMake. For both the GSL and libconfig, I found cmake files and copied them into the cmake/ directory of my project.
The scenario is the following: I want the project to have several different build types, like debug, release, etc., but also a custom one called cluster, which adds -static to the GCC flags and links against the .a libraries of the GSL and the libconfig, which I assume exist.
My CMakeLists.txt looks like this so far:
# version
SET(PACKAGE_VERSION "1.0")
SET(PACKAGE_NAME "INTERFACE")
PROJECT(interface C CXX)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# dirs -----------------------------------------------------
SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
FIND_PACKAGE(GSL REQUIRED)
INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIRS})
SET(LIBS ${LIBS} ${GSL_LIBRARIES})
FIND_PACKAGE(LibConfig REQUIRED)
INCLUDE_DIRECTORIES(${LIBCONFIG_INCLUDE_DIRS})
SET(LIBS ${LIBS} ${LIBCONFIG_LIBRARIES})
CONFIGURE_FILE("res/config.h.in" "config.h")
INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR})
SET_DIRECTORY_PROPERTIES(PROPERTIES
ADDITIONAL_MAKE_CLEAN_FILES "config.h"
)
# compilation ----------------------------------------------
ADD_EXECUTABLE( interface
interface.c interface.h config.h
helpers.c
output.c
lattice.c
genetic.c
)
# optional CFLAGS
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -std=c99")
SET(CMAKE_C_FLAGS_RELEASE "-O3 -ffast-math")
SET(CMAKE_C_FLAGS_CLUSTER "-O3 -ffast-math -static")
SET(CMAKE_C_FLAGS_DEBUG "-g")
SET(CMAKE_C_FLAGS_PROFILE "-g -ffast-math -pg")
TARGET_LINK_LIBRARIES(interface m ${LIBS})
# installation --------------------------------------------
INSTALL(TARGETS interface DESTINATION bin)
This adds the -static to the compiler, when I use -DCMAKE_BUILD_TYPE=cluster. The thing is, that it still links against the .so versions of the libs, which causes gcc to throw errors. At least the FindLibConfig.cmake scripts sets both a LIBCONFIG_LIBRARY and a LIBCONFIG_STATIC_LIBRARY variable, which I could use.
What is the most elegant or smart way to reach my goal?
Acording to cmake documentation, cmake uses the variable BUILD_SHARED_LIBS to determine the default for add_library().
If you set to ON cmake will assume all add_library() call will be as
add_library(target SHARED lib1 lib2 ...)
For example something like -DBUILD_SHARED_LIBS=ON on the cmake command line may do what you are asking.
I solved it like this:
The User can specify an additional variable -DSTATIC_LINKING=TRUE. Then, the script looks like this. (Only the important parts for the static linking and compilation are shown!)
# determine, whether we want a static binary
SET(STATIC_LINKING FALSE CACHE BOOL "Build a static binary?")
# do we want static libraries?
# When STATIC_LINKING is TRUE, than cmake looks for libraries ending
# with .a. This is for linux only!
IF(STATIC_LINKING)
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
ENDIF(STATIC_LINKING)
# set -static, when STATIC_LINKING is TRUE and set LINK_SEARCH_END_STATIC
# to remove the additional -bdynamic from the linker line.
IF(STATIC_LINKING)
SET(CMAKE_EXE_LINKER_FLAGS "-static")
SET_TARGET_PROPERTIES(surface PROPERTIES
LINK_SEARCH_END_STATIC 1)
ENDIF(STATIC_LINKING)

Resources