Before switching to CMake we were able to distribute tarballs containing our library as both a static library and a shared library (at least on Linux and macOS). What is the preferred way to do this with CMake 3.16+?
(Check Section 26.3: Multi Configuration Packages of my book). If you are using CMake 3.16 or later, definitely look at using the
cpack -C command line option. CMake 3.16 or later allows you to pass multiple configurations to this option. You are responsible for ensuring that each configuration has already been built when you run
cpack. This option only really makes sense if you’re using a multi-config generator, but since there’s now the
Ninja Multi-Config generator (since CMake 3.17), this is possible on all major platforms. It can be done with earlier CMake versions and/or single-config generators, but it is much more involved and much less intuitive.
So this would entail creating custom configurations? Like “RelWithDebInfoStatic”, for example? And would our users be able to pick which to use when they run
No it shouldn’t add any additional requirements unless you want builds that provide both static and shared versions of a target. That isn’t going to work out so good anyway because one target cannot have two different representations (static and shared). You would need to have two different targets in that case. One approach I’ve seen people suggest is to create
blahShared targets, then define a
blah interface target that links to one of those based on the value of
BUILD_SHARED_LIBS or some similar option. Consumers should then only link against
blah and the
blahShared libraries are somewhat of an internal implementation detail. I say “somewhat” because it would still probably impact consumers in terms of things they might need to add to their own packages, depending on how they are incorporating your library.
Well, I would like to provide both, but mutually exclusively is fine. Maybe like a variable that’s set ahead of
find_package(Halide) (in my case) that has it load the appropriate set of exports.
You might want to see how HDF5 did it, not perfect solution but usuable.
Allen - it looks like HDF5 uses separate packages for static and shared? I was specifically asking how to avoid that. Am I missing something?
No, it packages both into a single install. Only the builds are separated.
In the case of tools, there are static () and shared (-shared).
Libraries are static (lib.lib) and shared (.lib)
Not perfect, but workable. And the find_package can request either or both.
I still haven’t found a satisfactory solution to this, but this is what I’m planning and I’m interested to hear if anyone has any other perspectives.
@Allen – I looked into what HDF5 does, and I mostly like the structure.
@craig.scott – I’m still unsure how to use
cpack -C to combine, say, two Debug builds, one with
BUILD_SHARED_LIBS set to
YES and the other to
So here are my collected notes:
- When packaging for a Linux distribution / homebrew / something else, it is not acceptable to have separate packages for shared and static. They have to be packaged together. No one does
sudo apt install libpng-dev-shared libpng-dev-static. It’s just
- There are two ways to have separate shared/static targets:
a. Via a common object library. This is a no-go, not only because object libraries never behave how you expect (they require export/install afaik) but because it requires compiling all the objects with
-fPIC. This adds overhead that defeats the purpose of static libraries for many consumers.
b. Sources listed twice. This fixes the PIC issue and is correct.
- (2b) has the following drawbacks:
a. Both targets are always built, even if only one is required.
MyLib::MyLibalias must be consistently used within the
MyLibproject and must conditionally point to one or the other target, depending on the value of
- The user experience should be consistent between
find_packagevia vcpkg or another package manager, and
- (3a) and (4) imply that it is better to have a single target and multiple build directories.
- (4) requires that
MyLib::MyLib's type depends on
BUILD_SHARED_LIBSis awkward (have to set and reset) and unexpected. There are a few workarounds:
a. (Ab)use the components system – The user may (must?) specify either “shared” or “static” as a component, which directly determines the type.
b. Read a “namespaced” (cache?) variable – The value of
MyLib_SHARED_LIBSdetermines the type. This has the advantage of being command-line configurable. The right way to do this might be
c. All of the above. Component >
BUILD_SHARED_LIBS. If none are present, a default is chosen or an error is issued.
- Regardless, the active configuration (ie.
Release) should be respected. This means that a complete package combines four builds.
- Regarding (8),
cpack -C "Debug;Release"only takes me so far. Unless I’m missing something, it only helps produce a multi-config package for a single
BUILD_SHARED_LIBSsetting. If I want it to additionally package both static/shared, I have to compromise on something above.
The workflow I would like is to build Debug and Release via Ninja Multi-Config twice: once into a “shared” build tree and again into a “static” build tree, and then combine all four of them somehow.
It turns out it’s not so scary to merge a few build trees with CPack. Just create a working directory for several build trees that looks like:
build/ |- shared-Debug/ |- shared-Release/ |- static-Debug/ `- static-Release/
Then configure and build your project in each one, changing only
BUILD_SHARED_LIBS. Write a script titled
package.cmake, which contains:
include("shared-Release/CPackConfig.cmake") set(CPACK_INSTALL_CMAKE_PROJECTS static-Debug <PROJ> ALL / shared-Debug <PROJ> ALL / static-Release <PROJ> ALL / shared-Release <PROJ> ALL / )
Then a simple
cpack --config /path/to/package.cmake from the
build directory suffices. Note that this requires a single-config generator like Ninja. Make sure that you generate different targets export files for each of static and shared, then write the appropriate loading logic in your package configuration script. There, you can choose to use components, a variable, a fallback mechanism, or whatever to load the appropriate export script.