Modern CMake Library containing sub-libraries - cmake

I have several libraries (subproject) as part of one main library. How can I aggregate the sub-libraries (b) in a "main-library" (a)?
The Goal would be to only have a library a and use the sublibraries as follows #include <b/b.h>
(is there a way to have the include paths as follows: #include <a/b/b.h> ?)
assuming the following folder structure:
a
|-- CMakeLists.txt
|-- aConfig.cmake
`-- b
|-- CMakeLists.txt
|-- include
| `-- b
| `-- b.h
`-- src
`-- b.cpp
I have the following library a depending on library b. library a must be installed and thus found with the find_package(a CONFIG REQUIRED) command in a modern CMake way. The library does not provide any implementation but should aggregate the sublibraries. I thought that making the target_include_directories Public would propagate the INTERFACE_INCLUDE_DIRECTORIES property to the library a since it links against b but I could not manage to get it to work:
cmake_minimum_required(VERSION 3.0.2)
project(a)
find_package(Eigen3 3.2.2 REQUIRED)
add_subdirectory(b)
## Build
add_library(a)
add_library(a::a ALIAS a)
target_link_library(a
PUBLIC
b::b
)
## Install
include(GNUInstallDirs)
# create export set
install(TARGETS a
EXPORT "aTargets"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
install(
DIRECTORY
b/include/
DESTINATION
"${CMAKE_INSTALL_INCLUDEDIR}"
)
install(EXPORT "${PROJECT_NAME}Targets"
FILE
"aTargets.cmake"
NAMESPACE
"a::"
DESTINATION
lib/cmake/a
)
install(
FILES
"aConfig.cmake"
DESTINATION
"lib/cmake/a"
)

Using interface libraries together with relocatable packages seem to solve your problem. Below is a minimal working example.
Assume that we have the following directory structure
.
├── a
│ ├── b
│ │ ├── CMakeLists.txt
│ │ ├── include
│ │ │ └── b
│ │ │ └── b.h
│ │ └── src
│ │ └── b.cpp
│ ├── c
│ │ ├── CMakeLists.txt
│ │ ├── include
│ │ │ └── c
│ │ │ └── c.h
│ │ └── src
│ │ └── c.cpp
│ └── CMakeLists.txt
├── CMakeLists.txt
└── main.cpp
9 directories, 9 files
where a is your super project that contains other subprojects. The contents for the b and c libraries are as follows:
# a/b/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(b CXX)
add_library(b
include/b/b.h
src/b.cpp
)
add_library(b::b ALIAS b)
target_include_directories(b
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
install(TARGETS
b
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
install(DIRECTORY include/b DESTINATION include)
/* a/b/include/b/b.h */
#ifdef __cplusplus
extern "C" {
#endif
double funcb(const double);
#ifdef __cplusplus
}
#endif
/* a/b/src/b.cpp */
#include "b/b.h"
extern "C" {
double funcb(const double x) { return x + x; }
}
# a/c/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(c CXX)
add_library(c
include/c/c.h
src/c.cpp
)
add_library(c::c ALIAS c)
target_include_directories(c
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
install(TARGETS
c
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
install(DIRECTORY include/c DESTINATION include)
/* a/c/include/c/c.h */
#ifdef __cplusplus
extern "C" {
#endif
double funcc(const double);
#ifdef __cplusplus
}
#endif
/* a/c/src/c.cpp */
#include "c/c.h"
extern "C" {
double funcc(const double x) { return x * x; }
}
Then, you simply make a an INTERFACE library as follows:
# a/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(a)
add_subdirectory(b)
add_subdirectory(c)
add_library(a INTERFACE)
add_library(a::a ALIAS a)
target_link_libraries(a
INTERFACE
b::b
c::c
)
Finally, your application will be using the b and c libraries as usual:
#include <iostream>
using namespace std;
#include "b/b.h"
#include "c/c.h"
int main(int argc, char* argv[]) {
cout << funcb(5) << '\n';
cout << funcc(5) << '\n';
return 0;
}
and the corresponding CMakeLists.txt will read
cmake_minimum_required(VERSION 3.9)
project(app)
add_subdirectory(a)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE a::a)
I hope this solves your problem.

It will require you to change the directory structure. Header inclusion are tied to physical paths, and there are no physical path that resemble a/b/b.h. The solution will be to change the directory structure with something like this:
a
|-- CMakeLists.txt
|-- aConfig.cmake
`-- b
|-- CMakeLists.txt
|-- include
| `-- a
| `-- b
| `-- b.h
`-- src
`-- b.cpp
And set the target include directory to a/b/include

Related

CMake: Print properties of a target including its dependencies

I currently try to write a custom_target to print out properites of a target (e.g. COMPILE_DEFINITIONS).
I've placed the invocation of this custom_target creation almost at the end of my Top-Level-CMakeLists.txt to make sure all modules have been invoked.
The goal is to print out all properties of a target including properties given by dependencies via target_link_libraries.
Simplified example:
add_library(libA STATIC)
add_library(libB STATIC)
target_compile_definitions(libA
PRIVATE
PRIV_A
PUBLIC
PUB_A
INTERFACE
INT_A
)
target_compile_definitions(libB
PRIVATE
PRIV_B
PUBLIC
PUB_B
INTERFACE
INT_B
)
# create dependency from A -> B,
# this should compile A with all PUBLIC and INTERFACE defintions from B
target_link_libraries(libA libB)
get_target_property(compile_defs libA COMPILE_DEFINITIONS)
get_target_property(compile_defs_intf libA INTERFACE_COMPILE_DEFINITIONS)
message("compile_defs: ${compile_defs}")
message("compile_defs_intf: ${compile_defs_intf}")
This will print:
compile_defs: PRIV_A; PUB_A
compile_defs_intf: PUB_A; INT_A
Actually I would like to get:
compile_defs: PRIV_A; PUB_A; PUB_B; INT_B
But obviously at this stage, the dependencies are not yet resolved / included in the properties.
A possible workaround would be to iterate over all dependencies of target A and collect all the INTERFACE_PROPERTIES of the dependency target. But this would require quiet some recursion to resolve all dependencies in the tree (e.g. requires resolving of all dependencies...).
Is it possible to get properties of a target incl. its dependencies (PUBLIC, INTERFACE properties) in a more easy way?
First get all dependent libraries:
# target_get_linked_libraries.cmake
#
function(list_add_if_not_present list elem)
list(FIND "${list}" "${elem}" exists)
if(exists EQUAL -1)
list(APPEND "${list}" "${elem}")
set("${list}" "${${list}}" PARENT_SCOPE)
endif()
endfunction()
macro(_target_get_linked_libraries_in _target _outlist)
list_add_if_not_present("${_outlist}" "${_target}")
# get libraries
get_target_property(target_type "${_target}" TYPE)
if (${target_type} STREQUAL "INTERFACE_LIBRARY")
get_target_property(libs "${_target}" INTERFACE_LINK_LIBRARIES)
else()
get_target_property(libs "${_target}" LINK_LIBRARIES)
endif()
foreach(lib IN LISTS libs)
if(NOT TARGET "${lib}")
continue()
endif()
list(FIND "${_outlist}" "${lib}" exists)
if(NOT exists EQUAL -1)
continue()
endif()
_target_get_linked_libraries_in("${lib}" "${_outlist}")
endforeach()
endmacro()
function(target_get_linked_libraries _target _outlist)
set(${_outlist} "${_target}")
_target_get_linked_libraries_in(${_target} ${_outlist})
set(${_outlist} ${${_outlist}} PARENT_SCOPE)
endfunction()
Then just iterate over all dependent libraries and get the interface definitions:
cmake_minimum_required(VERSION 3.11)
project(A)
add_library(libB STATIC)
target_compile_definitions(libB
PRIVATE PRIV_B
PUBLIC PUB_B
INTERFACE INT_B
)
add_library(libA STATIC)
target_compile_definitions(libA
PRIVATE PRIV_A
PUBLIC PUB_A
INTERFACE INT_A
)
target_link_libraries(libA PUBLIC libB)
include(target_get_linked_libraries.cmake)
target_get_linked_libraries(libA libraries)
set(compile_defs)
foreach(i IN LISTS libraries)
if("${i}" STREQUAL libA)
get_target_property(tmp "${i}" COMPILE_DEFINITIONS)
else()
get_target_property(tmp "${i}" INTERFACE_COMPILE_DEFINITIONS)
endif()
list(APPEND compile_defs "${tmp}")
endforeach()
message("compile_defs: ${compile_defs}")
message(FATAL_ERROR "")
would output:
$ cmake .
compile_defs: PRIV_A;PUB_A;PUB_B;INT_B

cmake target_include_directories PUBLIC not working for object-library

lib1-CMakeLists.txt
add_library(lib1_objs OBJECT
hello.cpp
hello.h
)
target_include_directories(lib1_objs
PUBLIC ../third
)
add_library(lib1 STATIC $<TARGET_OBJECTS:lib1_objs>)
hello.h
#include<test.h> //Path: ../third/test.h
...
exe-CMakeLists.txt
add_executable(exe
main.cpp
)
target_link_libraries(exe
PRIVATE lib1
)
target_include_directories(lib1_objs
PRIVATE ../lib1
)
main.cpp
#include "hello.h"
...
When I build 'exe', it would show an error
../lib1/hello.h:2 fatal error test.h No such file or directory
I already include '../third' directory PUBLIC in lib1-CMakeLists.txt. But it didn't propaganda it to exe-CMakeLists.txt. How can I fix this?
Just actually link with the object library:
lib1/CMakeLists.txt:
add_library(lib1 OBJECT
hello.cpp
hello.h
)
target_include_directories(lib1
PUBLIC ../third
)
CMakeLists.txt:
add_subdirectory(lib1)
add_executable(exe
main.cpp
)
target_link_libraries(exe
PRIVATE lib1
)
$<TARGET_OBJECTS> are just object files - they do not know about include directories and such.
You could also:
lib1/CMakeLists.txt:
add_library(lib1_objs OBJECT
hello.cpp
hello.h
)
target_include_directories(lib1_objs
PUBLIC ../third
)
add_library(lib1 STATIC) # I think it works, if not create an empty source file
target_link_libraries(lib1 PUBLIC lib1_objs)
CMakeLists.txt:
add_subdirectory(lib1)
add_executable(exe
main.cpp
)
target_link_libraries(exe
PRIVATE lib1
)
but then lib1_objs could just be removed.

Split main.rs into files that refer each other

I have the following structure:
|-- Cargo.toml
|-- src
| |-- file1.rs
| |-- file2.rs
| `-- main.rs
src/file1.rs
pub fn function1() {}
src/file2.rs
// ERROR (1): error[E0583]: file not found for module `file1`
// mod file1;
use crate::file1;
pub fn function2() {
file1::function1();
}
src/main.rs
// ERROR (2): no `file1` in the root
// use crate::file1;
mod file1;
mod file2;
fn main() {
file1::function1();
file2::function2();
}
Basically, I have a different way to import function1, depending on the fact that I am in the crate root or in an arbitrary Rust file (please see ERROR (1) and ERROR (2)).
I am a bit lost on how Rust manages arbirary files: they behave differently from the root crate, where a simple mod keyword does the trick.
So, the mentioned answer for which this is a duplicate only partially only answers how to refer a file from the crate root, not why referring the same file from another one should be different (use crate::<filename>).

How to include file which in different folder in go templates

For example there is test.tpl in folder A:
{{define "test"}} hello I am test {{end}}
another index.tpl in folder B:
{{template "A/test"}} or {{template "test"}}
Both do not work.
Use template.ParseFiles and to parse all templates. Use different names for each. This directory layout
.
├── A
│   └── index-A.tpl
├── B
│   └── index-B.tpl
└── main.go
With A/index-A.tpl containing
A
and B/index-B.tpl containing
B1
{{template "index-A.tpl"}}
B2
used by this code
package main
import (
"os"
"text/template"
)
func main() {
t, err := template.ParseFiles("B/index-B.tpl", "A/index-A.tpl")
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, nil)
if err != nil {
panic(err)
}
}
will produce this output:
B1
A
B2
Note that both templates are named in templateParseFiles and that B/index-B.tpl references index-a.tpl by name without the path.

