INTERFACE library not getting installed and no error reported

After INTERFACE (header only library) is added with add_library and setting of INTERFACE for BUILD_INTERFACE and INSTALL_INTERFACE with target_include_directories, followed by install(TARGETS EXPORT and install(EXPORTS (see details below), the INTERFACE library is not installed. There were generated and configuration and generation phases completed successfully. Then make install passed fine but INTERFACE library was not installed.
It could be installed directly via install(DIRECTORY but that is not acceptable method. Attempts to get it installed as a dependent library to main mylib SHARED library also did not succeed.
Could you please advise how this should be done.

EXPECTED RESULTS:
${ARCHITECTURE}/myintlib/include DIR is created

ACTUAL RESULTS
no ${ARCHITECTURE}/myintlib/include DIR is created

CODE FLOW:

add_library(myintlib  INTERFACE) 
target_include_directories(myintlib
    INTERFACE
        $<BUILD_INTERFACE:${IMPORTED_LOCATION_ROOT}/include>
        $<INSTALL_INTERFACE:${ARCHITECTURE}/myintlib/include>  
)

target_link_libraries(mylib ...
        myintlib
)


install(TARGETS  myintlib 
    EXPORT ${proj_targets_export_name}         
    CONFIGURATIONS ${CMAKE_BUILD_TYPE}
    INCLUDES DESTINATION ${CMAKE_INSTALL_PREFIX}/myintlib/include
      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(EXPORT ${proj_targets_export_name}
      DESTINATION "${CONFIG_INSTALL_DIR}"
      FILE ${proj_targets_export_filename}  
      COMPONENT ${PROJECT_NAME}_Development
)

Note: the builtTargets obtained as get_directory_property(builtTargets BUILDSYSTEM_TARGETS) lists mintlib

What are you expecting to happen vs. what you’re seeing? Is myintlib mentioned in the installed -targets.cmake file?

expected results- ${ARCHITECTURE}/myintlib/include DIR is created and this is not happening.
Other non-INTERFACE (SHARED or STATIC) libraries (e.g. add_library(xyzlib SHARED) and main mylib) are installed properly. The myTarget.cmake (proj_targets_export_name) file does contain commands to import myintlib:

# Create imported target myintlib
 add_library(myintlib INTERFACE IMPORTED) , set_target_properties(myintlib PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/.../myintlib/;....myintlib/include"
)

and lists myintlib in INTERFACE_LINK_LIBRARIES property of mylib as normal

Hmm. I’ve not used HEADERS DESTINATION myself. Maybe this is an oversight?

Cc: @kyle.edwards @craig.scott

What do you mean by that? Do you call “headers-only-library” as HEADERS? That would correspond to cmake INTERFACE library. So are you doubting that cmake 3.17 I use actually supports installation of INTERFACE library despite documentation indications?

I’ve used INTERFACE libraries, but I haven’t used HEADERS DESTINATION to install the headers. I’ve used install(FILES) for a while (mainly out of habit).

I have not used “HEADERS DESTINATION” either so I assume you meant INCLUDES DESTINATION ${CMAKE_INSTALL_PREFIX}/myintlib/include. Or maybe you meant " PUBLIC_HEADER DESTINATION …include" ? Then would not I need to use PUBLIC, not INTERFAC Ein the add_library? The approach with install(FILES…) works but I want to use modern cmake approach with referencing only targets and setting their properties. So could you please check with Craig?

D’oh. Yes, INCLUDES DESTINATION. Sorry. Obviously I haven’t used this codepath myself much :slight_smile: .

NP, is theere a way to to contact other cmake developers on this? Thank you.

I’ve pinged them above. I don’t know their schedules though.

You aren’t specifying an install rule to copy the include folder. You can take this project for a working example: KonanM/tser

Thank you for looking into this, but the install rule for include folder is specified in the statement “INCLUDES DESTINATION” see lines above

Blockquote

install(TARGETS myintlib
…
INCLUDES DESTINATION ${CMAKE_INSTALL_PREFIX}/myintlib/include

Blockquote
The sample project you referring to uses the same statement, so the question is why the installation got blocked

INCLUDES DESTINATION is a special form that populates the INTERFACE_INCLUDE_DIRECTORIES property of the target(s) in the generated targets file. Obviously, there has to be something where that path is pointing to. See lines 55 and 56.

Yes it is and it is present as per my issue description and path ${CMAKE_INSTALL_PREFIX}/myintlib/include is a valid path. Again as I mentioned before when installing from the same path using install(DIRECTORY ) it gets installed fine.Your example KonnaM/tser illustrate the problem as it uses install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION "${tser_include_directory}") direct install instead of modern cmake approach using targets only. install(TARGETS …), install(EXPORTS…) should be sufficient to install target with its INCLUDE directory. This works fine for non INTERFACE targets (SHARED or STATIC) and should be working without use of install(DIRECTORY). So could you please use your own example to investigate the issue? Thanks

You seem to be mixing things up a lot. The repository in question is using modern CMake techniques.
You might want to either sink your teeth into the documentation or buy Craig Scott’s book.

probably, I did buy Craigs book. Can you be more specific (reference to book chapter)? The issue is that i expected install(TARGETS target …) would install files that are associated with the target via its properties without the need to install(FILES …)for theses files

There’s two main ways to install headers. You can either attach details to a target and have them install as part of that target (which is what it sounds like you are expecting and wanting to do) or you can install them separately as ordinary files. There are pros and cons for either approach.

To attach headers to a target, you use the PUBLIC_HEADER and PRIVATE_HEADER target properties. Then when you install a target, you specify where you want these headers to be installed to.

add_library(MyLibOrFramework SHARED
    foo.cpp
    foo.h
    foo_privateA.h
    nested/foo_privateB.h
)

set_target_properties(MyLibOrFramework PROPERTIES
    FRAMEWORK TRUE
    PUBLIC_HEADER foo.h
    PRIVATE_HEADER "foo_privateA.h;nested/foo_privateB.h"
)

install(TARGETS MyLibOrFramework
    FRAMEWORK            # Apple framework case
        DESTINATION ...
    LIBRARY              # Non-Apple case
        DESTINATION ...
    PUBLIC_HEADER
        DESTINATION include/MyProj             # (A)
    PRIVATE_HEADER
        DESTINATION include/MyProj/private...  # (B)
 )

Specify at (A) where public headers should be installed (relative to the install base directory) and at (B) the location for private headers. I made them different in this case, but they could be the same location. It would be up to you to ensure that your public headers can find any private headers they need.

The main reason for the separation of public and private headers comes from Apple framework structure, which has distinct standard locations for both. On other platforms, there usually isn’t such a distinction made.

Since you asked for references in my Professional CMake book, the above example is combining and slightly modifying examples from Section 23.3.3: Headers (which talks about Apple frameworks) and Section 26.2.3 Apple-specific Targets (which shows how to install those headers on non-Apple platforms).

A limitation of specifying public and private headers via target properties is that it assumes your headers have a flat directory structure. You can’t use this method if your headers need to preserve some kind of directory structure, or more specifically that some headers need to go in different directories to others (apart from the public/private separation). For example, you couldn’t reproduce the following public headers:

include/MyProj/coolthings.h
include/MyProj/Algorithms/specialsauce.h

In this case, you have to use the other method for installing headers, which is to manually do it via install(FILES) or install(DIRECTORY). Then you have complete control over where headers are installed to and can specify whatever directory structure you want. Both the CMake docs and the Professional CMake book include an example like the following for one way to do that, but you can use whatever way fits your needs:

install(DIRECTORY src/
    DESTINATION include/MyProj
    FILES_MATCHING
    PATTERN *.h
)

Personally, unless I’m working specifically on an Apple framework target, I tend to use the manual form because of the need to control the directory structure. For an Apple-only project though, the target property form would probably make more sense.

Yes, Great that is exactly what I meant - installing headers without use of install(FILES or DIRECTORIES, thank you. So now you could see why I am reporting cmake issue. While all works fine per your example, when SHARED is replaced by INTERFACE the headers are not being installed. Such limitation for INTERFACE libraries was not documented . So despite calling install(TARGETS we had to use install(FILES or DIRECTORIES. So first could you please confirm that cmake shall supports installing INTERFACE libraries or point to any limitation int the book or documentation?
Secondly, why the limitation of flat include directory exists? cmake is able to configure build with INTERFACE library with non-flat include structure and installation with EXPORT merely exports build tree to install path. cmake could have installed to directory specified by PUBLIC_HEADERS with all its subdirectories as a default. So why cmake is having problem with this install ?

1 Like

An INTERFACE library was originally only intended as a way of collecting usage requirements, not collecting files to be installed along with the target. Before CMake 3.19, attaching files to an interface library was only meaningful for making files show up in IDEs. There was never an expectation that those files would be installed. Now that CMake 3.19 allows sources to be attached to interface libraries, this might be less clear-cut. @brad.king I’m wondering if it makes sense for interface libraries to now honour the PUBLIC_HEADERS and PRIVATE_HEADERS target properties and install destinations?

Regarding why the flat directory structure limitation, consider how would you specify the location of every header otherwise? There is only one destination that can be specified in an install(TARGETS) call for each of public or private headers, but you can have arbitrarily many headers with an arbitrarily complex directory structure. There are subsets of scenarios that may be tractable (e.g. preserve structure below some specific directory), but they have deficiencies that don’t support general flexibility (e.g. what if headers are not all under a common base point with the directory structure you want when installed?).

These kinds of corner cases always come to mind when I realize install logic tends to flatten things in CMake. However, I think just raising an error for anything not under ${CMAKE_CURRENT_SOURCE_DIR}, ${CMAKE_CURRENT_BINARY_DIR}, or any other potential “ROOT_PATHS” arguments would be a hard error.

So we could have something like:

install(FILES
  ${header_list}
  PRESERVE_HIERARCHY
  ROOT_PATHS # current source and binary are implicit, up for discussion though
    ${some_other_prefix})