We want to create and install a "facade" static library with a public self-contained/independent API on top of an existing CMake build structure using CMake. It must hide the underlying complexity of the CMake targets when installed.
We have a rather complicated hierarchy of own and 3rd-party C and C++ targets that I want to present through a pure C "facade" static library aka public API/lib to end-users; basically a set of dependent static libraries and a very small set of independent header files ONLY from the facade library; aka the public API. The facade library must hide all the underlying complexity of the targets when installed i.e. all target include directories, public headers etc. should be ignored for the facade target.
The otherwise excellent target oriented structure of CMake is working against us in this case e.g. when configuring we get errors about include directories which are prefixed in the source directories, however these include dirs are not needed when the facade target/library is used, since it by design hides all this complexity.
Is there a recommended CMake pattern for this case? We have tried capping the dependeny hierarchy by doing library bundling using a 3rd party cmake library that traversed the dependency tree and collects static library paths, thereby avoiding all the target inheritance in that way, however it has drawbacks and it tricky to maintain xplat and we would prefer a CMake idiomatic solution.
Related
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?
Given an executable myExe, and 2 static libraries myLib1 and myLib2. Given the following dependencies myExe -> myLib1 -> myLib2, how should you model transitive dependency between myLib2 and myLib1?
It seems that the correct way to do it may be:
target_link_libraries(myLib2 myLib1)
But, according to the documentation:
Specify libraries or flags to use when linking a given target and/or its dependents
Also, add_dependencies does not seem to be transitive.
So I find this confusing to use target_link_libraries and I am wondering if there is another "cleaner" way.
For express usage dependency myLib1 -> myLib2 (that is, library myLib1 uses functions defined in myLib2), use
target_link_libraries(myLib2 myLib1)
While target_link_libraries doesn't affect on the file myLib2.a (because static libraries are never linked), an effect will be seen when myLib2 will be linked into shared library or executable:
target_link_libraries(myExe myLib2)
will automatically link myExe with myLib1.
Note again, that such linkage propagation for static libraries works only when myLib2 is used in the same project which calls target_link_libraries(myLib2 myLib1).
Attempt to target_link_libraries(myExe myLib2) from another project will link just with myLib2.a file, which doesn't contain information about myLib2.
I have a library that needs to carry some constant data injected from the content of non-source files (in this case, OpenGL shader code). To achieve this, I'm using add_custom_command() to generate include files that I can then #include into my code to initialize const static variables.
This works perfectly with regular libraries (static or shared), but now I'd like to make my library header-only. The ability of C++ to let static methods return static data without running the risk of having that data duplicated in each translation unit ("magic statics") makes this possible.
The problem however is that CMake seems to assume that an INTERFACE library (which is the CMake feature that I'm using to create header-only libraries) does not need building - which, in this case, is wrong.
(I realize that there is no actual obligation for my library to be header-only. In this particular case, the reason I want this is that I would like the library, which is doing OpenGL, to remain independent of any specific binding library [such as GLEW or GLee or the newcomer glbinding]. By keeping my library header-only, I can leave that choice to the user - all he needs to do is #include the header of the binding library before mine.)
Does anyone see a way to have CMake trigger the header-generating custom commands, at the latest when the consumer project is being built?
EDIT: I just realized that I could have the "best of both worlds" as it were by keeping my library static but still keeping all my code except for the constant data in the header files. That way, there would still be no need to choose a specific OpenGL binding library.
However, there are still advantages to having a library be header-only - simplicity of use for one - so I'm leaving my question open.
EDIT #2: Here is the relevant part of my CMakeLists.txt file (I only stripped the library dependencies - all header-only - from the end):
set(SHADER_FILES "src/vertex.glsl" "src/fragment.glsl")
add_library(libGPCGUIGLRenderer INTERFACE)
target_sources(libGPCGUIGLRenderer INTERFACE ${SHADER_FILES})
target_include_directories(libGPCGUIGLRenderer BEFORE
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Embed shader files
source_group("Shader files" FILES ${SHADER_FILES})
set(GENERATED "${CMAKE_CURRENT_BINARY_DIR}/generated")
target_include_directories(libGPCGUIGLRenderer INTERFACE ${GENERATED})
# Find the GPC Bin2C utility
find_package(GPCBin2C REQUIRED)
# Add a custom target and a dependency for each shader file
foreach(shader ${SHADER_FILES})
get_filename_component(name "${shader}" NAME)
set(shader_header "${GENERATED}/${name}.h")
add_custom_command(
OUTPUT ${shader_header}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${shader}
COMMAND GPCBin2C --input=${CMAKE_CURRENT_SOURCE_DIR}/${shader} --output=${shader_header}
)
target_sources(libGPCGUIGLRenderer INTERFACE ${shader_header})
endforeach()
Creating a static library with headers as the only sources worked for me. It is, of course, only a work-around.
Creating a static library with only header files results in an empty library. Mine says !<arch> as the only content.
CMake will automatically get the dependencies correct across sub-directories.
Since all sources are headers, you need to tell CMake which linker language should be used.
Code:
set(OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/generated_include")
add_custom_command(
OUTPUT "${OUTDIR}/outfile.h"
# Replace the next two lines with a proper generating script.
COMMAND mkdir -p ${OUTDIR}
COMMAND touch ${OUTDIR}/outfile.h
)
# Note, I am only adding header files to the library.
add_library(generated-headers STATIC
"${OUTDIR}/outfile.h"
)
set_target_properties(generated-headers
PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(generated-headers PUBLIC ${OUTDIR})
Use in other directories like this:
# In any other directory of the same CMake project:
add_executable(main main.cpp)
target_link_libraries(main generated-headers)
Tested on CMake 3.2, 3.8 and 3.9. Using Ninja and Make generators.
You can use target_sources in CMake 3.1 to tell consumers to compile interface files:
add_library(source_only INTERFACE)
target_sources(source_only INTERFACE foo.cpp)
http://www.cmake.org/cmake/help/v3.1/command/target_sources.html
I ran into comparable problems when trying to use glad: https://github.com/Dav1dde/glad
It uses a custom CMake command to build a binding, which means the files you need to include in the project which uses glad do not exist, so that CMake does not build glad (which would create those files)...
I did not get to try it yet, but example 3 of the following link seems to be a good solution and I believe it may work in your case:
https://samthursfield.wordpress.com/2015/11/21/cmake-dependencies-between-targets-and-files-and-custom-commands/
I'm trying to link my project to a external library that I also developed in which also also use CMake to build. When I try to find RelWithDebInfo or MinSizeRel like this:
FIND_LIBRARY(PCM_LIBRARY_DEBUG pcm
PATHS #CMAKE_LIBRARY_OUTPUT_DIRECTORY#
#CMAKE_LIBRARY_OUTPUT_DIRECTORY#/Debug
NO_DEFAULT_PATH
)
FIND_LIBRARY(PCM_LIBRARY_RELEASE pcm
PATHS #CMAKE_LIBRARY_OUTPUT_DIRECTORY#
#CMAKE_LIBRARY_OUTPUT_DIRECTORY#/Release
#CMAKE_LIBRARY_OUTPUT_DIRECTORY#/MinSizeRel
#CMAKE_LIBRARY_OUTPUT_DIRECTORY#/RelWithDebInfo
NO_DEFAULT_PATH
)
SET(PCM_LIBRARIES debug ${PCM_LIBRARY_DEBUG} optimized ${PCM_LIBRARY_RELEASE})
It does not search in ather directories that are not Release or Debug. I also tried creating PCM_LIBRARY_RELWITHDEBINFO and PCM_LIBRARY_MINSIZEREL but the same thing happens because there is only debug and optimized prefixes in SET. Anyone knows how can I link the correct libraries?
This is unfortunately one of the shortcomings of using find_library. There is no easy way around this without introducing tons of boilerplate code.
The problem here is that when passing files as dependencies to target_link_libraries, you can only distinguish between debug and optimized. If you need more fine-grained control, you will have to manipulate the respective target properties like LINK_INTERFACE_LIBRARIES directly. This is not only quite cumbersome, it also requires detailed knowledge about the inner workings of CMake's property system.
Fortunately, there is another way: The aforementioned limitation only applies when specifying dependencies via filenames. When specifying them as targets, this problem does not occur. The most obvious example is if a library and the executable that depends on it are built from the same source:
add_library(foo_lib some_files.cpp)
add_executable(bar_exe more_files.cpp)
target_link_libraries(bar_exe PUBLIC foo_lib)
This 'just works'. The correct library will be chosen for each build configuration. Things get a little more complicated if the library and the executable live in different independent projects. In that case the library has to provide a configure file with an exported target in addition to the binary files.
Instead of calling find_library to locate the binaries, the dependent executable now just loads that config file and can then use the imported target as if it was a target from the same project.
Many modern libraries already use this approach instead of the classical find_library technique (Qt5 is a prominent example). So if you are at liberty to change the CMakeLists of your dependency and you do not need to support very old CMake versions (<2.6), this is probably the way to go.
I have a pretty big 3rd party cmake directory as a part of my project that some of my projects depend on. I import this directory into my dependent projects using add_subdirectory(). Unfortunately, this also imports the libraries that the 3rd party project links to into my projects.
I was able to manually fix this by specifying LINK_PRIVATE in the cmakelists.txt file of the 3rd party directory for the target_link_libraries() command. I would much prefer to do it remotely from within cmakelists using set_property or similar.
Is this possible?
In general, when using add_subdirectory such effects are hard to contain. Apart from the build targets, you may also get similar pollution effects on global and cache variables, tests and other places, which is why I would not recommend this approach for third library dependencies.
A cleaner approach is provided by the ExternalProject module. This gives you a command ExternalProject_Add that can be used to configure and build a third party library with CMake (or other build systems). The advantage here is that the library's CMake run is completely independent of your own, so there are no pollution effects. The disadvantage is that no targets from that library get imported automatically into your own project, so you might need some additional glue code to get them back in. Still, overall this should be a much cleaner approach.