Home > Blockchain >  Installing nested includes with CMake
Installing nested includes with CMake

Time:01-03

I have project that I am able to build a static library for using CMake but I am running into issue when trying to install my headers in the way I want.

Here is a very stripped down example of the file structure of my project:

.
├── modules
|   ├── util1.cpp
|   ├── util2.cpp
│   ├── module_a
|   |   |── file1.cpp
|   |   └── file2.cpp
│   └── module_b
|       |── file3.cpp
|       └── file4.cpp
└── include
    ├── util1.hpp
    ├── util2.hpp
    ├── module_a
    |   |── file1.hpp
    |   └── file2.hpp
    └── module_b
        |── file3.hpp
        └── file4.hpp

The meat of my project is enclosed in a module directory with sub-modules within while the includes follow an identical format. Miscellaneous utility related headers are left floating in the includes dir.

Currently I am installing each header file individually as such:

set(PROJECT_INCLUDES
    ${PROJECT_SOURCE_DIR}/include/util1.hpp
    ${PROJECT_SOURCE_DIR}/include/util2.hpp
    ${PROJECT_SOURCE_DIR}/include/module_a/file1.hpp
    ${PROJECT_SOURCE_DIR}/include/module_a/file2.hpp
    ${PROJECT_SOURCE_DIR}/include/module_b/file3.hpp
    ${PROJECT_SOURCE_DIR}/include/module_b/file4.hpp
)

After installing my library to the necessary paths I am able to include as so:

#include <myproj/util1.hpp>
#include <myproj/file1.hpp>
#include <myproj/file3.hpp>

But wish to have a scheme more like so :

#include <myproj/util1.hpp>
#include <myproj/module_a/file1.hpp>
#include <myproj/module_b/file3.hpp>

What is the proper way to accomplish a process like this?

CodePudding user response:

The way I usually deal with modules Is to make subdirectories with proper src and include folders with their own CMakeLists.txt file and add them into the parent folders with add_subdirectories direttive.

I think that your header files, even though in different folders, are being included to the CMake project at the same level.

CodePudding user response:

The add_subdirectories would make sense if your directory tree would look a bit different. But because you have seperated the header files from the implementation files - it would only lead to bad practice, unless you redo your whole directory tree.

I've found/created and stored two macros that help me quickly solve issues across different best practices that I come across.

I assume you wish to install the library(/ies) after you build them and then use them in another project, while preserving the specified structure. I've worked in one company where we couldn't change the folder structure, but they didn't mention anything about the install folder. The source folder resembled the one you posted. Hence after some time I got ahold of this macro and it might be useful for you too (regex might've changed across versions hence it might need some updating):

macro(install_directory_headers_macro HEADER_LIST)

foreach(HEADER ${${HEADER_LIST}})
    #This gets 2 matches DIR and HEADER (folder path and header file name)
    string(REGEX MATCH "(.*\/*\\*)[\/\\]" DIR ${HEADER})
    install(FILES ${HEADER} DESTINATION include/${DIR})
endforeach(HEADER)

endmacro(install_directory_headers_macro)

What this macro does is to preserve the SOURCE directory structure of the include files in the install directory. Normally you would have to type it out yourself, this does it for you.

# USE:
# set(PROJ_HEADERS moduleA/moduleA.hpp moduleB/moduleB.hpp)
# install_directory_headers_macro(PROJ_HEADERS)

Bonus

Ofcourse I once had a different problem as well (this is most likely not your case) where within the same company, they wanted to build the files from source while developing it and didn't understand that they no longer can use system paths in the #include directives, unless they do some changes within the CMake files. If this is your case then this function solves this for you (so you don't have to move a finger):

function(export_headers TARGET HEADER_SOURCE_DIR HEADER_DEST_DIR)

# Put all headers that are in the source directory into EXPORT_HEADERS variable
file(GLOB_RECURSE EXPORT_HEADERS CONFIGURE_DEPENDS 
    RELATIVE "${HEADER_SOURCE_DIR}" 
    "${HEADER_SOURCE_DIR}/*.h"
)

# For each header that will be exported
foreach(HEADER ${EXPORT_HEADERS})

    # Get the directory portion that needs to be created        
    get_filename_component(HEADER_DIRECTORY "${HEADER}" DIRECTORY)

    # Create the directory  
    add_custom_command(TARGET ${TARGET} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E make_directory "${HEADER_DEST_DIR}/${HEADER_DIRECTORY}"
    )

    if (MSVC)

        # Make a hard link to the file
        add_custom_command(TARGET ${TARGET} POST_BUILD
            COMMAND if not exist "${HEADER_DEST_DIR}/${HEADER}" \( mklink /h "${HEADER_DEST_DIR}/${HEADER}" "${HEADER_SOURCE_DIR}/${HEADER}" \) 
        )

    else()

        # Make a symbolic link to the file
        add_custom_command(TARGET ${TARGET} POST_BUILD
            COMMAND ln -sf "${HEADER_SOURCE_DIR}/${HEADER}" "${HEADER_DEST_DIR}/${HEADER}"
        )

    endif()


endforeach(HEADER)

endfunction()

And then you can easily...

# USE:
# add_library(libA STATIC ${LIBA_SOURCES}
# export_headers(libA ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/include/libA)
# target_include_directories(libA INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/include)

CodePudding user response:

Since you want to install all files in a directory, use install(DIRECTORY):

install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ # note: tailing slash is important here
    DESTINATION include/myproj # installs files to e.g. <install root>/include/myproj/module_a/file1.hpp
)

There's also the possibility to include or exclude certain files using the FILES_MATCHING option, if you only want to copy some of the files. (This doesn't work well for including some files with a given extension while excluding others with the same extension though.)

Btw: I strongly recommend moving your headers to make the #include paths you want to use for users of the installed lib for build tree too. This way there's the option of using add_subdirectory instead of accessing an installed version for a project consuming the lib. Also it makes the project a bit easier to maintain, since you don't have to think about whether you need to use the include path for internal or external use...

  • Related