It's often useful to be able to make both static and shared varients of a
library available to users of a package.

My goal is to make the project cmake files as clean as possible, while
supporting usage of either or both types of library by users. It should
work from both build and installed locations, in a reasonably intuitive way.

I've tried a few methods so far, but none feel completely satisfactory. I
was wondering what was currently thought of as best practice with recent
versions of CMake?

1: Don't specify library type

add_library(myLib mySource.cpp)

The type of library is then controlled by the value of BUILD_SHARED_LIBS
This is nice:
* No duplication. Things common to both types are only specified once,
without resorting to custom variables.
* By default you only build one sort of library, so no build overhead.
* Type-dependent logic clearly presented by branching on a single, standard
variable

e.g.
if(NOT BUILD_SHARED_LIBS)
  target_compile_definitions(myLib PUBLIC MYLIB_STATIC_BUILD)
endif()

Using this sort of project to build an installation package that contains
both types of target can be accomplished with a little care.
You build the project twice in different directories, once with
BUILD_SHARED_LIBS and once without, but install to the same prefix.
* Namespace at the point of export based on type (MyPackage::SHARED::myLib
or MyPackage::STATIC::myLib)
* Export to a type-specific config (myLib_exports_static.cmake
myLib_exports_shared.config)
* Have your package config check for and include the exported static and
shared config files using if(EXISTS)
* Some MSVC specific code to prevent shared import lib and static lib
output names conflicting
* On cmake 3.11+, alias one flavour to MyPackage::myLib based on some user
variable for convenience

This all works well, the problem comes with registering a build tree in the
user repository.

export(PACKAGE myPackage)

In a downstream project, only one build location will be arbitrarily chosen
from the user registry.
While you could manually choose either the static or shared tree by setting
myPackage_DIR, you can't have both, at least not from the build tree. Even
if a user only wants one, the fact they might get the 'wrong' version by
default is likely to confuse.

Setting MYLIB_FOUND to FALSE in a package config aborts the search process
rather than continuing to search for other possible configs, else you could
do partial loading from two different build trees in much the same way that
you can merge installations (or search for a shared vs static based on a
user variable)

I've tried calling include_subdirectory(myLibDir) twice in a project, both
before and after setting BUILD_SHARED_LIBS.
This requires you to change the target name as duplicates are not allowed
(for obvious reasons). A variable target name requires a custom variable
anywhere your target is referenced, which is error prone. Also feels a bit
of an abuse of the variable.

You could export to two different packages, but having to do
find_package(MyLibShared) or find_package(MyLibStatic) feels a bit
counterintuitive from the installed perspective if both versions are
distributed together.

2: Create two separate targets, one for each type

One common approach is to assign sources and other build ingredients to a
custom variable, then call any target-modifying functions once for each
target, passing this variable.

set(mySources mySource.cpp)
add_target(myLib_static STATIC ${mySources})
add_target(myLib_shared SHARED ${mySources})

This can be difficult to debug as mistakes are often distant from the
location at which an error is caught. It's also a lot more verbose as every
target method is duplicated.

I think the need for a variable can be mitigated in some circumstances by
using generator expressions

e.g.

target_include_directories(myLib_shared
  PRIVATE $<TARGET_PROPERTY,myLib_static,INCLUDE_DIRECTORIES>
  INTERFACE $<TARGET_PROPERTY,myLib_static,INTERFACE_INCLUDE_DIRECTORIES>)

However, this only works for properties that are identical between
variants, necessitates always building the first target and is even more
verbose that using custom variables.

So from the upstream point of view, the first method is the clear winner,
but its restrictions when importing targets from the build tree can make it
less convenient for downstream users, who may be working on projects that
take the two target approach.

So what's considered the best option at the moment? Am I missing something
obvious?

Thanks,

Rich
-- 

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: 
http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more 
information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at 
http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
https://cmake.org/mailman/listinfo/cmake

Reply via email to