[CMake] Examples creating exported Makefiles and correct library link order

Michael Hertling mhertling at online.de
Wed Jun 15 04:05:32 EDT 2011


On 06/15/2011 12:56 AM, Lori Pritchett-Sheats wrote:
> My project needs to create and install a Makefile that other client 
> projects could import to link against our project. T
> 
>  I have a simple template file that generates an exported Makefile using 
> the configure_file command, however I'm struggling with how to create a 
> variable that contains all the libraries our project builds and the 
> correct ordering for linking. I noticed in the CMakeCache.txt file there 
> are variables {target name}_LIB_DEPS  with the dependencies listed, but 
> I can't find any documentation on these variables or find a way to 
> access all the library target names to even read these variables.
> 
> I've looked at the Trilinos build system to see how they do this but 
> it's not straightforward. Does anyone know of a smaller project that 
> creates a exported Makefile that generates correct library link orders?

Look at the following exemplary project:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(MAKEFILE C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
INSTALL(SCRIPT file.cmake)
FILE(WRITE ${CMAKE_BINARY_DIR}/f.c "void f(void){}\n")
FILE(WRITE ${CMAKE_BINARY_DIR}/g.c "void g(void){}\n")
FILE(WRITE ${CMAKE_BINARY_DIR}/h.c "void h(void){}\n")
ADD_LIBRARY(f SHARED f.c)
ADD_LIBRARY(g SHARED g.c)
ADD_LIBRARY(h SHARED h.c)
TARGET_LINK_LIBRARIES(f g)
TARGET_LINK_LIBRARIES(g h)
ADD_CUSTOM_COMMAND(TARGET f
    COMMAND sh ${CMAKE_SOURCE_DIR}/register.sh $<TARGET_FILE:f>)
ADD_CUSTOM_COMMAND(TARGET g
    COMMAND sh ${CMAKE_SOURCE_DIR}/register.sh $<TARGET_FILE:g>)
ADD_CUSTOM_COMMAND(TARGET h
    COMMAND sh ${CMAKE_SOURCE_DIR}/register.sh $<TARGET_FILE:h>)
INSTALL(TARGETS f DESTINATION /dev/shm/usr/bin)
INSTALL(TARGETS g h DESTINATION lib)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E remove register.txt)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E touch register.txt)
INSTALL(CODE "MESSAGE(\"Revert installs.txt to generate Makefile.\")")

# register.sh:
if ! grep "$1" register.txt; then
    echo "$1" >> register.txt
fi

# file.cmake:
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E
    copy register.txt installs.txt)
FUNCTION(FILE)
    IF(ARGV0 STREQUAL "INSTALL")
        MATH(EXPR N "${ARGC}-1")
        FOREACH(i RANGE 1 ${N})
            IF(ARGV${i} STREQUAL "DESTINATION")
                MATH(EXPR j "${i}+1")
                SET(d "${ARGV${j}}")
            ENDIF()
            IF(ARGV${i} STREQUAL "FILES")
                MATH(EXPR j "${i}+1")
                SET(f "${ARGV${j}}")
            ENDIF()
        ENDFOREACH()
        _FILE(STRINGS installs.txt INSTALLS)
        _FILE(WRITE installs.txt)
        FOREACH(i IN LISTS INSTALLS)
            IF(i STREQUAL f)
                GET_FILENAME_COMPONENT(n "${i}" NAME)
                _FILE(APPEND installs.txt "${d}/${n}\n")
            ELSE()
                _FILE(APPEND installs.txt "${i}\n")
            ENDIF()
        ENDFOREACH()
    ENDIF()
    UNSET(ARGS)
    FOREACH(i IN LISTS ARGN)
        IF(i STREQUAL "")
            LIST(APPEND ARGS "\"\"")
        ELSE()
            LIST(APPEND ARGS "${i}")
        ENDIF()
    ENDFOREACH()
    _FILE(${ARGS})
ENDFUNCTION()

The basic idea is as follows: The affected targets are equipped with
a custom command which invokes the register.sh shell script with the
target's location in the build tree as argument; these locations are
collected in the register.txt file. As CMake builds the targets in
the least-to-most dependent order, this order will be retained in
register.txt. The register.sh script might also be written as a
CMake script for increased platform independence.

Now, to get a target's location after it's installed, one must tweak
the cmake_install.cmake script since this is the only instance where
both locations are explicitly denoted, AFAIK. The file.cmake script
is called before the INSTALL(TARGETS ...) commands and redefines the
FILE() command. In FILE(INSTALL ...), the new FILE() command version
scans its parameters for the DESTINATION and FILES options, compares
the latter's argument with the entries in register.txt and writes out
the target's associated location in the installation directory to the
installs.txt file. Of course, the original FILE() command is finally
called; for this to work, empty parameters must be specified as \"\".

In the end, the installs.txt file contains the installed targets in
least-to-most dependent order. For the generation of a Makefile used
by other projects, one should write a CMake script that is invoked by
INSTALL(SCRIPT ...) after the INSTALL(TARGETS ...) commands, reads the
install.txt file and uses the entries in reverted order. Subsequently,
the newly generated Makefile can be installed via INSTALL(FILES ...).

Alternatively, one might consider to scan the cmake_install.cmake
script for "FILE(INSTALL ...)" patterns and pick up the DESTINATION
and FILES options' arguments instead of redefining FILE(). Moreover,
one should consider to remove installs.txt and the generated Makefile
from the build tree via INSTALL(CODE|SCRIPT ...) as they'll belong to
root after "su -c 'make install'", so otherwise, one could not delete
the build tree anymore without the reacquisition of root privileges.

'hope that helps.

Regards,

Michael


More information about the CMake mailing list