DependencyManager¶
Overview¶
This module facilitates a super-build model for structuring a project as part of a software ecosystem. It manages a tree of dependencies with possible duplicates and version clashes.
Declaring Dependency¶
- DependencyManager_Declare¶
DependencyManager_Declare(<name> <gitRepository>
[VERSION_RANGE <versionRange>]
[PARENT_NAME <parentName>]
[<contentOptions>...])
The DependencyManager_Declare()
function is a wrapper over FetchContent_Declare()
with specialised functionality
Source code is downloaded into
${DEPENDENCYMANAGER_BASE_DIR}/<name>
STAMP_DIR
is in source, by default at${DEPENDENCYMANAGER_BASE_DIR}/.cmake_stamp_dir
Only Git repositories are supported
Commit hash of dependency must be stored in a file
${name}_SHA1
in directory from whichDependencyManager_Declare()
is called
The cached variable DEPENDENCYMANAGER_BASE_DIR
is the top level location where source is cloned.
It is set to ${CMAKE_SOURCE_DIR}/dependencies
by default and should not be modified in the middle
of the configuration process.
The content <name>
must be supported by FetchContent_Declare().
For version checking <name>
must be the name given to top level call of project()
in
the dependencies CMakeLists.txt
.
<gitRepository>
must be a valid GIT_REPOSITORY
as understood by ExternalProject_Add
.
The <contentOptions>
can be any of the GIT download or update/patch options
that the ExternalProject_Add
command understands, except for GIT_TAG
and GIT_REPOSITORY
which are
specified separately.
The value of GIT_TAG
passed to FetchContent
must be a commit hash stored in
${DEPENDENCIES_DIR}/<name>_SHA1
file.
<versionRange>
is a list of compatible versions using comma ,
as a separator (NOT semicolon ;
).
Version must be specified as <major>.[<minor>[.<patch>[.<tweak>]]]
and only specified elements are compared, i.e. 1.2.3 = 1.2
is TRUE
where 1.2
is the version range.
It can be preceded by relational operators <
, <=
, >
, >=
to specify boundaries of the
range. If no relational operators are given that an exact match is requested.
For example, VERSION_RANGE ">=1.2.3,<1.8"
means from version 1.2.3
up to but not including version 1.8
.
Name of the parent node, <parentName>
, is needed to construct the dependency tree.
By default it is the name of the most recently called project()
, i.e. ${PROJECT_NAME}
.
In case there are multiple project()
calls parent name can be specified explicitly with option PARENT_NAME
.
Populating Dependency¶
- DependencyManager_Populate¶
DependencyManager_Populate(<name>
[PARENT_NAME <parentName>]
[DO_NOT_MAKE_AVAILABLE]
[NO_VERSION_ERROR])
This is a again a wrapper over FetchContent_Populate(). Dependency being populated must have been declared sometime before.
<name>
must be the same as in previous call to DependencyManager_Declare()
.
<parentName>
is the name of the parent node. By default, it is the last called project()
.
It must be the same value as in previous call to DependencyManager_Declare()
.
Even if PARENT_NAME
was not specified during declaration, the default values might differ
if a different project()
call was made at the same scope.
After populating the content add_subdirectory()
is called by default, unless DO_NOT_MAKE_AVAILABLE
is set.
During population stage .
When there are duplicate dependencies <versionRange>
is checked and if an already populated dependency
is outside that range an error is raised during configuration.
If subdirectory gets added, a version check is performed.
Version of a dependency is read from the ${<name>_VERSION}
variable which is
automatically set when VERSION
is specified in the project()
call.
If cloned version is not compatible with VERSION_RANGE
specified in DependencyManager_Populate()
than an error gets raised and build configuration stops.
With option NO_VERSION_ERROR
only a warning is printed and configuration continues.
${name}_VERSION
is also brought up to PARENT_SCOPE
.
If cached option DEPENDENCYMANAGER_VERSION_ERROR
is set to OFF
, then an error
is not raised when version clash is found. Note, that NO_VERSION_ERROR
takes precedence.
If option DEPENDENCYMANAGER_FETCHCONTENT
is set, then everything is implemented using standard
FetchContent
, instead of dependencies being brought in to ${DEPENDENCIES_DIR}
Note, that file locking is used which acts as a mutex when multiple configurations are run simultaneously.
The file lock files are stored in STAMP_DIR
.
Update of Commit Hash¶
Every time CMake configuration is rerun an update step is initiated which uses commit hash from <name>_SHA1
file,
checking out the correct version if for some reason a dependency is at a different commit.
Only advanced users with good knowledge of software stack should modify the <name>_SHA1
file.
This applies to developers who in this paradigm need to be able to modify the source code of dependencies
and/or checkout a different commit and successfully configure the build.
Setting cache variable DEPENDENCYMANAGER_HASH_UPDATE
to ON will overwrite <name>_SHA1
file with the
currently checked out hash before the update stage, making sure that the work is preserved.
Graph of Dependency Tree¶
- DependencyManager_DotGraph¶
DependencyManager_DotGraph([NAME <name>])
Writes a dot file with the current structure of dependency tree. It can be compiled to a graphics using graphviz.
By default, the dot file is written to ${CMAKE_CURRENT_BINARY_DIR}/dependencyManager_dotGraph.dot
.
This can be changed by passing <name>
, which can be an absolute path or
a path relative to ${CMAKE_CURRENT_BINARY_DIR}
[For Developers]¶
Structure of the Dependency Tree¶
To correctly resolve valid versions and provide useful summaries we need to store the structure of the dependency tree.
Each node
has a unique nodeID
, represented with a dot separated list of integers i1.i2.i3.i4. ...
,
where i1
is the position of root project (there might be multiple roots at top level),
i2
is the position among children of i1
at level 2, etc.
For example A->{B->{C}, C->{E}, D->{E}}
becomes 1->{1.1->{1.1.1}, 1.2->{1.2.1}, 1.3->{1.3.1}}
A node
contains the following features:
NAME
is name of the project on declarationPARENT_NAME
is name of the parent project on declarationCHILDREN
is an ordered set of nodeID’s for its childrenGIT_REPOSITORY
is the Git url to repositoryGIT_TAG
is the commit hash stored in relevant<name>_SHA1
fileVERSION_RANGE
is the range of required versions
During declaration stage we register each node and add it as a child of a parent node. If a child node by that name already exists, than its content is overwritten and a warning about duplicate node gets printed.
By design, nodes that are made available form a unique set. We call them parent nodes, as they are the only ones that can declare more dependencies as children. We track parent nodes and store the following features:
NAME
NODE_ID
VERSION
is the actual version of the project
This is the complete definition of dependency tree . It is used to check the version and generate its graphical representation.
Global Properties:
__DependencyManager_property_nodeFeatures_${nodeID}
– store node features, one for each node in the full tree__DependencyManager_property_nodeList
– keeps track of nodes as they are created by storing a list of names and node IDsin multi-value-arguements
NAME
andNODE_ID
respectively.
__DependencyManager_property_parentNodes
– store extra features of parent nodes in multi-value-arguments:NAME
- list of parent names;NODE_ID
- list of corresponding nodeIDs;VERSION
- list of corresponding versions
Verbose Output¶
Passing --log-level=debug -D DEPENDENCYMANAGER_VERBOSE=ON
to command-line, turns on
extra printouts. This is useful for debugging only.
Documentation of Utility Functions¶
__DependencyManager_hasDuplicates(<list> <out>)
If <list>
contains duplicates, sets variable called <out>
to TRUE
,
otherwise to FALSE
.
__DependencyManager_updateParentNodes(<name> <nodeID> <version>)
Register a node as a parent by adding its parent node features to the global property.
__DependencyManager_getParentNodes(<prefix>)
Makes full content of parent nodes property available at parent scope
via lists ${<prefix>}_NAME
, ${<prefix>}_NODE_ID
, ${<prefix>}_VERSION
.
__DependencyManager_getParentNodeInfo(<prefix> <name>)
Makes parent node features of a parent <name>
available at parent scope
via variables ${<prefix>}_NODE_ID
, ${<prefix>}_VERSION
.
__DependencyManager_getNodeFeatures(<prefix> <nodeID>)
Makes node features of a node <name>
available at parent scope via variables
${prefix}_name
,
${prefix}_parentName
,
${prefix}_gitRepository
,
${prefix}_gitTag
,
${prefix}_versionRange
,
${prefix}_children
.
If <nodeID>
is 1
, than this is a root node and it gets created
empty on the first call
__DependencyManager_addChild(<parentName> <child_nodeID>)
Appends a child to a parent node.
__DependencyManager_currentNodeID(<prefix> <name> <parentName>)
Deduces the current nodeID by looking at the children of the parent node. Sets the following variables at parent scope:
${prefix}_duplicate
toTRUE
if there is already a node under that name among children;${prefix}_nodeID
to deduced current node ID. If the node is a duplicate usesnodeID
of the relevant child.
__DependencyManager_updateNodeList(<name> <nodeID>)
Updates global list of nodes by storing <name>
and <nodeID>
.
__DependencyManager_getNodeList(<name> <nodeID>)
Updates global list of nodes by storing <name>
and <nodeID>
.
Makes node list available at parent scope via variables ${<prefix>}_NAME
, ${<prefix>}_NODE_ID
.
__DependencyManager_saveNode(<name> <parentName> <gitRepository> <gitTag> <versionRange>)
Store node features. If the node is a duplicate, overwrite content of the registered node. Otherwise, create a new node property and add itself as a child of the parent node.
__DependencyManager_update_SHA1(<name>)
If DEPENDENCYMANAGER_HASH_UPDATE
is ON
, than updates <name>_SHA1
of a cloned dependency. The repository and SHA1 file must be in current directory.
__DependencyManager_VersionCompare(<version1> <comp> <version2> <out>)
Compares <version1>
and <version2>
. <comp>
can be one of
""
, =
, <
, <=
, >
, >=
. Empty is equivalent to =
.
Comparison operators are mapped to VERSION_<COMPARISON>
options in
if()
statements.
If <version2>
has less digits than <version1>
, truncate
<version1>
so they are same length.
Result is set to variable <out>
in parent scope.
__DependencyManager_VersionCheck(<versionRange> <version> <out>)
Checks that <version>
is within <versionRange>
. If it is within, then
store TRUE
in variable <out>
, otherwise store FALSE
.
__DependencyManager_getProjectName(<path> <out>)
Determines the name of the first encountered project() call in the file pointed to by
the provided <path>
and writes the determined name into <out>
(in the parent scope).
If the parsing has failed or the given file does not contain a project() invocation, then
<out>
is set to an empty string.