[CMake] External projects and make clean

Michael Hertling mhertling at online.de
Mon Feb 20 22:52:47 EST 2012


On 02/17/2012 10:16 AM, Oliver Boesche wrote:
> Hi,
> 
> I use external projects e.g. to build BOOST for my own project. In that 
> case I want to prevent the project to be system or version dependent.
> 
> So I build my dependencies successful with external project (and its 
> great), but if use make clean it will clean all stuff and I have to 
> rebuild the external projects again (take some time with BOOST).
> 
> Is there a solution to prevent an external project from cleaning when I 
> call 'make clean'?
> 
> Kind regards
> 
> Oliver Boesche

For the Makefile generators, I could offer the following approach:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(P C)
SET(CMAKE_VERBOSE_MAKEFILE ON)

INCLUDE(ExternalProject)

ExternalProject_Add(external
    SOURCE_DIR ${CMAKE_SOURCE_DIR}/external
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)

FILE(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
ADD_EXECUTABLE(main main.c)
ExternalProject_Get_Property(external INSTALL_DIR)
TARGET_LINK_LIBRARIES(main ${INSTALL_DIR}/lib/libf.so)

ExternalProject_Get_Property(external STAMP_DIR)

ADD_CUSTOM_TARGET(restorestampfiles
    COMMAND ${CMAKE_COMMAND}
        -DSTAMP_DIR="${STAMP_DIR}"
        -P "${CMAKE_SOURCE_DIR}/restorestampfiles.cmake"
    COMMENT "Restoring stamp files")

ADD_CUSTOM_TARGET(backupstampfiles
    COMMAND ${CMAKE_COMMAND}
        -DSTAMP_DIR="${STAMP_DIR}"
        -P "${CMAKE_SOURCE_DIR}/backupstampfiles.cmake"
    COMMENT "Backing up stamp files")

ADD_CUSTOM_TARGET(toplevel-clean
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                             --target backupstampfiles
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                             --target clean
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                             --target restorestampfiles)

# external/CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(EXTERNAL C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
FILE(WRITE ${CMAKE_BINARY_DIR}/f.c "void f(void){}\n")
ADD_LIBRARY(f SHARED f.c)
INSTALL(TARGETS f DESTINATION lib)

# backupstampfiles.cmake:
FILE(GLOB_RECURSE STAMP_FILES "${STAMP_DIR}/*")
FOREACH(i IN LISTS STAMP_FILES)
    IF(NOT i MATCHES "\\.bak$" AND NOT IS_DIRECTORY "${i}")
        FILE(RENAME "${i}" "${i}.bak")
    ENDIF()
ENDFOREACH()

# restorestampfiles.cmake:
FILE(GLOB_RECURSE STAMP_FILES "${STAMP_DIR}/*")
FOREACH(i IN LISTS STAMP_FILES)
    IF(i MATCHES "\\.bak$")
        STRING(REGEX REPLACE "\\.bak$" "" j "${i}")
        FILE(RENAME "${i}" "${j}")
    ENDIF()
ENDFOREACH()

The basic idea is to backup the external project's stamp files before
cleaning, and restore them afterwards. In this way, the steps of the
external project aren't performed during the next build. As a proof,
configure and build the above-noted exemplary project, then issue:

make clean; make

You'll see that the external and the toplevel project are both rebuild
although the external one's binary survives the cleaning. Now, issue:

make toplevel-clean; make

This time, the external project's steps are left out, and only the
toplevel project is rebuilt. In order to be absolutely sure, remove
the ${CMAKE_BINARY_DIR}/external-prefix/lib/libf.so library file to
see "make toplevel-clean; make" fail, whereas "make clean; make" re-
builds the missing file and, thus, succeeds.

Regrettably, the targets "restorestampfiles" and "backupstampfiles"
can not be turned into a custom step of the external project:

ExternalProject_Add_Step(external restore
    COMMAND ${CMAKE_COMMAND} ... -P ...restorestampfiles.cmake
    DEPENDERS mkdir)

ExternalProject_Add_Step(external backup
    COMMAND ${CMAKE_COMMAND} ... -P ...backupstampfiles.cmake
    DEPENDEES install)

This would be quite elegant, but apparently, the out-of-dateness of the
external project's steps is recognized before the stamp files are re-
stored. Moreover, I didn't manage to make this appoach work with VS.

IMO, the principal question in this regard is: What do we expect from
the "clean" target w.r.t. external projects? Usually, the latters are
not subject to the toplevel project's development, so one would like
to build them once, but omit them from cleaning. OTOH, there must be
a possibility to clean the toplevel project along with the external
ones in order to perform a full rebuild. AFAICS, the EP module does
not support both variants. A conceptual solution I can imagine is:

- Add a "clean" step - running a CLEAN_COMMAND, possibly defaulting
  to "make clean" - to the external project which is performed when
  the toplevel project is cleaned.

- Add a flag, say, EXCLUDE_FROM_CLEAN that, when specified, excludes
  the external project from the toplevel project's "clean" target.

This EXCLUDE_FROM_CLEAN flag in conjunction with the STEP_TARGETS option
of ExternalProject_Add() would allow for the definition of fine-grained
"clean" targets for external projects. E.g.,

External_Project_Add(boost
    ...
    EXCLUDE_FROM_CLEAN
    ...
    STEP_TARGETS clean ....)

ADD_CUSTOM_TARGET(total-clean
    ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean)

ADD_DEPENDENCIES(total-clean boost-clean ...)

would exclude Boost from the usual "clean" target, provide a target
"boost-clean" that cleans Boost only, and finally a target "total-
clean" which cleans the toplevel project along with the external
ones. Perhaps, this might be feasible with an acceptable effort.

Regards,

Michael


More information about the CMake mailing list