forked from Eyescale/CMake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GitExternal.cmake
362 lines (336 loc) · 14.3 KB
/
GitExternal.cmake
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# Configures an external git repository
#
# Usage:
# * Automatically reads, parses and updates a .gitexternals file if it only
# contains lines in the form "# <directory> <giturl> <gittag>".
# This function parses the file for this pattern and then calls
# git_external on each found entry. Additionally it provides an
# update target to bump the tag to the master revision by
# recreating .gitexternals.
# * Provides function
# git_external(<directory> <giturl> <gittag> [VERBOSE,SHALLOW]
# [RESET <files>])
# which will check out directory in CMAKE_SOURCE_DIR (if relative)
# or in the given absolute path using the given repository and tag
# (commit-ish).
#
# Options which can be supplied to the function:
# VERBOSE, when present, this option tells the function to output
# information about what operations are being performed by git on
# the repo.
# SHALLOW, when present, causes a shallow clone of depth 1 to be made
# of the specified repo. This may save considerable memory/bandwidth
# when only a specific branch of a repo is required and the full history
# is not required. Note that the SHALLOW option will only work for a branch
# or tag and cannot be used for an arbitrary SHA.
# OPTIONAL, when present, this option makes this operation optional.
# The function will output a warning and return if the repo could not be
# cloned.
#
# Targets:
# * <directory>-rebase: fetches latest updates and rebases the given external
# git repository onto it.
# * rebase: Rebases all git externals, including sub projects
#
# Options (global) which control behaviour:
# GIT_EXTERNAL_VERBOSE
# This is a global option which has the same effect as the VERBOSE option,
# with the difference that output information will be produced for all
# external repos when set.
# GIT_EXTERNAL_TAG
# If set, git external tags referring to a SHA1 (not a branch) will be
# overwritten by this value.
#
# CMake or environment variables:
# GITHUB_USER
# If set, a remote called 'user' is set up for github repositories, pointing
# to [email protected]:<user>/<project>. Also, this remote is used by default
# for 'git push'.
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
if(NOT GIT_EXECUTABLE)
return()
endif()
include(CMakeParseArguments)
option(GIT_EXTERNAL_VERBOSE "Print git commands as they are executed" OFF)
if(NOT GITHUB_USER AND DEFINED ENV{GITHUB_USER})
set(GITHUB_USER $ENV{GITHUB_USER} CACHE STRING
"Github user name used to setup remote for 'user' forks")
endif()
macro(GIT_EXTERNAL_MESSAGE msg)
if(GIT_EXTERNAL_VERBOSE OR GIT_EXTERNAL_LOCAL_VERBOSE)
message(STATUS "${NAME}: ${msg}")
endif()
endmacro()
# utility function for printing a list with custom separator
function(JOIN VALUES GLUE OUTPUT)
string (REGEX REPLACE "([^\\]|^);" "\\1${GLUE}" _TMP_STR "${VALUES}")
string (REGEX REPLACE "[\\](.)" "\\1" _TMP_STR "${_TMP_STR}") #fixes escaping
set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
endfunction()
function(GIT_EXTERNAL DIR REPO tag)
cmake_parse_arguments(GIT_EXTERNAL_LOCAL "VERBOSE;SHALLOW;OPTIONAL" "" "RESET" ${ARGN})
set(TAG ${tag})
if(GIT_EXTERNAL_TAG AND "${tag}" MATCHES "^[0-9a-f]+$")
set(TAG ${GIT_EXTERNAL_TAG})
endif()
# check if we had a previous external of the same name
string(REGEX REPLACE "[:/]" "_" TARGET "${DIR}")
get_property(OLD_TAG GLOBAL PROPERTY ${TARGET}_GITEXTERNAL_TAG)
if(OLD_TAG)
if(NOT OLD_TAG STREQUAL TAG)
string(REPLACE "${CMAKE_SOURCE_DIR}/" "" PWD
"${CMAKE_CURRENT_SOURCE_DIR}")
git_external_message("${DIR}: already configured with ${OLD_TAG}, ignoring requested ${TAG} in ${PWD}")
return()
endif()
else()
set_property(GLOBAL PROPERTY ${TARGET}_GITEXTERNAL_TAG ${TAG})
endif()
if(NOT IS_ABSOLUTE "${DIR}")
set(DIR "${CMAKE_SOURCE_DIR}/${DIR}")
endif()
get_filename_component(NAME "${DIR}" NAME)
get_filename_component(GIT_EXTERNAL_DIR "${DIR}/.." ABSOLUTE)
if(NOT EXISTS "${DIR}")
# clone
set(_clone_options --recursive)
if(GIT_EXTERNAL_LOCAL_SHALLOW)
list(APPEND _clone_options --depth 1 --branch ${TAG})
else()
set(_msg_tag "[${TAG}]")
endif()
JOIN("${_clone_options}" " " _msg_text)
message(STATUS "git clone ${_msg_text} ${REPO} ${DIR} ${_msg_tag}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" clone ${_clone_options} ${REPO} ${DIR}
RESULT_VARIABLE nok ERROR_VARIABLE error
WORKING_DIRECTORY "${GIT_EXTERNAL_DIR}")
if(nok)
if(GIT_EXTERNAL_LOCAL_OPTIONAL)
message(STATUS "${DIR} clone failed: ${error}\n")
return()
else()
message(FATAL_ERROR "${DIR} clone failed: ${error}\n")
endif()
endif()
# checkout requested tag
if(NOT GIT_EXTERNAL_LOCAL_SHALLOW)
execute_process(
COMMAND "${GIT_EXECUTABLE}" checkout -q "${TAG}"
RESULT_VARIABLE nok ERROR_VARIABLE error
WORKING_DIRECTORY "${DIR}")
if(nok)
message(FATAL_ERROR "git checkout ${TAG} in ${DIR} failed: ${error}\n")
endif()
endif()
# checkout requested tag
execute_process(
COMMAND "${GIT_EXECUTABLE}" checkout -q "${TAG}"
RESULT_VARIABLE nok ERROR_VARIABLE error
WORKING_DIRECTORY "${DIR}")
if(nok)
message(FATAL_ERROR "git checkout ${TAG} in ${DIR} failed: ${error}\n")
endif()
endif()
# set up "user" remote for github forks and make it default for 'git push'
if(GITHUB_USER AND REPO MATCHES ".*github.com.*")
string(REGEX REPLACE ".*(github.com)[\\/:]().*(\\/.*)" "git@\\1:\\2${GITHUB_USER}\\3"
GIT_EXTERNAL_USER_REPO ${REPO})
execute_process(
COMMAND "${GIT_EXECUTABLE}" remote add user ${GIT_EXTERNAL_USER_REPO}
OUTPUT_QUIET ERROR_QUIET WORKING_DIRECTORY "${DIR}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" config remote.pushdefault user
OUTPUT_QUIET ERROR_QUIET WORKING_DIRECTORY "${DIR}")
endif()
if(COMMON_SOURCE_DIR)
file(RELATIVE_PATH __dir ${COMMON_SOURCE_DIR} ${DIR})
else()
file(RELATIVE_PATH __dir ${CMAKE_SOURCE_DIR} ${DIR})
endif()
string(REGEX REPLACE "[:/\\.]" "-" __target "${__dir}")
if(TARGET ${__target}-rebase)
return()
endif()
set(__rebase_cmake "${CMAKE_CURRENT_BINARY_DIR}/${__target}-rebase.cmake")
file(WRITE ${__rebase_cmake}
"if(NOT IS_DIRECTORY \"${DIR}/.git\")\n"
" message(FATAL_ERROR \"Can't update git external ${__dir}: Not a git repository\")\n"
"endif()\n"
# check if we are already on the requested tag (nothing to do)
"execute_process(COMMAND \"${GIT_EXECUTABLE}\" rev-parse --short HEAD\n"
" OUTPUT_VARIABLE currentref OUTPUT_STRIP_TRAILING_WHITESPACE\n"
" WORKING_DIRECTORY \"${DIR}\")\n"
"if(currentref STREQUAL ${TAG}) # nothing to do\n"
" return()\n"
"endif()\n"
"\n"
# reset generated files
"foreach(GIT_EXTERNAL_RESET_FILE ${GIT_EXTERNAL_RESET})\n"
" execute_process(\n"
" COMMAND \"${GIT_EXECUTABLE}\" reset -q \"\${GIT_EXTERNAL_RESET_FILE}\"\n"
" ERROR_QUIET OUTPUT_QUIET\n"
" WORKING_DIRECTORY \"${DIR}\")\n"
" execute_process(\n"
" COMMAND \"${GIT_EXECUTABLE}\" checkout -q -- \"${GIT_EXTERNAL_RESET_FILE}\"\n"
" ERROR_QUIET OUTPUT_QUIET\n"
" WORKING_DIRECTORY \"${DIR}\")\n"
"endforeach()\n"
"\n"
# fetch updates
"execute_process(COMMAND \"${GIT_EXECUTABLE}\" fetch origin -q\n"
" RESULT_VARIABLE nok ERROR_VARIABLE error\n"
" WORKING_DIRECTORY \"${DIR}\")\n"
"if(nok)\n"
" message(FATAL_ERROR \"Fetch for ${__dir} failed:\n \${error}\")\n"
"endif()\n"
"\n"
)
if("${TAG}" MATCHES "^[0-9a-f]+$")
# requested TAG is a SHA1, just switch to it
file(APPEND ${__rebase_cmake}
# checkout requested tag
"execute_process(\n"
" COMMAND \"${GIT_EXECUTABLE}\" checkout -q \"${TAG}\"\n"
" RESULT_VARIABLE nok ERROR_VARIABLE error\n"
" WORKING_DIRECTORY \"${DIR}\")\n"
"if(nok)\n"
" message(FATAL_ERROR \"git checkout ${TAG} in ${__dir} failed: ${error}\n\")\n"
"endif()\n"
)
else()
# requested TAG is a branch
file(APPEND ${__rebase_cmake}
# switch to requested branch
"execute_process(\n"
" COMMAND \"${GIT_EXECUTABLE}\" checkout -q \"${TAG}\"\n"
" OUTPUT_QUIET ERROR_QUIET WORKING_DIRECTORY \"${DIR}\")\n"
# try to rebase it
"execute_process(COMMAND \"${GIT_EXECUTABLE}\" rebase FETCH_HEAD\n"
" RESULT_VARIABLE nok ERROR_VARIABLE error OUTPUT_VARIABLE output\n"
" WORKING_DIRECTORY \"${DIR}\")\n"
"if(nok)\n"
" execute_process(COMMAND \"${GIT_EXECUTABLE}\" rebase --abort\n"
" WORKING_DIRECTORY \"${DIR}\" ERROR_QUIET OUTPUT_QUIET)\n"
" message(FATAL_ERROR \"Rebase ${__dir} failed:\n\${output}\${error}\")\n"
"endif()\n"
)
endif()
if(NOT GIT_EXTERNAL_SCRIPT_MODE)
add_custom_target(${__target}-rebase
COMMAND ${CMAKE_COMMAND} -P ${__rebase_cmake}
COMMENT "Rebasing ${__dir} [${TAG}]")
set_target_properties(${__target}-rebase PROPERTIES
EXCLUDE_FROM_DEFAULT_BUILD ON FOLDER git)
if(NOT TARGET rebase)
add_custom_target(rebase)
set_target_properties(rebase PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON)
endif()
add_dependencies(rebase ${__target}-rebase)
endif()
endfunction()
set(GIT_EXTERNALS ${GIT_EXTERNALS_FILE})
if(NOT GIT_EXTERNALS)
set(GIT_EXTERNALS "${CMAKE_CURRENT_SOURCE_DIR}/.gitexternals")
endif()
if(EXISTS ${GIT_EXTERNALS} AND NOT GIT_EXTERNAL_SCRIPT_MODE)
include(${GIT_EXTERNALS})
file(READ ${GIT_EXTERNALS} GIT_EXTERNAL_FILE)
string(REGEX REPLACE "\n" ";" GIT_EXTERNAL_FILE "${GIT_EXTERNAL_FILE}")
foreach(LINE ${GIT_EXTERNAL_FILE})
if(NOT LINE MATCHES "^#.*$")
message(FATAL_ERROR "${GIT_EXTERNALS} contains non-comment line: ${LINE}")
endif()
string(REGEX REPLACE "^#[ ]*(.+[ ]+.+[ ]+.+)$" "\\1" DATA ${LINE})
if(NOT LINE STREQUAL DATA)
string(REGEX REPLACE "[ ]+" ";" DATA "${DATA}")
list(LENGTH DATA DATA_LENGTH)
if(DATA_LENGTH EQUAL 3)
list(GET DATA 0 DIR)
list(GET DATA 1 REPO)
list(GET DATA 2 TAG)
# Create a unique, flat name
string(REPLACE "/" "-" GIT_EXTERNAL_NAME ${DIR}_${PROJECT_NAME})
if(NOT TARGET update-gitexternal-${GIT_EXTERNAL_NAME}) # not done
# pull in identified external
git_external(${DIR} ${REPO} ${TAG})
# Create update script and target to bump external spec
if(NOT TARGET update)
add_custom_target(update)
endif()
if(NOT TARGET update-gitexternal)
add_custom_target(update-gitexternal)
add_custom_target(flatten-gitexternal)
add_dependencies(update update-gitexternal)
endif()
if(NOT TARGET ${PROJECT_NAME}-flatten-gitexternal)
add_custom_target(${PROJECT_NAME}-flatten-gitexternal)
endif()
# Create a unique, flat name
file(RELATIVE_PATH GIT_EXTERNALS_BASE ${CMAKE_SOURCE_DIR}
${GIT_EXTERNALS})
string(REPLACE "/" "_" GIT_EXTERNAL_TARGET ${GIT_EXTERNALS_BASE})
set(GIT_EXTERNAL_TARGET update-gitexternal-${GIT_EXTERNAL_TARGET})
if(NOT TARGET ${GIT_EXTERNAL_TARGET})
set(GIT_EXTERNAL_SCRIPT
"${CMAKE_CURRENT_BINARY_DIR}/${GIT_EXTERNAL_TARGET}.cmake")
file(WRITE "${GIT_EXTERNAL_SCRIPT}"
"file(WRITE ${GIT_EXTERNALS} \"# -*- mode: cmake -*-\n\")\n")
add_custom_target(${GIT_EXTERNAL_TARGET}
COMMAND "${CMAKE_COMMAND}" -DGIT_EXTERNAL_SCRIPT_MODE=1 -P ${GIT_EXTERNAL_SCRIPT}
COMMENT "Recreate ${GIT_EXTERNALS_BASE}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
endif()
set(GIT_EXTERNAL_SCRIPT
"${CMAKE_CURRENT_BINARY_DIR}/gitupdate${GIT_EXTERNAL_NAME}.cmake")
file(WRITE "${GIT_EXTERNAL_SCRIPT}" "
include(\"${CMAKE_CURRENT_LIST_DIR}/GitExternal.cmake\")
execute_process(COMMAND \"${GIT_EXECUTABLE}\" fetch origin -q
WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}/${DIR}\")
execute_process(
COMMAND \"${GIT_EXECUTABLE}\" show-ref --hash=7 refs/remotes/origin/master
OUTPUT_VARIABLE newref OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}/${DIR}\")
if(newref)
file(APPEND ${GIT_EXTERNALS} \"# ${DIR} ${REPO} \${newref}\\n\")
git_external(${DIR} ${REPO} \${newref})
else()
file(APPEND ${GIT_EXTERNALS} \"# ${DIR} ${REPO} ${TAG}\n\")
endif()")
add_custom_target(update-gitexternal-${GIT_EXTERNAL_NAME}
COMMAND "${CMAKE_COMMAND}" -DGIT_EXTERNAL_SCRIPT_MODE=1 -P ${GIT_EXTERNAL_SCRIPT}
COMMENT "Update ${REPO} in ${GIT_EXTERNALS_BASE}"
DEPENDS ${GIT_EXTERNAL_TARGET}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
add_dependencies(update-gitexternal
update-gitexternal-${GIT_EXTERNAL_NAME})
# Flattens a git external repository into its parent repo:
# * Clean any changes from external
# * Unlink external from git: Remove external/.git and .gitexternals
# * Add external directory to parent
# * Commit with flattened repo and tag info
# - Depend on release branch checked out
add_custom_target(flatten-gitexternal-${GIT_EXTERNAL_NAME}
COMMAND "${GIT_EXECUTABLE}" clean -dfx
COMMAND "${CMAKE_COMMAND}" -E remove_directory .git
COMMAND "${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/.gitexternals"
COMMAND "${GIT_EXECUTABLE}" add -f .
COMMAND "${GIT_EXECUTABLE}" commit -m "Flatten ${REPO} into ${DIR} at ${TAG}" . "${CMAKE_CURRENT_SOURCE_DIR}/.gitexternals"
COMMENT "Flatten ${REPO} into ${DIR}"
DEPENDS ${PROJECT_NAME}-make-branch
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}")
add_dependencies(flatten-gitexternal
flatten-gitexternal-${GIT_EXTERNAL_NAME})
add_dependencies(${PROJECT_NAME}-flatten-gitexternal
flatten-gitexternal-${GIT_EXTERNAL_NAME})
foreach(_target flatten-gitexternal-${GIT_EXTERNAL_NAME} ${PROJECT_NAME}-flatten-gitexternal flatten-gitexternal update-gitexternal-${GIT_EXTERNAL_NAME} ${GIT_EXTERNAL_TARGET} update-gitexternal update)
set_target_properties(${_target} PROPERTIES
EXCLUDE_FROM_DEFAULT_BUILD ON FOLDER git)
endforeach()
endif()
endif()
endif()
endforeach()
endif()