[CMake] SuperBuild whoes

Michael Hertling mhertling at online.de
Tue Apr 26 08:48:42 EDT 2011


On 04/25/2011 05:15 PM, Michael Wild wrote:
> On 04/25/2011 04:51 PM, Michael Hertling wrote:
> [...]
>>>
>>> I also solved my installation problem. By having all sub-projects
>>> install into a common prefix, and thanks to the wonders of RPATH and
>>> install_name which can handle relative paths and thanks to the fact that
>>> install(EXPORT) generated files are also relocatable, I can simply do a
>>> install(DIRECTORY) in the super project ;-) [...]
>>
>> Does this mean you've the subprojects configured for installation with
>> a prefix of, say, ${CMAKE_BINARY_DIR}/externals and the superproject's
>> CMAKE_BINARY_DIR, and add
>>
>> INSTALL(DIRECTORY ${CMAKE_BINARY_DIR}/externals
>>         DESTINATION ${CMAKE_INSTALL_PREFIX})
>>
>> to the superproject's top-level CMakeLists.txt in order to relocate
>> the subprojects to the actual installation directory in the end?
>>
>> If so, how do you cope with subprojects which incorporate paths
>> derived from their installation prefix as hard-coded defaults, e.g.
>> <prefix>/etc - or /etc if <prefix> equals /usr - for the package's
>> configurational data? Following your approach, if I'm not mistaken,
>> the subprojects would end up with ${CMAKE_BINARY_DIR}/externals/etc
>> and CMAKE_BINARY_DIR pointing to the superproject's build tree. Of
>> course, this might result in certain failures, or do I completely
>> misunderstand your afore-noted outline?
> 
> That's true, but all the sub-projects are under my control, so I can
> take care of it.

To be more robust, one could possibly configure the subprojects with the
final installation prefix, i.e. the superproject's CMAKE_INSTALL_PREFIX,
and use a DESTDIR of ${CMAKE_BINARY_DIR}/externals in the installation
command to perform the intermediate installation in the build tree.
Finally, an

INSTALL(DIRECTORY ${CMAKE_BINARY_DIR}/externals/${CMAKE_INSTALL_PREFIX}/
        DESTINATION ${CMAKE_INSTALL_PREFIX})

would relocate the subprojects to their designated - and configured -
installation directory. Admittedly, this approach's shortcomings are:

- On Windows, it would need further ado because of the drive letters.
- Not all (sub)projects support a DESTDIR feature for installation; at
  least, CMake-based projects do, and autotooled ones do it, too, AFAIK.
- If the subprojects don't provide flexible alternatives for hard coded
  paths, e.g. environment variables or command line options, they might
  fail to run from within the superproject's build tree, but at least,
  their headers can be used to compile and the libraries to resolve
  symbols, and FIND_PACKAGE() will be happy when it's instructed:

CMAKE_PREFIX_PATH=${CMAKE_BINARY_DIR}/externals/${CMAKE_INSTALL_PREFIX}

At worst, i.e. with subprojects that actually rely on hard coded paths
and need to be run from within the build tree, it's probably necessary
to build these projects twice: Once for the intermediate installation
and another time for the final one. Relocation remains critical... ;)

>>> [...] The only thing that required
>>> some thinking was writing a relocatable XXXConfig.cmake file. I think I
>>> will update my tutorial on the WIKI to use a un-configured, relocatable
>>> XXXConfig.cmake file.
>>
>> Just a hint for that tutorial, though off-topic: Targets may usually
>> not be defined multiple times, i.e. the export file generated by
>> INSTALL(EXPORT ...) may not be included more than once, so the
>>
>> include("@FOOBAR_CMAKE_DIR@/FooBarLibraryDepends.cmake")
>>
>> should possibly be guarded by IF(NOT TARGET ...)/ENDIF() constructs.
>> Otherwise, the resulting FooBarConfig.cmake file must not be loaded
>> twice or more - unless one is aware of the imported targets' feature
>> of being, say, "half-scoped", cf. [1]. This might be an uncomfortable
>> limitation, in particular w.r.t. multi-component packages. Regrettably,
>> such IF(NOT TARGET ...)/ENDIF() constructs can hardly be automated, so
>> one could perhaps consider to allow redefinitions for imported targets.
>> Due to the absence of sources, this should be much easier to implement
>> than for non-imported targets.
> 
> Good point. I will do something like this:
> 
> get_property(FOOBAR_INCLUDED GLOBAL PROPERTY FOOBAR_INCLUDED DEFINED)
> if(NOT FOOBAR_INCLUDED)
> # include FooBarLibraryDepends.cmake here
> set_property(GLOBAL PROPERTY FOOBAR_INCLUDED TRUE)
> endif()

