summaryrefslogtreecommitdiffstats
path: root/Modules/ExternalProject
diff options
context:
space:
mode:
authorChris Wright <chris@inkyspider.co.uk>2023-04-21 06:47:19 (GMT)
committerCraig Scott <craig.scott@crascit.com>2023-04-26 08:30:36 (GMT)
commit550f63447d4c7d2db6ccbeaf1f6378aa6f7af4ed (patch)
tree2f9dca56e1a6891f614c52c78d8884f71c7e84ed /Modules/ExternalProject
parente256e35daa79732a200883cef398fcd0f8227a3d (diff)
downloadCMake-550f63447d4c7d2db6ccbeaf1f6378aa6f7af4ed.zip
CMake-550f63447d4c7d2db6ccbeaf1f6378aa6f7af4ed.tar.gz
CMake-550f63447d4c7d2db6ccbeaf1f6378aa6f7af4ed.tar.bz2
ExternalProject/FetchContent: Support relative remote URLs
Teach `ExternalProject_Add` and `FetchContent_Declare` to resolve relative remote URLs provided via `GIT_REPOSITORY`. Add policy CMP0150 to maintain compatibility. Fixes: #24211 Co-Authored-By: Craig Scott <craig.scott@crascit.com>
Diffstat (limited to 'Modules/ExternalProject')
-rw-r--r--Modules/ExternalProject/shared_internal_commands.cmake182
1 files changed, 182 insertions, 0 deletions
diff --git a/Modules/ExternalProject/shared_internal_commands.cmake b/Modules/ExternalProject/shared_internal_commands.cmake
new file mode 100644
index 0000000..ca3cd9f
--- /dev/null
+++ b/Modules/ExternalProject/shared_internal_commands.cmake
@@ -0,0 +1,182 @@
+cmake_policy(VERSION 3.25)
+
+# Determine the remote URL of the project containing the working_directory.
+# This will leave output_variable unset if the URL can't be determined.
+function(_ep_get_git_remote_url output_variable working_directory)
+ set("${output_variable}" "" PARENT_SCOPE)
+
+ find_package(Git QUIET REQUIRED)
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} symbolic-ref --short HEAD
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE git_symbolic_ref
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+
+ if(NOT git_symbolic_ref STREQUAL "")
+ # We are potentially on a branch. See if that branch is associated with
+ # an upstream remote (might be just a local one or not a branch at all).
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} config branch.${git_symbolic_ref}.remote
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE git_remote_name
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+ endif()
+
+ if(NOT git_remote_name)
+ # Can't select a remote based on a branch. If there's only one remote,
+ # or we have multiple remotes but one is called "origin", choose that.
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} remote
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE git_remote_list
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+ string(REPLACE "\n" ";" git_remote_list "${git_remote_list}")
+ list(LENGTH git_remote_list git_remote_list_length)
+
+ if(git_remote_list_length EQUAL 0)
+ message(FATAL_ERROR "Git remote not found in parent project.")
+ elseif(git_remote_list_length EQUAL 1)
+ list(GET git_remote_list 0 git_remote_name)
+ else()
+ set(base_warning_msg "Multiple git remotes found for parent project")
+ if("origin" IN_LIST git_remote_list)
+ message(WARNING "${base_warning_msg}, defaulting to origin.")
+ set(git_remote_name "origin")
+ else()
+ message(FATAL_ERROR "${base_warning_msg}, none of which are origin.")
+ endif()
+ endif()
+ endif()
+
+ if(GIT_VERSION VERSION_LESS 1.7.5)
+ set(_git_remote_url_cmd_args config remote.${git_remote_name}.url)
+ elseif(GIT_VERSION VERSION_LESS 2.7)
+ set(_git_remote_url_cmd_args ls-remote --get-url ${git_remote_name})
+ else()
+ set(_git_remote_url_cmd_args remote get-url ${git_remote_name})
+ endif()
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} ${_git_remote_url_cmd_args}
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE git_remote_url
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ COMMAND_ERROR_IS_FATAL LAST
+ ENCODING UTF-8 # Needed to handle non-ascii characters in local paths
+ )
+
+ set("${output_variable}" "${git_remote_url}" PARENT_SCOPE)
+endfunction()
+
+function(_ep_is_relative_git_remote output_variable remote_url)
+ if(remote_url MATCHES "^\\.\\./")
+ set("${output_variable}" TRUE PARENT_SCOPE)
+ else()
+ set("${output_variable}" FALSE PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Return an absolute remote URL given an existing remote URL and relative path.
+# The output_variable will be set to an empty string if an absolute URL
+# could not be computed (no error message is output).
+function(_ep_resolve_relative_git_remote
+ output_variable
+ parent_remote_url
+ relative_remote_url
+)
+ set("${output_variable}" "" PARENT_SCOPE)
+
+ if(parent_remote_url STREQUAL "")
+ return()
+ endif()
+
+ string(REGEX MATCH
+ "^(([A-Za-z0-9][A-Za-z0-9+.-]*)://)?(([^/@]+)@)?(\\[[A-Za-z0-9:]+\\]|[^/:]+)?([/:]/?)(.+(\\.git)?/?)$"
+ git_remote_url_components
+ "${parent_remote_url}"
+ )
+
+ set(protocol "${CMAKE_MATCH_1}")
+ set(auth "${CMAKE_MATCH_3}")
+ set(host "${CMAKE_MATCH_5}")
+ set(separator "${CMAKE_MATCH_6}")
+ set(path "${CMAKE_MATCH_7}")
+
+ string(REPLACE "/" ";" remote_path_components "${path}")
+ string(REPLACE "/" ";" relative_path_components "${relative_remote_url}")
+
+ foreach(relative_path_component IN LISTS relative_path_components)
+ if(NOT relative_path_component STREQUAL "..")
+ break()
+ endif()
+
+ list(LENGTH remote_path_components remote_path_component_count)
+
+ if(remote_path_component_count LESS 1)
+ return()
+ endif()
+
+ list(POP_BACK remote_path_components)
+ list(POP_FRONT relative_path_components)
+ endforeach()
+
+ list(APPEND final_path_components ${remote_path_components} ${relative_path_components})
+ list(JOIN final_path_components "/" path)
+
+ set("${output_variable}" "${protocol}${auth}${host}${separator}${path}" PARENT_SCOPE)
+endfunction()
+
+# The output_variable will be set to the original git_repository if it
+# could not be resolved (no error message is output). The original value is
+# also returned if it doesn't need to be resolved.
+function(_ep_resolve_git_remote
+ output_variable
+ git_repository
+ cmp0150
+ cmp0150_old_base_dir
+)
+ if(git_repository STREQUAL "")
+ set("${output_variable}" "" PARENT_SCOPE)
+ return()
+ endif()
+
+ _ep_is_relative_git_remote(_git_repository_is_relative "${git_repository}")
+
+ if(NOT _git_repository_is_relative)
+ set("${output_variable}" "${git_repository}" PARENT_SCOPE)
+ return()
+ endif()
+
+ if(cmp0150 STREQUAL "NEW")
+ _ep_get_git_remote_url(_parent_git_remote_url "${CMAKE_CURRENT_SOURCE_DIR}")
+ _ep_resolve_relative_git_remote(_resolved_git_remote_url "${_parent_git_remote_url}" "${git_repository}")
+
+ if(_resolved_git_remote_url STREQUAL "")
+ message(FATAL_ERROR
+ "Failed to resolve relative git remote URL:\n"
+ " Relative URL: ${git_repository}\n"
+ " Parent URL: ${_parent_git_remote_url}"
+ )
+ endif()
+ set("${output_variable}" "${_resolved_git_remote_url}" PARENT_SCOPE)
+ return()
+ elseif(cmp0150 STREQUAL "")
+ cmake_policy(GET_WARNING CMP0150 _cmp0150_warning)
+ message(AUTHOR_WARNING
+ "${_cmp0150_warning}\n"
+ "A relative GIT_REPOSITORY path was detected. "
+ "This will be interpreted as a local path to where the project is being cloned. "
+ "Set GIT_REPOSITORY to an absolute path or set policy CMP0150 to NEW to avoid "
+ "this warning."
+ )
+ endif()
+
+ set("${output_variable}" "${cmp0150_old_base_dir}/${git_repository}" PARENT_SCOPE)
+endfunction()