diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index c1c05320..6cf1b879 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -42,7 +42,11 @@ if(NOT COMMAND cpm_message) endfunction() endif() -set(CURRENT_CPM_VERSION 0.40.2) +if(DEFINED EXTRACTED_CPM_VERSION) + set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}") +else() + set(CURRENT_CPM_VERSION 0.43.1) +endif() get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) if(CPM_DIRECTORY) @@ -162,7 +166,7 @@ set(CPM_SOURCE_CACHE CACHE PATH "Directory to download CPM dependencies" ) -if(NOT CPM_DONT_UPDATE_MODULE_PATH) +if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) set(CPM_MODULE_PATH "${CMAKE_BINARY_DIR}/CPM_modules" CACHE INTERNAL "" @@ -198,9 +202,65 @@ function(cpm_package_name_from_git_uri URI RESULT) endif() endfunction() +# Find the shortest hash that can be used eg, if origin_hash is +# cccb77ae9609d2768ed80dd42cec54f77b1f1455 the following files will be checked, until one is found +# that is either empty (allowing us to assign origin_hash), or whose contents matches ${origin_hash} +# +# * .../cccb.hash +# * .../cccb77ae.hash +# * .../cccb77ae9609.hash +# * .../cccb77ae9609d276.hash +# * etc +# +# We will be able to use a shorter path with very high probability, but in the (rare) event that the +# first couple characters collide, we will check longer and longer substrings. +function(cpm_get_shortest_hash source_cache_dir origin_hash short_hash_output_var) + # for compatibility with caches populated by a previous version of CPM, check if a directory using + # the full hash already exists + if(EXISTS "${source_cache_dir}/${origin_hash}") + set(${short_hash_output_var} + "${origin_hash}" + PARENT_SCOPE + ) + return() + endif() + + foreach(len RANGE 4 40 4) + string(SUBSTRING "${origin_hash}" 0 ${len} short_hash) + set(hash_lock ${source_cache_dir}/${short_hash}.lock) + set(hash_fp ${source_cache_dir}/${short_hash}.hash) + # Take a lock, so we don't have a race condition with another instance of cmake. We will release + # this lock when we can, however, if there is an error, we want to ensure it gets released on + # it's own on exit from the function. + file(LOCK ${hash_lock} GUARD FUNCTION) + + # Load the contents of .../${short_hash}.hash + file(TOUCH ${hash_fp}) + file(READ ${hash_fp} hash_fp_contents) + + if(hash_fp_contents STREQUAL "") + # Write the origin hash + file(WRITE ${hash_fp} ${origin_hash}) + file(LOCK ${hash_lock} RELEASE) + break() + elseif(hash_fp_contents STREQUAL origin_hash) + file(LOCK ${hash_lock} RELEASE) + break() + else() + file(LOCK ${hash_lock} RELEASE) + endif() + endforeach() + set(${short_hash_output_var} + "${short_hash}" + PARENT_SCOPE + ) +endfunction() + # Try to infer package name and version from a url function(cpm_package_name_and_ver_from_url url outName outVer) - if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") + if(url MATCHES + "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|tar\\.xz|tar\\.zst|zip|ZIP)(\\?|/|$)" + ) # We matched an archive set(filename "${CMAKE_MATCH_1}") @@ -269,10 +329,25 @@ endfunction() # finding the system library function(cpm_create_module_file Name) if(NOT CPM_DONT_UPDATE_MODULE_PATH) - # erase any previous modules - file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake - "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" - ) + if(DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) + # Redirect find_package calls to the CPM package. This is what FetchContent does when you set + # OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for find_package in CONFIG + # mode, unlike the Find${Name}.cmake fallback. CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined + # in script mode, or in CMake < 3.24. + # https://cmake.org/cmake/help/latest/module/FetchContent.html#fetchcontent-find-package-integration-examples + string(TOLOWER ${Name} NameLower) + file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config.cmake + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" OPTIONAL)\n" + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n" + ) + file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake + "set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" "set(PACKAGE_VERSION_EXACT TRUE)\n" + ) + else() + file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake + "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" + ) + endif() endif() endfunction() @@ -475,7 +550,7 @@ function(cpm_add_patches) # Find the patch program. find_program(PATCH_EXECUTABLE patch) - if(WIN32 AND NOT PATCH_EXECUTABLE) + if(CMAKE_HOST_WIN32 AND NOT PATCH_EXECUTABLE) # The Windows git executable is distributed with patch.exe. Find the path to the executable, if # it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe. find_package(Git QUIET) @@ -575,14 +650,6 @@ endfunction() function(CPMAddPackage) cpm_set_policies() - list(LENGTH ARGN argnLength) - if(argnLength EQUAL 1) - cpm_parse_add_package_single_arg("${ARGN}" ARGN) - - # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM - set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") - endif() - set(oneValueArgs NAME FORCE @@ -605,10 +672,26 @@ function(CPMAddPackage) set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES) + list(LENGTH ARGN argnLength) + + # Parse single shorthand argument + if(argnLength EQUAL 1) + cpm_parse_add_package_single_arg("${ARGN}" ARGN) + + # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM + set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") + + # Parse URI shorthand argument + elseif(argnLength GREATER 1 AND "${ARGV0}" STREQUAL "URI") + list(REMOVE_AT ARGN 0 1) # remove "URI gh:<...>@version#tag" + cpm_parse_add_package_single_arg("${ARGV1}" ARGV0) + + set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}") + endif() + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") # Set default values for arguments - if(NOT DEFINED CPM_ARGS_VERSION) if(DEFINED CPM_ARGS_GIT_TAG) cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) @@ -685,10 +768,23 @@ function(CPMAddPackage) return() endif() + if(NOT DEFINED CPM_${CPM_ARGS_NAME}_SOURCE AND DEFINED ENV{CPM_${CPM_ARGS_NAME}_SOURCE}) + # Normalize separators to support Windows paths when reading from environment variables. + file(TO_CMAKE_PATH "$ENV{CPM_${CPM_ARGS_NAME}_SOURCE}" CPM_${CPM_ARGS_NAME}_SOURCE) + message(WARNING "${CPM_INDENT} '${CPM_ARGS_NAME}' version overridden by environment variable " + "CPM_${CPM_ARGS_NAME}_SOURCE='${CPM_${CPM_ARGS_NAME}_SOURCE}'" + ) + endif() + # Check for manual overrides if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) set(CPM_${CPM_ARGS_NAME}_SOURCE "") + if(NOT DEFINED ENV{CPM_${CPM_ARGS_NAME}_SOURCE}) + message(WARNING "${CPM_INDENT} '${CPM_ARGS_NAME}' version overridden by CMake variable " + "CPM_${CPM_ARGS_NAME}_SOURCE='${PACKAGE_SOURCE}'" + ) + endif() CPMAddPackage( NAME "${CPM_ARGS_NAME}" SOURCE_DIR "${PACKAGE_SOURCE}" @@ -779,9 +875,19 @@ function(CPMAddPackage) set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY}) elseif(CPM_USE_NAMED_CACHE_DIRECTORIES) string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") + cpm_get_shortest_hash( + "${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory + "${origin_hash}" # Input hash + origin_hash # Computed hash + ) set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) else() string(SHA1 origin_hash "${origin_parameters}") + cpm_get_shortest_hash( + "${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory + "${origin_hash}" # Input hash + origin_hash # Computed hash + ) set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) endif() # Expand `download_directory` relative path. This is important because EXISTS doesn't work for @@ -848,7 +954,9 @@ function(CPMAddPackage) endif() endif() - cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") + if(NOT "${DOWNLOAD_ONLY}") + cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") + endif() if(CPM_PACKAGE_LOCK_ENABLED) if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) @@ -869,8 +977,9 @@ function(CPMAddPackage) # Calling FetchContent_MakeAvailable will then internally forward these options to # add_subdirectory. Up until these changes, we had to call FetchContent_Populate and # add_subdirectory separately, which is no longer necessary and has been deprecated as of 3.30. + # A Bug in CMake prevents us to use the non-deprecated functions until 3.30.3. set(fetchContentDeclareExtraArgs "") - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0") + if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") if(${CPM_ARGS_EXCLUDE_FROM_ALL}) list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) endif() @@ -896,7 +1005,7 @@ function(CPMAddPackage) if(CPM_SOURCE_CACHE AND download_directory) file(LOCK ${download_directory}/../cmake.lock RELEASE) endif() - if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.28.0") + if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.30.3") cpm_add_subdirectory( "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" @@ -1098,7 +1207,7 @@ function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated) string(TOLOWER "${PACKAGE}" lower_case_name) if(NOT ${lower_case_name}_POPULATED) - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0") + if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") if(DOWNLOAD_ONLY) # MakeAvailable will call add_subdirectory internally which is not what we want when # DOWNLOAD_ONLY is set. Populate will only download the dependency without adding it to the @@ -1147,6 +1256,7 @@ function(cpm_parse_option OPTION) else() math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) + string(STRIP "${OPTION_VALUE}" OPTION_VALUE) endif() set(OPTION_KEY "${OPTION_KEY}"