|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# This script is meant to be sourced by other scripts. To source this script: |
| 4 | +# # shellcheck source=scripts/lib.sh |
| 5 | +# source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" |
| 6 | + |
| 7 | +set -euo pipefail |
| 8 | + |
| 9 | +# Avoid sourcing this script multiple times to guard against when lib.sh |
| 10 | +# is used by another sourced script, it can lead to confusing results. |
| 11 | +if [[ ${SCRIPTS_LIB_IS_SOURCED:-0} == 1 ]]; then |
| 12 | + return |
| 13 | +fi |
| 14 | +# Do not export to avoid this value being inherited by non-sourced |
| 15 | +# scripts. |
| 16 | +SCRIPTS_LIB_IS_SOURCED=1 |
| 17 | + |
| 18 | +# realpath returns an absolute path to the given relative path. It will fail if |
| 19 | +# the parent directory of the path does not exist. Make sure you are in the |
| 20 | +# expected directory before running this to avoid errors. |
| 21 | +# |
| 22 | +# GNU realpath relies on coreutils, which are not installed or the default on |
| 23 | +# Macs out of the box, so we have this mostly working bash alternative instead. |
| 24 | +# |
| 25 | +# Taken from https://stackoverflow.com/a/3915420 (CC-BY-SA 4.0) |
| 26 | +realpath() { |
| 27 | + local dir |
| 28 | + local base |
| 29 | + dir="$(dirname "$1")" |
| 30 | + base="$(basename "$1")" |
| 31 | + |
| 32 | + if [[ ! -d "$dir" ]]; then |
| 33 | + error "Could not change directory to '$dir': directory does not exist" |
| 34 | + fi |
| 35 | + echo "$( |
| 36 | + cd "$dir" || error "Could not change directory to '$dir'" |
| 37 | + pwd -P |
| 38 | + )"/"$base" |
| 39 | +} |
| 40 | + |
| 41 | +# We have to define realpath before these otherwise it fails on Mac's bash. |
| 42 | +SCRIPT="${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}" |
| 43 | +SCRIPT_DIR="$(realpath "$(dirname "$SCRIPT")")" |
| 44 | + |
| 45 | +function project_root { |
| 46 | + # Nix sets $src in derivations! |
| 47 | + [[ -n "${src:-}" ]] && echo "$src" && return |
| 48 | + |
| 49 | + # Try to use `git rev-parse --show-toplevel` to find the project root. |
| 50 | + # If this directory is not a git repository, this command will fail. |
| 51 | + git rev-parse --show-toplevel 2>/dev/null && return |
| 52 | + |
| 53 | + # This finds the Sapling root. This behavior is added so that @ammario |
| 54 | + # and others can more easily experiment with Sapling, but we do not have a |
| 55 | + # plan to support Sapling across the repo. |
| 56 | + sl root 2>/dev/null && return |
| 57 | +} |
| 58 | + |
| 59 | +PROJECT_ROOT="$(cd "$SCRIPT_DIR" && realpath "$(project_root)")" |
| 60 | + |
| 61 | +# pushd is a silent alternative to the real pushd shell command. |
| 62 | +pushd() { |
| 63 | + command pushd "$@" >/dev/null || error "Could not pushd to '$*'" |
| 64 | +} |
| 65 | + |
| 66 | +# popd is a silent alternative to the real popd shell command. |
| 67 | +# shellcheck disable=SC2120 |
| 68 | +popd() { |
| 69 | + command popd >/dev/null || error "Could not restore directory with popd" |
| 70 | +} |
| 71 | + |
| 72 | +# cdself changes directory to the directory of the current script. This should |
| 73 | +# not be used in scripts that may be sourced by other scripts. |
| 74 | +cdself() { |
| 75 | + cd "$SCRIPT_DIR" || error "Could not change directory to '$SCRIPT_DIR'" |
| 76 | +} |
| 77 | + |
| 78 | +# cdroot changes directory to the root of the repository. |
| 79 | +cdroot() { |
| 80 | + cd "$PROJECT_ROOT" || error "Could not change directory to '$PROJECT_ROOT'" |
| 81 | +} |
| 82 | + |
| 83 | +# execrelative can be used to execute scripts as if you were in the parent |
| 84 | +# directory of the current script. This should not be used in scripts that may |
| 85 | +# be sourced by other scripts. |
| 86 | +execrelative() { |
| 87 | + pushd "$SCRIPT_DIR" || error "Could not change directory to '$SCRIPT_DIR'" |
| 88 | + local rc=0 |
| 89 | + "$@" || rc=$? |
| 90 | + popd |
| 91 | + return $rc |
| 92 | +} |
| 93 | + |
| 94 | +dependency_check() { |
| 95 | + local dep=$1 |
| 96 | + |
| 97 | + # Special case for yq that can be yq or yq4. |
| 98 | + if [[ $dep == yq ]]; then |
| 99 | + [[ -n "${CODER_LIBSH_YQ:-}" ]] |
| 100 | + return |
| 101 | + fi |
| 102 | + |
| 103 | + command -v "$dep" >/dev/null |
| 104 | +} |
| 105 | + |
| 106 | +dependencies() { |
| 107 | + local fail=0 |
| 108 | + for dep in "$@"; do |
| 109 | + if ! dependency_check "$dep"; then |
| 110 | + log "ERROR: The '$dep' dependency is required, but is not available." |
| 111 | + if isdarwin; then |
| 112 | + case "$dep" in |
| 113 | + gsed | gawk) |
| 114 | + log "- brew install $dep" |
| 115 | + ;; |
| 116 | + esac |
| 117 | + fi |
| 118 | + fail=1 |
| 119 | + fi |
| 120 | + done |
| 121 | + |
| 122 | + if [[ "$fail" == 1 ]]; then |
| 123 | + log |
| 124 | + error "One or more dependencies are not available, check above log output for more details." |
| 125 | + fi |
| 126 | +} |
| 127 | + |
| 128 | +requiredenvs() { |
| 129 | + local fail=0 |
| 130 | + for env in "$@"; do |
| 131 | + if [[ "${!env:-}" == "" ]]; then |
| 132 | + log "ERROR: The '$env' environment variable is required, but is not set." |
| 133 | + fail=1 |
| 134 | + fi |
| 135 | + done |
| 136 | + |
| 137 | + if [[ "$fail" == 1 ]]; then |
| 138 | + log |
| 139 | + error "One or more required environment variables are not set, check above log output for more details." |
| 140 | + fi |
| 141 | +} |
| 142 | + |
| 143 | +gh_auth() { |
| 144 | + if [[ -z ${GITHUB_TOKEN:-} ]]; then |
| 145 | + if [[ -n ${GH_TOKEN:-} ]]; then |
| 146 | + export GITHUB_TOKEN=${GH_TOKEN} |
| 147 | + elif [[ ${CODER:-} == true ]]; then |
| 148 | + if ! output=$(coder external-auth access-token github 2>&1); then |
| 149 | + # TODO(mafredri): We could allow checking `gh auth token` here. |
| 150 | + log "${output}" |
| 151 | + error "Could not authenticate with GitHub using Coder external auth." |
| 152 | + else |
| 153 | + export GITHUB_TOKEN=${output} |
| 154 | + fi |
| 155 | + elif token="$(gh auth token --hostname github.com 2>/dev/null)"; then |
| 156 | + export GITHUB_TOKEN=${token} |
| 157 | + else |
| 158 | + error "GitHub authentication is required to run this command, please set GITHUB_TOKEN or run 'gh auth login'." |
| 159 | + fi |
| 160 | + fi |
| 161 | +} |
| 162 | + |
| 163 | +# maybedryrun prints the given program and flags, and then, if the first |
| 164 | +# argument is 0, executes it. The reason the first argument should be 0 is that |
| 165 | +# it is expected that you have a dry_run variable in your script that is set to |
| 166 | +# 0 by default (i.e. do not dry run) and set to 1 if the --dry-run flag is |
| 167 | +# specified. |
| 168 | +# |
| 169 | +# Usage: maybedryrun 1 gh release create ... |
| 170 | +# Usage: maybedryrun 0 docker push ghcr.io/coder/coder:latest |
| 171 | +maybedryrun() { |
| 172 | + if [[ "$1" == 1 ]]; then |
| 173 | + shift |
| 174 | + log "DRYRUN: $*" |
| 175 | + else |
| 176 | + shift |
| 177 | + logrun "$@" |
| 178 | + fi |
| 179 | +} |
| 180 | + |
| 181 | +# logrun prints the given program and flags, and then executes it. |
| 182 | +# |
| 183 | +# Usage: logrun gh release create ... |
| 184 | +logrun() { |
| 185 | + log $ "$*" |
| 186 | + "$@" |
| 187 | +} |
| 188 | + |
| 189 | +# log prints a message to stderr. |
| 190 | +log() { |
| 191 | + echo "$*" 1>&2 |
| 192 | +} |
| 193 | + |
| 194 | +# error prints an error message and returns an error exit code. |
| 195 | +error() { |
| 196 | + log "ERROR: $*" |
| 197 | + exit 1 |
| 198 | +} |
| 199 | + |
| 200 | +# isdarwin returns an error if the current platform is not darwin. |
| 201 | +isdarwin() { |
| 202 | + [[ "${OSTYPE:-darwin}" == *darwin* ]] |
| 203 | +} |
| 204 | + |
| 205 | +# issourced returns true if the script that sourced this script is being |
| 206 | +# sourced by another. |
| 207 | +issourced() { |
| 208 | + [[ "${BASH_SOURCE[1]}" != "$0" ]] |
| 209 | +} |
| 210 | + |
| 211 | +# We don't need to check dependencies more than once per script, but some |
| 212 | +# scripts call other scripts that also `source lib.sh`, so we set an environment |
| 213 | +# variable after successfully checking dependencies once. |
| 214 | +if [[ "${CODER_LIBSH_NO_CHECK_DEPENDENCIES:-}" != *t* ]]; then |
| 215 | + libsh_bad_dependencies=0 |
| 216 | + |
| 217 | + if ((BASH_VERSINFO[0] < 4)); then |
| 218 | + libsh_bad_dependencies=1 |
| 219 | + log "ERROR: You need at least bash 4.0 to run the scripts in the Coder repo." |
| 220 | + if isdarwin; then |
| 221 | + log "On darwin:" |
| 222 | + log "- brew install bash" |
| 223 | + # shellcheck disable=SC2016 |
| 224 | + log '- Add "$(brew --prefix bash)/bin" to your PATH' |
| 225 | + log "- Restart your terminal" |
| 226 | + fi |
| 227 | + log |
| 228 | + fi |
| 229 | + |
| 230 | + # BSD getopt (which is installed by default on Macs) is not supported. |
| 231 | + if [[ "$(getopt --version)" == *--* ]]; then |
| 232 | + libsh_bad_dependencies=1 |
| 233 | + log "ERROR: You need GNU getopt to run the scripts in the Coder repo." |
| 234 | + if isdarwin; then |
| 235 | + log "On darwin:" |
| 236 | + log "- brew install gnu-getopt" |
| 237 | + # shellcheck disable=SC2016 |
| 238 | + log '- Add "$(brew --prefix gnu-getopt)/bin" to your PATH' |
| 239 | + log "- Restart your terminal" |
| 240 | + fi |
| 241 | + log |
| 242 | + fi |
| 243 | + |
| 244 | + # The bash scripts don't call Make directly, but we want to make (ha ha) |
| 245 | + # sure that make supports the features the repo uses. Notably, Macs have an |
| 246 | + # old version of Make installed out of the box that doesn't support new |
| 247 | + # features like ONESHELL. |
| 248 | + # |
| 249 | + # We have to disable pipefail temporarily to avoid ERRPIPE errors when |
| 250 | + # piping into `head -n1`. |
| 251 | + set +o pipefail |
| 252 | + make_version="$(make --version 2>/dev/null | head -n1 | grep -oE '([[:digit:]]+\.){1,2}[[:digit:]]+')" |
| 253 | + set -o pipefail |
| 254 | + if [[ ${make_version//.*/} -lt 4 ]]; then |
| 255 | + libsh_bad_dependencies=1 |
| 256 | + log "ERROR: You need at least make 4.0 to run the scripts in the Coder repo." |
| 257 | + if isdarwin; then |
| 258 | + log "On darwin:" |
| 259 | + log "- brew install make" |
| 260 | + # shellcheck disable=SC2016 |
| 261 | + log '- Add "$(brew --prefix make)/libexec/gnubin" to your PATH' |
| 262 | + log "- Restart your terminal" |
| 263 | + fi |
| 264 | + log |
| 265 | + fi |
| 266 | + |
| 267 | + # Allow for yq to be installed as yq4. |
| 268 | + if command -v yq4 >/dev/null; then |
| 269 | + export CODER_LIBSH_YQ=yq4 |
| 270 | + elif command -v yq >/dev/null; then |
| 271 | + if [[ $(yq --version) == *" v4."* ]]; then |
| 272 | + export CODER_LIBSH_YQ=yq |
| 273 | + fi |
| 274 | + fi |
| 275 | + |
| 276 | + if [[ "$libsh_bad_dependencies" == 1 ]]; then |
| 277 | + error "Invalid dependencies, see above for more details." |
| 278 | + fi |
| 279 | + |
| 280 | + export CODER_LIBSH_NO_CHECK_DEPENDENCIES=true |
| 281 | +fi |
| 282 | + |
| 283 | +# Alias yq to the version we want by shadowing with a function. |
| 284 | +if [[ -n ${CODER_LIBSH_YQ:-} ]]; then |
| 285 | + yq() { |
| 286 | + command $CODER_LIBSH_YQ "$@" |
| 287 | + } |
| 288 | +fi |
0 commit comments