Build a library and multiple executables with one target name using cmake - cmake

I am currently working on migrating from an internal build system to cmake, and I am enjoying it so far.
Our source code is broken up into discrete named components. These components generally will build a library and a set of executables. I have setup cmake with a base CMakeLists.txt and then created a CMakeLists.txt in each code component that is then included in the base. The component CMakeLists.txt have multiple targets in them, one for a library and then a variable number of executables.
With our current system you can type something like:
make component_name
and that will build a library and any executables associated with component_name. Is something like that possible with cmake? Can I use one name to build all of the targets in a CMakeLists.txt file?

First define a custom target, then define the dependencies of the target:
ADD_CUSTOM_TARGET(component_name)
ADD_DEPENDENCIES(component_name lib1 lib2 exe1 exe2)

Related

How to build namespaced subprojects in cmake that find each other

I am trying to get a basic cmake project to work with 2 subprojects (each a library) where one depends on the other.
As such I have a self-contained project for lib1 and a self-contained project for lib2 (but that depends on lib1). Some lib2 authors could while others will not have access to lib1 source. If no access is desired, it seems that building and installing one after the other is the way to go. Is there a way to build them in one go using cmake? Currently, I am trying using an overall project, libs.
However, as the libraries are related I want to call them in a boost / poco like fashion. That is for lib2 I would like to write:
find_package(libs COMPONENTS lib1 REQUIRED)
target_link_libraries(lib2 PUBLIC libs::lib1)
I have created config files for both lib1 and lib2 and libs. However, I keep getting the same error
CMake Error at libs/lib2/CMakeLists.txt:67 (find_package):
By not providing "Findlibs.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "libs", but
CMake did not find one.
Could not find a package configuration file provided by "libs" with any of
the following names:
libsConfig.cmake
libs-config.cmake
Add the installation prefix of "libs" to CMAKE_PREFIX_PATH or set
"libs_DIR" to a directory containing one of the above files. If "libs"
provides a separate development package or SDK, be sure it has been
installed.
I don't understand where to put these files. It searches for them under prefix/ etc but at the time of building none of these libraries is yet installed. What is the proper place to put the -config.cmake files? I have put them all in
${CMAKE_BINARY_DIR}/libs
which seems similar to Poco, but the error remains. How can the libraries find each other?
// update:
From the comments I understand that lib1 first needs to be build in order to use find_package. Is there a way (in cmake) to
build lib1, install it, build lib2
or is this better to do using a bash / python / etc script?

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?

Using project() for dependent CMake subdirectories

I have several projects consisting of a few libraries, each living in its own subdirectory, knitted together by the topmost CMakeLists.txt file. I am in the habit of using project(<DIRNAME>) at the top of each CMakeLists.txt file and I try to structure the subprojects in such a way that they could be compiled separately from the top project. However, while this might make sense for standalone, core libraries, it cannot work for the libraries that depend on them because I need to do stuff like
target_link_libraries(gui core)
And core will nor be defined if I am trying to compile gui as a standalone project.
Is it wrong to use project() in this context, or am I missing something?
A Matter of Taste
This is in my opinion mainly a matter of taste. I don't think multiple project() commands itself are a problem, its more that projects I have seen using this approach tend to repeat itself in other parts and sometimes are running into problems with global cached variables.
Depending Libraries
The more relevant fact is, that the depending libraries will also add an include dependencies.
For standalone static library targets - not executable or shared library targets who really link the library - the simple target_link_libraries() command could be ignored with something like:
if (TARGET core)
target_link_libraries(gui core)
endif()
But the header files include dependency remains.
Standalone Projects in CMake
For me a (sub-)project to be really standalone needs not only the project() command, but it should also have a export(TARGETS ...) command. Then you could e.g. use find_package() commands to resolve any open dependencies with something like:
if (NOT TARGET core)
find_package(core REQUIRED)
endif()
target_link_libraries(gui core)
References
Making cmake library accessible by other cmake packages automatically
CMake share library with multiple executables

