3.17.12.1. Bad way¶
3.17.12.1.1. Merge sources¶
Assume we have library foo
:
src
└── foo
├── foo.cpp
└── foo.hpp
Library foo
depends on library boo
:
src
└── boo
├── boo.cpp
└── boo.hpp
The worst thing you can do is to merge both sources by copying boo
to the directory with foo
:
src
├── boo
│ ├── boo.cpp
│ └── boo.hpp
└── foo
├── foo.cpp
└── foo.hpp
C++ directive #include <foo/foo.hpp>
means that directory src/
should
be in list of paths to headers (in terms of compilers like GCC: -I/.../src
).
We want our local directory to have highest priority if there are several
paths (e.g. if there are system wide paths and another copy of library foo
installed system wide, then we want local copy to take a priority). Hence
whatever the rest of header paths is, the #include <boo/boo.hpp>
of
dependent library boo
will always fall to our local copy.
If somebody want to try to use another version of boo
the only choice
you left to him is to remove src/boo
directory.
3.17.12.1.2. Copy to “third_party” directory¶
Instead of copying to the same directory you can copy dependent library
to third_party
directory:
src
└── foo
├── foo.cpp
└── foo.hpp
third_party
└── boo
├── boo.cpp
└── boo.hpp
There will be two independent paths to headers (at least): -I/.../src
and
-I/.../third_party
hence if somebody want to try to use another version
of boo
it’s enough to modify CMake code without changing project structure.
Assuming that both libraries are under VCS control, by
doing plain copy operation you’re losing information about version of
boo
. Also if you want to modify boo
sources locally, then merging them
with update of boo
from upstream might be not a trivial operation.
3.17.12.1.3. Git submodule¶
Using same structure you can keep information about version of boo
by
adding it as a
Git submodule
instead of raw copying. Git functionality will help to track modification of
third party code and merging it with future releases.
Git submodules will work well for:
Adding sources that are extension of your project, hence it makes no sense to add these sources to another project. I.e. submodule is used as a dependency exactly for one node in dependency tree.
Managing dependencies in the project that can’t be used as another dependency, i.e. your project is the leaf in dependency tree, like executable. Though this still may raise the question of size optimization when package manager is used, it will be better to use dependencies as a shared libraries.
Short period of development when you’re actively modifying third party code.
Submodule is not a good long term solution for managing dependencies that can be reused. It leads to conflicts like this:
Note
If we are talking about reusable library then you can’t control final structure of dependency tree. If you are not experiencing such issue it doesn’t mean the same will be true for somebody else.
At first you will simply get target names conflict:
[examples]> rm -rf _builds
[examples]> cmake -Hdep-examples/deps-submodule-conflict -B_builds
-- The C compiler identification is GNU 5.4.1
-- The CXX compiler identification is GNU 5.4.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Error at third_party/b/third_party/x/CMakeLists.txt:4 (add_library):
add_library cannot create target "x" because another target with the same
name already exists. The existing target is a static library created in
source directory "/.../third_party/a/third_party/x".
You can protect including of third party code by condition:
if(NOT TARGET x::x)
add_subdirectory(third_party/x)
endif()
It will solve target name conflict however it may lead to tricky behavior. Effectively it will introduce “first win” strategy while doing dependency resolution, mixing two separate concepts:
Project structure
foo
depends ona
andb
a
depends onx
b
depends onx
Versions
Some
a
version availableSome
b
version availableTwo versions of
x
available:v1.0
andv2.0
Options is a common way to customize your CMake code, often it’s involve
the change of used dependencies and change of project structure. Let’s add option
FOO_WITH_A
to the example to control optional dependency foo
-> a
:
cmake_minimum_required(VERSION 3.2)
project(foo)
option(FOO_WITH_A "Use 'a' module" ON)
add_executable(foo foo.cpp)
if(FOO_WITH_A)
add_subdirectory(third_party/a)
target_link_libraries(foo PUBLIC a::a)
target_compile_definitions(foo PUBLIC FOO_WITH_A)
endif()
add_subdirectory(third_party/b)
target_link_libraries(foo PUBLIC b::b)
enable_testing()
add_test(NAME foo COMMAND foo)
When option FOO_WITH_A
is enabled the x
dependency from a
subdirectory will proceed first, hence v1.0
will be used. And if
FOO_WITH_A
is disabled the x
dependency from b
subdirectory will
proceed first, hence v2.0
will be used.
From user perspective it can be quite surprising and may look like some a
functionality is leaking into module b
:
[examples]> rm -rf _builds
[examples]> cmake -Hdep-examples/deps-submodule-option -B_builds -DFOO_WITH_A=ON
...
[examples]> cmake --build _builds
...
[examples]> cd _builds
[examples/_builds]> ctest -V
1: Test command: /.../examples/_builds/foo
1: Test timeout computed to be: 9.99988e+06
1: Running 'a' module
1: x say: nice
1: Running 'b' module
1: x say: nice
1/1 Test #1: foo .............................. Passed 0.00 sec
Disable module a
and behavior of b
changed!
[examples]> rm -rf _builds
[examples]> cmake -Hdep-examples/deps-submodule-option -B_builds -DFOO_WITH_A=OFF
...
[examples]> cmake --build _builds
...
[examples]> cd _builds
[examples/_builds]> ctest -V
1: Test command: /.../examples/_builds/foo
1: Test timeout computed to be: 9.99988e+06
1: Running 'a' module
1: (Module 'a' disabled)
1: Running 'b' module
1: x say: good
1/1 Test #1: foo .............................. Passed 0.00 sec
Note
Such behavior can be “stabilized” by adding foo
-> x
dependency:
# before 'a' and 'b'
add_subdirectory(third_party/x)
if(FOO_WITH_A)
add_subdirectory(third_party/a)
endif()
add_subdirectory(third_party/b)
But this obviously doesn’t scale well since x
is an implicit dependency
and we have no control over whether it will be used in future a
/b
releases or more dependencies will be introduced or on which options/platforms
they depends, etc.
Since version of x
tied to project structure every time you switch option
FOO_WITH_A
the whole project will rebuild:
[examples]> rm -rf _builds
[examples]> cmake -Hdep-examples/deps-submodule-option -B_builds -DFOO_WITH_A=ON
Build everything from scratch first time:
[examples]> cmake --build _builds
Scanning dependencies of target x
[ 12%] Building CXX object third_party/a/third_party/x/CMakeFiles/x.dir/x/x.cpp.o
[ 25%] Linking CXX static library libx.a
[ 25%] Built target x
Scanning dependencies of target b
[ 37%] Building CXX object third_party/b/CMakeFiles/b.dir/b/b.cpp.o
[ 50%] Linking CXX static library libb.a
[ 50%] Built target b
Scanning dependencies of target a
[ 62%] Building CXX object third_party/a/CMakeFiles/a.dir/a/a.cpp.o
[ 75%] Linking CXX static library liba.a
[ 75%] Built target a
Scanning dependencies of target foo
[ 87%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[100%] Linking CXX executable foo
[100%] Built target foo
Second time just checking:
[examples]> cmake --build _builds
[ 25%] Built target x
[ 50%] Built target b
[ 75%] Built target a
[100%] Built target foo
Disable component a
:
[examples]> cmake -Hdep-examples/deps-submodule-option -B_builds -DFOO_WITH_A=OFF
Whole project rebuild!
[examples]> cmake --build _builds
Scanning dependencies of target x
[ 16%] Building CXX object third_party/b/third_party/x/CMakeFiles/x.dir/x/x.cpp.o
[ 33%] Linking CXX static library libx.a
[ 33%] Built target x
Scanning dependencies of target b
[ 50%] Building CXX object third_party/b/CMakeFiles/b.dir/b/b.cpp.o
[ 66%] Linking CXX static library libb.a
[ 66%] Built target b
Scanning dependencies of target foo
[ 83%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[100%] Linking CXX executable foo
[100%] Built target foo
3.17.12.1.4. Summary¶
The best way to introduce bundled dependency is to add it to the separate
directory like third_party
as a submodule.
The downsides of such approach:
add_subdirectory
is not a shareable solution. Eachadd_subdirectory(third_party/x)
block from different projects has it’s own copy ofx
artifacts. Every time you start new project and addadd_subdirectory(third_party/x)
you’re buildingx
from scratch. It’s not convenient if such build takes a long time.Dependency resolution is not option friendly.
See also