blob: c6431d391cc13511283bfee8acd74f440c549027 [file] [log] [blame]
Chris Brand19bf2742022-07-05 11:15:17 -07001#-------------------------------------------------------------------------------
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] GIT_REPOSITORY, GIT_TAG, ... - see https://cmake.org/cmake/help/latest/module/ExternalProject.html
220# for more details
221#
222# This function set CMP0097 to NEW if CMAKE_VERSION is greater or equal than 3.18.0.
223# Because of https://gitlab.kitware.com/cmake/cmake/-/issues/20579 CMP0097 is
224# non-functional until cmake 3.18.0.
225# See https://cmake.org/cmake/help/latest/policy/CMP0097.html for more info.
226function(fetch_remote_library)
227 # Parse arguments
228 set(options "")
229 set(oneValueArgs LIB_NAME LIB_SOURCE_PATH_VAR LIB_BINARY_PATH_VAR LIB_BASE_DIR LIB_PATCH_DIR)
230 set(multiValueArgs FETCH_CONTENT_ARGS)
231 cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${oneValueArgs}" "${multiValueArgs}")
232
233 if(ARG_LIB_BASE_DIR)
234 set(FETCHCONTENT_BASE_DIR "${ARG_LIB_BASE_DIR}")
235 endif()
236
237 # Set to not download submodules if that option is available
238 if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.18.0")
239 cmake_policy(SET CMP0097 NEW)
240 endif()
241
242 if ("${${ARG_LIB_SOURCE_PATH_VAR}}" STREQUAL "DOWNLOAD")
243 # Process arguments which can be an empty string
244 # There is a feature/bug in CMake that result in problem with empty string arguments
245 # See https://gitlab.kitware.com/cmake/cmake/-/issues/16341 for details
246 extract_key_value(GIT_SUBMODULES GIT_SUBMODULES GIT_SUBMODULES_VALUE ARG_FETCH_CONTENT_ARGS)
247
248 # Validate that there is no empty arguments to FetchContent_Declare
249 LIST(FIND ARG_FETCH_CONTENT_ARGS "" EMPTY_VALUE_INDEX)
250 if(${EMPTY_VALUE_INDEX} GREATER_EQUAL 0)
251 # There is an unsupported empty string argument, FATAL ERROR!
252 math(EXPR EMPTY_KEY_INDEX "${EMPTY_VALUE_INDEX} - 1")
253 list(GET ARG_FETCH_CONTENT_ARGS ${EMPTY_KEY_INDEX} EMPTY_KEY)
254 # TODO: Use extract_key_value if you have argument with empty value (see GIT_SUBMODULES above)
255 message(FATAL_ERROR "fetch_remote_library: Unexpected empty string value for ${EMPTY_KEY}. "
256 "Please, validate arguments or update fetch_remote_library to support empty value for ${EMPTY_KEY}!!!")
257 endif()
258
259 # Content fetching
260 FetchContent_Declare(${ARG_LIB_NAME}
261 ${ARG_FETCH_CONTENT_ARGS}
262 "${GIT_SUBMODULES}" "${GIT_SUBMODULES_VALUE}"
263 )
264
265 FetchContent_GetProperties(${ARG_LIB_NAME})
266 if(NOT ${ARG_LIB_NAME}_POPULATED)
267 FetchContent_Populate(${ARG_LIB_NAME})
268
269 # Get remote properties
270 _get_fetch_remote_properties(REPO_URL_VAR TAG_VAR ${ARG_FETCH_CONTENT_ARGS})
271 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)
272 if (DEFINED ARG_LIB_BINARY_PATH_VAR)
273 set(${ARG_LIB_BINARY_PATH_VAR} ${${ARG_LIB_NAME}_BINARY_DIR} CACHE PATH "Path to build directory of \"${ARG_LIB_NAME}\"")
274 endif()
275 endif()
276 endif()
277
278 if (ARG_LIB_PATCH_DIR)
279 # look for patch files
280 file(GLOB PATCH_FILES "${ARG_LIB_PATCH_DIR}/*.patch")
281
282 if(PATCH_FILES)
283 # Apply patches for existing sources
284 apply_patches("${${ARG_LIB_SOURCE_PATH_VAR}}" "${PATCH_FILES}")
285 endif()
286 endif()
287endfunction()