DependencyManager¶
Introduction¶
DependencyManager
is a CMake module that facilitates development of super-build projects.
In particular it is designed for constructing software ecosystems, by which we mean
a collection of projects each providing a single solution within a particular field
favoring reliance on other solutions within the ecosystem over external dependencies.
Formation of an ecosystem is an alternative way to structure a single mega-program
by splitting it into individual repositories
and has some key advantages. Firstly, it forces a modular build and
encourages development of user friendly interface with better testing.
More importantly, it encourages more open and collaborative environment.
Projects in an ecosystem should be viewed as part of a single body, which necessitates that they are developed side by side. When working on a single project the source code of dependencies has to be readily available and modifiable without risking loss of work. The possibility of duplicate dependencies with different and some times conflicting versions also has to be managed.
DependencyManager
leverages FetchContent to provide the following features:
declaration and popluation of dependencies
placement of dependencies in source
locking to ensure multiple cmake configurations can run simultaneously without conflict
management of declared dependency commit hashes for needs of users OR developers (different modes)
manage version clashes at configure time
construction of dependency graphs
The Problem¶
Dependency structure¶
![_images/example1.png](_images/example1.png)
Dependency tree of project A
.
Content in ()
specifies the version declared by the parent.
Content in []
specifies the range of compatible versions.¶
The above tree shows multiple duplicate entries. Project C
is declared by both A
and B
,
but there can’t be multiple instances of the same module. We have to choose which version of C
to make available. The two declared nodes C
have different versions. B
was developed using
version 1.0.0, while A
wants some newer features only available from 1.1.0
.
The most intuitive way to resolve this dependency clash is to give priority to the first declared
version, in this case A->C
.
The mechanism for declaring dependencies and deciding which one to make available on population is implemented in FetchContent. After running the build configuration and resolving dependencies, we get the following tree.
![_images/full_dependency_tree_clash.png](_images/full_dependency_tree_clash.png)
Populated dependency tree of project A
.
Content in ()
specifies the checked out version.
Grey arrows point to declared nodes which were not used,
with dotted arrows showing which nodes were used instead.¶
The first declared node C
was chosen as expected. However, after checking compatible
version ranges of each duplicate against the checked out version we can
see that node E
that was selected is not compatible with project D
.
This could lead to hard to debug errors during compilation, but luckily
DependencyManager
stops cmake configuration with an error.
The error can be suppressed by setting DEPENDENCYMANAGER_VERSION_ERROR=OFF
,
which allows the above graph of dependency tree to be generated.
From the graph it’s clear that the simplest way to resolve this clash
is to reverse the order in which dependencies of A
were declared
and populated.
![_images/full_dependency_tree_no_clash.png](_images/full_dependency_tree_no_clash.png)
Populated dependency tree of project A
after reversing order of declaration and population.¶
Working with dependencies¶
After running the first configuration and fetching all of dependencies, project A
might have
the following structure:
A/
|-- CMakeLists.txt
|-- dependencies/
| |-- CMakeLists.txt
| |-- B_SHA1
| |-- C_SHA1
| |-- D_SHA1
| |-- B/
| | |-- ..
| |-- C/
| | |-- ..
| |-- D/
| |-- ..
|-- src/
| |-- CMakeLists.txt
| |-- ...
|-- ...
Files <name>_SHA1
store the commit hash for respective dependencies.
Contents of dependencies/CMakeLists.txt
should include
include(FetchContent)
FetchContent_Declare(
dependency_manager
GIT_REPOSITORY <REPOSITORY_NAME>
GIT_TAG <COMMIT_TAG>
)
FetchContent_MakeAvailable(dependency_manager)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE)
DependencyManager_Declare(B <B_repositoryName> VERSION_RANGE <B_versionRange>)
DependencyManager_Declare(C <C_repositoryName> VERSION_RANGE <C_versionRange>)
DependencyManager_Declare(D <D_repositoryName> VERSION_RANGE <D_versionRange>)
This way the DependencyManager module is automatically downloaded without needing pre-installation.
FetchContent_MakeAvailable()
includes DependencyManager and sets CMAKE_MODULE_PATH
.
To use the targets provided in dependencies they still have to be populated.
For example, src/CMakeLists.txt
could include the following
include(DependencyManager)
DependencyManager_Populate(B)
DependencyManager_Populate(C)
DependencyManager_Populate(D)
...
The source code of dependencies is downloaded into dependencies/<name>/
by default.
This can be changed by setting DEPENDENCYMANAGER_BASE_DIR
to a different path.
Contrary to the usual approach in CMake, we do not want dependencies out-of-source
in a build directory. This is because we might want to do some development of dependencies
as well as the main project.
For example, if we found a bug in project C
we might prefer to fix it
within the current workspace. Afterwards, we make a new commit and update it
to version 1.1.1
. By default, rerunning cmake configuration will
checkout the commit stored in C_SHA1
and get us back to the buggy version.
In this case we can update C_SHA1
with the new hash either by hand or
by setting DEPENDENCYMANAGER_HASH_UPDATE
to ON
and running
cmake configuration which will do it for us.
The default behaviour is to always checkout commit store in <name>_SHA1
.
That way when the stored commits are updated after a pull,
running cmake configuraiton will check out the correct version.
This is the behavior that most users will want and expect.
Managing Documentation¶
We provide a utility module for managing documentation of dependencies, DependencyManagerDocs
.
Note
Improve documentation.