Internal dependencies with cmake in a large project - cmake

I have a project with multiple libraries which have dependencies between them.
It looks similar to this:
project
+ libs
| + lib1
| + lib2
| + lib3
+ apps
| + app1
| + app2
I have dependencies between different targets. For example, lib2 depends on lib1 and lib3 depends on lib2 (note transitive dependency here). Apps mostly depends on arbitrary number of libs.
I would like to have a CMakeLists.txt in the project root and in each library and application folder.
I assume that in the main CMakeLists.txt I will use add_subdirectory() to include other CMakeLists.txt files but I'm not sure what is the best practice to reflect dependencies between different targets (library on other libraries and application on libraries) since they reside on the same level. In other words I would like to know how CMakeLists.txt for lib2 and lib3 could look like.
Update:
I managed to find a potential solution: people suggest to use add_dependencies(lib1 lib2). Should this be specified before all the add_subdirectory()?

Related

Dependency between two subdirectories in CMake

In my root CMakeLists.txt, I have:
add_subdirectory(libs)
add_subdirectory(main)
In libs, I have my own CMakeLists.txt to build external projects.
In main, there is a CMakeLists.txt from another repository, which I do not control.
To build main, libs needs to be built. I do not know how to specify the
dependency between main and libs.
In libs, with my external projects lib1 and lib2, I used add_dependencies(lib1 lib2) and I have lib2 built before lib1. I did not find how to do that for
main and libs.
The difficulty for me is I have to mix external projects and subdirectories, and I did not find any answer or was not able to adapt them.
I converted add_subdirectory(main) into an external project. Since it is not possible to make a dependency on subdirectories, I use directly the inner targets. With all that I got:
include(ExternalProject)
add_subdirectory(libs)
ExternalProject_Add(main
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/main
...
)
add_dependencies(main lib1 lib2)

Can I add a library from the folder above?

I can't add my static library to my project.
I have the following project structure:
+ root/
+ CmakeLists.txt // Include all projects
+ Base/
| + foo.cpp
| + CmakeLists.txt
+ App1/
| + app1.cpp
| + CmakeLists.txt // Requires Base lib
+ App2/
| + app2.cpp
| + CmakeLists.txt // Requires Base lib
I try to do it in the following way:
Base CmakeLists.txt:
cmake_minimum_required(VERSION 3.10.2)
add_library(Base STATIC foo.cpp)
App1 CmakeLists.txt:
cmake_minimum_required(VERSION 3.10.2)
project(App1)
add_executable(${CMAKE_PROJECT_NAME} app1.cpp)
include(../Base/CMakeLists.txt)
But I have the following error:
CMake Error at C:/DPA/Base/CMakeLists.txt:3 (add_library):
Cannot find source file:
foo.cpp
How I can properly setup library to include in all projects?
That's not how you use such a directory structure. Instead, in App1/CmakeLists.txt, go with
add_executable(App1 app1.cpp)
target_link_libraries(App1 Base)
This declares that App1 depends on Base and shall be linked against that library. "Linking" means not only passing the correct arguments to your linker, it also propagates include flags and other options to the compiler when building App1 sources.
If you intend to build only parts of your project, you can use an additional argument to add_executable, i.e.
add_executable(App1 EXCLUDE_FROM_ALL app1.cpp)
This way, when you build the default target, App1 won't be part of the build. You can still build it manually/upon request, e.g. when working with makefiles,
make App1
will build the executable App1 and everything that's required for it.

CMake and using git-submodule for dependence projects

Consider the following three projects.
ProjectA does not have any dependencies and its CMakeLists.txt at top level is like the following,
cmake_minimum_required(VERSION 2.8.4)
project(A CXX)
add_library(a ${PROJECT_SOURCE_DIR}/liba.cpp)
ProjectB depend on ProjectA, and I add ProjectA as a git-submodule, so its structure will be like below,
ProjectB
CMakeLists.txt
libb.cpp
ProjectA (git submodule)
CMakeLists.txt
liba.cpp
and ProjectB's CMakeLists.txt look like the following
cmake_minimum_required(VERSION 2.8.4)
project(B CXX)
add_subdirectory(ProjectA)
add_library(b ${PROJECT_SOURCE_DIR}/libb.cpp)
target_link_libraries(b a)
So far it is alright.
Now let's say it comes a ProjectC. It depends on both ProjectA and ProjectB. And let's assume that I am not aware that ProjectB depends on ProjectA already (e.g., I did not create the two before. Or think that ProjectC actually have many dependencies and I shall not be forced to figure out an exact dependency tree among them).
Anyway, I add both ProjectA and ProjectB as git submodules in ProjectC. So it has the following structure,
ProjectC
CMakeLists.txt
libc.cpp
ProjectA (git submodule)
CMakeLists.txt
liba.cpp
ProjectB (git submodule)
CMakeLists.txt
libb.cpp
ProjectA (git submodule of the submodule ProjectB)
CMakeLists.txt
liba.cpp
And it has the following CMakeLists.txt.
cmake_minimum_required(VERSION 2.8.4)
project(C CXX)
add_subdirectory(ProjectA)
add_subdirectory(ProjectB)
add_library(c ${PROJECT_SOURCE_DIR}/libc.cpp)
target_link_libraries(c a b)
Now if I try to run cmake for ProjectC, I get the following error.
add_library cannot create target "a" because another target with the same
name already exists....
I understand the reason of this error. It is because ProjectA is added as a subdirectory twice and all targets created by add_library is Global. For this particular case, I can fix it by remove add_subdirectory(ProjectA) in the ProjectC/CMakeLists.txt. However, consider a situation that ProjectC has many dependencies, and there might or might not be dependencies among them. From point of view of the developer of ProjectC, he should not need to care about the inter-dependencies among its own dependencies.
In this situation, what is the best way to have ProjectC include its dependencies? Having ProjectA and ProjectB as a git-submodule in source form is a must. I am aware that I can simply install ProjectA and ProjectB somewhere, and ProjectC only need to find the installed files somewhere. However, if possible, I would like to avoid that kind of solution (for example, if the installation was built with a different ABI than the one used by ProjectC, incompatibility issues arise). I would like all three projects to be built inside the build tree of ProjectC.
You can check whether the target a already exists before calling add_subdirectory:
if (NOT TARGET a)
add_subdirectory(ProjectA)
endif ()
so that it only adds the subdirectory once for your whole CMake project.

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.

