-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathupdate_dependencies.sh
executable file
·550 lines (457 loc) · 16.6 KB
/
update_dependencies.sh
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
#!/usr/bin/env bash
#
# Created by André Carvalho on 10th September 2021
# Last modified: 2nd December 2024
#
# Processes a json with the format:
# [
# {
# "group": "...",
# "name": "...",
# "currentVersion": "...",
# "availableVersion": "...",
# "dependsOn": [
# "group": "...",
# "name": "...",
# "currentVersion": "...",
# "availableVersion": "...",
# "changelog": "..."
# ]
# "changelog": "..."
# },
# (...)
# ]
#
# And creates a new branch where the dependency is updated.
#
readonly BRANCH_PREFIX="housechores"
readonly REMOTE="origin"
readonly DEFAULT_BRANCH="develop"
readonly VERSION="2.3.1"
readonly COMMIT_MESSAGE_PREFIX="dependencies:"
readonly BOLD='\033[1m'
readonly RED='\033[0;31m'
readonly GREEN='\u001b[32m'
readonly RESET='\033[0m'
readonly NONE="0"
readonly REBASE="1"
readonly DESTRUCTIVE="2"
readonly METADATA_ITEM_DELIMITER=";"
readonly METADATA_GROUP_DELIMITER="|"
function main() {
IFS="$METADATA_ITEM_DELIMITER" read -r -a metadata <<< "$1"
IFS="$METADATA_GROUP_DELIMITER" read -r -a dependsOn <<< "$2"
local releaseNotes="$3"
local affectedLibraries="$4"
local file="$5"
local mainBranch="$6"
local callback="$7"
local updateMechanism="$8"
local toml="$9"
local id="${metadata[0]}"
local declaration="${metadata[1]}"
local fromVersion="${metadata[2]}"
local toVersion="${metadata[3]}"
echo -e "\nResetting back to '$mainBranch' branch..."
git reset "$file" > /dev/null
git checkout --force "${mainBranch}" || {
echo -e "${RED}Couldn't fetch '$mainBranch'. Please check if '$mainBranch' exists.${RESET}"
exit 1
}
prepareBranch "$id" "$mainBranch" "$updateMechanism"
update "$id" "$declaration" "$fromVersion" "$toVersion" "$file" "$toml"
commit "$id" "$toVersion" "$file" "$updateMechanism"
for i in "${!dependsOn[@]}"; do
IFS="$METADATA_ITEM_DELIMITER" read -r -a dependencyAsList <<< "${dependsOn[$i]}"
local dependsOnId="${dependencyAsList[0]}"
local dependsOnDeclaration="${dependencyAsList[1]}"
local dependsOnFromVersion="${dependencyAsList[2]}"
local dependsOnToVersion="${dependencyAsList[3]}"
update "$dependsOnId" "$dependsOnDeclaration" "$dependsOnFromVersion" "$dependsOnToVersion" "$file" "$toml"
commit "$dependsOnId" "$dependsOnToVersion" "$file" "$updateMechanism"
done
publish "$id" "$fromVersion" "$toVersion" "$file" "$mainBranch" "$affectedLibraries" "$releaseNotes" "$callback"
if [[ "$updateMechanism" == "$REBASE" ]]; then
git rebase --abort &> /dev/null
fi
echo -e "\n${GREEN}Done processing $id${RESET}"
}
function isVersionUpdateAlreadyProcessed() {
local branch="$(id "$1")"
local command="$(git ls-remote --exit-code --heads "$REMOTE" "$branch")"
local hasError="$?"
if [[ "$hasError" != "0" ]] || [[ -z "$command" ]]; then
echo "false"
else
# The git command didn't return an error which means the branch already exists on the remote
echo "true"
fi
}
function prepareBranch() {
local branch="$(id "$1")"
local baseBranch="$2"
local updateMechanism="$3"
echo -e "\nPreparing working branch ($branch)..."
if [[ "$updateMechanism" == "$REBASE" ]]; then
git checkout --force "$branch"
git rebase "$baseBranch"
else
git checkout -b "$branch"
fi
}
function update() {
local id="$1"
local substring="$2"
local fromVersion="$3"
local toVersion="$4"
local file="$5"
local toml="$6"
if [[ "$toml" == "true" ]]; then
echo -e "\nUpdating '$id' from '$fromVersion' to '$toVersion'"
local transformedText=${substring/$fromVersion/$toVersion}
echo -e "Saving $file file..."
echo "$(echo "$(cat "$file")" | sed "s/${substring}/${transformedText}/g")" > "$file"
else
echo -e "\nUpdating '$substring' from '$fromVersion' to '$toVersion'"
# The space at the end of "$substring " is important here.
# It prevents false positives when we have something like:
#
# ...
# composeNavigation : '2.4.0-alpha06',
# compose : '1.0.1',
# ...
#
# And we are updating the compose version, and not the composeNavigation, for example
local originalVersion="$(findInFile "$substring " "$file")"
if [ -z "$originalVersion" ]; then
echo -e "${RED}Couldn't find the original version declaration. Please check if you declared with a space after the ':'. eg: KEY : VALUE${RESET}"
else
local versionInFile="$(echo "$originalVersion" | awk '{print $NF}')"
local versionInFileTransformed="$(echo "${versionInFile//\"}")"
local newVersion=$(echo "$originalVersion" | sed "s/${versionInFileTransformed}/${toVersion}/g")
echo -e "Saving $file file..."
echo "$(echo "$(cat "$file")" | sed "s/${originalVersion}/${newVersion}/g")" > "$file"
fi
fi
}
function findVersionsVariableName() {
local group="$1"
local name="$2"
local file="$3"
local toml="$4"
if [[ "$toml" == "true" ]]; then
local dependency=""
readonly lookups=(
"module = \"$group:$name\""
"group = \"$group\", name = \"$name\""
"id = \"$group\""
)
for item in "${lookups[@]}"; do
local result=$(findInFile "$item" "$file")
if [ -n "$result" ]; then
dependency="$result"
break
fi
done
if [ -n "$dependency" ]; then
if [[ "$dependency" == *"version.ref = "* ]]; then
local extVariable="$(substring "version.ref = \"" "\" }" "$dependency")"
echo "$(findInFile "$extVariable = \"" "$file")"
else
echo "$dependency"
fi
else
echo "-1"
fi
else
local dependency="$(findInFile "$group:$name:" "$file")"
if [ -n "$dependency" ]; then
# Handle normal dependencies
local versionVariable="$(substring "." "" "$(substring "{" "}" "$dependency")")"
if [ -n "$versionVariable" ]; then
echo "$versionVariable"
else
echo "-1"
fi
else
# Handle Gradle Plugins versions
dependency="$(findInFile "id(\"$group\")" "$file")"
if [[ -n "$dependency" ]]; then
versionVariable="$(substring "." "" "$(substring "version" "" "$dependency")")"
local versions=($versionVariable)
if [ -n "${versions[0]}" ]; then
echo "${versions[0]}"
else
echo "-1"
fi
else
echo "-1"
fi
fi
fi
}
function findInFile() {
local text="$1"
local file="$2"
while read line; do
if [[ "$line" == *"$text"* ]]; then
echo "$line"
break
fi
done < "$file"
echo ""
}
function substring() {
local fromChar="$1"
local untilChar="$2"
local text="$3"
if [ -z "$untilChar" ]; then
echo $(echo "$text" | awk -F "$fromChar" '{ print $2 }')
else
echo "$(sed "s/.*${fromChar}\(.*\)${untilChar}.*/\1/" <<< "$text")"
fi
}
function commit() {
local name="$1"
local toVersion="$2"
local file="$3"
local updateMechanism="$4"
echo -e "\nCommitting changes..."
git add "$file"
if [[ $(git status --porcelain) ]]; then
if [[ -n "$COMMIT_MESSAGE_PREFIX" ]]; then
git commit -m "$COMMIT_MESSAGE_PREFIX Update $name to version $toVersion"
else
git commit -m "Update $name to version $toVersion"
fi
fi
}
function publish() {
local name="$1"
local fromVersion="$2"
local toVersion="$3"
local file="$4"
local mainBranch="$5"
local affectedLibraries="$6"
local releaseNotes="$7"
local callback="$8"
local branch="$(id "$name")"
echo -e "Pushing changes to remote..."
git push --force "$REMOTE" "$branch"
if [[ "$updateMechanism" != "$REBASE" ]]; then
"$callback" --variable "$name" --fromVersion "$fromVersion" \
--toVersion "$toVersion" --modules "$affectedLibraries" \
--releaseNotes "$releaseNotes" --sourceBranch "$branch" --targetBranch "$mainBranch"
fi
}
function id() {
local input="$1"
local lowercased="$(echo "$(echo "$input" | awk '{print tolower($0)}')")"
echo "$BRANCH_PREFIX/$lowercased"
}
function differences() {
local fromBranch="$1"
local toBranch="$2"
git fetch "$REMOTE" "$toBranch"
# Returns the amount of changes that both branches had since their split
# Eg: Imagining that fromBranch is release and toBranch develop after both being created the output would be 0 0
# If I create a commit in develop it becomes 0 1, and if I later commit in release it becomes 1 1
echo "$(git rev-list --left-right --count "$REMOTE/$fromBranch"..."$REMOTE/$toBranch")"
}
function hasBaseBranchBeenUpdated() {
local metadata="$(differences "$1" "$2")"
local diff=( $metadata )
if [[ "$diff" != "0" ]]; then
echo "true"
else
echo "false"
fi
}
function hasOpenedBranchBeenUpdated() {
local metadata="$(differences "$1" "$2")"
local diff=( $metadata )
if [[ "$((diff[1]))" -ge 2 ]]; then
echo "true"
else
echo "false"
fi
}
function booleanInput() {
local param="$1"
if [[ "$param" == [tT] || "$param" == [tT][rR][uU][eE] || "$param" == "1" || "$param" == [yY] || "$param" == [yY][eE][sS] ]]; then
echo "true"
else
echo "false"
fi
}
function help() {
echo -e "${BOLD}Gradle Dependencies Updater ($VERSION) developed by André Carvalho${RESET}\n"
echo -e "Usage: $0 -j \"{ ... }\" -d \"path(s) to the file(s) where the dependencies are declared\" -v \"path to the file where the dependencies versions are declared\""
echo -e " -j, --json \t The dependencies json content."
echo -e " -d, --dependencies\t The path(s) to the file(s) where the dependencies are declared, separated by comma ','."
echo -e " -v, --versions \t The path to the file where the dependency versions are declared."
echo -e " -b, --branch \t The name of the main git branch."
echo -e " -i, --ignore \t The path to a shell script that returns either 0 or 1 to indicate if we should process the library update."
echo -e " -r, --rebase \t Wether or not to rebase an already processed dependencies updates."
echo -e " -pu, --postupdate The path to a shell script that gets called when an update to a dependency is made. It gets all the params as key values pairs.\n"
exit 1
}
clear
if ! [[ -x "$(command -v jq)" ]]; then
echo -e "${RED}\"jq\" couldn't be found. Please be sure it's installed and included in your PATH environment variable!\n${RESET}"
exit 1
fi
while [ $# -gt 0 ]; do
case "$1" in
-j|-json|--json) json="$2" ;;
-d|-dependencies|--dependencies) IFS=',' read -r -a dependenciesPath <<< "$2" ;;
-v|-versions|--versions) versionsPath="$2" ;;
-b|-branch|--branch) branch="$2" ;;
-i|-ignore|--ignore) ignore="$2" ;;
-r|-rebase|--rebase) reprocess="$(booleanInput "$2")" ;;
-pu|-postupdate|--postupdate) callback="$2" ;;
esac
shift
shift
done
remoteVersion="$(curl --retry 3 --silent https://api.github.com/repos/Andr3Carvalh0/Gradle_Dependencies_Updater/tags | jq -r '.[0].name')"
if [[ -n "$remoteVersion" && "$remoteVersion" != "$VERSION" ]]; then
echo -e "${BOLD}\n[i] A new version ($remoteVersion) is available!\n[i] You can download it at https://github.com/Andr3Carvalh0/Gradle_Dependencies_Updater${RESET}\n"
fi
if [ -z "$json" ] || [ -z "$dependenciesPath" ] || [ -z "$versionsPath" ]; then
echo -e "${RED}You are missing one of the require parameters.\n${RESET}"
help
fi
if [ -z "$branch" ]; then
branch="$DEFAULT_BRANCH"
fi
echo -e "Fetching '$branch' branch."
git fetch "$REMOTE" "$branch"
transformedDependencies=()
transformedVersions=()
transformedAffectedLibraries=()
transformedReleaseNotes=()
transformedDependsOn=()
updateMechanism=()
shortIds=()
toml=$([[ "$dependenciesPath" == *".toml"* ]] && echo "true" || echo "false")
for row in $(echo "$json" | jq -r '.[] | @base64'); do
_jq() {
echo "$1" | base64 --decode | jq -r "$2"
}
_versionVariable() {
local group="$1"
local name="$2"
local isToml="$3"
local output="-1"
for i in "${!dependenciesPath[@]}"; do
result="$(findVersionsVariableName "$group" "$name" "${dependenciesPath[${i}]}" "$isToml")"
if [[ "$result" != "-1" ]]; then
output="$result"
break
fi
done
echo "$output"
}
_shortId() {
variable="$1"
isToml="$2"
shortId="$variable"
if [[ "$isToml" == "true" ]]; then
shortId="$(echo "$variable" | awk -F " " '{ print $1 }')"
fi
echo "$shortId"
}
group=$(_jq "$row" ".group")
name=$(_jq "$row" ".name")
currentVersion=$(_jq "$row" ".currentVersion")
availableVersion=$(_jq "$row" ".availableVersion")
changelog=$(_jq "$row" ".changelog")
echo -e "Evaluating if $group:$name can be updated..."
processingVersionVariable="$(_versionVariable "$group" "$name" "$toml")"
if [[ "$processingVersionVariable" != "-1" ]]; then
shortId="$(_shortId "$processingVersionVariable" "$toml")"
mechanism="$NONE"
# If the update already exists. We will check the amount of differences between the source branch and the updated branch.
# If the source branch has received an update, we will delete the updated branch and process it again to get the latest changes.
if [[ "$(isVersionUpdateAlreadyProcessed "$shortId")" == "true" ]]; then
if [[ "$reprocess" == "false" ]]; then
echo -e "'$group:$name:$availableVersion' was processed before and rebasing is disabled. Skipping it..."
continue
fi
remoteBranch="$(id "$shortId")"
if [[ "$(hasBaseBranchBeenUpdated "$branch" "$remoteBranch")" == "true" ]]; then
echo -e "'$branch' has changed since the update to '$group:$name:$availableVersion'"
if [[ "$(hasOpenedBranchBeenUpdated "$branch" "$remoteBranch")" == "true" ]]; then
echo -e "Previous version of the update branch has more work than just the version bump. Trying to rebase it..."
mechanism="$REBASE"
else
echo -e "Previous version of the update branch hasn't changed, destroying it and processing the dependency update again..."
git branch -D "$remoteBranch" || {
echo -e "[i] Failed to delete '$remoteBranch' locally, probably because it doesn't exist. Continuing..."
}
mechanism="$DESTRUCTIVE"
fi
else
echo -e "An updated branch for '$group:$name:$availableVersion' is already available! Skipping it..."
continue
fi
fi
index=${#transformedDependencies[@]}
affectedLibraries="${group}:${name}"
releaseNotes="$changelog"
for i in "${!transformedDependencies[@]}"; do
if [[ "${transformedDependencies[$i]}" = "$processingVersionVariable" ]]; then
index="$i"
affectedLibraries="${transformedAffectedLibraries[$i]},${affectedLibraries}"
transformedReleaseNotes[index]="${transformedReleaseNotes[$i]},${releaseNotes}"
fi
done
for dependsOn in $(_jq "$row" ".dependsOn" | jq -r '.[] | @base64'); do
dependsOnGroup=$(_jq "$dependsOn" ".group")
dependsOnName=$(_jq "$dependsOn" ".name")
dependsOnVersion=$(_jq "$dependsOn" ".currentVersion")
dependsOnAvailableVersion=$(_jq "$dependsOn" ".availableVersion")
dependsOnChangelog=$(_jq "$dependsOn" ".changelog")
echo -e "Evaluating if sub dependency $dependsOnGroup:$dependsOnName can be updated..."
dependsOnVersionVariable="$(_versionVariable "$dependsOnGroup" "$dependsOnName" "$toml")"
if [[ "$dependsOnVersionVariable" != "-1" ]]; then
dependsOnShortId="$(_shortId "$dependsOnVersionVariable" "$toml")"
transformedValue="${dependsOnShortId}${METADATA_ITEM_DELIMITER}${dependsOnVersionVariable}${METADATA_ITEM_DELIMITER}${dependsOnVersion}${METADATA_ITEM_DELIMITER}${dependsOnAvailableVersion}"
transformedDependsOn[index]=$([[ -n "${transformedDependsOn[$index]}" ]] && echo "${transformedDependsOn[$index]}${METADATA_GROUP_DELIMITER}$transformedValue" || echo "$transformedValue")
releaseNotes="${releaseNotes},${dependsOnChangelog}"
affectedLibraries="${affectedLibraries},${dependsOnGroup}:${dependsOnName}"
else
echo -e "${RED}Couldn't find the version variable for '$dependsOnGroup:$dependsOnName' ${RESET}"
fi
done
shortIds[index]="$shortId"
updateMechanism[index]="$mechanism"
transformedDependencies[index]="$processingVersionVariable"
transformedVersions[index]="$currentVersion $availableVersion"
transformedReleaseNotes[index]="$releaseNotes"
transformedAffectedLibraries[index]="$affectedLibraries"
else
echo -e "${RED}Couldn't find the version variable for '$group:$name' ${RESET}"
fi
done
for i in "${!transformedDependencies[@]}"; do
versions=(${transformedVersions[$i]})
uniqueId="${transformedDependencies[$i]}"
shortId="${shortIds[$i]}"
metadata="${shortId}${METADATA_ITEM_DELIMITER}${uniqueId}${METADATA_ITEM_DELIMITER}${versions[0]}${METADATA_ITEM_DELIMITER}${versions[1]}"
dependsOn="${transformedDependsOn[$i]}"
if [[ -n "$uniqueId" ]]; then
if [[ -z "$ignore" ]]; then
main "$metadata" "$dependsOn" "${transformedReleaseNotes[$i]}" "${transformedAffectedLibraries[$i]}" "$versionsPath" "$branch" "$callback" "${updateMechanism[$i]}" "$toml"
else
if [[ "$("$ignore" --variable "$shortId" --fromVersion "${versions[0]}" --toVersion "${versions[1]}")" == "0" ]]; then
main "$metadata" "$dependsOn" "${transformedReleaseNotes[$i]}" "${transformedAffectedLibraries[$i]}" "$versionsPath" "$branch" "$callback" "${updateMechanism[$i]}" "$toml"
else
echo -e "$shortId is in the ignore list. Skipping it..."
fi
fi
else
echo -e "${RED}Got invalid variable id for: ${transformedAffectedLibraries[$i]}${RESET}"
fi
done