[CMake] Changing installation prefix triggers re-linking of all libraries

Michael Hertling mhertling at online.de
Sun May 8 21:08:28 EDT 2011


On 05/05/2011 05:14 PM, Pere Mato Vila wrote:
> 
> 
>> If I understand correctly, it's this second point which causes your
>> astonishment, but it already happens during the build phase in the
>> build tree, not just when installing with "make install", right?
> 
> Yes,  this is indeed my astonishment. I understand that for 'normal' projects this re-link in the build tree will be almost unnoticeable. Unfortunately in our project we build at least one executable using these libraries that is a code generator and therefore all generated code for many other libraries gets regenerated, re-compiled and re-linked. It is a major re-build just for changing the length of the installation directory. So, I see that the only solution I have is to avoid using CMAKE_INSTALL_RPATH. Thanks very much for your explanations.
> Regards,
> 
> Pere

Uuhhh, a code generator with its far-reaching dependency implications
is quite unpleasant in this regard, indeed. Perhaps, there's a chance
to accomplish nearly what you have in mind. If I've got it right, the
problem is that one can not distinguish the "good" relink operations,
i.e. due to changes in source code or prerequisite targets, from the
"bad" ones, i.e. due to the placeholder mechanism when the RPATH has
changed. So, a possible approach to prevent the code generator from
becoming out-of-date due to a mere RPATH change in its prerequisite
libraries is to dissolve the generator's dependency on the affected
libraries, take it into account by other means and ignore it when
solely a new RPATH is written to the binaries. See the following
CMakeLists.txt file et al. for an example how to achieve this:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(RPATH C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
ADD_EXECUTABLE(gen gen.c)
ADD_CUSTOM_COMMAND(OUTPUT main.c COMMAND gen > main.c DEPENDS gen)
ADD_EXECUTABLE(main main.c)
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
ADD_LIBRARY(f SHARED f.c)
IF(NOT RPATH)
    ADD_CUSTOM_COMMAND(TARGET f POST_BUILD COMMAND ${CMAKE_COMMAND} -E
        copy $<TARGET_FILE:f> ${CMAKE_BINARY_DIR}/libf_gen.so)
ENDIF()
ADD_LIBRARY(g SHARED g.c)
IF(NOT RPATH)
    ADD_CUSTOM_COMMAND(TARGET g POST_BUILD COMMAND ${CMAKE_COMMAND} -E
        copy $<TARGET_FILE:g> ${CMAKE_BINARY_DIR}/libg_gen.so)
ENDIF()
INSTALL(TARGETS f g LIBRARY DESTINATION lib)
ADD_CUSTOM_TARGET(gen_libs
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target f
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target g)
ADD_DEPENDENCIES(gen gen_libs)
ADD_LIBRARY(f_gen SHARED IMPORTED)
SET_TARGET_PROPERTIES(f_gen PROPERTIES
    IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/libf_gen.so)
ADD_LIBRARY(g_gen SHARED IMPORTED)
SET_TARGET_PROPERTIES(g_gen PROPERTIES
    IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/libg_gen.so)
TARGET_LINK_LIBRARIES(gen f_gen g_gen)

/* f.c: */
void f(void){}

/* g.c: */
void g(void){}

/* gen.c: */
#include <stdio.h>
int main(void)
{
    printf("int main(void){return 0;}\n"); return 0;
}

The executable gen is a code generator that produces the main target's
source code and is linked against the libraries f and g which in turn
are affected by the RPATH placeholder mechanism. If I'm not mistaken,
this models the setup of your project as you have outlined it in your
previous posting. Now, if there's the usual dependency of gen on f/g,
i.e. TARGET_LINK_LIBRARIES(gen f g), the generator would become out-
of-date when f or g is touched, including simple RPATH changes, and
this would result in the generator and the entire depending stuff
being rebuilt. So, the job is to address gen's dependency on f/g.

The dependency of gen on f/g is realised in an unusual manner: The
libraries have a POST_BUILD custom command that copies the library
files libf.so and libg.so to libf_gen.so and libg_gen.so, and gen is
linked against these copies. Additionally, gen depends on the custom
target gen_lib which (re)builds the libraries including their copies
via "cmake --build". In this way, whenever gen is to be rebuilt, the
libraries are rebuilt before - if necessary - and gen links against
the updated libf_gen.so and libg_gen.so. Such a dependency is *not*
tracked by CMake, so it might be dissolved without invalidating the
generator target. This is done by reconfiguring the project while not
taking the libraries' POST_BUILD custom commands into account anymore,
i.e. a reconfiguration with -DRPATH=ON, cf. the IF(NOT RPATH)-ENDIF()
constructs around the custom commands. Thereafter, lib{f,g}_gen.so are
not updated along with lib{f,g}.so and accordingly, the generator with
its file-level dependencies on solely the formers is not relinked, i.e.
gen's dependency on f and g is removed without invalidating gen at the
same time. Afterwards, you can change CMAKE_INSTALL_PREFIX and rebuild
and you'll see that just lib{f,g}.so are relinked with the new RPATH's
placeholders, but gen and especially main are left alone. A subsequent
"make install" installs the project with lib{f,g}.so equipped with the
final RPATH derived from CMAKE_INSTALL_PREFIX and without any rebuild
operation, the issue you've complaint about in your initial posting.

To crown it all, you have to link gen against lib{f,g}_gen.so using
imported targets and their IMPORTED_LOCATION property; if you try to
link against lib{f,g}_gen.so directly, the linker command will change
from -L/-l combinations to relative paths when the project is switched
to, say, RPATH mode, so gen's link.txt will be touched and gen relinked
which is exactly to be prevented. Furthermore, the above-noted approach
has some other drawbacks: While targets are not unnecessarily rebuilt,
they are unnecessarily often checked for being out-of-date, but this
should be a minor penalty compared to rather unnecessary rebuilds of
considerable parts of the whole project. Additionally, after you've
switched the project to RPATH mode by reconfiguring with -DRPATH=ON,
a change in the source code or prerequisite targets of f/g wouldn't
trigger a rebuild of gen, although this could be required in such a
case - as I said before, you can't distinguish good from bad... ;)
Finally, after switching back to, say, normal mode by -DRPATH=OFF,
the targets are out-of-date, so a "make" will rebuild everything.

Due to the latter point, I would recommend the following procedure:

- Work on the project and build it until you are ready for installation.
- Enable RPATH mode by "cmake . -DRPATH=ON -DCMAKE_INSTALL_PREFIX=...",
  rebuild the affected targets by "make" and install by "make install".
- Set new installation prefix by "cmake . -DCMAKE_INSTALL_PREFIX=...",
  rebuild the affected targets by "make" and install by "make install".
- Repeat the previous step for each installation you want to perform.
- Reenable normal mode by "cmake . -DRPATH=OFF" to continue working
  on the project, but be aware of the imminent extensive rebuild.

To my regret, I don't see any easy solution for your concern, but if
the unnecessary rebuilds due to the the RPATH placeholder mechanism
are a serious issue in your project, the above-noted approach can
possibly be adapted to your needs.

'hope that helps.

Regards,

Michael


More information about the CMake mailing list