[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