Skip to content

Commit

Permalink
Refactor YCMEPHelper to implement git safe clone (without delete) as…
Browse files Browse the repository at this point in the history
… an externally specified DOWNLOAD step (#457)

* Refactor YCMEPHelper to implement git safe clone (without delete) as an externally specified DOWNLOAD step

* Set YCM_USE_CMAKE_NEXT by default to OFF
  • Loading branch information
traversaro authored Aug 14, 2024
1 parent acafece commit 50ce674
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 11 deletions.
6 changes: 3 additions & 3 deletions YCMConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ if(NOT DEFINED YCM_USE_CMAKE OR YCM_USE_CMAKE)
endif()

# Use modules from unreleased CMake (default ON)
if(NOT DEFINED YCM_USE_CMAKE_NEXT OR YCM_USE_CMAKE_NEXT)
if(NOT DEFINED YCM_USE_CMAKE_NEXT OR NOT YCM_USE_CMAKE_NEXT)
set_property(GLOBAL PROPERTY YCM_USE_CMAKE_NEXT OFF)
else()
set_property(GLOBAL PROPERTY YCM_USE_CMAKE_NEXT ON)
list(APPEND YCM_MODULE_PATH "${YCM_MODULE_DIR}/cmake-next/proposed")
else()
set_property(GLOBAL PROPERTY YCM_USE_CMAKE_NEXT OFF)
endif()

# Use modules from specific CMake versions (default ON)
Expand Down
207 changes: 200 additions & 7 deletions modules/YCMEPHelper.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ if(DEFINED __YCMEPHELPER_INCLUDED)
endif()
set(__YCMEPHELPER_INCLUDED TRUE)

# Handle CMP0114 (see https://cmake.org/cmake/help/latest/policy/CMP0114.html
# Handle CMP0114 (see https://cmake.org/cmake/help/latest/policy/CMP0114.html
# and https://github.com/robotology/ycm-cmake-modules/pull/452)
get_property(_yeph_YCM_USE_CMAKE_NEXT GLOBAL PROPERTY YCM_USE_CMAKE_NEXT)

Expand Down Expand Up @@ -753,7 +753,7 @@ function(_YCM_EP_ADD_OPEN_STEP _name)
if(DEFINED _cmd)
get_property(_yeph_NO_DEPENDS GLOBAL PROPERTY _yeph_NO_DEPENDS)
get_property(_yeph_INDEPENDENT GLOBAL PROPERTY _yeph_INDEPENDENT)

ExternalProject_Add_Step(${_name} open
COMMAND ${CMAKE_COMMAND} -E echo \"\"
COMMAND ${_cmd}
Expand Down Expand Up @@ -784,6 +784,105 @@ function(_YCM_EP_ADD_INSTALLATION _name)
COMPONENT ${_name})
endfunction()



########################################################################
# _YCM_EP_WRITE_GITCLONE_SCRIPT
#
# Helper function to generate a gitclone script for download repos without deleting it
# inspired from https://gitlab.kitware.com/cmake/cmake/-/blob/v3.30.2/Modules/ExternalProject/shared_internal_commands.cmake#L381

function(_ycm_ep_write_gitclone_script
script_filename
source_dir
git_EXECUTABLE
git_repository
git_tag
git_remote_name
init_submodules
git_submodules_recurse
git_submodules
git_shallow
git_progress
git_config
src_name
work_dir
gitclone_infofile
gitclone_stampfile
tls_version
tls_verify
)

if(NOT GIT_VERSION_STRING VERSION_LESS 1.8.5)
# Use `git checkout <tree-ish> --` to avoid ambiguity with a local path.
set(git_checkout_explicit-- "--")
else()
# Use `git checkout <branch>` even though this risks ambiguity with a
# local path. Unfortunately we cannot use `git checkout <tree-ish> --`
# because that will not search for remote branch names, a common use case.
set(git_checkout_explicit-- "")
endif()
if("${git_tag}" STREQUAL "")
message(FATAL_ERROR "Tag for git checkout should not be empty.")
endif()

if(GIT_VERSION_STRING VERSION_LESS 2.20 OR
2.21 VERSION_LESS_EQUAL GIT_VERSION_STRING)
set(git_clone_options "--no-checkout")
else()
set(git_clone_options)
endif()
if(git_shallow)
if(NOT GIT_VERSION_STRING VERSION_LESS 1.7.10)
list(APPEND git_clone_options "--depth 1 --no-single-branch")
else()
list(APPEND git_clone_options "--depth 1")
endif()
endif()
if(git_progress)
list(APPEND git_clone_options --progress)
endif()
foreach(config IN LISTS git_config)
list(APPEND git_clone_options --config \"${config}\")
endforeach()
if(NOT ${git_remote_name} STREQUAL "origin")
list(APPEND git_clone_options --origin \"${git_remote_name}\")
endif()

# The clone config option is sticky, it will apply to all subsequent git
# update operations. The submodules config option is not sticky, because
# git doesn't provide any way to do that. Thus, we will have to pass the
# same config option in the update step too for submodules, but not for
# the main git repo.
set(git_submodules_config_options "")
if(NOT "x${tls_version}" STREQUAL "x")
list(APPEND git_clone_options -c http.sslVersion=tlsv${tls_version})
list(APPEND git_submodules_config_options -c http.sslVersion=tlsv${tls_version})
endif()
if(NOT "x${tls_verify}" STREQUAL "x")
if(tls_verify)
# Default git behavior is "true", but the user might have changed the
# global default to "false". Since TLS_VERIFY was given, ensure we honor
# the specified setting regardless of what the global default might be.
list(APPEND git_clone_options -c http.sslVerify=true)
list(APPEND git_submodules_config_options -c http.sslVerify=true)
else()
list(APPEND git_clone_options -c http.sslVerify=false)
list(APPEND git_submodules_config_options -c http.sslVerify=false)
endif()
endif()

string (REPLACE ";" " " git_clone_options "${git_clone_options}")

# Once we can require CMake >= 3.17 this can be substituted with ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
configure_file(
${YCM_MODULE_DIR}/modules/YCMEPHelper/gitsafeclone.txt.in
${script_filename}
@ONLY
)
endfunction()


########################################################################
# YCM_EP_HELPER
#
Expand All @@ -792,7 +891,7 @@ endfunction()
function(YCM_EP_HELPER _name)
# Adding target twice is not allowed
if(TARGET ${_name})
message(WARNING "Failed to add target ${_name}. A target with the same name already exists.")
message(WARNING "Failed to add target ${_name}. A target with the same name already exists.!!!")
return()
endif()
# Check arguments
Expand Down Expand Up @@ -1032,7 +1131,8 @@ function(YCM_EP_HELPER _name)
# Specific setup for GIT
_ycm_setup_git()

list(APPEND ${_name}_REPOSITORY_ARGS GIT_REPOSITORY ${YCM_GIT_${_YH_${_name}_STYLE}_BASE_ADDRESS}${_YH_${_name}_REPOSITORY})
set(git_repository ${YCM_GIT_${_YH_${_name}_STYLE}_BASE_ADDRESS}${_YH_${_name}_REPOSITORY})
list(APPEND ${_name}_REPOSITORY_ARGS GIT_REPOSITORY ${git_repository})

if(DEFINED _YH_${_name}_TAG)
list(APPEND ${_name}_REPOSITORY_ARGS GIT_TAG ${_YH_${_name}_TAG})
Expand All @@ -1052,6 +1152,99 @@ function(YCM_EP_HELPER _name)
set(_setup_repo_cmd ${_setup_repo_cmd}
COMMAND ${GIT_EXECUTABLE} config --local user.email ${YCM_GIT_${_YH_${_name}_STYLE}_COMMIT_EMAIL})
endif()

# If GIT is used, we do not want to use the default GIT DOWNLOAD command provided by ExternalProject, as it deletes
# existing folders (see https://cmake.org/cmake/help/latest/module/ExternalProject.html#directory-options)
# and this is not convenient if you create multiple build of the superbuild, as each one would
# delete the source directory cloned by the other. Instead, we define our own DOWNLOAD command that do not delete
# a git repo folder if it already exists

# We do not define the custom GIT DOWNLOAD command if the outside call of YCMEPHelper already redefined it
if(NOT DEFINED _YH_${_name}_DOWNLOAD_COMMAND)
# Coherently with how the gitclone command is created inside ExternalProject, we also define a CMake
# script that defines the clone commands, and then we call it
# This part is inspired by https://gitlab.kitware.com/cmake/cmake/-/blob/v3.30.2/Modules/ExternalProject/shared_internal_commands.cmake#L945-1034
set(method git)
# FetchContent gives us these directly, so don't try to recompute them
if(NOT GIT_EXECUTABLE OR NOT GIT_VERSION_STRING)
unset(CMAKE_MODULE_PATH) # Use CMake builtin find module
find_package(Git QUIET)
if(NOT GIT_EXECUTABLE)
message(FATAL_ERROR "error: could not find git for clone of ${name}")
endif()
endif()

set(git_tag "${_YH_${_name}_TAG}")
if(NOT git_tag)
set(git_tag "master")
endif()

set(git_init_submodules TRUE)

set(git_remote_name "")
if(NOT git_remote_name)
set(git_remote_name "origin")
endif()

_ep_get_tls_version(${_name} tls_version)
_ep_get_tls_verify(${_name} tls_verify)
set(git_shallow "${_YH_${_name}_SHALLOW}")
set(git_progress "")
set(git_config "")

# If git supports it, make checkouts quiet when checking out a git hash.
# This avoids the very noisy detached head message.
if(GIT_VERSION_STRING VERSION_GREATER_EQUAL 1.7.7)
list(PREPEND git_config advice.detachedHead=false)
endif()

# The command doesn't expose any details, so we need to record additional
# information in the RepositoryInfo.txt file. For the download step, only
# the things specifically affecting the clone operation should be recorded.
# If the repo changes, the clone script should be run again.
# But if only the tag changes, avoid running the clone script again.
# Let the 'always' running update step checkout the new tag.
#
set(extra_repo_info
"repository=${git_repository}
remote=${git_remote_name}
init_submodules=${git_init_submodules}
recurse_submodules=${git_submodules_recurse}
submodules=${git_submodules}
")
get_filename_component(src_name "${${_name}_SOURCE_DIR}" NAME)
get_filename_component(work_dir "${${_name}_SOURCE_DIR}" PATH)

set(clone_script ${${_name}_TMP_DIR}/${_name}-gitsafeclone.cmake)
_ycm_ep_write_gitclone_script(
${clone_script}
${${_name}_SOURCE_DIR}
${GIT_EXECUTABLE}
${git_repository}
${git_tag}
${git_remote_name}
"${git_init_submodules}"
"${git_submodules_recurse}"
"${git_submodules}"
"${git_shallow}"
"${git_progress}"
"${git_config}"
${src_name}
${work_dir}
${stamp_dir}/${_name}-gitinfo.txt
${stamp_dir}/${_name}-gitsafeclone-lastrun.txt
"${tls_version}"
"${tls_verify}"
)
set(comment "Performing download step (YCM's safe git clone) for '${name}'")
set(cmd ${CMAKE_COMMAND}
-DCMAKE_MESSAGE_LOG_LEVEL=VERBOSE
-P ${clone_script}
)

list(APPEND ${_name}_COMMAND_ARGS DOWNLOAD_COMMAND "${cmd}")
endif()

elseif("${_YH_${_name}_TYPE}" STREQUAL "SVN")
# Specific setup for SVN
_ycm_setup_svn()
Expand Down Expand Up @@ -1445,9 +1638,9 @@ macro(YCM_BOOTSTRAP)
file(READ ${YCM_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/YCMTmp/YCM-cfgcmd.txt _cmd)
string(STRIP "${_cmd}" _cmd)
string(REGEX REPLACE "^cmd='(.+)'" "\\1" _cmd "${_cmd}")
# The -DCMAKE_PREFIX_PATH in YCM-cfgcmd.txt uses | as list separator, so it is not
# usable as it is when invoking CMake from the config line. As YCM during bootstrap
# does not need to find any package via CMAKE_PREFIX_PATH, we just remove it
# The -DCMAKE_PREFIX_PATH in YCM-cfgcmd.txt uses | as list separator, so it is not
# usable as it is when invoking CMake from the config line. As YCM during bootstrap
# does not need to find any package via CMAKE_PREFIX_PATH, we just remove it
string(REGEX REPLACE "-DCMAKE_PREFIX_PATH:PATH=.+;-C" "-C" _cmd "${_cmd}")
# The cache file is generated with 'file(GENERATE)', therefore it is not yet
# available. Since we cannot use CMAKE_CACHE_ARGS or CMAKE_CACHE_DEFAULT_ARGS,
Expand Down
Loading

0 comments on commit 50ce674

Please sign in to comment.