Linking a C++ exe to a C lib in cmake

I thought I understood cmake pretty well until I came accross this problem that I just can't figure out. I've built a static library in C, and I'm trying to run a unit test on it in C++, but I can't seem to link to any of the static functions in that library and I can't figure out why. I've reproduced the problem in the skeleton project below:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.6)
project(myproj)
add_library(mylib STATIC mylib.c)
find_package(Boost 1.64.0 COMPONENTS unit_test_framework)
add_executable(mytestapp mytest.cpp)
target_include_directories(mytestapp PRIVATE .)
target_link_libraries(mytestapp mylib)
enable_testing()
add_test( mytest mytestapp)
mylib.c:
int add(int a, int b)
{
return a + b;
}
mylib.h
int add(int a, int b);
mytest.cpp:
#define BOOST_TEST_MODULE mylib_test
#include <boost/test/included/unit_test.hpp>
#include "mylib.h"
BOOST_AUTO_TEST_CASE(mylib_test)
{
BOOST_TEST( add(2,2) == 4);
}
Then my output is:
$ make
Scanning dependencies of target mylib
[ 25%] Building C object CMakeFiles/mylib.dir/mylib.c.o
[ 50%] Linking C static library libmylib.a
[ 50%] Built target mylib
Scanning dependencies of target mytestapp
[ 75%] Building CXX object CMakeFiles/mytestapp.dir/mytest.cpp.o
[100%] Linking CXX executable mytestapp
CMakeFiles/mytestapp.dir/mytest.cpp.o: In function `mylib_test::test_method()':
mytest.cpp:(.text+0x1e411): undefined reference to `add(int, int)'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/mytestapp.dir/build.make:96: mytestapp] Error 1
make[1]: *** [CMakeFiles/Makefile2:105: CMakeFiles/mytestapp.dir/all] Error 2
make: *** [Makefile:95: all] Error 2
If I compile mylib in C++, then it links fine, but not in C. That's a problem for me because I have a huge library in C and I'm trying to use the boost_test_framework (in C++) to load this lib and test it.
Solution:
I needed to import the library with extern "C" like so:
#define BOOST_TEST_MODULE mylib_test
#include <boost/test/included/unit_test.hpp>
extern "C" {
#include "mylib.h"
}
BOOST_AUTO_TEST_CASE(mylib_test)
{
BOOST_TEST( add(2,2) == 4);
}