[CMake] IMPORTED / EXPORTED targets

Michael Hertling mhertling at online.de
Mon May 16 21:16:01 EDT 2011


On 05/12/2011 10:41 PM, Jérémy Coulon wrote:
> Hello,
> 
> I'm currently playing with imported / exported targets.
> Let's say I build a project A for both static and shared variants.
> A depends on a 3rd party static library called B.
> Internally, B depends on another static library called C.
> 
> My project A builds fine.
> But when I try to export my targets it seems like the dependency graph 
> is not correct.
> Thus I probably won't be able to use A in another project.
> 
> See attached CMakeLists.txt to see how I import / export targets.
> 
> For target A_SHARED everything looks OK.
> But I would expect that for target A_STATIC the following property
> IMPORTED_LINK_INTERFACE_LIBRARIES_NOCONFIG "B_STATIC;C_STATIC"
> but the "C_STATIC" dependency is missing from the generated 
> MyExports-noconfig.cmake
> 
> Please, can you tell me if there is something wrong in my cmake script ?
> What is the expected behavior ?
> 
> Thanks.
> 
> Jérémy

Indeed, there would be difficulties w.r.t. {B,C}_STATIC when using your
project A, but A_STATIC's IMPORTED_LINK_INTERFACE_LIBRARIES properties
don't cause them. Rather, it is the way export files are generated.

First of all, don't use "./lib" and "./share" as destinations in the
INSTALL() command. In the export files generated by INSTALL(EXPORT),
CMake computes the targets' IMPORTED_LOCATION property by ascending
from the export file's location to the CMAKE_INSTALL_PREFIX and sub-
sequently appending the targets' relative destination directory. The
ascension is obviously done by counting intermediate directories, i.e.
from ${CMAKE_INSTALL_PREFIX}/./share to ${CMAKE_INSTALL_PREFIX}/./lib,
it will be ${CMAKE_INSTALL_PREFIX}/./share/../.././lib which evaluates
to ${CMAKE_INSTALL_PREFIX}/../lib since "." and ".." don't neutralize.
Possibly, this is a bug in CMake; circumvent it by specifying "share"
and "lib" as relative destination directories without a leading dot.

Now, when you generate an export file with INSTALL(EXPORT), it will
only contain information about the targets defined by your project,
but not the ones your project imports, or: Export files are minimal,
cf. [1]. So, MyExports-noconfig.cmake will not have any idea of the
{B,C}_STATIC targets, and since A_STATIC is linked against B_STATIC,
the latter appears in the former's IMPORTED_LINK_INTERFACE_LIBRARIES
property but without further relations, particularly no properties
for the location or interface libraries. To see the consequences,
configure/build/install project A - with ./{lib,share} replaced
by {lib,share} - and look at the following CMakeLists.txt with
CMAKE_MODULE_PATH set to the directory of A's export file:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(IMEX C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
INCLUDE(MyExports)
FILE(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main A_STATIC)

The main target's link command contains the full path to A_STATIC but
only a -l switch for B_STATIC; this is due to the lack of information
about the B_STATIC target. Of course, the link command fails because
B_STATIC can't be found. Now, add B_STATIC's information, i.e.

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(IMEX C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
add_library(B_STATIC STATIC IMPORTED)
set_target_properties(B_STATIC PROPERTIES
  IMPORTED_LINK_INTERFACE_LANGUAGES "C"
  IMPORTED_LINK_INTERFACE_LIBRARIES "C_STATIC"
  IMPORTED_LOCATION "/usr/lib/libpng12.a"
)
INCLUDE(MyExports)
FILE(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main A_STATIC)

verbosely taken from your example. The link command contains the full
path to B_STATIC, i.e. /usr/lib/libpng12.a, and a -lC_STATIC because
C_STATIC is mentioned in B_STATIC's IMPORTED_LINK_INTERFACE_LIBRARIES
but without further relations. Still, the link command fails. Finally,
add the information about C_STATIC, again verbosely from your example:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(IMEX C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
add_library(C_STATIC STATIC IMPORTED)
set_target_properties(C_STATIC PROPERTIES
  IMPORTED_LINK_INTERFACE_LANGUAGES "C"
  IMPORTED_LOCATION "/usr/lib/libz.a"
)
add_library(B_STATIC STATIC IMPORTED)
set_target_properties(B_STATIC PROPERTIES
  IMPORTED_LINK_INTERFACE_LANGUAGES "C"
  IMPORTED_LINK_INTERFACE_LIBRARIES "C_STATIC"
  IMPORTED_LOCATION "/usr/lib/libpng12.a"
)
INCLUDE(MyExports)
FILE(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main A_STATIC)

The link command contains the full path to the C_STATIC library, i.e.
/usr/lib/libz.a, and succeeds. Note that there is no need to mention
C_STATIC in A_STATIC's IMPORTED_LINK_INTERFACE_LIBRARIES because this
property works in a *transitive* manner. However, you need to provide
enough information about the prerequisite libraries like {B,C}_STATIC;
otherwise, CMake will do its best and try to link with -l{B,C}_STATIC.

To make your project A useful for other projects, you should provide a
configuration file AConfig.cmake or a-config.cmake that typically does:

1. Invoke FIND_PACKAGE(B) which will provide the information about the
B_STATIC library as an imported target. B's configuration file or find
module will in turn call FIND_PACKAGE(C), so C_STATIC's information is
also available in A's configuration file when FIND_PACKAGE(B) returns.
2. Include the export file providing the definition of A's own targets.

Thus, when another project calls FIND_PACKAGE(A), it will receive the
information about C_STATIC, B_STATIC and A_STATIC, just as the last
example above, and any attempt to link against A_STATIC results in
B_STATIC *and* C_STATIC appearing in the link command as expected.
Refer to the CMake Wiki for a detailed tutorial on config files.

'hope that helps.

Regards,

Michael

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


More information about the CMake mailing list