3.11.3. Static + shared — CGold 0.1 documentation (original) (raw)
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.
3.11.3.1. Right way¶
We will start with the right one. Command add_library should be used withoutSTATIC 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 belibfoo.so and libfoo.a, works fine for OSX since libraries names will belibfoo.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, effectivelyoverwriting 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:
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 fooexecutable:
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 librarybar 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.sobecause 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/MODULEonly if library designedto have no other types - Use no specifiers if library designed to be used as static or shared. Respect
BUILD_SHARED_LIBSvariable - Install static and shared libraries to separate directories