Header files with prefix in CMake project - build

I have set up a CMake project whose directory structure looks as follows:
src/
--CMakeLists.txt
--libA/
----CMakeLists.txt
----foo.h
----foo.cpp
--main/
----CMakeLists.txt
----main.cpp
src/CMakeLists.txt uses add_subdirectory to pull in libA and main. libA/CMakeLists.txt uses add_library to define a library called libA, which exports foo.h via target_include_directories. If I now link against libA in main using target_link_library, I can include foo.h via #include <foo.h> in main.cpp.
Question: Is it possible to provide the public interface of libA with a prefix, so that I can (and have to) write #include <libA/foo.h> in main.cpp instead?

This is an old question but I was having exactly the same issue. I ended up getting around this by adding an export_headers() function that creates symbolic links to the headers within the binary:
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()
You would call this with something like:
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)
Then, if you link to libA, you will be able to #include <libA/foo.h>.

You can use the root source directory (or some other directory which is a parent to libA) in the target_include_directories() call. This will allow the INTERFACE_INCLUDE_DIRECTORIES target property to be defined with respect to another directory (in this example, the CMAKE_SOURCE_DIR). So it would look something like this:
In libA/CMakeLists.txt:
add_library(libA foo.cpp)
# Define the include files with respect to the directory above this.
target_include_directories(libA PUBLIC ${CMAKE_SOURCE_DIR})
The main/main.cpp file:
#include <iostream>
#include <libA/foo.h>
int main() {
FooClass fooclass;
fooclass.myFunction();
std::cout << "Hello World!" << std::endl;
return 0;
}

Related

Cmake install nested static library target_link_library undefined reference

Install nested static library, and target_link_library not working
File structure:
HelloLib
WorldLib
CMakeLists.txt
WorldLib.cpp
WorldLib.h
CMakeLists.txt
HelloLib.cpp
HelloLib.h
CMakeLists.txt
main.cpp
main.cpp
#include <iostream>
#include "HelloLib/HelloLib.h"
int main() {
std::cout << hello() << std::endl;
return 0;
}
CMakeLists.txt (root)
cmake_minimum_required(VERSION 3.16.3)
project(HelloWorld)
add_executable(${PROJECT_NAME} main.cpp)
add_subdirectory(HelloLib)
target_link_libraries(
${PROJECT_NAME}
HelloLib
)
HelloLib.cpp
#include <string>
#include "WorldLib/WorldLib.h"
std::string hello() { return "Hello " + world(); }
HelloLib.h
#pragma once
#include <string>
std::string hello();
CMakeLists.txt (HelloLib)
add_library(HelloLib HelloLib.cpp)
add_subdirectory(WorldLib)
target_link_libraries(
HelloLib
WorldLib
)
WorldLib.cpp
#include <string>
std::string world() { return "World!"; }
WorldLib.h
#pragma once
#include <string>
std::string world();
CMakeLists.txt (WorldLib)
add_library(WorldLib WorldLib.cpp)
Build and run, successfully print out Hello World!
Now I would like to make HelloLib as static library
Change CMakeLists.txt (HelloLib), install to /usr/lib and /usr/include
add_library(HelloLib HelloLib.cpp)
add_subdirectory(WorldLib)
target_link_libraries(
HelloLib
WorldLib
)
install(
TARGETS HelloLib
ARCHIVE DESTINATION lib
)
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/" # source directory
DESTINATION "include" # target directory
FILES_MATCHING # install only matched files
PATTERN "*.h" # select header files
)
Run (make install) (CMAKE_INSTALL_PREFIX=/usr)
mkdir -p out/build
cmake -S src -B out/build -DCMAKE_INSTALL_PREFIX=/usr
cd out/build
make
make install
[ 33%] Built target WorldLib
[ 66%] Built target HelloLib
[100%] Built target HelloWorld
Install the project...
-- Install configuration: ""
-- Installing: /usr/lib/libHelloLib.a
-- Up-to-date: /usr/include
-- Installing: /usr/include/HelloLib
-- Installing: /usr/include/HelloLib/HelloLib.h
-- Installing: /usr/include/HelloLib/WorldLib
-- Installing: /usr/include/HelloLib/WorldLib/WorldLib.h
Static library (.a) should generate to /usr/lib/libHelloLib.a
Let's test it, change CMakeLists.txt (root)
cmake_minimum_required(VERSION 3.16.3)
project(HelloWorld)
add_executable(${PROJECT_NAME} main.cpp)
add_subdirectory(HelloLib)
target_link_libraries(
${PROJECT_NAME}
/usr/lib/libHelloLib.a
)
Then build, it give undefined reference error
/bin/ld: /usr/lib/libHelloLib.a(HelloLib.cpp.o): in function `hello[abi:cxx11]()':
HelloLib.cpp:(.text+0x20): undefined reference to `world[abi:cxx11]()'
What's wrong with me? This can be successful with no nested library
What is the correct way to install nested static library?
The issue here is not the install process but the fact that you link only to libHelloLib.a.
Your libHelloLib.a need the symbol in libWorldLib.a because libHelloLib.a is a static lib and so only contains its own symbol. It does not contains the symbol world that is defined in libWorldLib.a.
To make your project works, you need to install WorldLib and HelloLib and links against this two lib.
target_link_libraries(
${PROJECT_NAME}
HelloLib
WorldLib
)
Or you can change HelloLib into a shared library. This way the libHelloLib.so will also contains the symbol of WorldLib.
You can also look at this Exported Target.
It's an install command that will create some FindXX.cmake file that you will be able to use with the find_package command. You also will be able to defined the dependency of your lib. But if you want that HelloLib stay a static library you will have to install WorldLib