Don't do it in this way. First, the DEFINED clause of GET_PROPERTY()
queries if the property has been *defined* by DEFINE_PROPERTY(); if
it is *set* by SET_PROPERTY() et al. doesn't matter in this regard.
See the following CMakeLists.txt:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(PROPERTIES NONE)
GET_PROPERTY(d GLOBAL PROPERTY P DEFINED)
GET_PROPERTY(s GLOBAL PROPERTY P SET)
MESSAGE("P initially: defined=${d}, set=${s}")
SET_PROPERTY(GLOBAL PROPERTY P TRUE)
GET_PROPERTY(d GLOBAL PROPERTY P DEFINED)
GET_PROPERTY(s GLOBAL PROPERTY P SET)
MESSAGE("P after setting: defined=${d}, set=${s}")
DEFINE_PROPERTY(GLOBAL PROPERTY P BRIEF_DOCS "P" FULL_DOCS "P")
GET_PROPERTY(d GLOBAL PROPERTY P DEFINED)
GET_PROPERTY(s GLOBAL PROPERTY P SET)
MESSAGE("P after defining: defined=${d}, set=${s}")

Thus, you must use GET_PROPERTY()'s SET clause to achieve your aim.

Moreover, imported targets - in contrast to traditional ones - are, as
I said, "half-scoped", i.e. they've a scope like variables, but cannot
be overwritten/redefined. As a consequence, you can define an imported
target in a subdirectory, and subsequently, i.e. after returning from
ADD_SUBDIRECTORY(), you can define it again the current scope. If you
guard an export file's inclusion by a global property, the inclusion
will fail if it has taken place earlier in a subdirectory, so the
imported targets will be undefined in the current scope. See the
following CMakeLists.txt files:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(FOOBAR NONE)
ADD_SUBDIRECTORY(subdir)
GET_PROPERTY(FOOBAR_DEFINED GLOBAL PROPERTY FOOBAR_DEFINED SET)
IF(NOT FOOBAR_DEFINED)
    ADD_EXECUTABLE(foobar IMPORTED)
    SET_PROPERTY(GLOBAL PROPERTY FOOBAR_DEFINED TRUE)
ENDIF()
IF(TARGET foobar)
    MESSAGE("Target foobar defined at toplevel")
ELSE()
    MESSAGE("Target foobar NOT defined at toplevel")
ENDIF()

# subdir/CMakeLists.txt:
GET_PROPERTY(FOOBAR_DEFINED GLOBAL PROPERTY FOOBAR_DEFINED SET)
IF(NOT FOOBAR_DEFINED)
    ADD_EXECUTABLE(foobar IMPORTED)
    SET_PROPERTY(GLOBAL PROPERTY FOOBAR_DEFINED TRUE)
ENDIF()
IF(TARGET foobar)
    MESSAGE("Target foobar defined in subdir")
ELSE()
    MESSAGE("Target foobar NOT defined in subdir")
ENDIF()

Instead, you should use either directory properties to protect imported
targets from being redefined - appropriate to their scoping - or just
ordinary variables, though none of these solutions is bullet-proof.

Alternatively, one might consider to provide a function specifically
designed for the accident-free inclusion of export files, e.g. like:

FUNCTION(INCLUDE_EXPORT EXPORTFILE)
    SET(TARGETSDEFINED FALSE)
    FOREACH(i IN LISTS ARGN)
        IF(TARGET "${i}")
            SET(TARGETSDEFINED TRUE)
        ENDIF()
    ENDFOREACH()
    IF(NOT TARGETSDEFINED)
        INCLUDE(${EXPORTFILE})
    ENDIF()
ENDFUNCTION()

Apparently, a function's scope does not shield an imported target from
the surrounding scope as a directory's scope does. However, one needs
to provide the targets when invoking INCLUDE_EXPORT(), but probably
it's quite possible to retrieve them automatically by scanning the
export file, e.g. with an ADD_.+\(([^ ]+)IMPORTED\) or the like.

IMO, the interdiction of - equivalently - redefining imported targets
is currently the latters' main disadvantage since this means exactly
the above-mentioned trouble when dealing with them in config files.

Regards,

Michael

>> Regards,
>>
>> Michael
>>
>> [1] http://www.mail-archive.com/cmake@cmake.org/msg29441.html
> 
> Michael


More information about the CMake mailing list