CMake share library with multiple executables

My project contains several executables that share some common code. I would like to put the common code in a static library that the executables can link to. (The common code is pretty small and I prefer not to deal with shared libraries).
The source tree looks something like this:
project
CMakeLists.txt
common
CMakeLists.txt
src
include
app1
src
CMakeLists.txt
app2
src
CMakeLists.txt
app1 and app2 both depend on the code in common.
This common code is very application specific and will never need to be used by another project outside this directory tree. For that reason I would prefer not to install the library in any sort of global location.
The top-level CMakeLists.txt file just adds the subdirectories:
project(toplevel)
cmake_minimum_required(VERSION 3.1)
add_subdirectory(common)
add_subdirectory(app1)
add_subdirectory(app2)
The common library's CMakeLists.txt file creates the static library and sets include directories:
add_library(common STATIC common.cpp)
target_include_directories(common PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include")
And the file for the executables looks like this:
project(app1)
cmake_minimum_required(VERSION 3.1)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} common)
Now for my question. If I run CMake from the top level project directory, I can build app1 and app2 and they build successfully. However, if I want to build a single one of these projects (by running CMake from app1, for example) instead of building from the top level directory, I get an error because common/include is not added to the header search path.
I can see why this happens. There is nothing in the CMakeLists.txt file for app1 or app2 that "pulls in" common. This is only done at the top level.
Is there a way around this, or is this behavior generally considered acceptable? Is something about my setup sub-optimal? I'm just thinking it would be nice to be able to build the projects individually instead of from the top level in the event that we start to develop more and more executables that use this common library, but perhaps this is something I shouldn't be concerned about.
When you setup your build environment, you should put some thought into the following three topics (beside others, but for this discussion/answer I reduced it to the three I feel are relevant here):
Dependecies / Coupling
Deployment
Teams
"Strong Coupling"
I came to think of the add_subdirectory() command as supporting "strong coupling" and your current setup implicitly supports:
A frequently changing common library
A single deployment (process and timing) for all your apps
A single team working on the complete source base
An IDE would show it all in one solution
You generate one build environment for everything
"Loose Coupling"
If you want a more "loose coupling" you could make use of external scripts in other languages or the use of CMake's ExternalProject_Add() macro. So if you setup the common library (maybe even including "binary delivery") and each app as a separate project you do support:
A less often changing common library
Probably with its own release cycles
An independent development/deployment cycle for each app
A team of different developers working on each app
A Mixture of Both
So as you can see there a lot of things to consider and CMake can give you support for all kind of approaches. Considering that your project might be in the early stages, you probably go by a mixed approach (not right away de-coupling the common library):
CMakeLists.txt
project(toplevel)
cmake_minimum_required(VERSION 3.1)
include(ExternalProject)
ExternalProject_Add(
app1
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app1"
PREFIX app1
INSTALL_COMMAND ""
)
ExternalProject_Add(
app2
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app2"
PREFIX app2
INSTALL_COMMAND ""
)
app1/CMakeLists.txt
project(app1)
cmake_minimum_required(VERSION 3.1)
add_subdirectory(../common common)
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} common)
This will actually generate three build environments. One directly in your binary output directory and one each in app1 and app2 sub-directories.
And in such approaches you may want to think about common CMake toolchain files.
References
Use CMake-enabled libraries in your CMake project (II)
CMake: How to setup Source, Library and CMakeLists.txt dependencies?
You should use project() command in subdirectories only if this subproject is intended to be built both as standalone and as a part of toplevel project. This is the case for LLVM and Clang, for example: Clang can be compiled separately, but when LLVM build system detects Clang source, it includes its targets too.
In your case you don't need subprojects. To compile only app1 or app2 target issue make app1/make app2 in projects build dir.

CMake find_package dependency on subproject

