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