blob: ef726cb3f25e8762182755104c367567b2387d59 [file] [log] [blame]
Anton Komlev4d4cc682023-09-05 16:33:53 +01001#-------------------------------------------------------------------------------
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
10include(FetchContent)
11set(FETCHCONTENT_QUIET FALSE)
12
13find_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.
20function(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()
114endfunction()
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.
121function(_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()
132endfunction()
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"
170function(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()
199endfunction()
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.
229function(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)
David Hu0c3e2e32023-11-06 15:22:13 +0800283 set(FORCE_PATCH ${ARG_LIB_FORCE_PATCH})
Anton Komlev4d4cc682023-09-05 16:33:53 +0100284 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()
295endfunction()