[CMake] ProjectConfig.cmake files with both dynamic and static libraries?

Michael Hertling mhertling at online.de
Thu May 5 17:48:45 EDT 2011


On 05/04/2011 02:30 PM, Ben Morgan wrote:
> Hi,
> 
> I've been working on adding <Project>Config.cmake support to my CMake based
> projects, and just had a quick question regarding 'best practice' in writing
> these when the project builds both dynamic and (optionally) static
> libraries, with clients of the project wishing to choose one or the other.
> 
> Thanks to the ProjectConfig tutorial on the Wiki :-), I've got the basic
> files for a test project 'mlvl' working nicely from the build and install
> trees.
> By default, mlvl builds a dynamic library with a user option to additionally
> build a static version, hence the mlvlLibraryDepends.cmake files
> have the imported targets
> 
> mlvl
> mlvl-static
> 
> though the mlvl-static target will only be present if the build static
> option was enabled. The relevant section of my mlvlConfig.cmake.in file
> then contains
> 
> 
> include("@mlvl_CMAKE_DIR@/mlvlLibraryDepends.cmake")
> 
> set(mlvl_LIBRARY mlvl)
> 
> set(mlvl_LIBRARY_STATIC "mlvl_LIBRARY_STATIC-NOTFOUND")
> if(TARGET mlvl-static)
>     set(mlvl_LIBRARY_STATIC mlvl-static)
> endif()
> 
> if(mlvl_USE_STATIC)
>     if(mlvl_LIBRARY_STATIC)
>         set(mlvl_LIBRARIES ${mlvl_LIBRARY_STATIC})
>     else()
>         message(WARNING "no static build of mlvl available, falling back to
> use dynamic library")
>         set(mlvl_LIBRARIES ${mlvl_LIBRARY})
>     endif()
> else()
>     set(mlvl_LIBRARIES ${mlvl_LIBRARY})
> endif()
> 
> 
> where mlvl_USE_STATIC is a variable clients of mlvl can set before calling
> find_package. This seems to work o.k., but I'm a
> little unsure about:
> 
> a) if it looks reasonable, and within the scope of what a <NAME>Config.cmake
> script is supposed to do (my impression from reading the lists
> is that they're supposed to be very simple...).
> 
> b) If a FATAL_ERROR could/should be issued if the client has requested use
> of the static library but the install of mlvl that's been found doesn't have
> it.
> 
> Replacing message(WARNING ...) with message(FATAL_ERROR ...) does work, and
> I'm not sure if this should be done within the config script or
> whether I should just leave it up to the client to decide what to do post
> the find_package call - it would seem within the spirit of module mode
> find_package to emit a FATAL_ERROR if both mlvl_USE_STATIC is set and
> find_package was called with REQUIRED...?
> 
> I realize I could make my life much easier by always building both libraries
> ;-), but I'm interested in the limits of what should be done in a
> ProjectConfig.cmake
> file versus how much responsibility to pass onto the user.. This seems to
> relate to previous discussions on this list about how to write config files
> for
> projects with components or/and using external third-party packages, though
> I didn't find a definitive answer (apologies if I missed an obvious
> posting...).
> 
> Thanks,
> 
> Ben.

IMO, your approach looks quite reasonable; however, I'm not completely
happy with it... ;) Please note that the following remarks are just my
personal point of view, and of course, one can have different notions.

The information if there is a static library is already available at
mlvl's configuration time, so there's no actual need for the config
file to make a dynamic decision, i.e. "if(TARGET mlvl-static)" and
"if(mlvl_LIBRARY_STATIC)"; this is typically done by find modules.
Instead, one might consider to hard-code the affected passages,
e.g. as an additional include file next to the export file:

# mlvlConfig.cmake.in:
include("@mlvl_CMAKE_DIR@/mlvlLibraryDepends.cmake")
include("@mlvl_CMAKE_DIR@/mlvlLibrarySetup.cmake")
set(mlvl_LIBRARIES ${mlvl_LIBRARY})

# mlvlLibrarySetup.cmake with mlvl-static:
if(mlvl_USE_STATIC)
    set(mlvl_LIBRARY mlvl-static CACHE STRING "...")
else()
    set(mlvl_LIBRARY mlvl CACHE STRING "...")
endif()

# mlvlLibrarySetup.cmake w/o mlvl-static:
if(mlvl_USE_STATIC)
    if(mlvl_FIND_REQUIRED)
        message(FATAL_ERROR "no static build of...")
    elseif(NOT mlvl_FIND_QUIETLY)
        message(WARNING "no static build of...")
    endif()
endif()
set(mlvl_LIBRARY mlvl CACHE STRING "...")

