How to include CMake files from source tree of included project - cmake

I'm setting up a CMake based build system for an old framework, written in C. It consists of a single binary, some core libraries and many dynamically linked libraries, which are built against the core libraries. My goal is to structure those libraries in several CMake projects ("base framework", "extension libraries", …), supporting two scenarios:
Independent build of extension libraries against base framework's build tree:
- base_framework/
- cmake/
- functions.cmake
- core_libraries/
- CMakeLists.txt
- extension_libs/
- lib1/
- lib2/
- CMakeLists.txt
For this scenario, I use
export(EXPORT foo
FILE FooFrameworkConfig.cmake)
and
find_package(FooFramework)
and add the build tree to CMAKE_PREFIX_PATH when configuring the extension_libs project.
Using base framework and extension libraries as sub-projects (e.g. as git submodules) of an application specific project:
- my_project/
- base_framework/
- cmake/
- functions.cmake
- core_libraries/
- CMakeLists.txt
- extension_libs/
- lib1/
- lib2/
- CMakeLists.txt
- my_lib1/
- CMakeLists.txt
For this scenario, I use add_subdirectory() for both, the base framework and the extension libs.
(A third scenario would be building the extension libraries against an install tree of the base framework. Unfortunately, this scenario is currently prevented by other CMake problems.)
Now, I want to include the base_framework/cmake/functions.cmake file, containing some custom CMake functions for all libraries, into all projects' top-level CMakeLists.txts in both scenarios.
For the second scenario, I simply set a cached CMake variable in base_framework/CMakeLists.txt:
set(BASE_FRAMEWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR}
CACHE PATH "" FORCE)
and use this variable for including the functions file in extension_libraries/CMakeLists.txt as well as my_project/CMakeLists.txt:
include(${BASE_FRAMEWORK_DIR}/cmake/functions.cmake)
But how do I find the function.cmake file in the base framework's source tree from extenstion_libs/cmake in the first scenario? All the directories (base_framework, extension_libs, base framework's build tree) may be anywhere on my computer.

