Managing namelink install target export

Hi Everyone,
I have a problem using install(TARGETS EXPORT and install(EXPORT.
I have to support CMake 3.2 so I can’t use NAMELINK_COMPONENT.
My simplified code is below, it’s taken mostly from @craig.scott’s book.
My problem is that MyLibTargetsDevelopment on windows when building a dll has no contents so the command install(EXPORT MyLibTargetsDevelopment complains that the export does not exist.
Does anyone know how to fix it without having to wrap stuff in IF (WIN32)?
Thanks in advance!

add_library(MyLib some.cpp sources.cpp)
add_library(MyLib::MyLib ALIAS MyLib)
set_target_properties(MyLib PROPERTIES
        CXX_STANDARD 11
        CXX_STANDARD_REQUIRED ON
        CXX_VISIBILITY_PRESET hidden
        VISIBILITY_INLINES_HIDDEN ON
        SOVERSION ${VERSION_MAJOR}
        VERSION ${VERSION_SHORT}
        EXPORT_NAME "MyLib"
)
install(TARGETS MyLib
        EXPORT MyLibTargetsRuntime
        RUNTIME
            DESTINATION ${CMAKE_INSTALL_BINDIR}
            COMPONENT MyLib_Runtime
        LIBRARY
            DESTINATION ${CMAKE_INSTALL_LIBDIR}
            COMPONENT MyLib_Runtime
            NAMELINK_SKIP
)
install(TARGETS MyLib
        EXPORT MyLibTargetsDevelopment
        INCLUDES
            DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
            COMPONENT MyLib_Development
        LIBRARY
            DESTINATION ${CMAKE_INSTALL_LIBDIR}
            COMPONENT MyLib_Development
            NAMELINK_ONLY
        ARCHIVE
            DESTINATION ${CMAKE_INSTALL_LIBDIR}
            COMPONENT MyLib_Development
)
install(EXPORT MyLibTargetsRuntime
        FILE MyLibTargetsRuntime.cmake
        NAMESPACE MyLib::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
        COMPONENT MyLib_Runtime
)
install(EXPORT MyLibTargetsDevelopment
        FILE MyLibTargetsDevelopment.cmake
        NAMESPACE MyLib::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
        COMPONENT MyLib_Development
)

We don’t support splitting parts of targets into different export sets. Basically, use a single export set for each target, but components to split bits of targets into different “packages”. Please see this thread for earlier discussion.

The problem with that thread is that they are using NAMELINK_COMPONENT. If I could use that I would be set and would just collapse the 2 exports in 1 but unfortunately it was added in 3.12 and I’m stuck with CMake 3.2
I found this talk from Mr Scott where he states that it’s possible to do even without NAMELINK_COMPONENT but it doesn’t say how

@craig.scott Do you have details on that?

I’m afraid I’m not sure how it’d be done without NAMELINK_COMPONENT, but I’m also not any kind of authority in that area either. If you’re stuck with 3.2, you’re stuck with 3.2’s feature set which might not cover this use case. I would recommend pushing to require a newer CMake to achieve the desired results as well.

You may have a look onto this discussion. We solved it the following way:

  if(NOT CMAKE_VERSION VERSION_LESS 3.12)
    install(TARGETS myProj EXPORT myProj
      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime NAMELINK_COMPONENT development
      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT development
      RESOURCE DESTINATION ${CMAKE_INSTALL_DATADIR} COMPONENT development
    )
    if(BUILD_SHARED_LIBS)
      if(MSVC)
        install(FILES $<TARGET_PDB_FILE:myProj> DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Debug RelWithDebInfo COMPONENT debug)
      endif()
    endif()
  else()
    install(TARGETS myProj EXPORT myProj
      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime NAMELINK_SKIP
      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT development
      RESOURCE DESTINATION ${CMAKE_INSTALL_DATADIR} COMPONENT development
    )
    if(BUILD_SHARED_LIBS)
      install(TARGETS myProj EXPORT myProj LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT development NAMELINK_ONLY)
      if(MSVC)
        install(FILES $<TARGET_PDB_FILE:myProj> DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Debug RelWithDebInfo COMPONENT debug)
      endif()
    endif()
  endif()

This code uses the same name of export for the NAMELINK_SKIP and NAMELINK_ONLY I’m afraid the second one overwrites the first leading to cmake --build . --target install to just not copy the library files (I tested it). Am I missing something obvious?

Using different export names isn’t supported even with CMake master today either.

No, i get this, using different names was my failed attempt of making it work, however, using the same name owerwrites it, correct?

I can see that happening, yes, but it’s not an area of CMake that I’m intimately familiar with either. I think your use case was enabled by the addition of the NAMELINK_COMPONENT feature and that 3.2 just doesn’t support what you’re looking for (or I could be misunderstanding your end goal here).

I don’t see the overwriting behavior you describe. See my demonstrator project below which seems to work as expected for me. Some points to note:

  • As @ben.boeckel already noted, there should be only one export set for this scenario.
  • The func.cpp file needs to have a #include "func.h" or else the func() function isn’t exported. The header is what marks the symbol as to be exported, so without that header being included, the compiler wouldn’t know to export the func() function when it compiles func.cpp and it compiles it with hidden visibility. That would result in no import library being generated or installed, which maybe is what you’re seeing?
Demonstrator project

CMakeLists.txt:

cmake_minimum_required(VERSION 3.2)
project(namelinks)

add_library(func SHARED func.cpp)
set_target_properties(func PROPERTIES
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN TRUE
    SOVERSION 1
    VERSION 1.2.3
)
target_include_directories(func PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

include(GNUInstallDirs)
include(GenerateExportHeader)

generate_export_header(func)

install(TARGETS func EXPORT myProj
    RUNTIME
        DESTINATION ${CMAKE_INSTALL_BINDIR}
        COMPONENT runtime
    LIBRARY
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
        COMPONENT runtime
        NAMELINK_SKIP
    ARCHIVE
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
        COMPONENT development
)
install(TARGETS func EXPORT myProj
    LIBRARY
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
        COMPONENT development
        NAMELINK_ONLY
)
install(EXPORT myProj
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myProj
    NAMESPACE MyProj::
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/func_export.h
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    COMPONENT development
)

func.h

#pragma once

#include "func_export.h"

FUNC_EXPORT int func();

func.cpp

#include "func.h"

int func() { return 23; }