Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cmake install(FILES ...) doesn't seem to be working

I have a project written in C++ and I am using cmake to build it. The project has many sub-projects, one of them is a lib that is required for the other sub-projects. I can compile and move the .so to the build directory by using add_library, and INSTALL(TARGETS ...)

However I also need the header files of the lib to be installed under the include directory of the build dir. I use install(FILES ...) to do it, but it seems it simply doesn't do a thing.

To demonstrate it, I have created a test project via qtcreator,

& ls test
CMakeLists.txt empty.hh main.cpp

$ cat test/CMakeLists.txt
project(test)
cmake_minimum_required(VERSION 2.8)
install(FILES empty.hh DESTINATION include)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})

$ cat test/main.cpp
#include

using namespace std;

int main()
{
cout << "Hello World!" << endl;
return 0;
}

$ cat test/empty.hh
#ifndef EMPTY_HH
#define EMPTY_HH

#endif // EMPTY_HH

If the files under "test" qtcreator will compile (by default) the files to test-build.
$ ls test-build/
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake test test.cbp
$ ./test-build/test
Hello World!

As you can see, there is no include directory or empty.hh file. Also tried to use

install(FILES empty.hh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

But still see no header files.

$ cmake --help
cmake version 2.8.12

If you have any idea please let me know.

like image 774
czappa Avatar asked Dec 27 '14 09:12

czappa


People also ask

How does CMake install work?

CMake provides the install command to specify how a project is to be installed. This command is invoked by a project in the CMakeLists file and tells CMake how to generate installation scripts. The scripts are executed at install time to perform the actual installation of files.

How do I know if CMake is installed?

You can check your CMake version by using the command cmake --version. cmake version 3.11. 2CMake suite maintained and supported by Kitware (kitware.com/cmake).

Where does CMake install files?

Install directory used by install. If “make install” is invoked or INSTALL is built, this directory is prepended onto all install directories. This variable defaults to /usr/local on UNIX and c:/Program Files on Windows.


1 Answers

Using the install command to move files around at build-time is generally a bad approach. The command is meant to be used to set up files and targets which will be installed when the user does make install or equivalent. Since you're not running make install, I expect that's why the install(FILES ...) command appears to not work.

You have a few slightly different approaches here to make this work.

I'd recommend not moving the headers if you don't have to. Say your library is called MyLib, then you can make these headers available as part of the MyLib target via target_include_directories:

target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

This means if there's a dependent target, e.g MyExe:

target_link_libraries(MyExe MyLib)

then it automatically has access to MyLib's source dir.


This can be less desirable if the library's directory structure doesn't lend itself to separating the API headers from the rest of the sources and headers. Say MyLib is comprised of the following files: my_lib.cpp, my_lib_api.hh (API header - for inclusion by other projects) and my_lib_detail.hh (not for inclusion by other projects). An ideal structure would keep the API header separate from the rest, e.g.

/my_lib
   - CMakeLists.txt
   - src/
       - my_lib.cpp
       - my_lib_detail.hh
   - include/
       - my_lib/
            - my_lib_api.hh

With this structure you can just specify

target_include_directories(MyLib PUBLIC  "${CMAKE_CURRENT_SOURCE_DIR}/include"
                                 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")

and MyLib sources will be able to contain #include "my_lib_detail.hh" and #include "my_lib/my_lib_api.hh", but dependent targets' sources will only be able to contain #include "my_lib/my_lib_api.hh".


So, if MyLib has just a flat structure or doesn't separate the internal headers from API ones, you may want to copy out the API headers to a location in your build tree and add that path to the target_include_directories call. In that case, if MyLib itself doesn't need access to the copied files (just the originals in the source tree), you can use INTERFACE instead of PUBLIC in the target_include_directories call.

However, the crux of this (and getting round to answering your actual question I suppose) is to copy these files as part of the configure process (when CMake runs) or build process (when make runs) - not as part of the install process.

So, let's assume MyLib doesn't have the useful directory structure shown above, but instead is flat (i.e. CMakeLists.txt and three source files are all in same dir). We could use a post-build command to copy out the API header to the build tree:

project(my_lib)
cmake_minimum_required(VERSION 2.8.12.2)  # for 'target_include_directories'
add_library(MyLib SHARED my_lib.cpp my_lib_api.hh my_lib_detail.hh)
target_include_directories(MyLib INTERFACE "${CMAKE_BINARY_DIR}/include"
                                 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")

file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/include/my_lib")
add_custom_command(TARGET MyLib POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${CMAKE_CURRENT_SOURCE_DIR}/my_lib_api.hh"
        "${CMAKE_BINARY_DIR}/include/my_lib"
    COMMENT "Copying MyLib public headers to ${CMAKE_BINARY_DIR}/include/my_lib"
    VERBATIM)

Then for MyExe's CMakeLists.txt, you can just do:

project(my_exe)
cmake_minimum_required(VERSION 2.8.12.2)
add_executable(MyExe main.cpp)
target_link_libraries(MyExe MyLib)

# Copy MyLib.so to this build dir to allow MyExe to find it at runtime
add_custom_command(TARGET MyExe POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:MyLib>"
        "$<TARGET_FILE_DIR:MyExe>"
    VERBATIM)


This won't work as-is for MSVC - you'd need to handle Windows export libs (see the CMake wiki and the docs for GenerateExportHeader for further details), but it would just involve something like adding the following to MyLib's CMakeLists.txt:

include(GenerateExportHeader)
generate_export_header(MyLib
    BASE_NAME MyLib
    EXPORT_MACRO_NAME MyLib_EXPORT
    EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/include/my_lib/my_lib_export.hh"
    STATIC_DEFINE MyLib_BUILT_AS_STATIC)

If you do need this, you'll have to change INTERFACE to PUBLIC in target_include_directories(MyLib ...), since MyLib will then itself need access to the generated file "${CMAKE_BINARY_DIR}/include/my_lib/my_lib_export.hh".

like image 83
Fraser Avatar answered Sep 20 '22 07:09

Fraser