To expand on my comment regarding your first scenario, it looks like you can set one of the properties of your exported core_libraries targets to contain the path to the base_framework/cmake modules. So you can set this in the base_framework/CMakeLists.txt file, let's set it in the LABELS target property. Then, we tell CMake to export the LABELS property using EXPORT_PROPERTIES:
Yourbase_framework/CMakeLists.txt file:
# Define the path to 'base_framework/cmake'
set(BASE_FRAMEWORK_CMAKE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_library(CoreLibrary1 ...)
# Set the LABELS property, and tell CMake to export it.
set_target_properties(CoreLibrary1 PROPERTIES
LABELS "${BASE_FRAMEWORK_CMAKE_MODULES}"
EXPORT_PROPERTIES "LABELS"
)
# Define the export 'foo'.
install(TARGETS CoreLibrary1 EXPORT foo DESTINATION /path/to/install/targets)
export(EXPORT foo FILE FooFrameworkConfig.cmake)
This will ensure the path to the base_framework CMake modules gets exported explicitly, and can be accessed when imported by another project:
Your extension_libs/CMakeLists.txt file:
find_package(FooFramework)
# Get the LABELS property from the imported target.
get_target_property(BASE_FRAMEWORK_MODULE_DIR CoreLibrary1 LABELS)
message(STATUS "BASE_FRAMEWORK_MODULE_DIR: ${BASE_FRAMEWORK_MODULE_DIR}")
With a debug message(), we can verify the CMake modules directory was imported:
BASE_FRAMEWORK_MODULE_DIR: /your/path/to/base_framework/cmake
EDIT: Instead of hijacking the LABELS target property, you could also use define_property() to define your own custom property for the target.

Related

How to export CMake property and set it differently in install(EXPORT …)

I'm setting up a CMake based build system for an old framework, written in C. It consists of a single binary, some core libraries and many dynamically linked libraries, which are built against the core libraries. All the libraries require a custom code generation step, which generates C code from model files, written in a domain-specific language. The code generator reads the library's own model file as well as those of all dependency libraries.
My goal is to structure those libraries in several CMake projects ("base framework", "extension libraries" and application specific projects), supporting three scenarios (with the same code base):
Including base framework and extension libraries as sub-projects (i.e. git submodules) into an application specific project. In this case, those CMake projects are included via add_subdirectory() and are part of one single CMake project.
Building application specific projects against the base framework's build tree. Public library header files and model files for code generation should be read from the source tree.
This can be achieved by using export(EXPORT foo FILE FooFrameworkConfig.cmake) in the base framework's CMakeLists and find_package(FooFramework) (with an appropriate CMAKE_PREFIX_PATH) to include the CMake properties of the base framework's build targets into the application project.
Building application specific projects against the base framework's install tree (without having the sources available). All required public header files and model files for code generation are copied to the install tree via appropriate install(…) rules.
To include the CMake properties of the base framework's build targets into the application project, I tried to use install(EXPORT foo FILE FooFrameworkConfig.cmake) in the base framework's CMakeLists and the already mentioned find_package(FooFramework) (with an appropriate CMAKE_PREFIX_PATH).
To manage the model search paths for the code generator, I introduced a custom CMake property on the library targets (let's call it FOO_MODEL_PATHS). This property contains the paths to the model files of the library itself and all its dependency libraries. I made this custom property exportable via set_property(TARGET ${OV_LIBRARY_NAME} APPEND PROPERTY EXPORT_PROPERTIES FOO_MODEL_PATHS).
Unfortunately, I cannot get it working for the third scenario. In this case, I need this custom property to be set to different values in the install tree export file (generated by install(EXPORT …)) and the base framework's build system. For CMake's internal properties, one can use the $<BUILD_INTERFACE:…> and $<INSTALL_INTERFACE:…> generator expressions for this purpose. However, for custom exported properties, generator expressions are forbidden (see CMake documentation and this commit).
So, how can I manage the code generator's search paths in CMake and share them to the dependent projects via EXPORTs, but fix them in the generated install(EXPORT …) file?

How do I specify a specific CMake target as a dependency?

Our CMake project, hosted on GitHub, has a CMake git submodule as a dependency. The file structure, then, is roughly:
project/
CMakeLists.txt
extern/
big_lib/
CMakeLists.txt
include/
*.hpp
static/
CMakeLists.txt
shared/
CMakeLists.txt
We have authorship of both project and big_lib.
The top level CMakeLists.txt for project includes something like:
add_subdirectory(${PROJECT_SOURCE_DIR}/extern/big_lib)
target_link_libraries(${PROJECT_NAME} big_lib::static)
big_lib::static is a library we don't install/publish; it's not specified as such in the big_lib configuration, it's for internal consumption only - namely for tests. We deliver a client facing shared library, but the shared library is not appropriate for project.
This is why ExternalProject_Add may not be the most appropriate solution for satisfying our dependency - as it is my understanding installing the dependency in the build directory won't install the specific build target we need. Also, I haven't had luck getting it to work yet.
What I've also noticed is that we're building all targets in big_lib, of which there are hundreds - mostly tests, and that shared library I don't want or need. I suspect this is because we're including the entire library from it's base directory.
I've tried:
add_subdirectory(${PROJECT_SOURCE_DIR}/extern/big_lib/static)
But it seems there's configuration from the big_lib base directory that now goes unspecified, which is why I'm including the base directory in project instead of this.
So my questions are:
Is there a better way to specify the static build target so only that gets built?
Is there a better way to organize the configuration of big_lib so I can add only the static library as the dependency folder, and not duplicate configuration from the base directory between static and shared?
What options am I not aware of? Maybe I should use ExternalProject_Add and specify some sort of custom build command where I issue just the static lib as the build target and install target, and then link against that artifact?

Copy target file before linking another (CMake)

I have a CMakeLists.txt that builds some c++ plugin and another CMakeLists.txt that builds application that should load this plugin. Before launching application I need to copy plugin file into some path, relative to the application binary (say path_to_executable/Plugins).
plugin's CMakeLists.txt:
...
add_library(plugin SHARED lib.cpp)
...
application's CMakeLists.txt:
...
target_sources(app PRIVATE main.cpp)
add_subdirectory(plugin_subfolder)
add_dependencies(app plugin)
???
How can I create rule that will insure plugin file is in correct place before compiling target app?
You can produce you library directly to the place you need w/o any 2 step process of first creating it somewhere and then copying (which is also possible). To achieve this you can set the LIBRARY_OUTPUT_DIRECTORY property on your library:
set_target_properties(plugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"path_to_executable/Plugins")

CMake setting to Ninja rebuild when header change?

Apparently, Ninja has to be configured to treat headers as dependencies: https://ninja-build.org/manual.html#ref_headers.
How do I tell CMake to generate this into my build.ninja?
If I look into my build.ninja files generated with CMake for e.g. GCC I do have the necessary dependency file entries automatically generated (see DEP_FILE):
#=============================================================================
# Object build statements for EXECUTABLE target MyExe
build CMakeFiles/MyExe.dir/foo.cc.obj: CXX_COMPILER ../foo.cc
DEP_FILE = CMakeFiles/MyExe.dir/foo.cc.obj.d
FLAGS = -fdiagnostics-color=always -Wconversion
OBJECT_DIR = CMakeFiles\MyExe.dir
OBJECT_FILE_DIR = CMakeFiles\MyExe.dir
And the dependency checking works as expected. Just touch or change one of the header dependencies and ninja will automatically rebuild the necessary sources.
There is the file property OBJECT_DEPENDS, which can be used to specify the dependency on other files of any objects created from a specific file.
From the docs:
Additional files on which a compiled object file depends.
Specifies a ;-list of full-paths to files on which any object files compiled from this source file depend. On Makefile Generators and the Ninja generator an object file will be recompiled if any of the named files is newer than it. Visual Studio Generators and the Xcode generator cannot implement such compilation dependencies.
This property need not be used to specify the dependency of a source file on a generated header file that it includes. Although the property was originally introduced for this purpose, it is no longer necessary. If the generated header file is created by a custom command in the same target as the source file, the automatic dependency scanning process will recognize the dependency. If the generated header file is created by another target, an inter-target dependency should be created with the add_dependencies() command (if one does not already exist due to linking relationships).
To set this property on a given source file, use:
set_property(SOURCE first.cpp second.cpp
APPEND PROPERTY OBJECT_DEPENDS "${PROJECT_SOURCE_DIR}/inc/header1.h;${PROJECT_SOURCE_DIR}/inc/header2;${PROJECT_SOURCE_DIR}/inc/global_deps.h"

cmake: install header order and dependencies on target

I've a set of libraries and executables all with their own CMakeLists.txt. All libraries are building their targets to the same path (for example ../build/bin and ../build/lib)... as well as exporting their header files (../build/inc).
Now I wish to build one build system and let CMake figure out the dependencies (using add_subdirectory and global build settings).
The problem is: all libraries do export their headers to build/inc after they are build (when make install in invoked). When I do a whole system build make install is not invoked until after the end (and everything has build). So for example executable progfoo with target_link_libraries( progfoo onelib ) will fail, because CMake figures out the dependency to onelib (which builds fine), but progfoo fails because it looks for headers in build/inc... which were not exported yet. The same thing in general applies to many libraries.
What is the correct CMake-Way to handle these cases? Thank you!
Install is the final step, the one that should be visible to the user. So when you export binaries and headers you should already have binaries built against headers in their original locations.
From CMake point of view you have only 1 target at a time. For example you can build a Web Server and using as dependencies libcurl and boost::asio. It is very possible (and good) to add dependencies to current target using add_subdirectory, but when you have to add include directories you have to do that on a per dependency basis, it would be convenient in example if each of the dependencies provides already a variable with current absolute path to includes.
In example see this dummy libcurl's CMakeLists.txt that set path to absolute include directory
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
// export into parent scope libcurl header location
set (LIBCURL_INCLUDES ${_DIR}/include PARENT_SCOPE)
Then you can use it from Web Server's CMakeLists.txt for building and later use the same path again to extract files for installing them where required
add_subdirectory(PATH_TO_LIBCURL_CMAKELISTS)
# add include directories for building web server
include_directories( ${LIBCURL_INCLUDES})
#I assume you are installing headers because final user will need them,
#in case you needed them just for building you are already done here
#without even installing them
#gather headers list
file(GLOB libCurlHeadersList
"${LIBCURL_INCLUDES}/*.h"
"${LIBCURL_INCLUDES}/*.hpp"
)
#install header list
install( FILES
${libCurlHeadersList}
DESTINATION ../build/inc/libcurl
)