CMake:Improving Find* Modules: Difference between revisions

From KitwarePublic
Jump to navigationJump to search
m (→‎Proposed solutions to Find* issues: Divide this section into subsections. This will allow me to post ConvertLibraryFlags module during next edit.)
(→‎Converting -L/-l flags: Here is some CMake code (module ConvertLibraryFlags) that tries to do this conversion.)
Line 23: Line 23:
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.
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. --[[User:Kernigh|Kernigh]] 22:17, 26 December 2008 (UTC))
Module in file ''ConvertLibraryFlags.cmake'':
<pre>
#- 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)
</pre>
Here is an example:
<pre>
# 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}")
</pre>
Which gave this output for an OpenBSD system with installed X11 and libpng:
<pre>
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
</pre>


=== Dependent cache variables ===
=== Dependent cache variables ===

Revision as of 22:17, 26 December 2008

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.


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.

Current workarounds