[CMake] INSTALL(EXPORT) does not honor LINK_INTERFACE_LIBRARIES?

Michael Hertling mhertling at online.de
Sun Apr 3 11:39:19 EDT 2011


On 04/02/2011 10:52 AM, Rolf Eike Beer wrote:
> Am Donnerstag, 31. März 2011, 15:26:31 schrieb Brad King:
>> On 03/31/2011 09:14 AM, Rolf Eike Beer wrote:
>>> See below. Looks like the only way to prevent this is to set
>>> LINK_INTERFACE_LIBRARIES to empty for every lib that uses the static
>>> lib.
>>> Which may be a good idea anyway as that transitive linking is harmful.
>>
>> CMake has always done said transitive linking for historical reasons.
>> The default behavior cannot change now.  It is not necessarily harmful
>> if the static library objects are built with -fPIC and are not all
>> absorbed into the shared library that initially links it.
> 
> set(DONT_DO_SILLY_LEGACY_OVERLINKING true)? ;)
> 
>> The documented way to turn off transitive linking is to set the
>> LINK_INTERFACE_LIBRARIES property.  Once that is done then install(EXPORT)
>> will not complain about the transitive static libraries anymore.  However,
>> it *will* complain about the *shared* libraries for the reason I explained
>> in my earlier response to this thread.
> 
> I start to get the point ;)
> 
>> On 03/29/2011 05:36 PM, Rolf Eike Beer wrote:
>>> Am Dienstag, 29. März 2011, 09:41:36 schrieb Brad King:
>>>> CMake running in an outside application needs to know these private
>>>> runtime dependencies.  It needs them ensure that the application link
>>>> line is generated such that the linker can find the transitive
>>>> dependencies (e.g. -rpath-link) of the public library.
>>>
>>> No, why should it?
>>
>> See the sample script below.  CMake needs to know where bar's transitive
>> dependencies are installed so that it can pass the right thing to
>> -rpath-link. This is is how IMPORTED_LINK_DEPENDENT_LIBRARIES is used and
>> why the install(EXPORT) command needs all the targets in the export.
>>
>> Since one target can be installed to multiple locations, or accidentally
>> not at all, the install(TARGETS) for 'foo' must list the same EXPORT as
>> the install(TARGETS) for 'bar' so that CMake can associate the right copy
>> of the dependency.
> 
> Isn't the rpath stored inside those shared libraries? So every shared library 
> (and the executable itself) know where it's own dependencies are located?

Sometimes, there are good reasons to not set the RPATH tag in the
resulting binaries, and as a default, CMake indeed doesn't do this
during the installation. Look at the following CMakeLists.txt file:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(P1 C)
FILE(WRITE ${CMAKE_BINARY_DIR}/private.c
    "void private(void){}\n")
ADD_LIBRARY(private SHARED private.c)
FILE(WRITE ${CMAKE_BINARY_DIR}/public.c
    "void public(void){private();}\n")
ADD_LIBRARY(public SHARED public.c)
TARGET_LINK_LIBRARIES(public private)
SET_TARGET_PROPERTIES(public PROPERTIES LINK_INTERFACE_LIBRARIES "")
INSTALL(TARGETS public EXPORT p1 LIBRARY DESTINATION lib)
INSTALL(TARGETS private EXPORT p1 LIBRARY DESTINATION lib/p1)
INSTALL(EXPORT p1 DESTINATION share)

Configure, build and install it, e.g. with CMAKE_INSTALL_PREFIX set to
/dev/shm/usr. It's allowed and quite common to have a project's private
libraries in ${CMAKE_INSTALL_PREFIX}/lib/${PROJECT_NAME} instead of the
usual library directory ${CMAKE_INSTALL_PREFIX}/lib, even if the project
is installed to /usr[/local]. Now, look at the following CMakeLists.txt:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(P2 C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
INCLUDE(p1)
FILE(WRITE ${CMAKE_BINARY_DIR}/main.c
    "int main(void){public();return 0;}\n")
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main public)
INSTALL(TARGETS main RUNTIME DESTINATION bin)

Configure it with CMAKE_MODULE_PATH set to /dev/shm/usr/share, e.g., to
have it find p1.cmake, and build: Everything should work perfectly. Now,
edit /dev/shm/usr/share/p1-noconfig.cmake and remove the line with the
public target's IMPORTED_LINK_DEPENDENT_LIBRARIES_NOCONFIG property,
rebuild and... undefined reference to `private'.

What happens here: You removed the sole information that libpublic.so
depends on libprivate.so, so not only it does not appear in the link
line - that's desired to prevent an explicit dependency of main - but
also its directory does not appear either, and this makes ld fail to
resolve the 'private' symbol. The IMPORTED_LINK_DEPENDENT_LIBRARIES
property's job is exactly to provide that information in case of the
transitive linking being disabled for a shared library, or in other
words: If you disable transitive linking for libpublic.so and do not
set the RPATH tag either, you do need the information conveyed by the
IMPORTED_LINK_DEPENDENT_LIBRARIES property to make the linker succeed
in resolving libprivate.so's symbols, even if they are just indirectly
referred to via libpublic.so. BTW, how would you address this issue on
platforms that do not have a concept like ELF's RPATH tags, e.g. W...?

> Ok, at the end that actually makes sense. But since I'm installing every 
> library to the same directory on Un*x wouldn't it be possible to just drop out 
> all those libraries somehow? [...]

In fact, they *are* dropped out: If you link a binary against a public
shared library which is itself linked against a private shared library,
and if the transitive linking is disabled for the public library, there
will be no references from the installed binary to the private library,
neither by the NEEDED tag nor by the RPATH tag - unless you have CMake
set the latter explicitly. Install P2's main target - after restoring
p1-noconfig.cmake - and investigate with "readelf -d" to prove this.

> [...] It will not matter on Windows anyway as that 
> doesn't support rpath. So private shared libraries will not show up if the 
> rpath would already be set to the directory anyway?

The RPATH has only a mediate meaning for this issue since the linker
not only writes it to the resulting binary but also uses it to look
for libraries in order to resolve symbols, and again: Usually, the
RPATH is empty for installed binaries. However, if you install the
private libraries along the public ones in the same directories,
they are found once the public libraries are found, of course.

> Just to get that right: these targets will show up when importing to another 
> build but CMake will only use them to get the rpath right. [...]

That's true for the build tree, AFAICS, but after the installation,
these targets - I suppose you talk about private shared libraries a
public shared one is linked against with disabled transitive linking -
won't show up anyway except for the public library's NEEDED tag on ELF.

> [...] It will not link 
> against those targets so the transitive dependencies will not be propageted?

Exact. Actually, the IMPORTED_LINK_DEPENDENT_LIBRARIES property,
put into play by disabling the transitive linking, results in the
-rpath-link linker option as Brad has already mentioned, and this
option does not modify the resulting binary - in contrast to -rpath.

To summarize shortly: You don't need to have any dependencies you don't
want to have, and the irremovable connection between private and public
shared libraries in case of a disabled transitive linking, i.e. the
IMPORTED_LINK_DEPENDENT_LIBRARIES property, may be indispensable.

Regards,

Michael


More information about the CMake mailing list