CMake #include path relative to project root [duplicate]

I have set up a CMake project whose directory structure looks as follows:
src/
--CMakeLists.txt
--libA/
----CMakeLists.txt
----foo.h
----foo.cpp
--main/
----CMakeLists.txt
----main.cpp
src/CMakeLists.txt uses add_subdirectory to pull in libA and main. libA/CMakeLists.txt uses add_library to define a library called libA, which exports foo.h via target_include_directories. If I now link against libA in main using target_link_library, I can include foo.h via #include <foo.h> in main.cpp.
Question: Is it possible to provide the public interface of libA with a prefix, so that I can (and have to) write #include <libA/foo.h> in main.cpp instead?
This is an old question but I was having exactly the same issue. I ended up getting around this by adding an export_headers() function that creates symbolic links to the headers within the binary:
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()
You would call this with something like:
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)
Then, if you link to libA, you will be able to #include <libA/foo.h>.
You can use the root source directory (or some other directory which is a parent to libA) in the target_include_directories() call. This will allow the INTERFACE_INCLUDE_DIRECTORIES target property to be defined with respect to another directory (in this example, the CMAKE_SOURCE_DIR). So it would look something like this:
In libA/CMakeLists.txt:
add_library(libA foo.cpp)
# Define the include files with respect to the directory above this.
target_include_directories(libA PUBLIC ${CMAKE_SOURCE_DIR})
The main/main.cpp file:
#include <iostream>
#include <libA/foo.h>
int main() {
FooClass fooclass;
fooclass.myFunction();
std::cout << "Hello World!" << std::endl;
return 0;
}

CMake: can't find my header file in a different directory

