# Cmake module that improves upon CheckLibrariesExists

The following module AssertLibraryFunction improves upon CheckLibrariesExists in two respects:

• It honors the optional argument REQUIRED of find_package: if the check fails, it raises a FATAL_ERROR. It also honors QUIET.
• Its result is cached in a variable named ${LIBNAME}_${FUNCTION}. Therefore, argument 4 of CheckLibrariesExists, VARIABLE, is no longer needed.

Of course this comes at a slight expense in flexibility. Nevertheless, I believe this module could help substantially in writing compact Find<Package>.cmake modules.

Questions:

• Is this novel functionality, or could I achieve about the same, with comparable brevity, with an existing module?
• Would it make sense to propose AssertLibraryFunction for inclusion in future cmake versions?
• Regardless of the above, are there ways to improve my cmake coding style?

Here the module AssertLibraryFunction:

#.rst:
# AssertLibraryFunction
# ---------------------
#
# ASSERT_LIBRARY_FUNCTION checks whether given libraries contain
# a given function. If this is not the case, a fatal error is raised.
#
# CHECK_LIBRARY_EXISTS (LIBNAME FUNCTION LOCATION)
#
# ::
#
#   LIBNAME  - library name (case sensitive)
#   FUNCTION - name of the function to be searched in ${LIBNAME}_LIBRARIES # LOCATION - where the library should be found (if unsure, use "") # # # The following variables may be set before calling this macro to modify # the way the check is run: # # :: # # CMAKE_REQUIRED_FLAGS = string of compile command line flags # CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) # CMAKE_REQUIRED_LIBRARIES = list of libraries to link # CMAKE_REQUIRED_QUIET = execute quietly without messages # # This function is meant to be used in Find<Package>.cmake modules, # which in turn should be called through # # :: # # find_package(<Package> [QUIET] [REQUIRED]) # # Within a Find<Package>.cmake module, find_package_handle_standard_args # must be called before any call of ASSERT_LIBRARY_FUNCTION. # Typically, Find<Package>.cmake looks like the following: # # :: # # find_path(<Package>_INCLUDE_DIR <include_file>) # find_library(<Package>_LIBRARIES NAMES <library_name> <Package>) # # include(FindPackageHandleStandardArgs) # find_package_handle_standard_args(<Package> DEFAULT_MSG <Package>_LIBRARIES <Package>_INCLUDE_DIR) # # include(AssertLibraryFunction) # assert_library_function(<Package> <function_name> "") # # mark_as_advanced(<Package>_INCLUDE_DIR <Package>_LIBRARIES) # # The result of ASSERT_LIBRARY_FUNCTION is cached in a variable named #${LIBNAME}_${FUNCTION}. #============================================================================= # Based on CheckLibrariesExists (Copyright 2002-2009 Kitware, Inc.) # Author: Joachim Wuttke (Copyright 2015 Forschungszentrum Jülich) # License: BSD (see cmake License for details) #============================================================================= macro(alf_status_message _msg) if(${LIBNAME}_FIND_QUIETLY )
else()
message(STATUS ${_msg}) endif() endmacro() function(ASSERT_LIBRARY_FUNCTION LIBNAME FUNCTION LOCATION) set(LIBRARY${${LIBNAME}_LIBRARIES}) set(VARIABLE${LIBNAME}_${FUNCTION}) set(_MSG "Search${FUNCTION} in ${LIBRARY}") if(DEFINED "${VARIABLE}")
if(${${VARIABLE}})
alf_status_message("${_MSG} -- cached") return() endif() endif() alf_status_message("Search${FUNCTION} in ${LIBRARY}") set(MACRO_CHECK_LIBRARY_EXISTS_DEFINITION "-DCHECK_FUNCTION_EXISTS=${FUNCTION} ${CMAKE_REQUIRED_FLAGS}") set(CHECK_LIBRARY_EXISTS_LIBRARIES${LIBRARY})
if(CMAKE_REQUIRED_LIBRARIES)
set(CHECK_LIBRARY_EXISTS_LIBRARIES
${CHECK_LIBRARY_EXISTS_LIBRARIES}${CMAKE_REQUIRED_LIBRARIES})
endif()
try_compile(COMPILE_OK
${CMAKE_BINARY_DIR}${CMAKE_ROOT}/Modules/CheckFunctionExists.c
COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} LINK_LIBRARIES${CHECK_LIBRARY_EXISTS_LIBRARIES}
CMAKE_FLAGS
-DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_LIBRARY_EXISTS_DEFINITION} -DLINK_DIRECTORIES:STRING=${LOCATION}
OUTPUT_VARIABLE OUTPUT)

