Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using --whole-archive in CMake on a library you build

I have a CMake project while builds a static library and links it with other code into an executable. For reasons I won't go into, I want this linking to happen with the --whole-archive linker flag.

Now, this flag is tricky, since you can't just add it anywhere - you have to toggle it, then list the libraries for which you want it to apply, then untoggle it.

I've read somewhere (the URL escapes me) that if you have a pre-existing library, you can effectively add this linker flags by doing the following:

# Just an example, find_library calls should really be isolated to separate find modules
find_library(FOO_LIBRARY foo)
set(FOO_LIBRARY "-Wl,--whole-archive ${FOO_LIBRARY} -Wl,--no-whole-archive")

add_executable(hello main.c)
target_link_libraries(hello ${FOO_LIBRARY})

and that's fine. But what if it's a static library that you are building for which you don't have a pre-existing variable (i.e. something for which you have an add_library() CMake command)? Do you have to manually indicate its path instead of ${FOO_LIBRARY}? Or is there some other trick you can use to obtain the path CMake would put on the command-line for it?

Also, if I were to use some kind of ${FOO_LIBRARY}-like string instead of my static library target identifier - I believe CMake might miss the dependency, i.e. it might not re-link with the modified library (or not even build it) because the target_link_libraries command will see a strange string rather than the identifier of another target.

like image 391
einpoklum Avatar asked Apr 16 '17 12:04

einpoklum


1 Answers

This can be done using:

add_dependencies(the_exe the_static_lib)
set_target_properties(the_exe LINK_FLAGS
    "-L/path/to/the_static_lib -Wl,--whole-archive,-lthe_static_lib,--no-whole-archive")

The value of -L/path/to/the_static_lib follows transparently from what you specify the output directory of the target to be, or the default output directory of the target if you don't. E.g. if the_static_lib is just output in the build directory, then -L/path/to/the_static_lib would be -L.

Here is a project to illustrate in a reasonably general way, where we have structured subdirectories for sources and headers and distinct conventional output directories, bin, lib

$ ls -R
.:
app  build  CMakeLists.txt  foobar  inc

./app:
main.c

./build:

./foobar:
bar.c  foo.c

./inc:
foobar.h

With files:

app/main.c

#include <foobar.h>

int main(void)
{
    foo();
    bar();
    return 0;
}

foobar/foo.c

#include <foobar.h>
#include <stdio.h>

void foo(void)
{
    puts(__func__);
}

foobar/bar.c

#include <foobar.h>
#include <stdio.h>

void bar(void)
{
    puts(__func__);
}

inc/foobar.h

#ifndef FOOBAR_H
#define FOOBAR_H

void foo(void);
void bar(void);

#endif

CMakeLists.txt (1)

cmake_minimum_required(VERSION 3.0)
project(app)
include_directories(inc)
add_library(foobar STATIC foobar/foo.c foobar/bar.c)
set_target_properties(foobar
    PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
add_executable(app app/main.c)
add_dependencies(app foobar)
set_target_properties(app PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    LINK_FLAGS "-Llib -Wl,--whole-archive,-lfoobar,--no-whole-archive")

We want to make ./build/lib/libfoobar.a from ./foobar/foo.c and ./foobar/bar.c. And we want to make ./build/bin/app from ./app/main.c, linking the whole archive ./build/lib/libfoobar.a

Generate, build and run:

$ cd build; cmake ..; make; ./bin/app
-- The C compiler identification is GNU 8.2.0
-- The CXX compiler identification is GNU 8.2.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/imk/develop/so/cmake_prob/build
Scanning dependencies of target foobar
[ 20%] Building C object CMakeFiles/foobar.dir/foobar/foo.c.o
[ 40%] Building C object CMakeFiles/foobar.dir/foobar/bar.c.o
[ 60%] Linking C static library lib/libfoobar.a
[ 60%] Built target foobar
Scanning dependencies of target app
[ 80%] Building C object CMakeFiles/app.dir/app/main.c.o
[100%] Linking C executable bin/app
[100%] Built target app
foo
bar

Similarly if, e.g. instead of that CMakeLists.txt we have:

CMakeLists.txt (2)

cmake_minimum_required(VERSION 3.0)
project(app)
include_directories(inc)
add_subdirectory(foobar)
add_executable(app app/main.c)
add_dependencies(app foobar)
set_target_properties(app PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    LINK_FLAGS "-Llib -Wl,--whole-archive,-lfoobar,--no-whole-archive")

and separately:

foobar/CMakeLists.txt (1)

add_library(foobar STATIC foo.c bar.c)
set_target_properties(foobar
    PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")

And with the same separation, but default output directories, we'd have:

CMakeLists.txt (3)

cmake_minimum_required(VERSION 3.0)
project(app)
include_directories(inc)
add_subdirectory(foobar)
add_executable(app app/main.c)
add_dependencies(app foobar)
set_target_properties(app PROPERTIES
    LINK_FLAGS "-Lfoobar -Wl,--whole-archive,-lfoobar,--no-whole-archive")

foobar/CMakeLists.txt (2)

add_library(foobar STATIC foo.c bar.c)

which would go like:

$ cd build; cmake ..; make; ./app
-- The C compiler identification is GNU 8.2.0
-- The CXX compiler identification is GNU 8.2.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/imk/develop/so/cmake_prob/build
Scanning dependencies of target foobar
[ 20%] Building C object foobar/CMakeFiles/foobar.dir/foo.c.o
[ 40%] Building C object foobar/CMakeFiles/foobar.dir/bar.c.o
[ 60%] Linking C static library libfoobar.a
[ 60%] Built target foobar
Scanning dependencies of target app
[ 80%] Building C object CMakeFiles/app.dir/app/main.c.o
[100%] Linking C executable app
[100%] Built target app
foo
bar
like image 84
Mike Kinghan Avatar answered Oct 17 '22 19:10

Mike Kinghan