Overlapping dependencies between libraries in CMake

Let's say there's the following directory structure:
projects
|
+--lib1
| |
| +-CMakeFiles.txt
|
+--lib2
| |
| +-CMakeFiles.txt
|
+--test
|
+-CMakeFiles.txt
lib1/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
add_library(lib1 STATIC lib1.cpp)
lib2/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
add_subdirectory(../lib1 ${CMAKE_CURRENT_BINARY_DIR}/lib1)
add_library(lib2 STATIC lib2.cpp)
target_link_libraries(lib2 lib1)
test/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
project(test)
add_subdirectory(../lib1 ${CMAKE_CURRENT_BINARY_DIR}/lib1)
add_subdirectory(../lib2 ${CMAKE_CURRENT_BINARY_DIR}/lib2)
add_executable(test main.cpp)
target_link_libraries(test lib1 lib2)
I.e. lib2 depends on lib1 and test depends on both of them. (I know that technically static libraries don't "link", but that's just an example.)
The problem is that with the current setup, lib1 compiles twice - the first time it is within the "test" build directory, and a second time it is within "test/build_directory/lib2/build_directory". I'd like to avoid that.
I want to be able to add a dependency on lib1, lib2 or both of them (using add_subdirectory) to any project that's located elsewhere. So moving CMakeFiles isn't an option. I also want to avoid compiling any library several times.
How can I do that?
Platform: CMake v. 2.8.4 and Windows XP SP3
A top-level CMakeLists.txt file isn't an option, because I want to keep a clean top-level directory and be able to include libraries in other projects that can be located elsewhere. Because it is Windows, I can't "install package system-wide" - I don't want to lose ability to switch compiler on the fly. Utility libraries built with different compilers will use different C runtime libraries/ABI, and as a result will be incompatible.
One other solution is to add a guard at the top of the subdirectory-CMakeLists.txt:
if(TARGET targetname)
return()
endif(TARGET targetname)
Which will cause cmake to do nothing the second time the subdirectory is added (if targetname is being defined in that file, of course).
This will lead to the lib beeing build in an sort-of-arbitrary place (depending on which module added it first) in the build/ tree, but it will be built only once and linked everywhere.
In your example, you would add
if(TARGET lib1)
return()
endif(TARGET lib1)
at the top of lib1/CMakeFiles.txt
With CMake, library dependencies are transitive, so you shouldn't call add_subdirectory twice in test/CMakeFiles.txt (nor do you need to list lib1 as a dependency of test since it is already a dependency of lib2's).
So you could modify test's CMakeFiles.txt to:
cmake_minimum_required(VERSION 2.8.7) # Prefer the most current version possible
project(test)
add_subdirectory(../lib2 ${CMAKE_CURRENT_BINARY_DIR}/lib2)
add_executable(test main.cpp)
target_link_libraries(test lib2)
Also, you should probably remove the cmake_minimum_required calls from your non-project CMakeFiles.txt files (the lib ones). For further info, run:
cmake --help-policy CMP0000
This setup will still cause all libs to be recompiled if you add a similar test2 subdirectory and project which depends on lib1 and lib2. If you really don't want to have a top-level CMakeFiles.txt in projects/, then you're stuck with what you're doing, or you could use either the export or install command.
export would create a file which could be included by other projects and which imports the targets into the project which calls include.
install could install the libraries to another common subdirectory of projects/. Depending on your source directory structure, this could have the benefit of only making the intended library API headers available to dependent projects.
However, both of these options require the dependent library projects to be rebuilt (and installed) if they are modified, whereas your current setup includes all the dependent targets in your project, so any change to a source file in a dependent library will cause your test target to go out of date.
For further details about export and install, run:
cmake --help-command export
cmake --help-command install
Perhaps add a top-level CMakeLists.txt in your projects dir. Something like:
project( YourProjects )
add_subdirectory( lib1 )
add_subdirectory( lib2 )
add_subdirectory( test )
This should be sufficient and will give you a solution-file or makefile in your top-level build-dir. You should then remove the add_subdirectory( ../lib1 ... from your lib1 and lib2 projects, but instead simply link to them. CMake will know how to find lib1 and lib2 when compiling test.
I.e. in lib2:
project( lib2)
add_library(lib2 STATIC lib2.cpp)
target_link_libraries(lib2 lib1)
and in test:
project( test )
add_executable(test main.cpp)
target_link_libraries(test lib1 lib2)
Added bonus: you will get makefiles/solutionfiles for building lib2 (with dependent lib1) in the lib2 directory...