cvm
/
trunk
446 строк · 13.5 Кб
1#!/bin/bash
2
3###############################################################################
4# #
5# Setup #
6# #
7###############################################################################
8
9set -euo pipefail
10
11readonly TRUNK_LAUNCHER_VERSION="1.2.7" # warning: this line is auto-updated
12
13readonly SUCCESS_MARK="\033[0;32m✔\033[0m"
14readonly FAIL_MARK="\033[0;31m✘\033[0m"
15readonly PROGRESS_MARKS=("⡿" "⢿" "⣻" "⣽" "⣾" "⣷" "⣯" "⣟")
16
17# This is how mktemp(1) decides where to create stuff in tmpfs.
18readonly TMPDIR="${TMPDIR:-/tmp}"
19
20KERNEL=$(uname | tr "[:upper:]" "[:lower:]")
21if [[ ${KERNEL} == mingw64* || ${KERNEL} == msys* ]]; then
22KERNEL="mingw"
23fi
24readonly KERNEL
25
26MACHINE=$(uname -m)
27if [[ $MACHINE == "aarch64" ]]; then
28MACHINE="arm64";
29fi
30
31readonly MACHINE
32
33PLATFORM="${KERNEL}-${MACHINE}"
34readonly PLATFORM
35
36PLATFORM_UNDERSCORE="${KERNEL}_${MACHINE}"
37readonly PLATFORM_UNDERSCORE
38
39# https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
40# [nF is "cursor previous line" and moves to the beginning of the nth previous line
41# [0K is "erase display" and clears from the cursor to the end of the screen
42readonly CLEAR_LAST_MSG="\033[1F\033[0K"
43
44if [[ ! -z ${CI:-} && "${CI}" = true && -z ${TRUNK_LAUNCHER_QUIET:-} ]]; then
45TRUNK_LAUNCHER_QUIET=1
46else
47TRUNK_LAUNCHER_QUIET=${TRUNK_LAUNCHER_QUIET:-${TRUNK_QUIET:-false}}
48fi
49
50readonly TRUNK_LAUNCHER_DEBUG
51
52if [[ ${TRUNK_LAUNCHER_QUIET} != false ]]; then
53exec 3>&1 4>&2 &>/dev/null
54fi
55
56TRUNK_CACHE="${TRUNK_CACHE:-}"
57if [[ -n ${TRUNK_CACHE} ]]; then
58:
59elif [[ -n ${XDG_CACHE_HOME:-} ]]; then
60TRUNK_CACHE="${XDG_CACHE_HOME}/trunk"
61else
62TRUNK_CACHE="${HOME}/.cache/trunk"
63fi
64readonly TRUNK_CACHE
65readonly CLI_DIR="${TRUNK_CACHE}/cli"
66mkdir -p "${CLI_DIR}"
67
68# platform check
69readonly MINIMUM_MACOS_VERSION="10.15"
70check_darwin_version() {
71local osx_version
72osx_version="$(sw_vers -productVersion)"
73
74# trunk-ignore-begin(shellcheck/SC2312): the == will fail if anything inside the $() fails
75if [[ "$(printf "%s\n%s\n" "${MINIMUM_MACOS_VERSION}" "${osx_version}" |
76sort --version-sort |
77head -n 1)" == "${MINIMUM_MACOS_VERSION}"* ]]; then
78return
79fi
80# trunk-ignore-end(shellcheck/SC2312)
81
82echo -e "${FAIL_MARK} Trunk requires at least MacOS ${MINIMUM_MACOS_VERSION}" \
83"(yours is ${osx_version}). See https://docs.trunk.io for more info."
84exit 1
85}
86
87if [[ ${PLATFORM} == "darwin-x86_64" || ${PLATFORM} == "darwin-arm64" ]]; then
88check_darwin_version
89elif [[ ${PLATFORM} == "linux-x86_64" || ${PLATFORM} == "linux-arm64" || ${PLATFORM} == "windows-x86_64" || ${PLATFORM} == "mingw-x86_64" ]]; then
90:
91else
92echo -e "${FAIL_MARK} Trunk is only supported on Linux (x64_64, arm64), MacOS (x86_64, arm64), and Windows (x86_64)." \
93"See https://docs.trunk.io for more info."
94exit 1
95fi
96
97TRUNK_TMPDIR="${TMPDIR}/trunk-$(
98set -e
99id -u
100)/launcher_logs"
101readonly TRUNK_TMPDIR
102mkdir -p "${TRUNK_TMPDIR}"
103
104# For the `mv $TOOL_TMPDIR/trunk $TOOL_DIR` to be atomic (i.e. just inode renames), the source and destination filesystems need to be the same
105TOOL_TMPDIR=$(mktemp -d "${CLI_DIR}/tmp.XXXXXXXXXX")
106readonly TOOL_TMPDIR
107
108cleanup() {
109rm -rf "${TOOL_TMPDIR}"
110if [[ $1 == "0" ]]; then
111rm -rf "${TRUNK_TMPDIR}"
112fi
113}
114trap 'cleanup $?' EXIT
115
116# e.g. 2022-02-16-20-40-31-0800
117dt_str() { date +"%Y-%m-%d-%H-%M-%S%z"; }
118
119LAUNCHER_TMPDIR="${TOOL_TMPDIR}/launcher"
120readonly LAUNCHER_TMPDIR
121mkdir -p "${LAUNCHER_TMPDIR}"
122
123if [[ -n ${TRUNK_LAUNCHER_DEBUG:-} ]]; then
124set -x
125fi
126
127# launcher awk
128#
129# BEGIN{ORS="";}
130# use "" as the output record separator
131# ORS defaults to "\n" for bwk, which results in
132# $(printf "foo bar" | awk '{print $2}') == "bar\n"
133#
134# {gsub(/\r/, "", $0)}
135# for every input record (i.e. line), the regex "\r" should be replaced with ""
136# This is necessary to handle CRLF files in a portable fashion.
137#
138# Some StackOverflow answers suggest using RS="\r?\n" to handle CRLF files (RS is the record
139# separator, i.e. the line delimiter); unfortunately, original-awk only allows single-character
140# values for RS (see https://www.gnu.org/software/gawk/manual/gawk.html#awk-split-records).
141lawk() {
142awk 'BEGIN{ORS="";}{gsub(/\r/, "", $0)}'"${1}" "${@:2}"
143}
144awk_test() {
145# trunk-ignore-begin(shellcheck/SC2310,shellcheck/SC2312)
146# SC2310 and SC2312 are about set -e not propagating to the $(); if that happens, the string
147# comparison will fail and we'll claim the user's awk doesn't work
148if [[ $(
149set -e
150printf 'k1: v1\n \tk2: v2\r\n' | lawk '/[ \t]+k2:/{print $2}'
151) == 'v2' &&
152$(
153set -e
154printf 'k1: v1\r\n\t k2: v2\r\n' | lawk '/[ \t]+k2:/{print $2}'
155) == 'v2' ]]; then
156return
157fi
158# trunk-ignore-end(shellcheck/SC2310,shellcheck/SC2312)
159
160echo -e "${FAIL_MARK} Trunk does not work with your awk;" \
161"please report this at https://slack.trunk.io."
162echo -e "Your version of awk is:"
163awk --version || awk -Wversion
164exit 1
165}
166awk_test
167
168readonly CURL_FLAGS="${CURL_FLAGS:- -vvv --max-time 120 --retry 3 --fail}"
169readonly WGET_FLAGS="${WGET_FLAGS:- --verbose --tries=3 --limit-rate=10M}"
170TMP_DOWNLOAD_LOG="${TRUNK_TMPDIR}/download-$(
171set -e
172dt_str
173).log"
174readonly TMP_DOWNLOAD_LOG
175
176# Detect whether we should use wget or curl.
177if command -v wget &>/dev/null; then
178download_cmd() {
179local url="${1}"
180local output_to="${2}"
181# trunk-ignore-begin(shellcheck/SC2312): we don't care if wget --version errors
182cat >>"${TMP_DOWNLOAD_LOG}" <<EOF
183Using wget to download '${url}' to '${output_to}'
184
185Is Trunk up?: https://status.trunk.io
186
187WGET_FLAGS: ${WGET_FLAGS}
188
189wget --version:
190$(wget --version 2>&1)
191
192EOF
193# trunk-ignore-end(shellcheck/SC2312)
194
195# Support BusyBox wget
196if wget --help 2>&1 | grep BusyBox; then
197wget "${url}" -O "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" &
198else
199# trunk-ignore(shellcheck/SC2086): we deliberately don't quote WGET_FLAGS
200wget ${WGET_FLAGS} "${url}" --output-document "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" &
201fi
202}
203elif command -v curl &>/dev/null; then
204download_cmd() {
205local url="${1}"
206local output_to="${2}"
207# trunk-ignore-begin(shellcheck/SC2312): we don't care if curl --version errors
208cat >>"${TMP_DOWNLOAD_LOG}" <<EOF
209Using curl to download '${url}' to '${output_to}'
210
211Is Trunk up?: https://status.trunk.io
212
213CURL_FLAGS: ${CURL_FLAGS}
214
215curl --version:
216$(curl --version)
217
218EOF
219# trunk-ignore-end(shellcheck/SC2312)
220
221# trunk-ignore(shellcheck/SC2086): we deliberately don't quote CURL_FLAGS
222curl ${CURL_FLAGS} "${url}" --output "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" &
223}
224else
225download_cmd() {
226echo -e "${FAIL_MARK} Cannot download '${url}'; please install curl or wget."
227exit 1
228}
229fi
230
231download_url() {
232local url="${1}"
233local output_to="${2}"
234local progress_message="${3:-}"
235
236if [[ -n ${progress_message} ]]; then
237echo -e "${PROGRESS_MARKS[0]} ${progress_message}..."
238fi
239
240download_cmd "${url}" "${output_to}"
241local download_pid="$!"
242
243local i_prog=0
244while [[ -d "/proc/${download_pid}" && -n ${progress_message} ]]; do
245echo -e "${CLEAR_LAST_MSG}${PROGRESS_MARKS[${i_prog}]} ${progress_message}..."
246sleep 0.2
247i_prog=$(((i_prog + 1) % ${#PROGRESS_MARKS[@]}))
248done
249
250local download_log
251if ! wait "${download_pid}"; then
252download_log="${TRUNK_TMPDIR}/launcher-download-$(
253set -e
254dt_str
255).log"
256mv "${TMP_DOWNLOAD_LOG}" "${download_log}"
257echo -e "${CLEAR_LAST_MSG}${FAIL_MARK} ${progress_message}... FAILED (see ${download_log})"
258echo -e "Please check your connection and try again." \
259"If you continue to see this error message," \
260"consider reporting it to us at https://slack.trunk.io."
261exit 1
262fi
263
264if [[ -n ${progress_message} ]]; then
265echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${progress_message}... done"
266fi
267
268}
269
270# sha256sum is in coreutils, so we prefer that over shasum, which is installed with perl
271if command -v sha256sum &>/dev/null; then
272:
273elif command -v shasum &>/dev/null; then
274sha256sum() { shasum -a 256 "$@"; }
275else
276sha256sum() {
277echo -e "${FAIL_MARK} Cannot compute sha256; please install sha256sum or shasum"
278exit 1
279}
280fi
281
282###############################################################################
283# #
284# CLI resolution functions #
285# #
286###############################################################################
287
288trunk_yaml_abspath() {
289local repo_head
290local cwd
291
292if repo_head=$(git rev-parse --show-toplevel 2>/dev/null); then
293echo "${repo_head}/.trunk/trunk.yaml"
294elif [[ -f .trunk/trunk.yaml ]]; then
295cwd="$(pwd)"
296echo "${cwd}/.trunk/trunk.yaml"
297else
298echo ""
299fi
300}
301
302read_cli_version_from() {
303local config_abspath="${1}"
304local cli_version
305
306cli_version="$(
307set -e
308lawk '/[ \t]+version:/{print $2; exit;}' "${config_abspath}"
309)"
310if [[ -z ${cli_version} ]]; then
311echo -e "${FAIL_MARK} Invalid .trunk/trunk.yaml, no cli version found." \
312"See https://docs.trunk.io for more info." >&2
313exit 1
314fi
315
316echo "${cli_version}"
317}
318
319download_cli() {
320local dl_version="${1}"
321local expected_sha256="${2}"
322local actual_sha256
323
324readonly TMP_INSTALL_DIR="${LAUNCHER_TMPDIR}/install"
325mkdir -p "${TMP_INSTALL_DIR}"
326
327TRUNK_NEW_URL_VERSION=0.10.2-beta.1
328if sort --help 2>&1 | grep BusyBox; then
329readonly URL="https://trunk.io/releases/${dl_version}/trunk-${dl_version}-${PLATFORM}.tar.gz"
330else
331if [[ "$(printf "%s\n%s\n" "${TRUNK_NEW_URL_VERSION}" "${dl_version}" |
332sort --version-sort |
333head -n 1 || true)" == "${TRUNK_NEW_URL_VERSION}"* ]]; then
334readonly URL="https://trunk.io/releases/${dl_version}/trunk-${dl_version}-${PLATFORM}.tar.gz"
335else
336readonly URL="https://trunk.io/releases/trunk-${dl_version}.${KERNEL}.tar.gz"
337fi
338fi
339
340readonly DOWNLOAD_TAR_GZ="${TMP_INSTALL_DIR}/download-${dl_version}.tar.gz"
341
342download_url "${URL}" "${DOWNLOAD_TAR_GZ}" "Downloading Trunk ${dl_version}"
343
344if [[ -n ${expected_sha256:-} ]]; then
345local verifying_text="Verifying Trunk sha256..."
346echo -e "${PROGRESS_MARKS[0]} ${verifying_text}"
347
348actual_sha256="$(
349set -e
350sha256sum "${DOWNLOAD_TAR_GZ}" | lawk '{print $1}'
351)"
352
353if [[ ${actual_sha256} != "${expected_sha256}" ]]; then
354echo -e "${CLEAR_LAST_MSG}${FAIL_MARK} ${verifying_text} FAILED"
355echo "Expected sha256: ${expected_sha256}"
356echo " Actual sha256: ${actual_sha256}"
357exit 1
358fi
359
360echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${verifying_text} done"
361fi
362
363local unpacking_text="Unpacking Trunk..."
364echo -e "${PROGRESS_MARKS[0]} ${unpacking_text}"
365tar --strip-components=1 -C "${TMP_INSTALL_DIR}" -xf "${DOWNLOAD_TAR_GZ}"
366echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${unpacking_text} done"
367
368rm -f "${DOWNLOAD_TAR_GZ}"
369mkdir -p "${TOOL_DIR}"
370readonly OLD_TOOL_DIR="${CLI_DIR}/${version}"
371# Create a backwards compatability link for old versions of trunk that want to write their
372# crashpad_handlers to that dir.
373if [[ ! -e ${OLD_TOOL_DIR} ]]; then
374ln -sf "${TOOL_PART}" "${OLD_TOOL_DIR}"
375fi
376mv -n "${TMP_INSTALL_DIR}/trunk" "${TOOL_DIR}/" || true
377rm -rf "${TMP_INSTALL_DIR}"
378}
379
380###############################################################################
381# #
382# CLI resolution #
383# #
384###############################################################################
385
386CONFIG_ABSPATH="$(
387set -e
388trunk_yaml_abspath
389)"
390readonly CONFIG_ABSPATH
391
392version="${TRUNK_CLI_VERSION:-}"
393if [[ -n ${version:-} ]]; then
394:
395elif [[ -f ${CONFIG_ABSPATH} ]]; then
396version="$(
397set -e
398read_cli_version_from "${CONFIG_ABSPATH}"
399)"
400version_sha256="$(
401set -e
402lawk "/${PLATFORM_UNDERSCORE}:/"'{print $2}' "${CONFIG_ABSPATH}"
403)"
404else
405readonly LATEST_FILE="${LAUNCHER_TMPDIR}/latest"
406download_url "https://trunk.io/releases/latest" "${LATEST_FILE}"
407version=$(
408set -e
409lawk '/version:/{print $2}' "${LATEST_FILE}"
410)
411version_sha256=$(
412set -e
413lawk "/${PLATFORM_UNDERSCORE}:/"'{print $2}' "${LATEST_FILE}"
414)
415fi
416
417readonly TOOL_PART="${version}-${PLATFORM}"
418readonly TOOL_DIR="${CLI_DIR}/${TOOL_PART}"
419
420if [[ ! -e ${TOOL_DIR}/trunk ]]; then
421download_cli "${version}" "${version_sha256:-}"
422echo # add newline between launcher and CLI output
423fi
424
425if [[ ${TRUNK_LAUNCHER_QUIET} != false ]]; then
426exec 1>&3 3>&- 2>&4 4>&-
427fi
428
429###############################################################################
430# #
431# CLI invocation #
432# #
433###############################################################################
434
435if [[ -n ${LATEST_FILE:-} ]]; then
436mv -n "${LATEST_FILE}" "${TOOL_DIR}/version" >/dev/null 2>&1 || true
437fi
438
439# NOTE: exec will overwrite the process image, so trap will not catch the exit signal.
440# Therefore, run cleanup manually here.
441cleanup 0
442
443exec \
444env TRUNK_LAUNCHER_VERSION="${TRUNK_LAUNCHER_VERSION}" \
445env TRUNK_LAUNCHER_PATH="${BASH_SOURCE[0]}" \
446"${TOOL_DIR}/trunk" "$@"
447