[CMake] Find_package: general question

Michael Hertling mhertling at online.de
Wed Jan 26 09:21:27 EST 2011


On 01/21/2011 02:59 PM, Vincent Garcia wrote:
> Dear CMake users, 
> 
> I have this BIG project which has tons of subdirectories (many levels). 
> In each (final) subdirectory, I create targets (executables and libraires). 
> So I have my top level CMakeLists.txt and one CMakeLists.txt file (which is also a project) in each subdirectory. 
> Everything compiles just fine! 
> 
> My question is a general CMake question and is the following: 
> Imagine I can use ITK in my project. To do that, in the top level CMakeLists.txt I define a variable USE_ITK. 
> If ITK is used, I search for it and I include useful files: 
> 
> OPTION(USE_ITK "If ON, search for Insight Toolkit package" OFF) 
> IF(USE_ITK) 
> FIND_PACKAGE(ITK) 
> IF(ITK_FOUND) 
> INCLUDE(${ITK_USE_FILE}) 
> ELSE(ITK_FOUND) 
> MESSAGE(FATAL_ERROR "ITK not found. Please set ITK_DIR.") 
> ENDIF(ITK_FOUND) 
> ELSE(USE_ITK) 
> SET(ITK_FOUND OFF) 
> ENDIF(USE_ITK) 
> 
> Now, if one subdirectory's project uses ITK, I have the following code (in the subdir's CMakeLists.txt): 
> 
> FIND_PACKAGE( ITK ) 
> IF( NOT ITK_FOUND ) 
> MESSAGE( "Project ${PROJECT_NAME} requires ITK and ITK was not found. ${PROJECT_NAME} will not be built." ) 
> RETURN() 
> ENDIF() 
> INCLUDE( ${ITK_USE_FILE} ) 
> 
> 
> As you can see, find_package(ITK) and INCLUDE( ${ITK_USE_FILE} ) are at least called twice (in fact much more). 
> Is there any performance issue the method describe above? 

Usually, find modules save their results in CMake's cache to avoid any
repetitive searching for the same executables/libraries/files/... when
invoked multiple times. E.g., the FIND_{PROGRAM,LIBRARY,...}() commands
- which are used heavily in find modules - store their positive results
in cached variables and don't perform their particular search again if
these variables don't evaluate to -NOTFOUND. In general, well written
find modules ensure that it is safe and cheap to call them more than
once, so the performance penalty due to multiple invocations should
be bearable, ideally even negligible.

> Since it's not my code and since i'm a CMake newbee, I'd like to understand if this is normal. 
> I would say that this should be done once in the top level CMakeLists.txt. 
> In the subdirs' CMakeLists, we should use only ITK_FOUND to decide if we build the target or not. 
> Some people of my team agree and some don't. 

For several reasons, I'd join the latter group. ;) Please note that the
following remarks are just my personal opinion, and of course, one can
take up a different position.

Basically, your approach tends to collect information about the needs
of your subprojects/submodules in the top-level CMakeLists.txt which
is in opposition to a good modularization and a tight locality. E.g.,
if one of your subprojects, situated deep in the overall project's
directory tree, needs package XXX, the top-level CMakeLists.txt must
know this fact to enable XXX by FIND_PACKAGE(XXX). As a result, your
top-level CMakeLists.txt must know and enable all prerequisites of
all subprojects which appears quite inconvenient to me, especially
if your subprojects are somewhat selfcontained.

When using multi-component packages, the situation might even become
worse: Suppose you have two subprojects, app1 uses QtGui while app2
uses QtXml. The top-level CMakeLists.txt according to your approach
would look like:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(APPS C CXX)
FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtXml)
INCLUDE(${QT_USE_FILE})
ADD_SUBDIRECTORY(app1)
ADD_SUBDIRECTORY(app2)

Now, look at the following app{1,2}/CMakeLists.txt:

# app1/CMakeLists.txt:
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.c
    "int main(void){return 0;}\n"
)
ADD_EXECUTABLE(app1 main.c)
TARGET_LINK_LIBRARIES(app1 ${QT_LIBRARIES})

# app2/CMakeLists.txt:
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.c
    "int main(void){return 0;}\n"
)
ADD_EXECUTABLE(app2 main.c)
TARGET_LINK_LIBRARIES(app2 ${QT_LIBRARIES})

With this configuration, app{1,2} get linked against Qt{Core,Gui,Xml},
i.e. both will be overlinked. This is a consequence of the joint list
of components in FIND_PACKAGE(Qt4 ...) for both subprojects; in other
words: A consequence of information about those subprojects' needs,
collected in the top-level CMakeLists.txt.

> What is the best way to proceed? 

In each sufficiently selfcontained subproject, one should issue the
FIND_PACKAGE() commands for all of the subprojects' prerequisites in
order to minimize the dependency on any higher-level CMakeLists.txt.
OTOH, if a subproject consists of several tightly connected modules,
e.g., it's possibly not appropriate to do the same for each module.
Instead, one might concentrate FIND_PACKAGE()'s invocations in the
subproject's CMakeLists.txt and let the modules inherit the results
as long as there are no difficulties like the one mentioned above.

Probably, the question of concentrating or distributing FIND_PACKAGE()
calls for prerequisites in extensive projects, or finding a reasonable
trade-off, respectively, can't be answered in a general way. Rather, it
depends on the degree of modularization, locality and independence you
want to achieve for your subprojects/submodules as well as the latters'
degree of interconnection. At least, I would not vote for the extreme
approach of concentrating everything in the top-level CMakeLists.txt.

There's another aspect of this topic when a package is needed in a
CMakeLists.txt as well as in a subordinated one, especially with
multi-component packages. E.g.:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(ACCUMULATION C CXX)
FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui)
INCLUDE(${QT_USE_FILE})
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.c
    "int main(void){return 0;}\n"
)
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main ${QT_LIBRARIES})
ADD_SUBDIRECTORY(module)

# module/CMakeLists.txt:
FIND_PACKAGE(Qt4 COMPONENTS QtCore QtXml)
INCLUDE(${QT_USE_FILE})
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.c
    "int main(void){return 0;}\n"
)
ADD_EXECUTABLE(module main.c)
TARGET_LINK_LIBRARIES(module ${QT_LIBRARIES})

Here, the module gets linked against QtGui although the requested
components are QtCore and QtXml. This is because FindQt4.cmake acts
in an accumulative manner, i.e. its results and effects incorporate
those of a previous invocation within the same scope. IMO, this is
problematic, and I'd prefer that the results and effects of a find
module do depend only on the parameters passed to FIND_PACKAGE() -
the list of components, in particular - well-known variables like
CMAKE_PREFIX_PATH and explicitly documented variables like, e.g.,
XXX_ROOT_DIR.

In the example above, one can prevent the results of the top-level
CMakeLists.txt from propagating to the module's one by issuing the
ADD_SUBDIRECTORY() before the FIND_PACKAGE(). So, when distributing
FIND_PACKAGE() invocations among subprojects or modules, it's worth
to look where they appear in the diverse CMakeLists.txt files w.r.t.
ADD_SUBDIRECTORY() in order to control the downward propagation of
their results/effects.

BTW, introducing the USE_ITK option in the top-level CMakeLists.txt
is a good method to enable/disable ITK in the subprojects. Although
this means a certain coupling between the subprojects and the top-
level CMakeLists.txt, too, it's a quite loose one; particularly the
subprojects' configurations remain valid even if this option would
not be defined anywhere.

'hope that helps.

Regards,

Michael


More information about the CMake mailing list