aboutsummaryrefslogtreecommitdiff
path: root/utils/utils.sh
blob: ed073a44df4f816c32c566c6ce2f95f83882e94a (plain)
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
#!/bin/bash
#
# Copyright (c) 2016-2021, Linaro Ltd.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Common utility functions.

source "$(dirname "${BASH_SOURCE[0]}")/internal/utils.sh"

readonly JCPU_COUNT=$(nproc)

readonly info_log="1"
readonly error_log="2"
readonly success_log="1"
readonly warning_log="1"
declare -A timer_start
declare -A timer_stop
no_colour=
bold=
red=
green=
yellow=
blue=

info() { echo -e "${blue}INFO: $* ${no_colour}" >&"${info_log}"; }
success() { echo -e "${green}SUCCESS: $* ${no_colour}" >&"${success_log}"; }
warning() { echo -e "${yellow}WARNING: $* ${no_colour}" >&"${warning_log}"; }
error() { echo -e "${red}ERROR: $* ${no_colour}" >&"${error_log}"; }
filelog() { echo "${2}" >> "$1" 2>&1; }

init_coloured_logging() {
  # Check how many colors the terminal can display.
  local ncolors=$(tput colors 2>/dev/null)

  # Check that stdout is connected to a terminal and that we have at least 8
  # colors.
  if [[ -t 1 && ${ncolors} && ${ncolors} -ge 8 ]]; then
    no_colour="$(tput sgr0)"
    bold="$(tput bold)"
    red="${bold}$(tput setaf 1)"
    green="${bold}$(tput setaf 2)"
    yellow="${bold}$(tput setaf 3)"
    blue="${bold}$(tput setaf 4)"
  fi

  readonly no_colour
  readonly bold
  readonly red
  readonly green
  readonly yellow
  readonly blue
}

init_shell() {
  enable_error_on_unset_expansion
  # Use extended globs for pattern matching. Many distributions including Ubuntu
  # enable extglob by default in interactive shells, so there should be no
  # undesirable interaction with any sourced Android script (e.g.
  # build/envsetup.sh).
  shopt -s extglob
}

# Log function.
# Arguments:
#   ${1} - I|S|W|E|F (log level).
#   ${2} - log message.
#   ${3} - log file name (only on log F).
log() {
  case $1 in
    I)
      info "$2"
      ;;
    S)
      success "$2"
      ;;
    W)
      warning "$2"
      ;;
    E)
      error "$2"
      ;;
    F)
      filelog "$3" "$2"
      ;;
    *)
      error "Unknown log level: $1"
      abort
      ;;
  esac
}

# Walk the caller stack and abort execution.
abort() {
  local frame=0

  log E "abort()"
  # Walk caller stack.
  while caller ${frame} >&2; do
    ((frame++));
  done
  # Before resorting to extreme measures, flush the caches so that at least log
  # messages get written out.
  sync
  # Send SIGTERM to all processes in the current process group, which means
  # the parent script and all the subshells and other children.
  kill 0
}

# Assert - if "condition" fails call abort().
# Arguments:
#   ${1} - condition.
assert() {
  if [[ ! $1 ]]; then
    log E "assert() FAILED: \"$1\""
    abort
  fi
}

