CMake:Improving Find* Modules
On the CMake mailing list there was a long discussion on how to improve the Find* modules in CMake. Currently (CMake 2.6.2), there are several issues with the Find* modules that cause trouble on some configurations. See this thread :[1]
Problems with current Find* modules
- Finding packages in non-standard locations can be difficult and is not standardized across all the Find* modules.
- The parsing and use of pkg-config, and other config tools does not always find the full paths to libraries that are required for the build.
- Often times a Find* module has a key variable that when changed should cause CMake to re-discover many other cached variables. Currently, there is no uniform way to do this in CMake
- If multiple ABI's exist on a machine, CMake may find a .so or .a library that will not work with the rest of the libraries found or with the compiler and flags picked.
- Often libraries are linked with static libraries in which case the recursive dependencies must be linked. These dependencies may depend on how the interface library was configured.
Proposed solutions to Find* issues
Packages in unusual locations
Each module should have an environment variable that is looked at first, much like the CC and CXX environment variables do for the compiler tool chain discovery. Once the module finds the package based on the environment variable, a CMake cache variable of the same name should be created. If the environment variable is present, but the software is NOT found, then the module should stop looking and report the package not found.
Converting -L/-l flags
Add ability to parse compile lines like the ones produced from pkg-config and turn them into full paths to libraries. So, turn -L/my/path -lfoo into /my/path/libfoo.a.
(Then I thought that this might be easy, so I tried to code this as a CMake module, but I encountered some complications and marked them with FIXME. --Kernigh 22:17, 26 December 2008 (UTC))
Module in file ConvertLibraryFlags.cmake:
#- convert from -L and -l flags to full paths of libraries # convert_library_flags(<variable> [flags ...]) # # Given some compiler flags, replace the -L... and -l... flags with full # paths to libraries, and store the result into <variable>. This is # useful for converting the output from scripts like pkg-config. # # FIXME - put an example here! function(convert_library_flags variable) # grab libdirs from the -L flags set(libdirs) foreach(flag ${ARGN}) if(flag MATCHES "^-L") # chop -L and append to libdirs string(REGEX REPLACE "^-L(.*)$" "\\1" dir ${flag}) list(APPEND libdirs ${dir}) endif() endforeach(flag) # now convert flags to result set(result) foreach(flag ${ARGN}) if(flag MATCHES "^-L") # do nothing, removes -L flags from result elseif(flag MATCHES "^-l") # chop -l string(REGEX REPLACE "^-l(.*)$" "\\1" lib ${flag}) # We cannot use find_library, because we do not want a cache # variable. So do the search manually. This uses three nested # foreach loops (for dir, prefix, suffix). # # FIXME - Where does the compiler look for libraries, when # there is no -L flag? Assuming /usr/lib and /usr/local/lib # but this is wrong and nonportable. # # FIXME - This fails to find shared libraries in OpenBSD, # because of no "*.so" symlinks without a version number. # set(go TRUE) foreach(dir ${libdirs} /usr/lib /usr/local/lib) foreach(prefix ${CMAKE_FIND_LIBRARY_PREFIXES}) foreach(suffix ${CMAKE_FIND_LIBRARY_SUFFIXES}) if(go) set(file ${dir}/${prefix}${lib}${suffix}) if(EXISTS ${file}) # found it! append to result list(APPEND result ${file}) set(go FALSE) # break from nested loops endif() endif() endforeach(suffix) endforeach(prefix) endforeach(dir) if(go) message(SEND_ERROR "library for flag ${flag}: not found") endif(go) else() # Flag is not -L or -l, might be something like -pthread, so # just preserve it. list(APPEND result ${flag}) endif() endforeach(flag) # return the result set("${variable}" ${result} PARENT_SCOPE) endfunction(convert_library_flags)
Here is an example:
# test-CLF/CMakeLists.txt cmake_minimum_required(VERSION 2.6) project(test-CLF C) include(${CMAKE_SOURCE_DIR}/../ConvertLibraryFlags.cmake) function(getflags variable) # quick hack, does not check exit status or standard error execute_process(COMMAND ${ARGN} OUTPUT_VARIABLE output) # I would use separate_arguments, but I also need to chomp # the \n at end of output. string(REGEX REPLACE "[ \t\n]+" \; output "${output}") set("${variable}" ${output} PARENT_SCOPE) endfunction(getflags) getflags(png pkg-config libpng --libs) getflags(xv pkg-config xv --libs) message("PNG libs: ${png}") message("XVideo libs: ${xv}") convert_library_flags(fullpng ${png}) convert_library_flags(fullxv ${xv}) message("PNG libs (full paths): ${fullpng}") message("XVideo libs (full paths): ${fullxv}")
Which gave this output for an OpenBSD system with installed X11 and libpng:
PNG libs: -L/usr/local/lib;-lpng;-lz;-lm XVideo libs: -L/usr/X11R6/lib;-lXv;-lXext;-lX11;-lXdmcp;-lXau PNG libs (full paths): /usr/local/lib/libpng.a;/usr/lib/libz.a;/usr/lib/ libm.a XVideo libs (full paths): /usr/X11R6/lib/libXv.a;/usr/X11R6/lib/libXext. a;/usr/X11R6/lib/libX11.a;/usr/X11R6/lib/libXdmcp.a;/usr/X11R6/lib/libXa u.a
Dependent cache variables
Add an easy way to create dependent cache variables, that when changed unset a number of other variables. Something like this:
check_cache_depend(VAR1 DVAR1 DVAR2 DVAR3 DVAR4)
If VAR1 changes in the cache from a previous value, then DVAR1, DVAR2, DVAR3, and DVAR4 are all removed from the cache. You would put something like that at the top of a FindFoo.cmake module. For Qt it would be:
check_cache_depend(QT_QMAKE_EXECUTABLE QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE ...)
ABI issue
For the ABI issue, a try-compile should most likely be used in each Find* module. However, ideally this would have to be integrated into the find_library command so that it would not find libraries that were of the wrong ABI. Currently, there is not a good proposal for dealing with this issue.