3.11.3. Static + shared¶
Those users who has worked with autotools knows that it’s possible to build both static and shared libraries at one go. Here is an overview how it should be done in CMake.
Examples on GitHub
3.11.3.1. Right way¶
We will start with the right one. Command add_library should be used without
STATIC
or SHARED
specifier, type of the library will be determined by
value of BUILD_SHARED_LIBS variable (default type is static):
cmake_minimum_required(VERSION 3.4)
project(foo)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS YES CACHE BOOL "Export all symbols")
add_library(foo foo.cpp)
install(
TARGETS foo
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
Note
STATIC
/SHARED
/MODULE
specifiers should be used only in cases
when other type of library is by design not possible for any reasons.
That’s not our case of course since we are trying to build both variants,
hence library designed to be used as static or shared.
Libraries should be installed to separate directories. So there will be two builds and two root directories. Out of source will kindly help us:
> cd library-examples
[library-examples]> rm -rf _builds _install
[library-examples]> cmake -Hright-way -B_builds/shared -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="`pwd`/_install/configuration-A"
[library-examples]> cmake --build _builds/shared --target install
Scanning dependencies of target foo
[ 50%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[100%] Linking CXX shared library libfoo.so
[100%] Built target foo
Install the project...
-- Install configuration: ""
-- Installing: /.../library-examples/_install/configuration-A/lib/libfoo.so
[library-examples]> cmake -Hright-way -B_builds/static -DCMAKE_INSTALL_PREFIX="`pwd`/_install/configuration-B"
[library-examples]> cmake --build _builds/static --target install
Scanning dependencies of target foo
[ 50%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[100%] Linking CXX static library libfoo.a
[100%] Built target foo
Install the project...
-- Install configuration: ""
-- Installing: /.../library-examples/_install/configuration-B/lib/libfoo.a
3.11.3.1.1. Autotools two builds¶
Note that autotools do build library twice too under the hood, so performance is the same:
> mkdir temp
> cd temp
[temp]> wget http://www.x.org/releases/individual/lib/libpciaccess-0.13.4.tar.bz2
[temp]> tar xf libpciaccess-0.13.4.tar.bz2
[temp]> cd libpciaccess-0.13.4
[libpciaccess-0.13.4]> ./configure --enable-shared --enable-static
[libpciaccess-0.13.4]> make V=1
...
libtool: compile: gcc ... -c linux_devmem.c -fPIC -o .libs/linux_devmem.o
libtool: compile: gcc ... -c linux_devmem.c -o linux_devmem.o
3.11.3.2. Install to one directory¶
Another autotools feature is that both libraries will be installed to the one
directory. That’s works fine on Linux since libraries names will be
libfoo.so
and libfoo.a
, works fine for OSX since libraries names will be
libfoo.dylib
and libfoo.a
, but not for Windows. Static build will
produce foo.lib
:
> cd library-examples
[library-examples]> rmdir _builds _install /S /Q
[library-examples]> cmake -Hright-way -B_builds\static -G "Visual Studio 14 2015" -DCMAKE_INSTALL_PREFIX=%cd%\_install
[library-examples]> cmake --build _builds\static --config Release --target install
...
-- Install configuration: "Release"
-- Installing: C:/.../library-examples/_install/lib/foo.lib
But shared build will produce both foo.lib
and foo.dll
, effectively
overwriting static library and making it unusable:
[library-examples]> cmake -Hright-way -B_builds\shared -G "Visual Studio 14 2015" -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=%cd%\_install
[library-examples]> cmake --build _builds\shared --config Release --target install
...
-- Install configuration: "Release"
-- Installing: C:/.../library-examples/_install/lib/foo.lib
-- Installing: C:/.../library-examples/_install/bin/foo.dll
3.11.3.2.1. Configs¶
Even if libraries doesn’t conflict on file level their configs will conflict:
> cd library-examples
[library-examples]> rm -rf _install _builds
[library-examples]> cmake -Hbar -B_builds/shared -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="`pwd`/_install"
[library-examples]> cmake --build _builds/shared --target install
[library-examples]> grep lib/libbar.so -IR _install
_install/lib/cmake/bar/barTargets-release.cmake: IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libbar.so"
_install/lib/cmake/bar/barTargets-release.cmake:list(APPEND _IMPORT_CHECK_FILES_FOR_bar::bar "${_IMPORT_PREFIX}/lib/libbar.so" )
Config for static variant will have the same barTargets-release.cmake
name:
[library-examples]> cmake -Hbar -B_builds/static -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="`pwd`/_install"
[library-examples]> cmake --build _builds/static --target install
[library-examples]> grep lib/libbar.a -IR _install
_install/lib/cmake/bar/barTargets-release.cmake: IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libbar.a"
_install/lib/cmake/bar/barTargets-release.cmake:list(APPEND _IMPORT_CHECK_FILES_FOR_bar::bar "${_IMPORT_PREFIX}/lib/libbar.a" )
Now since configuration files for shared variant are overwritten there is
no way to load libbar.so
using find_package(bar CONFIG REQUIRED)
.
[library-examples]> grep lib/libbar.so -IR _install
[library-examples]> echo $?
1
3.11.3.3. Two targets¶
Problems with two versions of library described in previous section can be solved by using two different targets. This section cover building of two targets simultaneously. One target build at the time is equivalent to this code:
add_library(foo foo.cpp)
Even if names differs, e.g. by using option
:
option(FOO_STATIC_LIB "Build static library" ON)
if(FOO_STATIC_LIB)
add_library(foo_static STATIC foo.cpp)
else()
add_library(foo_shared SHARED foo.cpp)
endif()
Warning
This is logically equivalent to the add_library(foo foo.cpp)
+
BUILD_SHARED_LIBS
functionality so should not be used.
Use standard CMake features!
So assuming we have code like this:
# Don't do that!
add_library(foo_static STATIC foo.cpp)
add_library(foo_shared SHARED foo.cpp)
3.11.3.3.1. Philosophical¶
CMake code describe abstract configuration. User can choose how this abstraction used on practice. Let’s run this example on OSX:
cmake_minimum_required(VERSION 2.8)
project(foo)
add_library(foo foo.cpp)
add_executable(boo boo.cpp)
target_link_libraries(boo PUBLIC foo)
By default we will build executable and static library:
> cd library-examples
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hcustom -B_builds
[library-examples]> cmake --build _builds
[library-examples]> ls _builds/libfoo.a _builds/boo
_builds/libfoo.a
_builds/boo
But we are free to switch to shared library:
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hcustom -B_builds -DBUILD_SHARED_LIBS=ON
[library-examples]> cmake --build _builds
[library-examples]> ls _builds/libfoo.dylib _builds/boo
_builds/libfoo.dylib
_builds/boo
Create bundle:
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hcustom -B_builds -DCMAKE_MACOSX_BUNDLE=ON
[library-examples]> cmake --build _builds
[library-examples]> ls -d _builds/libfoo.a _builds/boo.app
_builds/libfoo.a
_builds/boo.app
Or do the both:
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hcustom -B_builds -DCMAKE_MACOSX_BUNDLE=ON -DBUILD_SHARED_LIBS=ON
[library-examples]> cmake --build _builds
[library-examples]> ls -d _builds/libfoo.dylib _builds/boo.app
_builds/libfoo.dylib
_builds/boo.app
Forcing any of this violates customization principle.
3.11.3.3.2. Non-default behavior¶
Let’s see how two targets approach will be used on user’s side:
# Top-level CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(foo)
add_subdirectory(boo) # 3rd party library
add_executable(foo foo.cpp)
target_link_libraries(foo PUBLIC boo)
Targets defined in directory boo
:
# boo/CMakeLists.txt
# Don't do that!
add_library(boo STATIC boo.cpp)
add_library(boo_shared SHARED boo.cpp)
User builds library and link by default static libboo.a
to foo
executable:
> cd library-examples
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hsurprise -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON
[library-examples]> cmake --build _builds
...
/usr/bin/c++ -o foo ... boo/libboo.a
User knows that there is BUILD_SHARED_LIBS
variable that change type of
library, so he expects shared in next configuration:
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hsurprise -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=ON
But of course he still got static because type of library is forced:
[library-examples]> cmake --build _builds
/usr/bin/c++ -o foo ... boo/libboo.a
3.11.3.3.3. Build time¶
Note that in previous example time of compilation of boo
library
is doubled. We are building boo.cpp
twice even if we are not
planning to use one of the variants:
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hsurprise -B_builds
[library-examples]> cmake --build _builds
Scanning dependencies of target boo
[ 16%] Building CXX object boo/CMakeFiles/boo.dir/boo.cpp.o
[ 33%] Linking CXX static library libboo.a
[ 33%] Built target boo
Scanning dependencies of target foo
[ 50%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[ 66%] Linking CXX executable foo
[ 66%] Built target foo
Scanning dependencies of target boo_shared
[ 83%] Building CXX object boo/CMakeFiles/boo_shared.dir/boo.cpp.o
[100%] Linking CXX shared library libboo_shared.so
[100%] Built target boo_shared
User of such library pays for something he doesn’t really need.
3.11.3.3.4. PIC conflicts¶
Assume we want to build everything statically but some part of out code force library to be shared:
cmake_minimum_required(VERSION 2.8)
project(use_bar)
find_package(bar CONFIG REQUIRED)
add_library(use_bar_static STATIC use_bar.cpp)
target_link_libraries(use_bar_static PUBLIC bar::bar)
add_library(use_bar_shared SHARED use_bar.cpp)
target_link_libraries(use_bar_shared PUBLIC bar::bar)
If bar
is static we will have problem with target use_bar_shared
which
in fact we don’t really interested in:
> cd library-examples
[library-examples]> rm -rf _builds _install
[library-examples]> cmake -Hbar -B_builds -DCMAKE_INSTALL_PREFIX="`pwd`/_install"
[library-examples]> cmake --build _builds --target install
[library-examples]> rm -rf _builds
[library-examples]> cmake -Huse_bar -B_builds -DCMAKE_PREFIX_PATH="`pwd`/_install"
[library-examples]> cmake --build _builds
Scanning dependencies of target use_bar_shared
[ 25%] Building CXX object CMakeFiles/use_bar_shared.dir/use_bar.cpp.o
[ 50%] Linking CXX shared library libuse_bar_shared.so
/usr/bin/ld: /.../library-examples/_install/lib/libbar.a(bar.cpp.o):
relocation R_X86_64_PC32 against symbol `_Z4bar1v' can not be used when
making a shared object; recompile with -fPIC
Note
Such issue can’t be solved by library usage requirements since library
bar
doesn’t know a priori if will it be linked to shared library or not.
3.11.3.3.5. Scalability¶
Two targets approach doesn’t scale. If we have add_library(foo foo.cpp)
we
can do control of such code:
add_library(foo foo.cpp)
add_executable(boo boo.cpp)
target_link_libraries(boo PUBLIC foo)
Using BUILD_SHARED_LIBS
:
ON
- executable linked with shared libraryOFF
- executable linked with static library
In this code:
add_library(foo_static STATIC foo.cpp)
add_library(foo_shared SHARED foo.cpp)
What should we do? Create two targets?
add_executable(boo_static boo.cpp)
target_link_libraries(boo_static PUBLIC foo_static)
add_executable(boo_shared boo.cpp)
target_link_libraries(boo_shared PUBLIC foo_shared)
What if there will be more dependencies?
add_library(foo_static STATIC foo.cpp)
add_library(foo_shared SHARED foo.cpp)
add_library(bar_static STATIC foo.cpp)
add_library(bar_shared SHARED foo.cpp)
# 1 - shared, 0 - static
add_executable(boo_0_0 boo.cpp)
add_executable(boo_0_1 boo.cpp)
add_executable(boo_1_0 boo.cpp)
add_executable(boo_1_1 boo.cpp)
target_link_libraries(boo_0_0 PUBLIC foo_static boo_static)
target_link_libraries(boo_0_1 PUBLIC foo_static boo_shared)
target_link_libraries(boo_1_0 PUBLIC foo_shared boo_static)
target_link_libraries(boo_1_1 PUBLIC foo_shared boo_shared)
3.11.3.3.6. Duplication¶
Additionally to scalability problems in previous example we have a risk
to have same code repeated twice for system with complex dependencies. Assume
we have library bar
in two variants simultaneously:
# bar/CMakeLists.txt
# Don't do that!
add_library(bar_static STATIC bar.cpp)
add_library(bar_shared SHARED bar.cpp)
And target baz
that for some reason decide that shared variant of linkage
is preferable:
# baz/CMakeLists.txt
add_library(baz SHARED baz.cpp)
target_link_libraries(baz PUBLIC bar_shared)
Our executable links to both libraries. Probably we don’t know/not interested
in fact that baz
use bar
too. We decide that static linkage is
preferable for any reason:
cmake_minimum_required(VERSION 2.8)
project(foo)
add_subdirectory(bar)
add_subdirectory(baz)
add_executable(foo foo.cpp)
target_link_libraries(foo PUBLIC bar_static baz)
Let’s build it:
[library-examples]> rm -rf _builds
[library-examples]> cmake -Hdup -B_builds
[library-examples]> cmake --build _builds
We are linked to the libbaz.so
and we do linked to libbar_shared.so
because it’s dependency of baz
:
> ldd _builds/foo
...
libbaz.so => /.../library-examples/_builds/baz/libbaz.so (0x00007f6d2f2a4000)
libbar_shared.so => /.../library-examples/_builds/bar/libbar_shared.so (0x00007f6d2e927000)
At the same time we have bar
linked statically:
> objdump -d _builds/foo | grep -A5 'barv.*:'
0000000000400c12 <_Z3barv>:
400c12: 55 push %rbp
400c13: 48 89 e5 mov %rsp,%rbp
400c16: b8 42 00 00 00 mov $0x42,%eax
400c1b: 5d pop %rbp
400c1c: c3 retq
So effectively code of function bar
present in our dependencies twice!
First time in executable and second time in linked shared library:
> objdump -d _builds/bar/libbar_shared.so | grep -A5 'barv.*:'
0000000000000610 <_Z3barv>:
610: 55 push %rbp
611: 48 89 e5 mov %rsp,%rbp
614: b8 42 00 00 00 mov $0x42,%eax
619: 5d pop %rbp
61a: c3 retq
3.11.3.4. Summary¶
Use
STATIC
/SHARED
/MODULE
only if library designed to have no other typesUse no specifiers if library designed to be used as static or shared. Respect
BUILD_SHARED_LIBS
variableInstall static and shared libraries to separate directories
CMake mailing list