1#------------------------------------------------------------------------------- 2# Copyright (c) 2022 Cypress Semiconductor Corporation (an Infineon company) 3# or an affiliate of Cypress Semiconductor Corporation. All rights reserved. 4# 5# SPDX-License-Identifier: BSD-3-Clause 6# 7#------------------------------------------------------------------------------- 8 9include(FetchContent) 10set(FETCHCONTENT_QUIET FALSE) 11 12find_package(Git) 13 14# This function applies patches if they are not applied yet. 15# It assumes that patches have not been applied if it's not possible to revert them. 16# 17# WORKING_DIRECTORY - working directory where patches should be applied. 18# PATCH_FILES - list of patches. Patches will be applied in alphabetical order. 19function(apply_patches WORKING_DIRECTORY PATCH_FILES) 20 # Validate if patches are already applied by reverting patches in reverse order 21 # Step 1 - keep changes in stash with random message/name to detect 22 # that stash has been created by git 23 string(RANDOM LENGTH 16 STASH_NAME) 24 set(STASH_NAME "tfm-remote_library-apply_patches-${STASH_NAME}") 25 execute_process(COMMAND "${GIT_EXECUTABLE}" stash push -u -m "${STASH_NAME}" 26 WORKING_DIRECTORY ${WORKING_DIRECTORY} 27 RESULT_VARIABLE VALIDATION_STATUS 28 ERROR_QUIET OUTPUT_QUIET 29 ) 30 # Step 2 - get list of stashes to validate that stash has been created 31 if (VALIDATION_STATUS EQUAL 0) 32 execute_process(COMMAND "${GIT_EXECUTABLE}" stash list 33 WORKING_DIRECTORY ${WORKING_DIRECTORY} 34 OUTPUT_VARIABLE STASH_LIST 35 RESULT_VARIABLE VALIDATION_STATUS 36 ERROR_QUIET 37 ) 38 # Look for stash message to detect stash creation 39 string(FIND "${STASH_LIST}" "${STASH_NAME}" STASH_INDEX) 40 if (STASH_INDEX LESS 0) 41 # Stash is not created, most probably because there is no changes 42 set(VALIDATION_STATUS 0) 43 else() 44 # Step 3 - restore changes with git stash apply 45 if (VALIDATION_STATUS EQUAL 0) 46 execute_process(COMMAND "${GIT_EXECUTABLE}" stash apply 47 WORKING_DIRECTORY ${WORKING_DIRECTORY} 48 RESULT_VARIABLE VALIDATION_STATUS 49 ERROR_QUIET OUTPUT_QUIET 50 ) 51 endif() 52 endif() 53 endif() 54 # Step 4 - revert patches in reverse order 55 if (VALIDATION_STATUS EQUAL 0) 56 # Sort list of patches in descending order for validation 57 list(SORT PATCH_FILES ORDER DESCENDING) 58 foreach(PATCH ${PATCH_FILES}) 59 execute_process(COMMAND "${GIT_EXECUTABLE}" apply --reverse --verbose "${PATCH}" 60 WORKING_DIRECTORY ${WORKING_DIRECTORY} 61 RESULT_VARIABLE VALIDATION_STATUS 62 ERROR_QUIET OUTPUT_QUIET 63 ) 64 if (NOT VALIDATION_STATUS EQUAL 0) 65 # patch failed to be applied, assume that we need to restore and 66 # apply all patch set 67 break() 68 endif() 69 endforeach() 70 endif() 71 # Step 5 - pop stash to restore original state 72 if (STASH_INDEX GREATER_EQUAL 0) 73 # Clear index before restore 74 execute_process(COMMAND "${GIT_EXECUTABLE}" clean -df 75 WORKING_DIRECTORY ${WORKING_DIRECTORY} 76 ERROR_QUIET OUTPUT_QUIET 77 ) 78 execute_process(COMMAND "${GIT_EXECUTABLE}" reset --hard 79 WORKING_DIRECTORY ${WORKING_DIRECTORY} 80 ERROR_QUIET OUTPUT_QUIET 81 ) 82 execute_process(COMMAND "${GIT_EXECUTABLE}" stash pop --index 83 WORKING_DIRECTORY ${WORKING_DIRECTORY} 84 ERROR_QUIET OUTPUT_QUIET 85 ) 86 else() 87 # There is no stash, restore commit by clearing index 88 execute_process(COMMAND "${GIT_EXECUTABLE}" clean -df 89 WORKING_DIRECTORY ${WORKING_DIRECTORY} 90 ERROR_QUIET OUTPUT_QUIET 91 ) 92 execute_process(COMMAND "${GIT_EXECUTABLE}" reset --hard 93 WORKING_DIRECTORY ${WORKING_DIRECTORY} 94 ERROR_QUIET OUTPUT_QUIET 95 ) 96 endif() 97 98 if (NOT VALIDATION_STATUS EQUAL 0) 99 # Validation has been failed, so we assume that patches should be applied 100 # Sort list of patches in ascending order 101 list(SORT PATCH_FILES ORDER ASCENDING) 102 103 set(EXECUTE_COMMAND "${GIT_EXECUTABLE}" apply --verbose ${PATCH_FILES}) 104 execute_process(COMMAND ${EXECUTE_COMMAND} 105 WORKING_DIRECTORY ${WORKING_DIRECTORY} 106 RESULT_VARIABLE PATCH_STATUS 107 COMMAND_ECHO STDOUT 108 ) 109 if (NOT PATCH_STATUS EQUAL 0) 110 message( FATAL_ERROR "Failed to apply patches at ${WORKING_DIRECTORY}" ) 111 endif() 112 endif() 113endfunction() 114 115 116# Returns a repository URL and a reference to the commit used to checkout the repository. 117# 118# REPO_URL_VAR - name of variable which receives repository URL. 119# TAG_VAR - name of variable which receives reference to commit. 120function(_get_fetch_remote_properties REPO_URL_VAR TAG_VAR) 121 # Parse arguments 122 set(options "") 123 set(oneValueArgs GIT_REPOSITORY GIT_TAG) 124 set(multiValueArgs "") 125 cmake_parse_arguments(PARSE_ARGV 2 ARG "${options}" "${oneValueArgs}" "${multiValueArgs}") 126 127 if (ARG_GIT_REPOSITORY) 128 set(${REPO_URL_VAR} ${ARG_GIT_REPOSITORY} PARENT_SCOPE) 129 set(${TAG_VAR} ${ARG_GIT_TAG} PARENT_SCOPE) 130 endif() 131endfunction() 132 133 134# This function helps to handle options with an empty string values. 135# There is a feature/bug in CMake that result in problem with the empty string arguments. 136# See https://gitlab.kitware.com/cmake/cmake/-/issues/16341 for details 137# 138# Arguments: 139# [in] KEY - option name 140# [out] KEY_VAR - name of variable that is set to ${KEY} on exit if value is not 141# an empty string otherwise to the empty string. 142# [out] VALUE_VAR - name of variable that is set to option value for ${KEY}. 143# [in/out] ARG_LIST_VAR - name of variable that holds list of key/value pairs - arguments. 144# Function looks for key/value pair specified by ${KEY} variable in 145# this list. Function removes key/value pair specified by ${KEY} on 146# exit. 147# 148# Example #1: 149# # We have following key/options: 150# # GIT_SUBMODULES "" 151# # BOO "abc" 152# # HEY "hi" 153# set(ARGS GIT_SUBMODULES "" BOO "abc" HEY "hi") 154# # Extract key/value for option "GIT_SUBMODULES" 155# extract_key_value(GIT_SUBMODULES GIT_SUBMODULES_VAR GIT_SUBMODULES_VALUE_VAR ARGS) 156# # ${GIT_SUBMODULES_VAR} is equal to "" 157# # ${GIT_SUBMODULES_VALUE_VAR} is equal to "" 158# 159# Example #2: 160# # We have following key/options: 161# # GIT_SUBMODULES "name" 162# # BOO "abc" 163# # HEY "hi" 164# set(ARGS GIT_SUBMODULES "name" BOO "abc" HEY "hi") 165# # Extract key/value for option "GIT_SUBMODULES" 166# extract_key_value(GIT_SUBMODULES GIT_SUBMODULES_VAR GIT_SUBMODULES_VALUE_VAR ARGS) 167# # ${GIT_SUBMODULES_VAR} is equal to "GIT_SUBMODULES" 168# # ${GIT_SUBMODULES_VALUE_VAR} is equal to "name" 169function(extract_key_value KEY KEY_VAR VALUE_VAR ARG_LIST_VAR) 170 list(FIND ${ARG_LIST_VAR} ${KEY} KEY_INDEX) 171 if(${KEY_INDEX} GREATER_EQUAL 0) 172 # Variable has been set, remove KEY 173 list(REMOVE_AT ${ARG_LIST_VAR} ${KEY_INDEX}) 174 175 # Validate that there is an option value in the list of arguments 176 list(LENGTH ${ARG_LIST_VAR} ARG_LIST_LENGTH) 177 if(${KEY_INDEX} GREATER_EQUAL ${ARG_LIST_LENGTH}) 178 message(FATAL_ERROR "Missing option value for ${KEY}") 179 endif() 180 181 # Get value 182 list(GET ${ARG_LIST_VAR} ${KEY_INDEX} VALUE) 183 184 # Remove value in the list 185 list(REMOVE_AT ${ARG_LIST_VAR} ${KEY_INDEX}) 186 187 # Update argument list 188 set(${ARG_LIST_VAR} ${${ARG_LIST_VAR}} PARENT_SCOPE) 189 190 # Set KEY_VAR & VALUE_VAR 191 set(${KEY_VAR} ${KEY} PARENT_SCOPE) 192 set(${VALUE_VAR} ${VALUE} PARENT_SCOPE) 193 else() 194 # Variable is not defined, set KEY_VAR & VALUE_VAR to empty strings 195 set(${KEY_VAR} "" PARENT_SCOPE) 196 set(${VALUE_VAR} "" PARENT_SCOPE) 197 endif() 198endfunction() 199 200 201# This function allows to fetch library from a remote repository or use a local 202# library copy. 203# 204# You can specify location of directory with patches. Patches are applied in 205# alphabetical order. 206# 207# Arguments: 208# [in] LIB_NAME <name> - library name 209# [in/out] LIB_SOURCE_PATH_VAR <var> - name of variable which holds path to library source 210# or "DOWNLOAD" if sources should be fetched from the remote repository. This 211# variable is updated in case if library is downloaded. It will point 212# to the path where FetchContent_Populate will locate local library copy. 213# [out] LIB_BINARY_PATH_VAR <var> - optional name of variable which is updated to 214# directory intended for use as a corresponding build directory if 215# library is fetched from the remote repository. 216# [in] LIB_BASE_DIR <path> - is used to set FETCHCONTENT_BASE_DIR. 217# [in] LIB_PATCH_DIR <path> - optional path to local folder which contains patches 218# that should be applied. 219# [in] LIB_FORCE_PATCH - optional argument to force applying patches when the path 220# is a local folder instead of fetching from the remote repository. 221# [in] GIT_REPOSITORY, GIT_TAG, ... - see https://cmake.org/cmake/help/latest/module/ExternalProject.html 222# for more details 223# 224# This function set CMP0097 to NEW if CMAKE_VERSION is greater or equal than 3.18.0. 225# Because of https://gitlab.kitware.com/cmake/cmake/-/issues/20579 CMP0097 is 226# non-functional until cmake 3.18.0. 227# See https://cmake.org/cmake/help/latest/policy/CMP0097.html for more info. 228function(fetch_remote_library) 229 # Parse arguments 230 set(options "") 231 set(oneValueArgs LIB_NAME LIB_SOURCE_PATH_VAR LIB_BINARY_PATH_VAR LIB_BASE_DIR LIB_PATCH_DIR LIB_FORCE_PATCH) 232 set(multiValueArgs FETCH_CONTENT_ARGS) 233 cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${oneValueArgs}" "${multiValueArgs}") 234 235 if(ARG_LIB_BASE_DIR) 236 set(FETCHCONTENT_BASE_DIR "${ARG_LIB_BASE_DIR}") 237 endif() 238 239 # Set to not download submodules if that option is available 240 if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.18.0") 241 cmake_policy(SET CMP0097 NEW) 242 endif() 243 244 if ("${${ARG_LIB_SOURCE_PATH_VAR}}" STREQUAL "DOWNLOAD") 245 set(SOURCE_PATH_IS_DOWNLOAD TRUE) 246 # Process arguments which can be an empty string 247 # There is a feature/bug in CMake that result in problem with empty string arguments 248 # See https://gitlab.kitware.com/cmake/cmake/-/issues/16341 for details 249 extract_key_value(GIT_SUBMODULES GIT_SUBMODULES GIT_SUBMODULES_VALUE ARG_FETCH_CONTENT_ARGS) 250 251 # Validate that there is no empty arguments to FetchContent_Declare 252 LIST(FIND ARG_FETCH_CONTENT_ARGS "" EMPTY_VALUE_INDEX) 253 if(${EMPTY_VALUE_INDEX} GREATER_EQUAL 0) 254 # There is an unsupported empty string argument, FATAL ERROR! 255 math(EXPR EMPTY_KEY_INDEX "${EMPTY_VALUE_INDEX} - 1") 256 list(GET ARG_FETCH_CONTENT_ARGS ${EMPTY_KEY_INDEX} EMPTY_KEY) 257 # TODO: Use extract_key_value if you have argument with empty value (see GIT_SUBMODULES above) 258 message(FATAL_ERROR "fetch_remote_library: Unexpected empty string value for ${EMPTY_KEY}. " 259 "Please, validate arguments or update fetch_remote_library to support empty value for ${EMPTY_KEY}!!!") 260 endif() 261 262 # Content fetching 263 FetchContent_Declare(${ARG_LIB_NAME} 264 ${ARG_FETCH_CONTENT_ARGS} 265 "${GIT_SUBMODULES}" "${GIT_SUBMODULES_VALUE}" 266 ) 267 268 FetchContent_GetProperties(${ARG_LIB_NAME}) 269 if(NOT ${ARG_LIB_NAME}_POPULATED) 270 FetchContent_Populate(${ARG_LIB_NAME}) 271 272 # Get remote properties 273 _get_fetch_remote_properties(REPO_URL_VAR TAG_VAR ${ARG_FETCH_CONTENT_ARGS}) 274 set(${ARG_LIB_SOURCE_PATH_VAR} ${${ARG_LIB_NAME}_SOURCE_DIR} CACHE PATH "Library has been downloaded from \"${REPO_URL_VAR}\", tag \"${TAG_VAR}\"" FORCE) 275 if (DEFINED ARG_LIB_BINARY_PATH_VAR) 276 set(${ARG_LIB_BINARY_PATH_VAR} ${${ARG_LIB_NAME}_BINARY_DIR} CACHE PATH "Path to build directory of \"${ARG_LIB_NAME}\"") 277 endif() 278 endif() 279 endif() 280 281 if (ARG_LIB_FORCE_PATCH) 282 set(FORCE_PATCH ${${ARG_LIB_FORCE_PATCH}}) 283 endif() 284 285 if (ARG_LIB_PATCH_DIR AND (SOURCE_PATH_IS_DOWNLOAD OR FORCE_PATCH)) 286 # look for patch files 287 file(GLOB PATCH_FILES "${ARG_LIB_PATCH_DIR}/*.patch") 288 289 if(PATCH_FILES) 290 # Apply patches for existing sources 291 apply_patches("${${ARG_LIB_SOURCE_PATH_VAR}}" "${PATCH_FILES}") 292 endif() 293 endif() 294endfunction() 295