# Check exit code of the previous command; if non-zero, abort.
# Arguments:
#   ${1} - error message.
#   ${2} - optional dump file to be dumped before exiting.
if_error() {
  # shellcheck disable=SC2181
  if [[ $? -ne 0 ]]; then
    log E "$1"
    if [[ $# -gt 2 ]]; then
      cat "$2"
    fi
    abort
  fi
}

# Execute the arguments; if the exit code is non-zero, abort.
safe() {
  "$@"
  if_error "Failed command: $*"
}

# Execute the arguments; if the exit code is non-zero, exit with this value.
# Unlike the functions above based on abort, the shell will return with this
# value (with abort it is killed and returns 143).
exit_on_failure() {
  "$@" || exit $?
}

start_timer() {
  timer_start[$1]="$(date +%s.%N)"
}

stop_timer() {
  # Check that the timer key exists in timer_start[].
  if [[ -v "timer_start[$1]" ]]; then
    timer_stop[$1]="$(date +%s.%N)"
  else
    log E "Timer \"$1\" has not been started!"
    abort
  fi
}

print_timer() {
  # Check that the timer key exists in timer_start[] and timer_stop[].
  if [[ -v "timer_start[$1]" && -v "timer_stop[$1]" ]]; then
    local timer=$(echo "${timer_stop[$1]} - ${timer_start[$1]}" | bc)
    # Convert to an integer.
    timer=${timer%.*}
    timer=${timer:-0}

    echo "$((timer / 3600))h $((timer % 3600 / 60))m $((timer % 60))s"
  else
    log E "Timer \"$1\" has not been started or stopped!"
    abort
  fi
}

# This function writes a timer in a format that is appropriate for
# both logs and Jenkins plots.
# Arguments:
#   ${1} - timer's name.
#   ${2} - optional: output file.
dump_timer() {
  local timer=$(print_timer "$1")
  log I "$1 took: ${timer}"
  # Do we need to write info out for plots?
  if [[ $# -gt 1 ]]; then
    safe echo "YVALUE=${timer}" > "$2"
  fi
}

# Returns 0 if the given variable exists and has a specific attribute.
# Arguments:
#   ${1} - name of the variable.
#   ${2} - attribute, as passed to declare (e.g. -A for associative arrays).
var_has_attribute() {
  [[ "$(declare -p "$1" 2>/dev/null)" == "declare $2"* ]]
}

# This function is used to set command line options.
# The existence of an `options` associative array with
# all options already added is assumed.
# Arguments:
#   ${1} - option name as key
#   ${2} - optional: option value. if not set, value is "true"
set_option() {
  local current_option=${1#"--"}
  if [[ -v "options[${current_option}]" ]]; then
    options[${current_option}]=${2-true}
  else
    # There might be something wrong with the option parser since this option
    # does not have a default.
    abort
  fi
}

# This function is a command-line parser, parsing arguments according to option
# specifications, and storing the options and positional arguments in arrays.
#
# Usage:
# arguments_parser format options [pos_args [trailing_args]] -- "$@"
#
# Arguments:
# * `format`        - the name of an associative array specifying the possible
#                     options (see below).
# * `options`       - the name of an associative array where the option values
#                     will be stored (see below).
# * `pos_args`      - the name of an array where positional arguments will be
#                     stored. If not specified, positional arguments are not
#                     allowed.
# * `trailing_args` - the name of an array where any argument after '--' will be
#                     stored, if "$@" contains '--'. If not specified,
#                     arguments after '--' (if any) are stored in `pos_args`.
# * `"$@"`          - the arguments to be parsed (must be preceded by '--'!).
#
# Options specification:
# The `format` associative array associates each option (the key) with a format
# string (the value).
# * Key: the name of the option.
#   If it is one character, it is understood as a short option (e.g. '-o' for 'o').
#   Otherwise, it is understood as a long option (e.g. '--foo' for 'foo').
# * Value: the format string associated to the option.
#   The format is '<type>:<value>', where <type> is a one-letter descriptor
#   and <value> is interpreted based on the type (see list below).
#   As a shorthand, 'false' and 'true' are equivalent to 'b:false' and 'b:true',
#   and any other string is equivalent to 's:<string>'.
#
# Option types:
# * 'b' - a boolean option, taking no argument.
#         <value> is the default value.
# * 's' - a string option, taking one mandatory argument.
#         <value> is the default value.
#         If the option is present in the arguments, the value is set to the
#         option's argument.
# * 'S' - same as 's', but the argument is optional. If no argument is passed,
#         the value is set to "".
# * 'c' - collect strings option. Instead of saving only the last occurence of
#         the argument, collect all of them into an array variable named after
#         <value>.
# * 'r' - a reference to another option. Makes the option an alias to another
#         option.
#         <value> is '&<opt2>', where <opt2> is the name of another
#         non-reference option.
#         If the option is present in the arguments, the behaviour is exactly
#         the same as if <opt2> had been passed instead.
# * 'p' - a function callback. Calls a given function when the option is passed.
#         <value> is '<func>()', where <func> is the name of a function.
#         If the option is present in the arguments, <func> is called with the
#         following arguments:
#         $1 = `format`
#         $2 = `options`
#         $3 = option name
# * 'f' - same as 'p', but the option takes one mandatory argument instead of
#         none, and <func> is called with:
#         $1 = `format`
#         $2 = `options`
#         $3 = option name
#         $4 = argument
# * 'F' - same as 'f', but the argument is optional.
#
# The value of normal options (all but function callbacks and references) is
# stored in `options`.
#
# Example:
#
# myfunc() { echo "myfunc got \"${4-nothing}\""; }
#
# declare -A format=(
#   [o]='b:false'
#   [a]='false'
#   [foo]='s:'
#   [coll]='c:collection'
#   [f]='r:&foo'
#   [fun]='F:myfunc()'
# )
# declare -A options=()
# declare -a pos_args=()
# declare -a collection=()
#
# # Called with: -oa arg1 --foo=bar --fun
# arguments_parser format options pos_args -- "$@"
# # Prints:
# # myfunc got "nothing"
# # Now:
# # options == (
# #   [o]='true'
# #   [a]='true'
# #   [foo]='bar'
# # )
# # pos_args=('arg1')
#
# # Called with: -f bar2 --fun 5 -- --noopt
# arguments_parser format options pos_args -- "$@"
# # Prints:
# # myfunc got "5"
# # Now:
# # options == (
# #   [o]='false'
# #   [a]='false'
# #   [foo]='bar2'
# # )
# # pos_args=('noopt')
arguments_parser() {
  _arguments_parser "$@"
}

# This function outputs the `options` array.
# The existence of the `options` associative array with
# all options already added is assumed.
dump_options() {
  log I "Running $0 with the following configuration:"
  local IFS=$'\n'
  for key in $(echo "${!options[*]}" | sort); do
    log I "options[${key}] = ${options[${key}]}"
  done
}

# Sets default options depending on host or target
# The existence of the `options` associative array with
# all options already added is assumed.
#
# Arguments:
#   ${1}: options
#   ${2}: host/target
#   ${3}: job count
set_default_options() {
  declare -A options=${1#*=}
  if [[ "$2" != "host" && "$2" != "target" ]]; then
    log E "set_default_options() requires host or target as second argument"
    log E "got $2 instead"
    exit 1
  fi
  # Options common to both host and target
  options["32bit"]="true"
  options["64bit"]="true"
  options["gtest"]="true"
  options["interpreter"]="false"
  options["jit"]="false"
  options["keep-going"]="true"
  options["optimizing"]="true"
  options["gcstress"]="false"
  if [[ "$2" == "target" ]]; then
  # Target specific options
    options["concurrent-gc"]="false"
    options["debug"]="false"
    options["jdwp"]="false"
    options["libcore"]="false"
    options["jobs"]="${3}"
    options["rerun-failed-tests"]="true"
    options["restart-adb-server"]="false"
    options["fvp"]="false"
  fi
  echo " $(declare -p options)"
}

# Whether it's defined by Jenkins or simply undefined, specify the concept of
# a workspace that can be used elsewhere. Defaults to $PWD.
get_workspace() {
  echo "${WORKSPACE:-$PWD}"
}

# Get a path to the art directory.
get_art_dir() {
  echo "${PWD}/art"
}

# Get a path to the art/tools directory.
get_art_tools_dir() {
  echo "$(get_art_dir)/tools"
}

# Get a path to the prebuilt adb directory.
get_prebuilt_adb_dir() {
  echo "${PWD}/prebuilts/runtime"
}

# Get a path to the prebuilt adb.
get_prebuilt_adb() {
  echo "$(get_prebuilt_adb_dir)/adb"
}

# Add the prebuilt adb directory to PATH.
add_prebuilt_adb_dir_to_PATH() {
  export PATH="$(get_prebuilt_adb_dir):${PATH}"
}

# Set the global ADB to the prebuilt adb
set_global_ADB_to_prebuilt_adb() {
  export ADB="$(get_prebuilt_adb)"
}

# Check if a particular tool is available in your path.
exists() {
  if which "$1" >/dev/null 2>&1; then
    return 0
  else
    return 1
  fi
}

declare -a logging_fd_stack

# Start logging all standard output and error onto a file.
# Arguments:
#   ${1} - output file name.
start_logging() {
  local saved_stdout saved_stderr

  # Move the stdout and stderr file descriptors to new file descriptors
  # allocated by Bash.
  # shellcheck disable=SC2093
  exec {saved_stdout}>&1- {saved_stderr}>&2-

  # The standard output is redirected to a subprocess, tee, which sends the
  # output to both a file and the original stdout.
  exec > >(safe tee -i -a "$1" >&${saved_stdout})
  # Same for the standard error.
  exec 2>&1

  # Save the file descriptors on the fd stack.
  logging_fd_stack+=(${saved_stdout} ${saved_stderr})
}

stop_logging() {
  local cur_stack_size=${#logging_fd_stack[@]}

  if [[ ${cur_stack_size} -eq 0 ]]; then
    # Not currently logging, nothing to do.
    return 0
  elif ((cur_stack_size % 2 != 0)); then
    # This should never happen (we always push two fd's to the stack).
    abort
  fi

  local saved_stdout=${logging_fd_stack[cur_stack_size - 2]}
  local saved_stderr=${logging_fd_stack[cur_stack_size - 1]}

  # Move the saved file descriptors back to stdout / stderr and pop them from
  # the stack.
  exec >&"${saved_stdout}"- 2>&"${saved_stderr}"-

  unset -v "logging_fd_stack[cur_stack_size - 2]"
  unset -v "logging_fd_stack[cur_stack_size - 1]"
}

enable_verbose() {
  set -o xtrace
}

disable_verbose() {
  set +o xtrace
}

enable_error_on_unset_expansion() {
  set -o nounset
}

disable_error_on_unset_expansion() {
  set +o nounset
}

# Initialisation calls.
init_shell
init_coloured_logging