utilities.sh
Go to the documentation of this file.
1 # ============================================================================
2 # Copyright (c) 2011-2012 University of Pennsylvania
3 # Copyright (c) 2013-2016 Andreas Schuh
4 # All rights reserved.
5 #
6 # See COPYING file for license information or visit
7 # https://cmake-basis.github.io/download.html#license
8 # ============================================================================
9 
10 ##############################################################################
11 # @file utilities.sh
12 # @brief Main module of project-independent BASIS utilities.
13 #
14 # This module defines the default BASIS utility functions. These default
15 # implementations are not project-specific, i.e., do not make use of particular
16 # project attributes such as the name or version of the project. The utility
17 # functions defined by this module are intended for use in Bash scripts that
18 # are not build as part of a particular BASIS project. Otherwise, the
19 # project-specific implementations should be used instead, i.e., those defined
20 # by the basis.sh module of the project which is automatically added to the
21 # project during the configuration of the build tree. This basis.sh module and
22 # the submodules used by it are generated from template modules which are
23 # customized for the particular project that is being build.
24 #
25 # Besides the utility functions which are common to all implementations for
26 # the different programming languages, does this module further provide
27 # fundamental functions for the development in Bash.
28 #
29 # @note In Bash, there is no concept of namespaces. Hence, the utility functions
30 # are all defined by the utilities.sh module which is part of the BASIS
31 # installation. By simply setting the constants to the project specific
32 # values, these utility functions are customized for this particular
33 # package. This, however, also means that the BASIS utilities of two
34 # different packages cannot be used within a Bash script at the same
35 # time in general. The order in which the basis.sh modules are sourced
36 # matters. Therefore, in Bash, care must be taken which modules of a
37 # BASIS-based package are being sourced and whether these in turn
38 # source either the utilities.sh module of BASIS or the basis.sh module
39 # which has been configured/customized for this particular package.
40 # If all modules make only use of the utilities.sh module, there are
41 # no conflicts. Thus, Bash should in general only be used for executable
42 # scripts within a project, but not to provide library functions to
43 # other developers. Therefore, consider the use of C++, Python, or Perl,
44 # instead.
45 #
46 # @ingroup BasisBashUtilities
47 ##############################################################################
48 
49 [ "${_BASIS_UTILITIES_INCLUDED}" == 'true' ] || {
50 _BASIS_UTILITIES_INCLUDED='true'
51 
52 
53 # ============================================================================
54 # constants
55 # ============================================================================
56 
57 BASIS_UTILITIES_DIR="`cd -P -- "\`dirname -- "${BASH_SOURCE}"\`" && pwd`"
58 readonly BASIS_UTILITIES_DIR
59 
60 # ============================================================================
61 # source other modules
62 # ============================================================================
63 
64 . "${BASIS_UTILITIES_DIR}/core.sh" || exit 1 # core utilities, i.e., import()
65 
66 import basis.config # constants
67 import basis.os.path # file path manipulation
68 import basis.shflags # command-line parsing library
69 
70 # ============================================================================
71 # configuration
72 # ============================================================================
73 
74 # the following contanst are set by the basis.sh module, if not, because
75 # this module is used in a script which does not belong to a BASIS-based
76 # project, they are initialized to project-independent defaults here
77 
78 ## @brief Project name.
79 [ -n "${PROJECT}" ] || readonly PROJECT=''
80 ## @brief Project version.
81 [ -n "${VERSION}" ] || readonly VERSION=''
82 ## @brief Major project version.
83 [ -n "${VERSION_MAJOR}" ] || VERSION_MAJOR=''
84 ## @brief Minor project version.
85 [ -n "${VERSION_MINOR}" ] || VERSION_MINOR=''
86 ## @brief Project patch number.
87 [ -n "${VERSION_PATCH}" ] || VERSION_PATCH=''
88 ## @brief Project release.
89 [ -n "${RELEASE}" ] || readonly RELEASE=''
90 ## @brief Default copyright of executables.
91 [ -n "${COPYRIGHT}" ] || readonly COPYRIGHT='2011-12 University of Pennsylvania, 2013-14 Carnegie Mellon University, 2013-16 Andreas Schuh'
92 ## @brief Default license of executables.
93 [ -n "${LICENSE}" ] || readonly LICENSE='See https://cmake-basis.github.io/download.html#license or COPYING file.'
94 ## @brief Default contact to use for help output of executables.
95 [ -n "${CONTACT}" ] || readonly CONTACT='andreas.schuh.84@gmail.com'
96 
97 
98 # common prefix of target UIDs belonging to this project
99 [ -n "${_BASIS_TARGET_UID_PREFIX}" ] || _BASIS_TARGET_UID_PREFIX=''
100 # used to make relative paths in executable target information map absolute
101 [ -n "${_BASIS_EXECUTABLE_TARGETS_BASE}" ] || _BASIS_EXECUTABLE_TARGETS_BASE="${BASIS_UTILITIES_DIR}"
102 
103 # declare constants as readonly
104 readonly PROJECT
105 readonly VERSION
106 readonly VERSION_MAJOR
107 readonly VERSION_MINOR
108 readonly VERSION_PATCH
109 readonly RELEASE
110 readonly COPYRIGHT
111 readonly LICENSE
112 readonly CONTACT
113 
114 readonly _BASIS_TARGET_UID_PREFIX
115 readonly _BASIS_EXECUTABLE_TARGETS_BASE
116 
117 
118 ## @addtogroup BasisBashUtilities
119 # @{
120 
121 
122 # ============================================================================
123 # executable information
124 # ============================================================================
125 
126 # ----------------------------------------------------------------------------
127 ## @brief Print contact information.
128 #
129 # @param [in] contact Name of contact. Defaults to <tt>${CONTACT}</tt>.
130 #
131 # @returns Nothing.
133 {
134  [ -n "$1" ] && echo -e "Contact:\n $1" || echo -e "Contact:\n ${CONTACT}"
135 }
136 
137 # ----------------------------------------------------------------------------
138 ## @brief Print version information including copyright and license notices.
139 #
140 # Example:
141 # @code
142 # print_version 'foo' '1.0'
143 # print_version 'foo' -v '1.0'
144 # print_version -v 1.0 -p BarStool 'foo'
145 # print_version 'foo' -v '1.2' -c '2012 University of Pennsylvania' \
146 # -l 'Apache License, Version 2.0'
147 # @endcode
148 #
149 # @param [in] options Function options as documented below.
150 # @param [in] name Name of executable. Should not be set programmatically
151 # to the first argument of the main script, but a string
152 # literal instead.
153 # @param [in] version Version of executable. Defaults to <tt>${RELEASE}</tt>
154 # if defined, otherwise this argument is required.
155 # @par Options:
156 # <table border="0">
157 # <tr>
158 # @tp @b -v, @b --version &lt;version&gt; @endtp
159 # <td>Version of executable. Can be either given as option or second
160 # positional argument after the @p name.</td>
161 # </tr>
162 # <tr>
163 # @tp @b -p, @b --project &lt;name&gt; @endtp
164 # <td>Name of project this executable belongs to.
165 # Defaults to <tt>${PROJECT}</tt> if defined.
166 # If 'none', no project information is printed.</td>
167 # </tr>
168 # <tr>
169 # @tp @b -c @b --copyright &lt;copyright&gt; @endtp
170 # <td>The copyright notice. Defaults to <tt>${COPYRIGHT}</tt>.
171 # If 'none', no copyright notice is printed.</td>
172 # </tr>
173 # <tr>
174 # @tp @b -l @b --license &lt;license&gt; @endtp
175 # <td>Information regarding licensing. Defaults to <tt>${LICENSE}</tt>.
176 # If 'none', no license information is printed.</td>
177 # </tr>
178 # </table>
179 #
180 # @returns Nothing.
182 {
183  local _basis_pv_name=''
184  local _basis_pv_version="${RELEASE}"
185  local _basis_pv_project="${PROJECT}"
186  local _basis_pv_copyright="${COPYRIGHT:-}"
187  local _basis_pv_license="${LICENSE:-}"
188  while [ $# -gt 0 ]; do
189  case "$1" in
190  -v|--version)
191  if [ -n "${_basis_pv_version}" ]; then
192  echo "print_version(): Version specified twice!" 1>&2
193  return 1
194  fi
195  if [ $# -gt 1 ]; then
196  _basis_pv_version="$2"
197  else
198  echo "print_version(): Option -v, --version is missing an argument!" 1>&2
199  return 1
200  fi
201  shift
202  ;;
203  -p|--project)
204  if [ $# -gt 1 ]; then
205  _basis_pv_project="$2"
206  else
207  echo "print_version(): Option -p, --project is missing an argument!" 1>&2
208  return 1
209  fi
210  shift
211  ;;
212  -c|--copyright)
213  if [ $# -gt 1 ]; then
214  _basis_pv_copyright="$2"
215  else
216  echo "print_version(): Option -c, --copyright is missing an argument!" 1>&2
217  return 1
218  fi
219  shift
220  ;;
221  -l|--license)
222  if [ $# -gt 1 ]; then
223  _basis_pv_license="$2"
224  else
225  echo "print_version(): Option -l, --license is missing an argument!" 1>&2
226  return 1
227  fi
228  shift
229  ;;
230  *)
231  if [ -z "${_basis_pv_name}" ]; then
232  _basis_pv_name=$1
233  elif [ -z "${_basis_pv_version}" ]; then
234  _basis_pv_version=$1
235  else
236  echo "print_version(): Too many arguments or invalid option: $1" 1>&2
237  return 1
238  fi
239  ;;
240  esac
241  shift
242  done
243  [ -n "${_basis_pv_name}" ] || { echo "print_version(): Missing name argument" 1>&2; return 1; }
244  [ -n "${_basis_pv_version}" ] || { echo "print_version(): Missing version argument" 1>&2; return 1; }
245  echo -n "${_basis_pv_name}"
246  [ -n "${_basis_pv_project}" ] && [ "${_basis_pv_project}" != 'none' ] && {
247  echo -n " (${_basis_pv_project})"
248  }
249  echo " ${_basis_pv_version}"
250  [ -n "${_basis_pv_copyright}" ] && [ "${_basis_pv_copyright}" != 'none' ] && {
251  echo -e "Copyright (c) ${_basis_pv_copyright}. All rights reserved."
252  }
253  [ -n "${_basis_pv_license}" ] && [ "${_basis_pv_license}" != 'none' ] && {
254  echo -e "${_basis_pv_license}"
255  }
256 }
257 
258 # ----------------------------------------------------------------------------
259 ## @brief Get UID of build target.
260 #
261 # The UID of a build target is its name prepended by a namespace identifier
262 # which should be unique for each project.
263 #
264 # This function further initializes the dictionary storing the information
265 # about the executable targets upon the first invocation. Reason to do it
266 # here is that every access to the dictionaries first calls this function
267 # to get the UID of a build target. Moreover, also this function needs to
268 # have the already initialized dictionaries to ensure that an already valid
269 # target identifier is not modified. As Bash does not provide hash tables,
270 # dictionary data structures, the imitation of these is necessary which,
271 # however, results in many eval() calls with noticeable impact on running time.
272 # Therefore, to decrease the time required to source the BASIS utilities,
273 # the required dictionary structure is initialized only upon first use.
274 #
275 # @param [out] uid UID of named build target.
276 # @param [in] name Name of build target.
277 #
278 # @returns Nothing.
279 #
280 # @retval 0 On success.
281 # @retval 1 On failure.
282 targetuid()
283 {
284  [ -n "$1" ] && [ $# -eq 2 ] || return 1
285  local _basis_targetuid_target="$2"
286  # initialize module if not done yet - this is only done here because
287  # whenever information is looked up about an executable target, this
288  # function is invoked first
289  if [ "${_BASIS_EXECUTABLETARGETINFO_INITIALIZED}" != 'true' ]; then
290  _basis_executabletargetinfo_initialize || return 1
291  fi
292  # empty string as input remains unchanged
293  [ -z "${_basis_targetuid_target}" ] && local "$1" && upvar $1 '' && return 0
294  # in case of a leading namespace separator, do not modify target name
295  [ "${_basis_targetuid_target:0:1}" == '.' ] && local "$1" && upvar $1 "${_basis_targetuid_target}" && return 0
296  # project namespace
297  local _basis_targetuid_prefix="${_BASIS_TARGET_UID_PREFIX}.DUMMY"
298  # try prepending namespace or parts of it until target is known
299  local _basis_targetuid_path=''
300  while [ "${_basis_targetuid_prefix/\.*/}" != "${_basis_targetuid_prefix}" ]; do
301  _basis_targetuid_prefix="${_basis_targetuid_prefix%\.*}"
302  _basis_executabletargetinfo_get _basis_targetuid_path "${_basis_targetuid_prefix}.${_basis_targetuid_target}" LOCATION
303  if [ -n "${_basis_targetuid_path}" ]; then
304  local "$1" && upvar $1 "${_basis_targetuid_prefix}.${_basis_targetuid_target}"
305  return 0
306  fi
307  done
308  # otherwise, return target name unchanged
309  local "$1" && upvar $1 "${_basis_targetuid_target}"
310 }
311 
312 # ----------------------------------------------------------------------------
313 ## @brief Determine whether a given build target is known.
314 #
315 # @param [in] target Name of build target.
316 #
317 # @returns Whether the named target is a known executable target.
318 istarget()
319 {
320  local _basis_istarget_uid && targetuid _basis_istarget_uid "$1"
321  [ -n "${_basis_istarget_uid}" ] || return 1
322  local _basis_istarget_path && _basis_executabletargetinfo_get _basis_istarget_path "${_basis_istarget_uid}" LOCATION
323  [ -n "${_basis_istarget_path}" ]
324 }
325 
326 # ----------------------------------------------------------------------------
327 ## @brief Get absolute path of executable file.
328 #
329 # This function determines the absolute file path of an executable. If no
330 # arguments are given, the absolute path of this executable is returned.
331 # If the given argument is a known build target name, the absolute path
332 # of the executable built by this target is returned. Otherwise, the named
333 # command is searched in the system PATH and it's absolute path returned
334 # if found. If the given argument is neither the name of a known build target
335 # nor an executable found on the PATH, an empty string is returned and
336 # the return value is 1.
337 #
338 # @param [out] path Absolute path of executable file.
339 # @param [in] target Name/UID of build target. If no argument is given,
340 # the file path of the calling executable is returned.
341 #
342 # @returns Nothing.
343 #
344 # @retval 0 On success.
345 # @retval 1 On failure.
346 exepath()
347 {
348  [ -n "$1" ] && [ $# -eq 1 -o $# -eq 2 ] || return 1
349  local _basis_exepath_path=''
350  # if no target name given, get path of this executable
351  if [ -z "$2" ]; then
352  _basis_exepath_path="`realpath "$0"`"
353  # otherwise, get path of executable built by named target
354  else
355  # get UID of target
356  local _basis_exepath_uid && targetuid _basis_exepath_uid "$2"
357  [ "${_basis_exepath_uid:0:1}" == '.' ] && _basis_exepath_uid=${_basis_exepath_uid:1}
358  if [ -n "${_basis_exepath_uid}" ]; then
359  # get path relative to this module
360  _basis_executabletargetinfo_get _basis_exepath_path "${_basis_exepath_uid}" LOCATION
361  if [ -n "${_basis_exepath_path}" ]; then
362  # replace IntDir when binary built from C++ with Xcode
363  if [[ ${_basis_exepath_path/\$<CONFIG>} != ${_basis_exepath_path} ]]; then
364  local _basis_exepath_tmp=''
365  local _basis_exepath_cfg=''
366  for _basis_exepath_cfg in 'Release' 'Debug' 'RelWithDebInfo' 'MinSizeRel'; do
367  _basis_exepath_tmp=${_basis_exepath_path/\$<CONFIG>/$_basis_exepath_cfg}
368  if [ -f "${_basis_exepath_tmp}" ]; then
369  _basis_exepath_path="${_basis_exepath_tmp}"
370  break
371  fi
372  done
373  _basis_exepath_path=${_basis_exepath_path/\$<CONFIG>}
374  fi
375  # make path absolute
376  _basis_exepath_path=`abspath "${_BASIS_EXECUTABLE_TARGETS_BASE}" "${_basis_exepath_path}"`
377  [ $? -eq 0 ] || return 1
378  else
379  _basis_exepath_path=`/usr/bin/which "$2" 2> /dev/null`
380  fi
381  else
382  _basis_exepath_path=`/usr/bin/which "$2" 2> /dev/null`
383  fi
384  fi
385  # return path
386  local "$1" && upvar $1 "${_basis_exepath_path}"
387  [ $? -eq 0 ] && [ -n "${_basis_exepath_path}" ]
388 }
389 
390 # ----------------------------------------------------------------------------
391 ## @brief Get name of executable file.
392 #
393 # @param [out] file Name of executable file or an empty string if not found.
394 # If @p name is not given, the name of this executable is returned.
395 # @param [in] name Name of command or an empty string.
396 #
397 # @returns Whether or not the command was found.
398 #
399 # @retval 0 On success.
400 # @retval 1 On failure.
401 exename()
402 {
403  [ -n "$1" ] && [ $# -eq 1 -o $# -eq 2 ] || return 1
404  local _basis_exename_path && exepath _basis_exename_path "$2"
405  [ $? -eq 0 ] || return 1
406  local _basis_exename_name="`basename "${_basis_exename_path}"`"
407  local "$1" && upvar $1 "${_basis_exename_name}"
408 }
409 
410 # ----------------------------------------------------------------------------
411 ## @brief Get directory of executable file.
412 #
413 # @param [out] dir Directory of executable file or an empty string if not found.
414 # If @p name is not given, the directory of this executable is returned.
415 # @param [in] name Name of command or an empty string.
416 #
417 # @returns Whether or not the command was found.
418 #
419 # @retval 0 On success.
420 # @retval 1 On failure.
421 exedir()
422 {
423  [ -n "$1" ] && [ $# -eq 1 -o $# -eq 2 ] || return 1
424  local _basis_exedir_path && exepath _basis_exedir_path "$2"
425  [ $? -eq 0 ] || return 1
426  local _basis_exedir_dir="`dirname "${_basis_exedir_path}"`"
427  local "$1" && upvar $1 "${_basis_exedir_dir}"
428 }
429 
430 # ============================================================================
431 # command execution
432 # ============================================================================
433 
434 # ----------------------------------------------------------------------------
435 ## @brief Build quoted string from array.
436 #
437 # Example:
438 # @code
439 # tostring str 'this' "isn't" a 'simple example of "a quoted"' 'string'
440 # echo "${str}"
441 # @endcode
442 #
443 # @param [out] var Name of result variable for quoted string.
444 # @param [in] elements All remaining arguments are considered to be the
445 # elements of the array to convert.
446 #
447 # @returns Nothing.
448 tostring()
449 {
450  local _basis_tostring_str=''
451  local _basis_tostring_element=''
452  # GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
453  # turns the array into a single string value if local is used
454  if [ ${BASH_VERSION_MAJOR} -gt 3 ] || [ ${BASH_VERSION_MAJOR} -eq 3 -a ${BASH_VERSION_MINOR} -gt 0 ]; then
455  local _basis_tostring_args=("$@")
456  else
457  _basis_tostring_args=("$@")
458  fi
459  local _basis_tostring_i=1 # first argument is name of return variable
460  while [ $_basis_tostring_i -lt ${#_basis_tostring_args[@]} ]; do
461  _basis_tostring_element="${_basis_tostring_args[$_basis_tostring_i]}"
462  # escape double quotes
463  _basis_tostring_element=`printf -- "${_basis_tostring_element}" | sed 's/\\"/\\\\"/g'`
464  # surround element by double quotes if necessary
465  match "${_basis_tostring_element}" "[' ]|^$" && _basis_tostring_element="\"${_basis_tostring_element}\""
466  # append element
467  [ -n "${_basis_tostring_str}" ] && _basis_tostring_str="${_basis_tostring_str} "
468  _basis_tostring_str="${_basis_tostring_str}${_basis_tostring_element}"
469  # next argument
470  let _basis_tostring_i++
471  done
472  local "$1" && upvar $1 "${_basis_tostring_str}"
473 }
474 
475 # ----------------------------------------------------------------------------
476 ## @brief Split (quoted) string.
477 #
478 # This function can be used to split a (quoted) string into its elements.
479 #
480 # Example:
481 # @code
482 # str="'this' 'isn\'t' a \"simple example of \\\"a quoted\\\"\" 'string'"
483 # qsplit array "${str}"
484 # echo ${#array[@]} # 5
485 # echo "${array[3]}" # simple example of "a quoted"
486 # @endcode
487 #
488 # @param [out] var Result variable for array.
489 # @param [in] str Quoted string.
490 #
491 # @returns Nothing.
492 qsplit()
493 {
494  [ $# -eq 2 ] || return 1
495  # GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
496  # turns the array into a single string value if local is used
497  if [ ${BASH_VERSION_MAJOR} -gt 3 ] || [ ${BASH_VERSION_MAJOR} -eq 3 -a ${BASH_VERSION_MINOR} -gt 0 ]; then
498  local _basis_qsplit_array=()
499  else
500  _basis_qsplit_array=()
501  fi
502  local _basis_qsplit_str=$2
503  # match arguments from left to right
504  while match "${_basis_qsplit_str}" "[ ]*('([^']|\\\')*[^\\]'|\"([^\"]|\\\")*[^\\]\"|[^ ]+)(.*)"; do
505  # matched element including quotes
506  _basis_qsplit_element="${BASH_REMATCH[1]}"
507  # remove quotes
508  if [[ ${_basis_qsplit_element:0:1} == '"' && ${_basis_qsplit_element: -1} == '"' ]]; then
509  _basis_qsplit_element="${_basis_qsplit_element:1}"
510  _basis_qsplit_element="${_basis_qsplit_element%\"}"
511  elif [[ ${_basis_qsplit_element:0:1} == "'" && ${_basis_qsplit_element: -1} == "'" ]]; then
512  _basis_qsplit_element="${_basis_qsplit_element:1}"
513  _basis_qsplit_element="${_basis_qsplit_element%\'}"
514  fi
515  # replace quoted quotes within argument by quotes
516  _basis_qsplit_element=`printf -- "${_basis_qsplit_element}" | sed "s/[\\]'/'/g;s/[\\]\"/\"/g"`
517  # add to resulting array
518  _basis_qsplit_array[${#_basis_qsplit_array[@]}]="${_basis_qsplit_element}"
519  # continue with residual command-line
520  _basis_qsplit_str="${BASH_REMATCH[4]}"
521  done
522  # return
523  local "$1" && upvar $1 "${_basis_qsplit_array[@]}"
524 }
525 
526 # ----------------------------------------------------------------------------
527 ## @brief Execute command as subprocess.
528 #
529 # This function is used to execute a subprocess within a Bash script.
530 #
531 # Example:
532 # @code
533 # # the next command will exit the current shell if it fails
534 # execute ls /not/existing
535 # # to prevent this, provide the --allow_fail option
536 # execute --allow_fail ls /not/existing
537 # # to make it explicit where the command-line to execute starts, use --
538 # execute --allow_fail -- ls /not/existing
539 # @endcode
540 #
541 # Note that the output of the command is not redirected by this function.
542 # In order to execute the command quietly, use this function as follows:
543 # @code
544 # execute ls / &> /dev/null
545 # @endcode
546 # Or to store the command output in a variable including error messages
547 # use it as follows:
548 # @code
549 # output=`execute ls / 2>&1`
550 # @endcode
551 # Note that in this case, the option --allow_fail has no effect as the
552 # calling shell will never be terminated. Only the subshell in which the
553 # command is executed will be terminated. Checking the exit code $? is
554 # in this case required.
555 #
556 # @param [in] options Function options as documented below.
557 # @param [in] cmd Executable of command to run or corresponding build
558 # target name. This is assumed to be the first
559 # non-option argument or the argument that follows the
560 # special '--' argument.
561 # @param [in] args All remaining arguments are passed as arguments to
562 # the given command.
563 # @par Options:
564 # <table border="0">
565 # <tr>
566 # @tp <b>-f, --allow_fail</b> @endtp
567 # <td>Allows the command to fail. By default, if the command
568 # returns a non-zero exit code, the exit() function is
569 # called to terminate the current shell.</td>
570 # </tr>
571 # <tr>
572 # @tp <b>-v, --verbose</b> [int] @endtp
573 # <td>Print command-line to stdout before execution. Optionally, as it is
574 # sometimes more convenient to pass in the value of another variable
575 # which controls the verbosity of the parent process itself, a verbosity
576 # value can be specified following the option flag. If this verbosity
577 # less or equal to zero, the command-line of the subprocess is not
578 # printed to stdout, otherwise it is.</td>
579 # </tr>
580 # <tr>
581 # @tp <b>-s, --simulate</b> @endtp
582 # <td>If this option is given, the command is not actually
583 # executed, but the command-line printed to stdout only.</td>
584 # </tr>
585 # </table>
586 #
587 # @returns Exit code of subprocess.
588 execute()
589 {
590  # parse arguments
591  local _basis_execute_allow_fail='false'
592  local _basis_execute_simulate='false'
593  local _basis_execute_verbose=0
594  local _basis_execute_args=''
595  while [ $# -gt 0 ]; do
596  case "$1" in
597  -f|--allow_fail) _basis_execute_allow_fail='true'; ;;
598  -s|--simulate) _basis_execute_simulate='true'; ;;
599  -v|--verbose)
600  match "$2" '^-?[0-9]+$'
601  if [ $? -eq 0 ]; then
602  _basis_execute_verbose=$2
603  shift
604  else
605  let _basis_execute_verbose++
606  fi
607  ;;
608  --) shift; break; ;;
609  *) break; ;;
610  esac
611  shift
612  done
613  # command to execute and its arguments
614  local _basis_execute_command="$1"; shift
615  [ -n "${_basis_execute_command}" ] || { echo "execute_process(): No command specified to execute" 1>&2; return 1; }
616  # get absolute path of executable
617  local _basis_execute_exec && exepath _basis_execute_exec "${_basis_execute_command}"
618  [ -n "${_basis_execute_exec}" ] || { echo "${_basis_execute_command}: Command not found" 1>&2; exit 1; }
619  # some verbose output
620  if [ ${_basis_execute_verbose} -gt 0 ] || [ "${_basis_execute_simulate}" == 'true' ]; then
621  tostring _basis_execute_args "$@"
622  echo "\$ ${_basis_execute_exec} ${_basis_execute_args}"
623  fi
624  # execute command
625  [ "${_basis_execute_simulate}" == 'true' ] || "${_basis_execute_exec}" "$@"
626  local _basis_execute_status=$?
627  # if command failed, exit
628  [ ${_basis_execute_status} -eq 0 -o "${_basis_execute_allow_fail}" == 'true' ] || {
629  [ -n "${_basis_execute_args}" ] || tostring _basis_execute_args "$@"
630  echo
631  echo "Command ${_basis_execute_exec} ${_basis_execute_args} failed" 1>&2
632  exit 1
633  }
634  # return exit code
635  return ${_basis_execute_status}
636 }
637 
638 
639 ## @}
640 # end of Doxygen group
641 
642 # ============================================================================
643 # private
644 # ============================================================================
645 
646 # ----------------------------------------------------------------------------
647 # @brief Sanitize string for use in variable name.
648 #
649 # @param [out] out Sanitized string.
650 # @param [in] str String to be sanitized.
651 #
652 # @returns Nothing.
653 #
654 # @retval 0 On success.
655 # @retval 1 On failure.
656 _basis_executabletargetinfo_sanitize()
657 {
658  [ $# -eq 2 ] || return 1
659  [ -n "$2" ] || {
660  upvar $1 ''
661  return 0
662  }
663  local sane="`printf -- "$2" | tr '[:space:]' '_' | tr -c '[:alnum:]' '_'`"
664  [ -n "${sane}" ] || {
665  echo "_basis_executabletargetinfo_sanitize(): Failed to sanitize string '$2'" 1>&2
666  exit 1
667  }
668  local "$1" && upvar $1 "${sane}"
669 }
670 
671 # ----------------------------------------------------------------------------
672 # @brief Add (key, value) pair to executable target info "hash".
673 #
674 # @param [in] key Hash key.
675 # @param [in] name Name of the hash table.
676 # @param [in] value Value associated with the given hash key.
677 #
678 # @returns Sets a readonly variable that represents the (key, value) entry.
679 #
680 # @sa _basis_executabletargetinfo_get()
681 _basis_executabletargetinfo_add()
682 {
683  [ $# -eq 3 ] || return 1
684 
685  local key && _basis_executabletargetinfo_sanitize key "$1"
686  local name && _basis_executabletargetinfo_sanitize name "$2"
687  [ -n "${key}" ] && [ -n "${name}" ] || {
688  if [ -z "${key}" ] && [ -z "${name}" ]; then
689  echo "_basis_executabletargetinfo_add(): Neither lookup table nor key specified" 1>&2
690  elif [ -z "${key}" ]; then
691  echo "_basis_executabletargetinfo_add(): No key specified for addition to hash table '${name}'" 1>&2
692  else
693  echo "_basis_executabletargetinfo_add(): No lookup table given for addition of key '${key}'" 1>&2
694  fi
695  exit 1
696  }
697  eval "readonly __BASIS_EXECUTABLETARGETINFO_${name}_${key}='$3'"
698  if [ $? -ne 0 ]; then
699  echo "Failed to add ${name} of key ${key} to executable target info map!" 1>&2
700  echo "This may be caused by two CMake build target names being converted to the same key." 1>&2
701  exit 1
702  fi
703 }
704 
705 # ----------------------------------------------------------------------------
706 # @brief Get value from executable target info "hash".
707 #
708 # @param [out] value Value corresponding to given @p key
709 # or an empty string if key is unknown.
710 # @param [in] key Hash key.
711 # @param [in] name Name of the hash table.
712 #
713 # @returns Nothing.
714 #
715 # @retval 0 On success.
716 # @retval 1 On failure.
717 #
718 # @sa _basis_executabletargetinfo_add()
719 _basis_executabletargetinfo_get()
720 {
721  [ $# -eq 3 ] || return 1
722 
723  local key && _basis_executabletargetinfo_sanitize key "$2"
724  local name && _basis_executabletargetinfo_sanitize name "$3"
725  [ -n "${key}" ] && [ -n "${name}" ] || {
726  if [ -z "${key}" ] && [ -z "${name}" ]; then
727  echo "_basis_executabletargetinfo_get(): Neither lookup table nor key specified" 1>&2
728  elif [ -z "${key}" ]; then
729  echo "_basis_executabletargetinfo_get(): No key specified for lookup in hash table '${name}'" 1>&2
730  else
731  echo "_basis_executabletargetinfo_get(): No lookup table given for lookup of key '${key}'" 1>&2
732  fi
733  exit 1
734  }
735  eval "local value=\${__BASIS_EXECUTABLETARGETINFO_${name}_${key}}"
736 
737  local "$1" && upvar $1 "${value}"
738 }
739 
740 # initialize table of executable target information upon first use
741 # this function is redefined by the basis.sh module, see targetuid()
742 _basis_executabletargetinfo_initialize()
743 {
744  [ $# -eq 0 ] || return 1
745  _BASIS_EXECUTABLETARGETINFO_INITIALIZED='true'
746  return 0
747 }
748 
749 
750 } # _BASIS_UTILITIES_INCLUDED
const unsigned int VERSION_MAJOR
The major version number.
Definition: basis.cxx:41
string PROJECT
Project name.
Definition: utilities.sh:79
function targetuid(out uid, in name)
Get UID of build target.
function istarget(in target)
Determine whether a given build target is known.
function is(in result, in expected, in name)
Test whether a given result is equal to the expected result.
def which(command, path=None, verbose=0, exts=None)
Definition: which.py:257
function tostring(out var, in elements)
Build quoted string from array.
cmake BASH_VERSION_MAJOR
Definition: FindBASH.cmake:12
function upvar(in var, in values)
Assign variable one scope above the caller.
function print_contact(in contact)
Print contact information.
Definition: basis.h:34
string COPYRIGHT
Default copyright of executables.
Definition: utilities.sh:91
cmake BASH_VERSION_MINOR
Definition: FindBASH.cmake:37
string VERSION
Project version.
Definition: utilities.sh:81
function exepath(out path, in target)
Get absolute path of executable file.
Definition: path.h:37
string RELEASE
Project release.
Definition: utilities.sh:89
function qsplit(out var, in str)
Split (quoted) string.
function abspath(in path)
Get absolute path given a relative path.
std::string dirname(const std::string &path)
Get file directory.
Definition: path.cxx:265
function match(in value, in pattern)
This function implements a more portable way to do pattern matching.
function exedir(out dir, in name)
Get directory of executable file.
string LICENSE
Default license of executables.
Definition: utilities.sh:93
MultiSwitchArg verbose("v", "verbose", "Increase verbosity of output messages.", false)
const unsigned int VERSION_MINOR
The minor version number.
Definition: basis.cxx:42
const unsigned int VERSION_PATCH
The patch number.
Definition: basis.cxx:43
function exename(out file, in name)
Get name of executable file.
function print_version(in options, in name, in version)
Print version information including copyright and license notices.
string CONTACT
Default contact to use for help output of executables.
Definition: utilities.sh:95
function execute(in options, in cmd, in args)
Execute command as subprocess.