[CMake] Trying to set up a "superbuild" with external project and install it...

Michael Hertling mhertling at online.de
Mon May 16 06:42:14 EDT 2011


On 05/15/2011 05:42 PM, Alexander Neundorf wrote:
> On Sunday 15 May 2011, Michael Hertling wrote:
>> On 05/14/2011 07:57 PM, Alexander Neundorf wrote:
> ...
>> Indeed, RPATH-related dependencies among the intermediately installed
>> subprojects are malicious. In order to solve the problem you outlined
>> without doing the whole stuff as root, you would need to install foo
>> to its final destination and reconfigure/rebuild foo-{bar,blub} with
>> CMAKE_PREFIX_PATH set to CMAKE_INSTALL_PREFIX before they're finally
>> installed, too - another example for the worst case. This could be
>> achieved by an own installation target, say, "superinstall", e.g.:
>>
>> ADD_CUSTOM_TARGET(superinstall
>>     COMMAND ${CMAKE_COMMAND} -P cmake_install.cmake
>>     COMMAND <reconfigure/rebuild foo-bar with CMAKE_PREFIX_PATH=...>
>>     COMMAND <reconfigure/rebuild foo-blub with CMAKE_PREFIX_PATH=...>
>>     COMMAND ${CMAKE_COMMAND} -P cmake_install.cmake)
> 
> Well, this basically means rebuilding the whole thing, since e.g. the include 
> directories will have changed, which will cause everything to be recompiled.

Yes, absolutely: Worst case, as already mentioned.

>> The first command installs the superproject including the subprojects
>> with the wrong RPATHs to their final location, the following commands
>> reconfigure/rebuild the affected subprojects as far as necessary, and
>> the final command reinstalls the superproject with correct RPATHs in
>> the subprojects. If there are chained or cascaded dependencies among
>> the subprojects, e.g. foo-blub incorporating ${FOO-BAR_LIBRARY_PATH}
>> in its RPATHs, one would need to intersperse the cmake_install.cmake
>> script more often. In order to prevent that the reconfigure/rebuild
>> steps are run with root privileges, one could add, e.g.,
>>
>> SET(BUILD_USER $ENV{USER} CACHE STRING "...")
>>
>> to the superproject's CMakeLists.txt and write the reconfigure/rebuild
>> commands as "COMMAND su -c '<reconfigure/rebuild ...>' ${BUILD_USER}",
>> provided the superproject is primarily targeted at *nix systems.
>>
>> At the end of the day, this entire issue seems to boil down to the
>> question if one is willing either to rebuild possibly considerable
>> parts of the project during the installation or to acquire root's
>> privileges early to build the project - or to fragment and intermix
>> the build/installation process: make foo; su -c 'make install_foo';
>> make foo-bar foo-blub; su -c 'make install_foo-bar install_foo-blub'
>> etc. Apparently, that's the price of non-trivial superbuild set-ups.
>> Sometimes, I think they lead to more problems than they solve... ;-)
>>
>> An interesting option is the manipulation of the binaries' RPATH just
>> before or after their final installation, similar to CMake's own post-
>> treatment of freshly installed libraries/executables. To do this, one
>> might use utilities like the obviously ceased chrpath or the actively
>> developed PatchELF in association with INSTALL(CODE|SCRIPT ...) or a
>> custom "preinstall" target. 
> 
> Didn't know about patchelf.
> Looks interesting: "PatchELF takes care of “growing” the executable with 
> sufficient space at the beginning to contain the new interpreter field."
> 
> Otherwise, if there is enough room in the executable, one could also use the 
> cmake FILE() command for changing the RPATH.
> 
> But also a problem is that I don't really know how the RPATH is computed for 
> all the subprojects, so they have to do it themselves.
> 
>> However, this would take care of RPATHs
>> but not other paths incorporated in the binaries and derived from
>> results of FIND_PACKAGE() for the intermediate installations.
>>
>> If you actually decide to install the subprojects immediately to their
>> final destinations, I would suggest to think about the above-mentioned
>> BUILD_USER variable or the like and, as the case may be, encompass the
>> configure/build commands in the ExternalProject invocations by "su -c
>> '...' ${BUILD_USER}" to ensure that only the pure installation steps
>> are performed with root's privileges. 
> 
> I think this will be for developers only, as a convenience, and I think it 
> will have the limitation that the developer will need write permission to the 
> install location. Could be e.g. in his home, then it's no problem.
> Everything else seems more like a hack.

A possible implementation of the BUILD_USER approach might look like:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(SUPERBUILD C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
UNSET(SU)
IF(BUILD_USER)
    SET(SU ${CMAKE_SOURCE_DIR}/su ${BUILD_USER})
ENDIF()
INCLUDE(ExternalProject)
ExternalProject_Add(subproject
    CONFIGURE_COMMAND ${SU} ${CMAKE_COMMAND}
        <SOURCE_DIR> -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
    BUILD_COMMAND ${SU} make
    INSTALL_COMMAND make install
    SOURCE_DIR ${CMAKE_SOURCE_DIR}/subproject
    BINARY_DIR ${CMAKE_BINARY_DIR}/subproject
)

# subproject/CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(SUBPROJECT C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
FILE(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
ADD_EXECUTABLE(main main.c)
INSTALL(TARGETS main RUNTIME DESTINATION bin)

The ${CMAKE_SOURCE_DIR}/su script is actually a one-liner

#!/bin/sh
U=$1; shift; exec su -c "$*" $U

that avoids the otherwise difficult insertion of "su -c ..." into the
commands of ExternalProject_Add() dependent on BUILD_USER's presence.

If you want to install the superproject to a location you are allowed
to write to, just leave out the BUILD_USER and proceed as usual, e.g.:

cmake -UBUILD_USER -DCMAKE_INSTALL_PREFIX=/dev/shm/usr <srcdir>

A subsequent "make" builds and installs the subproject as expected.

Now, suppose you want to install to /usr/local without configuring/
building with root privileges; do a configuration with BUILD_USER:

cmake -DBUILD_USER=$USER -UCMAKE_INSTALL_PREFIX <srcdir>

A subsequent "make" will fail because the subproject's main executable
can't be written to /usr/local/bin; moreover, you'll be intermittently
asked for your password. However, a "su -c make" will succeed, and the
subproject's configuration and build step won't be performed with root
privileges.

Finally, try to install the superproject to /usr/local by building with
"su -c make" but *without* the BUILD_USER, i.e. configure as follows:

cmake -UBUILD_USER -UCMAKE_INSTALL_PREFIX <srcdir>

The "su -c make" will succeed, but afterwards, some directories in the
build tree including the subproject's CMakeFiles will be owned by root,
so you don't have absolute mastery of your binary directories anymore;
in particular, you can not get rid of the build tree without acquiring
root privileges again. So, building the superproject as root tends to
spread out, and the question whether the subproject's configure and
build operations are also performed with root privileges shouldn't
be thought little of, IMO.

In summary, if one wants to install to a location for which one needs
root privileges, the superproject must be built as root, but then one
should ensure that preferably anything except for the superproject's
and the subprojects' actual installation procedures is performed with
ordinary privileges. This means good style and increased security and
prevents the pollution of the build tree with root-owned debris. The
above-noted usage of a BUILD_USER variable is a possible approach to
achieve this, and to my mind, it does not look overly like a hack.

Regards,

Michael


More information about the CMake mailing list