I have the following directory layout:
main_folder
+ static_lib1
+ executable
Both 'static_lib1' and 'executable' have a full CMakeLists so that they can be
built independently.
The 'executable' depends on 'static_lib1'. It uses find_package() to locate 'static_lib1'.
The main_folder contains a CMakeLists that includes both 'static_lib1' and 'executable' via add_subdirectory for conveniently building the whole project in one go.
Everything works fine if I manually build 'static_lib1' and then 'executable'. But when running the CMakeLists from the main folder, I get an error because find_package is unable to find the library files from 'static_lib1' which have not yet been built.
How can I resolve this while keeping the CMakeLists files separate (i.e. without including the static_lib's CMakeLists from the executable's CMakeLists)?
In executable's CMakeLists.txt you can check if you are building stand-alone or as part of project:
if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR )
# stand-alone build
find_package(static_lib1)
else()
include_directories(../static_lib1)
link_directories(../static_lib1)
...
target_link_libraries(executable static_lib1)
endif()
Switch from a file-based approach to a target-based approach for handling the dependency from executable to static_lib1.
The original problem occurred because executable called find_package for locating static_lib1, which then attempted to fill a variable like STATIC_LIB1_LIBRARY with the paths to the library files by calling find_library. executable then consumes the content of that variable in a target_link_libraries(executable ${STATIC_LIB1_LIBRARY}) call. The problem here is, since those library files only get generated as part of the build, that call to find_library will not be able to find anything.
Building executable needs to support two scenarios here:
Building standalone, where a pre-compiled version of static_lib1 is located somewhere on the disc.
Building from main_folder, where both executable and static_lib1 are part of the same build.
The approach from the question supports scenario 1, but not scenario 2.
Instead of using using a variable to communicate a dependency between the two builds, use a target. The CMakeLists.txt for static_lib1 likely creates a library target like add_library(static_lib1 [...]). In executable we now simply do target_link_libraries(executable PUBLIC static_lib1). This is sufficient to support scenario 2.
To also allow for scenario 1 at the same time, we look at the call to find_package(static_lib1) in the CMakeLists.txt for executable. Instead of providing a variable like before, this call now needs to provide a target static_lib1 for consumption.
So we adapt the find script for static_lib1 to the following behavior:
If a target static_lib1 already exists, there's nothing to be done and the find script can just return (this is scenario 2).
Otherwise, we call find_library to locate the library file on disc (as before in the original approach) and then create a new imported target: add_library(static_lib1 STATIC IMPORTED). We then configure all relevant properties of the static library to that target. For instance, to add the location of the library file, we could do
set_target_properties(static_lib1 PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION ${STATIC_LIB1_LIBRARY}
)
To support multi-config generators like MSVC, instead of setting IMPORTED_LOCATION and IMPORTED_LINK_INTERFACE_LANGUAGES, you will want to set the configuration specific properties like IMPORTED_LOCATION_DEBUG and IMPORTED_LOCATION_RELEASE instead. Since this can get quite tedious to do manually, you can have CMake generate this information (and a bunch of other convenient stuff) for you in a package script. The find mechanism for package scripts works slightly different under the hood, but the code in the CMakeLists.txt for executable will look just the same, a simple call to find_package(static_lib1). The main difference is that this call will then not dispatch to a hand-written find script, but to a package script that was automatically generated by CMake as part of the build process of static_lib1.
I guess I will leave this answer for posterity since only recently I have searched for a solution to this problem and found out that...
Since CMake 3.24 it is possible!
It is possible to override subsequent calls to find_package() with FetchContent_Declare() flag OVERRIDE_FIND_PACKAGE.
Your
add_subdirectory("path/to/static_lib1")
call has to be replaced in main_folder/CMakeLists.txt with:
include(FetchContent)
FetchContent_Declare(
static_lib1
SOURCE_DIR "path/to/static_lib1"
OVERRIDE_FIND_PACKAGE
)
Any calls to find_package(static_lib1) will call FetchContent_MakeAvailable() for you, virtually making it identical to add_subdirectory() call.
You can read more about OVERRIDE_FIND_PACKAGE in CMake documentation.