I have a CMake project with these relevant folders:
project_folder
-src
|--main.c
peripherals
|--something.h
|--something.c
My CMake in project_folder includes:
add_subdirectory(peripherals)
if (NOT $ENV{TARGET_SOURCES} STREQUAL "")
target_sources(app PRIVATE $ENV{TARGET_SOURCES})
else()
target_sources(app PRIVATE src/main.c)
endif()
My CMake under peripherals incudes:
add_library (peripherals something.c)
target_include_directories (peripherals PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
CMake under src:
add_executable(app main.c)
target_link_libraries(app PRIVATE peripherals)
My project is building fully but when I try to include my header file in main.c I get:
project_folder/peripherals/something.h:No such file or directory
In my main.c file I have #include "peripherals/something.h". Does anyone know how to fix this? I'm not sure if my #include statement is correct and I think I am missing stuff in my CMakeLists files
You can either do "#include "../peripherals/i2c_test.h" in your main.cpp
OR
in your CMake in project_folder:
target_include_directories(app ${CMAKE_CURRENT_SOURCE_DIR})
and then in main.c:
#include <peripherals/i2c_test.h>
....
You'll need to define executable and "link" it with the library:
# project_folder/CMakeLists.txt
add_subdirectory(peripherals)
add_subdirectory(src)
# project_folder/src/CMakeLists.txt
add_executable(my_executable main.c)
target_link_libraries(my_executable PRIVATE peripherals)
Then, you'll need to include the header in main.c properly- since you've linked against a library that includes peripherals directory, you can now directly include it:
#include "something.h"
Your include statement has to be relative to the included directory.
Edit: In your example ${CMAKE_CURRENT_SOURCE_DIR} is the project directory (where your CMake file is).
Therefore you should be able to write:
#include <peripherals/something.h>
You can always check a cmake variable's content by printing it. In your CMake file you can write:
message(STATUS ${CMAKE_CURRENT_SOURCE_DIR})
When running cmake you should see the path printed in the console output.
in your CMake in project_folder:
include_directories(src)
and then in main.c:
#include <peripherals/i2c_test.h>
OR
in your CMake in project_folder:
include_directories(src/peripherals)
and then in main.c:
#include <i2c_test.h>

CMake relative linking, executable fails to find shared libraries

I am trying to learn CMake and for that purpose I am working with an example project of the structure shown below. I am trying to configure all the CMakeLists.txt files such that after make install I can copy the resulting build directory and copy-paste it around freely such that other people are able run the executable.
Problem: after running make install (maxOS 10.14.6 (Darwin 18.7.0)) everything works if I run the executable inside build BUT if I move the build directory from its original location - for example to Desktop - the executable is unable to find the shared libraries. It seems that the reason for this is that the paths to the shared libraries are defined as absolute paths instead of relative paths with respect to the build directory.
Question: How can I build the project such that the executable finds the shared libs?
Project structure:
myapp
|
CMakeLists.txt (top-level)
- build
- app
| CMakeLists.txt (app)
| - inc
| - app
| app.h
| - src
| app.cpp
| main.cpp
|
- external
|
- mylib
CMakeLists.txt (mylib)
- inc
- mylib
mylib.h
- src
mylib.cpp
CMakeLists:
CMakeLists.txt (top-level)
cmake_minimum_required(VERSION 3.0)
project(myapp)
add_subdirectory(external/mylib)
add_subdirectory(app)
CMakeLists.txt (app)
# myapp program
cmake_minimum_required(VERSION 3.0)
project(myapp_prog)
set(SOURCES ./src/)
set(HEADERS ./inc/app/)
set(SOURCE_FILES
${SOURCES}/app.cpp)
set(HEADER_FILES
${HEADERS}/app.h)
# All sources that need to be tested in unit test go into a static library
add_library(myapp_lib SHARED ${HEADER_FILES} ${SOURCE_FILES})
target_include_directories(myapp_lib PUBLIC ${HEADERS})
# The main program
add_executable(prog ./src/main.cpp)
target_include_directories(prog PUBLIC ./inc/)
# Link the libraries
target_link_libraries(prog PRIVATE myapp_lib mylib)
install(TARGETS myapp_lib DESTINATION ${CMAKE_BINARY_DIR}/lib)
CMakeLists.txt (mylib)
# mylib
cmake_minimum_required(VERSION 3.0)
project(mylib)
set(SOURCES src/)
set(HEADERS inc/mylib/)
set(SOURCE_FILES
${SOURCES}/mylib.cpp)
set(HEADER_FILES
${HEADERS}/mylib.h)
add_library(mylib SHARED ${HEADER_FILES} ${SOURCE_FILES})
target_include_directories(mylib PUBLIC inc/)
install(TARGETS mylib DESTINATION ${CMAKE_BINARY_DIR}/lib)
C++ code:
mylib.h
void mylib_print_hello();
mylib.cpp
#include "library.h"
#include <iostream>
void mylib_print_hello() {
std::cout << "Hello from mylib!" << std::endl;
}
app.h
void myapp_hello();
app.cpp
#include <iostream>
#include "app.h"
void myapp_hello()
{
std::cout << "Hello from myapp!" << std::endl;
}
main.cpp
#include <iostream>
#include "app/app.h"
#include "mylib/mylib.h"
int main()
{
myapp_hello();
mylib_print_hello();
}
I think the issue is that you might be confusing the build step with the install step. By using ${CMAKE_BINARY_DIR}, you are passing an absolute path to your binary directory (build), which may interfere with the moving of the build afterwards. If you would like to ouput your libraries to a folder after the build, you may set the target of the library with:
set_target_property(<library-name> PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
This will affect only the build stage of your project. As far as installs go, you are better off using only relative paths in the install directives. These paths will be relative to your ${CMAKE_INSTALL_PREFIX} variable. For example, you would use:
install(TARGETS mylib DESTINATION lib)
To install the library under the ${CMAKE_INSTALL_PREFIX}/lib folder. Best practice states that you should NEVER manipulate this variable directly in your CMakeLists.txt files, as it could break a build somewhere down the line. Starting with CMake 3.15, it is possible to use cmake --install to install a project. This command allows setting an installation prefix, such as:
cmake --install . --prefix desired/install/path
With this, all your link path should stay valid.
Please add a macos tag to this question.
I think that you are looking for a relative RPATH. CMake builds the executable with an absolute RPATH, so if you move or rename the build directory the program stop working. In your example, you only need to modify the main program CMakeLists.txt, to change the value of the RPATHs that CMake inserts in your executable.
# The main program
add_executable(prog ./src/main.cpp)
target_include_directories(prog PUBLIC ./inc/)
set_target_properties(prog PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "#executable_path/;#executable_path/../external/mylib")
You can verify the values of the LC_RPATHs with this command from your build directory:
$ otool -l app/prog
For dependencies between libraries, it may also be useful "#loader_path" instead of #executable_path. And for other Unix operating systems, use "$ORIGIN". More information here.

How to properly use file GLOB?

I'm trying to use GLOB_RECURSE to specify my sources and headers files. Currently, my CMakeLists.txt for a static library is:
project(LinearSystemLib)
file(GLOB_RECURSE ${PROJECT_NAME}_headers ${PROJECT_SOURCE_DIR}/*.h)
file(GLOB_RECURSE ${PROJECT_NAME}_sources ${PROJECT_SOURCE_DIR}/*.cpp)
add_library(
${PROJECT_NAME} STATIC ${${PROJECT_NAME}_headers}
${${PROJECT_NAME}_sources}
)
install(
TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION libs
ARCHIVE DESTINATION archives
)
The library directory looks like this:
LinearSystemLib
CMakeLists.txt
source
LinearSystemLib.cpp
include
LinearSystemLib.h
When I run command cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug (in the build directory) everything goes ok. Yet, command make it displays the following:
/home/felipe/Documents/Dados/SINMEC/Eclipse/LinearSystemLib/source/LinearSystemLib.cpp:1:29: fatal error: LinearSystemLib.h: No such file or directory
Is my CMakeLists wrong? I don't want to set specify the sources and headers files by name. I'm not finding information about glob_recurse easily.
I can make it work by listing the sources and headers files by name. However, it MUST be done with the glob_recurse or with glob.
I solved my problem, here's what LinearSystemLib directory looks like:
LinearSystemLib
CMakeLists.txt
source
LinearSystemLib.cpp
include
LinearSystemLib.h
The CMakeLists.txt contains:
project(LinearSystemLib)
#INCLUDE DIRECTORIES
include_directories(${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/include)
#SEARCH FOR .CPP AND .H FILES
file(GLOB ${PROJECT_NAME}_headers ${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/include/*.h)
file(GLOB ${PROJECT_NAME}_sources ${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/source/*.cpp)
#ADD LIBRARY
add_library(${PROJECT_NAME} STATIC ${${PROJECT_NAME}_sources})
#DEFINE OUTPUT LOCATION
install(
TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION static_libs
)
You don't actually NEED to add the .h/.hpp files using GLOB. I did it because otherwise, Visual Studio (or CodeBlocks) wouldn't create a "Header Files" folder on the project menu.
I wasn't properly specifying the path where GLOB would find the files.
${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/source/
You need to add
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})
so the correct -I argument is added to your compilation step. Use make VERBOSE=1 to see exactly what commands make is executing.