[cmake-developers] Convention driven CMAKE_USE_PACKAGE macro

Clinton Stimpson clinton at elemtech.com
Fri Feb 24 13:56:12 EST 2012


On Friday, February 24, 2012 03:31:35 am Stephen Kelly wrote:
> 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/CMa
> keLists.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.
> 


What about a more generic approach like the following?

add_library(foo IMPORTED ...)
set_target_properties(foo PROPERTIES 
      DEPENDENT_COMPILE_DEFINITIONS "FOO_DEFINE" 
      DEPENDENT_INCLUDE_DIRECTORIES "/path/to/foo/include")

add_executable(bar ...)
target_link_libraries(foo bar)

And that could automatically add -DFOO_DEFINE and -I/path/to/foo/include to 
the bar executable.
So basically any DEPENDENT_<property> can be pushed to <property> on the other 
target.

-- 
Clinton Stimpson
Elemental Technologies, Inc
Computational Simulation Software, LLC
www.csimsoft.com



More information about the cmake-developers mailing list