[cmake-developers] Convention driven CMAKE_USE_PACKAGE macro

Stephen Kelly steveire at gmail.com
Fri Feb 24 05:31:35 EST 2012


Hi there,

When building Qt5 projects with CMake, you will use something like this:
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Xml REQUIRED)

The result is that several variables are populated with information needed 
to build your project with Qt5, such as in this case 
* ${Qt5Widgets_INCLUDE_DIRS}
* ${Qt5Xml_INCLUDE_DIRS} 
* ${Qt5Widgets_DEFINITONS}
* ${Qt5Xml_DEFINITONS}
* ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}
* ${Qt5Xml_EXECUTABLE_COMPILE_FLAGS} 
and several others.

When building Qt4 applications with CMake, it is common to use 

include(${QT_USE_FILE})

This caused the include directories and the definitions to be used by CMake 
at directory level scope, meaning that all targets in this directory and in 
lower subdirectories would be built with those definitions and includes 
available.

One common problem with that was that when creating unit tests which do not 
link to QtGui, the QT_GUI_LIB macro was defined, and so linker errors 
occured. 

The way around that was one or more of:
* Don't use the USE file (KDE doesn't use it)
* remove_definitions(-DQT_GUI_LIB)
* be very careful about which Qt4 components are passed to find_package 
(Grantlee does this - it finds Qt multiple times - templates/CMakeLists and 
textdocument/CMakeLists because the templates library and tests do not use 
QtGui).

When building with Qt5, we want to change the convention away from USE 
files, and towards a concept of using packages whose variables have 
conventional names, mostly those conventions in the Modules/readme.txt, so 
that after find_package(Foo), Foo_INCLUDE_DIRS contains the include 
directories that targets using Foo need to use, and Foo_COMPILE_DEFINITIONS 
contains the compile definitions that they need etc. This would also be an 
incentive for other packages to follow the convention.

These variables can then be used in a macro like this:

include(CMakeParseArguments)

macro(cmake_use_package _target _package)

    if (NOT ${_package}_FOUND)
        # TODO: This might not work with CMake 2.8.8
        find_package(${_package} REQUIRED) 
    endif()

    set(options
        INTERFACE_PRIVATE
        INTERFACE_PUBLIC
    )
    set(oneValueArgs)
    set(multiValueArgs)

    cmake_parse_arguments(_CUP
      "${options}"
      "${oneValueArgs}"
      "${multiValueArgs}"
      ${ARGN}
    )
    
    if (_CUP_INTERFACE_PUBLIC)
        set(_link_arg LINK_PUBLIC)
    elseif (_CUP_INTERFACE_PRIVATE)
        set(_link_arg LINK_PRIVATE)
    endif()
    target_link_libraries(${_target} ${_link_arg} ${${_package}_LIBRARIES})
    # ### Requires CMake 2.8.8:
    set_property(TARGET ${_target} APPEND PROPERTY INCLUDE_DIRECTORIES 
${${_package}_INCLUDE_DIRS})
    set_property(TARGET ${_target} APPEND PROPERTY COMPILE_DEFINITIONS 
${${_package}_COMPILE_DEFINITIONS})

    # We can't just append to the COMPILE_FLAGS property. That creats a ';' 
separated list
    # which breaks the compile commmand line.
    # Ensure non-duplication here manually instead.
    get_property(_target_type TARGET ${_target} PROPERTY TYPE)
    get_target_property(_flags ${_target} COMPILE_FLAGS)
    if (_flags)
      list(APPEND _flags ${${_package}_${_target_type}_COMPILE_FLAGS})
      list(REMOVE_DUPLICATES _flags)
    else()
      set(_flags ${${_package}_${_target_type}_COMPILE_FLAGS})
    endif()
    if (_flags)
      set_target_properties(${_target} PROPERTIES COMPILE_FLAGS ${_flags})
    endif()
endmacro()


We can then wrap that macro with some Qt related stuff like this:

macro(qt5_use_package _target _package)
    cmake_use_package(${_target} Qt5${_package} ${ARGN})
    get_property(_target_type TARGET ${_target} PROPERTY TYPE)
    if ("${_target_type}" STREQUAL "EXECUTABLE")
        # TODO: How can I ensure this is done only once?
        target_link_libraries(${_target} ${Qt5Core_WINMAIN_LIBRARY}) 
    endif()
endmacro()

# The two target uses Qt5Test and Qt5Xml
qt5_use_package(two Test)
qt5_use_package(two Xml)

# The three target uses Qt5Widgets, but the QT_GUI_LIB definition doesn't 
pollute 
# the flags used when building two, so there is no linking error.
qt5_use_package(three Gui)


find_package(Qt5Sql <some funny arguments>)
qt5_use_package(three Sql) # This will use the package found above.

I am assuming that there is only one package of each name that would need to 
be found per project. So once Foo_FOUND is true, it doesn't need to be found 
again.

Would I be able to get a macro like cmake_use_package into CMake upstream? 

It is not yet finished, but I'm asking about the concept mostly. It is 
already a work in progress in the Qt5 CMake tests: 
http://qt.gitorious.org/qt/qtbase/blobs/master/tests/manual/cmake/pass1/CMakeLists.txt

Putting it in Qt is not so optimal as it wouldn't get a chance to be useful 
conventionally outside of Qt. Putting it in ECM would make Qt depend on ECM, 
which would be undesirable. I think CMake is the best place for it.

I would like the KDE frameworks use a similar macro too.

Thanks,

Steve.




More information about the cmake-developers mailing list