During mlvl's configuration, you decide which mlvlLibrarySetup.cmake
file is installed along with mlvlConfig.cmake. In this way, you even
get rid of the rather unusual mlvl_LIBRARY_STATIC variable, provided
that the project's users are not to refer to the shared *and* static
library at the same time. Additionally, the entire stuff gets a bit
simpler. BTW, variables as mlvl_LIBRARY should definitely be cached.

Having said that, an alternative solution would be a multi-component
package with shared and static library components, e.g. as follows:

# mlvlConfig.cmake.in:
include("@mlvl_CMAKE_DIR@/mlvlLibraryDepends.cmake")
# Handle default component(s):
if(NOT mlvl_FIND_COMPONENTS)
    set(mlvl_FIND_COMPONENTS shared)
    set(mlvl_FIND_REQUIRED_shared true)
endif()
# Handle inter-component dependencies:
if(mlvl_FIND_REQUIRED_shared AND mlvl_FIND_REQUIRED_static)
    # shared and static are mutually exclusive,
    # and shared takes precedence over static:
    list(REMOVE_ITEM mlvl_FIND_COMPONENTS static)
    unset(mlvl_FIND_REQUIRED_static)
    if(NOT mlvl_FIND_QUIETLY)
        message(WARNING "Dropped static library...")
    endif()
endif()
# Handle "static" component:
include("@mlvl_CMAKE_DIR@/mlvlLibraryStatic.cmake" OPTIONAL)
# Handle fallback from static to shared:
if(mlvl_FIND_REQUIRED_static)
    # static requested but not found,
    # therefore, fall back to shared:
    list(APPEND mlvl_FIND_COMPONENTS shared)
    set(mlvl_FIND_REQUIRED_shared true)
    if(NOT mlvl_FIND_QUIETLY)
        message(WARNING "Falling back to shared...")
    endif()
endif()
# Handle "shared" component:
SET(mlvl_shared_FOUND true)
SET(mlvl_shared_LIBRARY mlvl CACHE STRING "...")
if(mlvl_FIND_REQUIRED_shared)
    list(APPEND mlvl_LIBRARIES mlvl)
    list(REMOVE_ITEM mlvl_FIND_COMPONENTS shared)
    unset(mlvl_FIND_REQUIRED_shared)
endif()
# Handle remaining components:
list(REMOVE_DUPLICATES mlvl_FIND_COMPONENTS)
foreach(i IN LISTS mlvl_FIND_COMPONENTS)
    if(mlvl_FIND_REQUIRED)
        message(FATAL_ERROR "Component ${i} not found")
    elseif(NOT mlvl_FIND_QUIETLY)
        message(WARNING "Component ${i} not found")
    endif()
    unset(mlvl_FIND_REQUIRED_${i})
endforeach()

# mlvlLibraryStatic.cmake, installed if static library is built:
SET(mlvl_static_FOUND true)
SET(mlvl_static_LIBRARY mlvl-static CACHE STRING "...")
if(mlvl_FIND_REQUIRED_static)
    list(APPEND mlvl_LIBRARIES mlvl-static)
    list(REMOVE_ITEM mlvl_FIND_COMPONENTS static)
    unset(mlvl_FIND_REQUIRED_static)
endif()

While this is a bit more work, it's a quite systematic approach:

- There are explicit variables like mlvl_{shared,static}_FOUND which
  are covered by the conventions in ${CMAKE_ROOT}/Modules/readme.txt.
- The blocks for the individual components have identical structure.
- The static library component's availability is determined solely
  and simply by the presence of the mlvlLibraryStatic.cmake file.
- The supplementary logic, i.e.
  - default components
  - inter-component dependencies
  - fallback from static to shared
  is implemented centrally in the config file at suitable locations.
- The final block for the remaining components ensures the correct
  handling of the REQUIRED and the QUIET flag. For this and other
  things to work, the component-related blocks must remove their
  respective component from the components list and unset the
  mlvl_FIND_REQUIRED_XYZ variables after they are done.

Besides, an approach like the above-noted would make it much easier for
the user to explicitly prefer a static library to a shared one without
messing around with CMAKE_FIND_LIBRARY_SUFFIXES or setting XYZ_LIBRARY
explicitly, an issue which comes up on the ML every now and then. In
addition, if you drop the mutual exclusion, you gain the possibility
to link against the shared and the static library at the same time in
case they differ in some regard. That's hardly possibly with the usual
single-component approach although I don't have an example at hand ATM.

However, when thinking about multi-component packages, be aware of some
issues that do not matter for single-component packages, refer to [1,2]
for discussions of this topic. Furthermore, note that an export file as
mlvlLibraryDepends.cmake needs to be protected from being included more
than once in the current scope, see [3].

Regards,

Michael

[1] http://www.mail-archive.com/cmake@cmake.org/msg28431.html
[2] http://www.mail-archive.com/cmake@cmake.org/msg32836.html
[3] http://www.mail-archive.com/cmake@cmake.org/msg35873.html


More information about the CMake mailing list