Ensure that Git Submodules are Initialized
You've checked out a new repository, try to build it, and are confronted with a wall of errors.
The CI is green and your colleagues have assured you that all the dependencies are included in the repository. Why is the build failing?
You scratch your head -- why is your environment different?
You look through the CMakeLists.txt, do some grepping, but you can't find those missing dependencies. Puzzled you look at the README, and there you see it, you forgot to clone the submodules, ...
Facepalm ...
After making this mistake when cloning a new project too many times, I resolved to find a solution.
The idea is simple, check if the project has submodules, and make sure that each submodule is properly initialised.
In git we can list submodules with git submodule. Helpfully, the command tells us the state of each submodule.
$ git submodule status
-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx path/submodule_path (branch)
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx path/submodule_path (branch)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx path/submodule_path (branch)
A line starting with - denotes a submodule that is not checked out yet, a line starting with + denotes a submodule where the currently checked out commit does not match the hash of the commit from the .gitmodules file (the submodule has changed, but the parent repository has not been updated).
Enter EnsureSubmodulesInitialised. Just include it in your main CMakeLists.txt, and if you ever forget to initialise a submodule, you'll get a nice helpful error message.
# Ensure that all submodules have been initialised.
#
# This is helpful in repositories that include dependencies as
# submodules since it is easy to forget to initialise them on
# clone, or to forget to initialise a new dependency after a pull.
function(ensure_submodules_initialised)
# Check if the submodule is initialized.
#
# If the repository was cloned without `--recursive`, the
# submodule directory will be empty. If it has been
# initialised, it will contain a `.git` directory. We use
# it's present as a marker to ensure that the submodule has
# been initialised.
execute_process(
COMMAND git submodule
OUTPUT_VARIABLE _git_submodule_status
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
# Replace newlines with `;` to make the result into a list
string(REGEX REPLACE "\n" ";"
_git_submodule_status "${_git_submodule_status}")
set(_has_uninitialised_submodule FALSE)
foreach(_submodule_status_line ${_git_submodule_status})
# examples of status lines
#
# -ac9dac21a68eacaa826c69a3b42d0aa69a3c68cf
# path/submodule_path (branch)
# +ac9dac21a68eacaa826c69a3b42d0aa69a3c68cf
# path/submodule_path (branch)
# ac9dac21a68eacaa826c69a3b42d0aa69a3c68cf
# path/submodule_path (branch)
#
# A `-` in the first position indicates that the
# submodule is not initialised.
# Remove the leading space. It will only complicate
# parsing for us.
string(STRIP ${_submodule_status_line}
_submodule_status_line)
# Make the string into a list so that we get easier
# access to the different parts
string(REPLACE " " ";"
_submodule_status_line ${_submodule_status_line})
list(POP_FRONT _submodule_status_line
_submodule_hash _submodule_path _submodule_branch)
string(FIND ${_submodule_hash} "-" _submodule_not_initialised)
if("${_submodule_not_initialised}" EQUAL 0)
message(STATUS "Submodule ${_submodule_path} is not initialised.")
set(_has_uninitialised_submodule TRUE)
endif()
endforeach()
if(_has_uninitialised_submodule)
message(FATAL_ERROR
"Some submodules were not initialised. \n"
"Run 'git submodule update --init --recursive' to "
"initialise them."
)
endif()
endfunction()
Line Wrapping
This file has been reformatted for display on this blog. The file on gitlab is properly formatted.