diff --git a/YCMConfig.cmake.in b/YCMConfig.cmake.in index d3c000c3..dcde4e0e 100644 --- a/YCMConfig.cmake.in +++ b/YCMConfig.cmake.in @@ -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) diff --git a/modules/YCMEPHelper.cmake b/modules/YCMEPHelper.cmake index 19b567a8..c517f0d7 100644 --- a/modules/YCMEPHelper.cmake +++ b/modules/YCMEPHelper.cmake @@ -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) @@ -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} @@ -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 --` to avoid ambiguity with a local path. + set(git_checkout_explicit-- "--") + else() + # Use `git checkout ` even though this risks ambiguity with a + # local path. Unfortunately we cannot use `git checkout --` + # 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 # @@ -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 @@ -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}) @@ -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() @@ -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, diff --git a/modules/YCMEPHelper/gitsafeclone.txt.in b/modules/YCMEPHelper/gitsafeclone.txt.in new file mode 100644 index 00000000..9a1da620 --- /dev/null +++ b/modules/YCMEPHelper/gitsafeclone.txt.in @@ -0,0 +1,151 @@ +# Based on https://gitlab.kitware.com/cmake/cmake/-/blob/v3.30.2/Modules/ExternalProject/gitclone.cmake.in +# but with the modifications from https://github.com/robotology/ycm-cmake-modules/commit/582b5bca17c31ab511d6cba7b9a8111fc91e0e55 +# Furthermore, other modications were done to keep compatibility with 3.16 + +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +if(EXISTS "@gitclone_stampfile@" AND EXISTS "@gitclone_infofile@" AND + "@gitclone_stampfile@" IS_NEWER_THAN "@gitclone_infofile@") + message(VERBOSE + "Avoiding repeated git clone, stamp file is up to date: " + "'@gitclone_stampfile@'" + ) + return() +endif() + +# Even at VERBOSE level, we don't want to see the commands executed, but +# enabling them to be shown for DEBUG may be useful to help diagnose problems. +set(maybe_show_command "") +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + cmake_language(GET_MESSAGE_LOG_LEVEL active_log_level) + if(active_log_level MATCHES "DEBUG|TRACE") + set(maybe_show_command COMMAND_ECHO STDOUT) + endif() +endif() + +if(EXISTS "@source_dir@") + if(NOT IS_DIRECTORY "@source_dir@") + # FIXME Perhaps support symbolic links? + message(FATAL_ERROR "\"@source_dir@\" exists and is not a git repository. Remove it and try again") + elseif(NOT IS_DIRECTORY "@source_dir@/.git") + file(GLOB files "@source_dir@/*") + list(LENGTH files nfiles) + if(nfiles) + message(FATAL_ERROR "\"@source_dir@\" folder exists and is not a git repository. Remove it and try again") + endif() + else() + # Already initialized git repository: no need to clone again + execute_process( + COMMAND "@git_EXECUTABLE@" config --local --get remote.origin.url + WORKING_DIRECTORY "@work_dir@/@src_name@" + OUTPUT_VARIABLE origin_url + RESULT_VARIABLE error_code + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(error_code) + message(FATAL_ERROR "Failed to get origin remote url in: '@work_dir@/@src_name@'") + endif() + if("${origin_url}" STREQUAL "@git_repository@") + message(STATUS "Avoiding repeated git clone, repository already exists") + return() + else() + string(TIMESTAMP now "%Y%m%d%H%M%S") + message(WARNING "Repository URL is different. Renaming 'origin' remote to 'origin.${now}'") + execute_process( + COMMAND "@git_EXECUTABLE@" remote rename origin origin.${now} + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ) + if(error_code) + message(FATAL_ERROR "Failed to rename remote in: '@work_dir@/@src_name@'") + endif() + execute_process( + COMMAND "@git_EXECUTABLE@" remote add origin "@git_repository@" + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ) + if(error_code) + message(FATAL_ERROR "Failed to add origin remote in: '@work_dir@/@src_name@'") + endif() + # try the fetch 3 times incase there is an odd git fetch issue + set(error_code 1) + set(number_of_tries 0) + while(error_code AND number_of_tries LESS 3) + execute_process( + COMMAND "@git_EXECUTABLE@" fetch @git_clone_options@ origin + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ) + math(EXPR number_of_tries "${number_of_tries} + 1") + endwhile() + if(number_of_tries GREATER 1) + message(STATUS "Had to git fetch more than once: + ${number_of_tries} times.") + endif() + if(error_code) + message(FATAL_ERROR "Failed to fetch in: '@work_dir@/@src_name@'") + endif() + endif() + endif() +endif() + +# Now perform the clone if still required +if(NOT IS_DIRECTORY "@source_dir@/.git") + # try the clone 3 times in case there is an odd git clone issue + set(error_code 1) + set(number_of_tries 0) + while(error_code AND number_of_tries LESS 3) + execute_process( + COMMAND "@git_EXECUTABLE@" ${git_options} clone ${git_clone_options} "@git_repository@" "@src_name@" + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ) + math(EXPR number_of_tries "${number_of_tries} + 1") + endwhile() + if(number_of_tries GREATER 1) + message(STATUS "Had to git clone more than once: + ${number_of_tries} times.") + endif() + if(error_code) + message(FATAL_ERROR "Failed to clone repository: '@git_repository@'") + endif() +endif() + +execute_process( + COMMAND "@git_EXECUTABLE@" + checkout "@git_tag@" @git_checkout_explicit--@ + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ${maybe_show_command} +) +if(error_code) + message(FATAL_ERROR "Failed to checkout tag: '@git_tag@'") +endif() + +set(init_submodules @init_submodules@) +if(init_submodules) + execute_process( + COMMAND "@git_EXECUTABLE@" @git_submodules_config_options@ + submodule update @git_submodules_recurse@ --init @git_submodules@ + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ${maybe_show_command} + ) +endif() +if(error_code) + message(FATAL_ERROR "Failed to update submodules in: '@work_dir@/@src_name@'") +endif() + +# Complete success, update the script-last-run stamp file: +# +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy "@gitclone_infofile@" "@gitclone_stampfile@" + RESULT_VARIABLE error_code + ${maybe_show_command} +) +if(error_code) + message(FATAL_ERROR "Failed to copy script-last-run stamp file: '@gitclone_stampfile@'") +endif() \ No newline at end of file diff --git a/tools/UseYCMFromSource.cmake b/tools/UseYCMFromSource.cmake index ee59be6c..40f7f7d5 100644 --- a/tools/UseYCMFromSource.cmake +++ b/tools/UseYCMFromSource.cmake @@ -10,7 +10,8 @@ endif() set(__USEYCMFROMSOURCE_INCLUDED TRUE) get_filename_component(_YCM_SRC_DIR ${CMAKE_CURRENT_LIST_DIR} DIRECTORY) - +set(YCM_MODULE_DIR ${_YCM_SRC_DIR}) + list(APPEND CMAKE_MODULE_PATH ${_YCM_SRC_DIR}/modules) list(APPEND CMAKE_MODULE_PATH ${_YCM_SRC_DIR}/find-modules) list(APPEND CMAKE_MODULE_PATH ${_YCM_SRC_DIR}/build-modules)