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...