if(${COMPILE_OK}) if(NOT CMAKE_REQUIRED_QUIET) alf_status_message("${_MSG} -- found")
endif()
set(${VARIABLE} 1 CACHE INTERNAL "Library${LIBRARY} has ${function}") file(APPEND${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log "Determining if the function${FUNCTION} exists in the ${LIBRARY} " "passed with the following output:\n" "${OUTPUT}\n\n")
else()
set(${VARIABLE} "" CACHE INTERNAL "Library${LIBRARY} has no ${function}") file(APPEND${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log "Determining if the function${FUNCTION} exists in the ${LIBRARY} " "failed with the following output:\n" "${OUTPUT}\n\n")
if( ${LIBNAME}_FIND_REQUIRED ) message(FATAL_ERROR "${_MSG} -- not found")
else()
alf_status_message("${_MSG} -- not found") endif() endif() endfunction()  And here is a sample module FindReadline.cmake that would use the above: # Find libreadline # # Usage: # find_package(Readline [REQUIRED] [QUIET]) # # Sets the following variables: # - Readline_FOUND .. true if library is found # - Readline_LIBRARIES .. full path to library # - Readline_INCLUDE_DIR .. full path to include directory # # Copyright 2015 Joachim Wuttke, Forschungszentrum Jülich. # Redistribution permitted. find_path(Readline_INCLUDE_DIR readline/readline.h) find_library(Readline_LIBRARIES NAMES readline Readline) include(FindPackageHandleStandardArgs) # handles QUIET, REQUIRED; sets Readline_FOUND find_package_handle_standard_args(Readline DEFAULT_MSG Readline_LIBRARIES Readline_INCLUDE_DIR) include(AssertLibraryFunction) assert_library_function(Readline rl_initialize "") assert_library_function(Readline add_history "") mark_as_advanced(Readline_INCLUDE_DIR Readline_LIBRARIES)  ## 1 Answer I think your changes would definetly improve CHECK_LIBRARY_EXISTS(). If I understand correctly you want to improve upon something like cmake_push_check_state() set(CMAKE_REQUIRED_QUIET${Readline_FIND_QUIETLY})
check_library_exists("${Readline_LIBRARIES}" rl_initialize "" Readline_rl_initialize) cmake_pop_check_state()  with assert_library_function(Readline rl_initialize "")  ### assert_library_function() Review After giving your code some test runs, here are my thoughts: 1. If I think about the LIBNAME parameter more in terms of a PACKAGENAME parameter, then you can make use of CMAKE_FIND_PACKAGE_NAME: macro(alf_status_message _msg) if(NOT DEFINED CMAKE_FIND_PACKAGE_NAME OR NOT "${${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY}") message(STATUS${_msg})
endif()
endmacro()

• The fact that CMAKE_FIND_PACKAGE_NAME is defined inside a find_package() call gives two new possibilities
• omitting the LIBNAME parameter of ASSERT_LIBRARY_FUNCTION() completely
• or extending CHECK_LIBRARY_EXISTS() itself with this kind of checks
2. With one of my first tries the find module was delivering ${${LIBNAME}_LIBRARIES} as Readline_LIBRARIES-NOTFOUND. So your function should probably check and exit if the library was not found with e.g.

if (NOT LIBRARY)
return()
endif()

3. With your approach to take the content of Xxx_LIBRARIES I think you can remove the LOCATION parameter from ASSERT_LIBRARY_FUNCTION. The libraries will come as full paths. See

4. The last one is a very small one: to keep in mind the I possibly handle a list of libraries, I would rename local variable LIBRARY to LIBRARIES

### Side Note

Just as a side note FindBZip2.cmake has just recently switched to the use of CHECK_SYMBOL_EXISTS():

cmake_push_check_state()
set(CMAKE_REQUIRED_QUIET ${BZip2_FIND_QUIETLY}) CHECK_LIBRARY_EXISTS("${BZIP2_LIBRARIES}" BZ2_bzCompressInit "" BZIP2_NEED_PREFIX)
cmake_pop_check_state()


With the following comment for the commit:

Run our check for the '_' prefix using a bzip2 API function as declared with a prototype in the real header file. This is needed in case the function is provided in a DLL import library where the symbol name may not match without proper markup from the header.

### Footnote

Ever since I have read the following comment in the CMP0060 documentation

In fact, many Find Modules are learning to provide Imported Targets instead of just the traditional Foo_LIBRARIES variable listing library files.

the thought of "this being something all find modules should have" never left my mind. So here are my five cents for FindReadline.cmake:

find_path(Readline_INCLUDE_DIR readline/readline.h)

include(FindPackageHandleStandardArgs) # handles QUIET, REQUIRED; sets Readline_FOUND

IMPORTED_LOCATION "${Readline_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Readline_INCLUDE_DIR}"