#!/bin/bash # Copyright 1999-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 source "${PORTAGE_BIN_PATH}/eapi.sh" || exit 1 if ___eapi_has_version_functions; then source "${PORTAGE_BIN_PATH}/eapi7-ver-funcs.sh" || exit 1 fi # We need this next line for "die" and "assert". It expands # It _must_ preceed all the calls to die and assert. shopt -s expand_aliases assert() { local x pipestatus=${PIPESTATUS[*]} for x in $pipestatus ; do [[ $x -eq 0 ]] || die "$@" done } __assert_sigpipe_ok() { # When extracting a tar file like this: # # bzip2 -dc foo.tar.bz2 | tar xof - # # For some tar files (see bug #309001), tar will # close its stdin pipe when the decompressor still has # remaining data to be written to its stdout pipe. This # causes the decompressor to be killed by SIGPIPE. In # this case, we want to ignore pipe writers killed by # SIGPIPE, and trust the exit status of tar. We refer # to the bash manual section "3.7.5 Exit Status" # which says, "When a command terminates on a fatal # signal whose number is N, Bash uses the value 128+N # as the exit status." local x pipestatus=${PIPESTATUS[*]} for x in $pipestatus ; do # Allow SIGPIPE through (128 + 13) [[ $x -ne 0 && $x -ne ${PORTAGE_SIGPIPE_STATUS:-141} ]] && die "$@" done # Require normal success for the last process (tar). [[ $x -eq 0 ]] || die "$@" } shopt -s extdebug # __dump_trace([number of funcs on stack to skip], # [whitespacing for filenames], # [whitespacing for line numbers]) __dump_trace() { local funcname="" sourcefile="" lineno="" s="yes" n p declare -i strip=${1:-1} local filespacing=$2 linespacing=$3 # The __qa_call() function and anything before it are portage internals # that the user will not be interested in. Therefore, the stack trace # should only show calls that come after __qa_call(). (( n = ${#FUNCNAME[@]} - 1 )) (( p = ${#BASH_ARGV[@]} )) while (( n > 0 )) ; do [ "${FUNCNAME[${n}]}" == "__qa_call" ] && break (( p -= ${BASH_ARGC[${n}]} )) (( n-- )) done if (( n == 0 )) ; then (( n = ${#FUNCNAME[@]} - 1 )) (( p = ${#BASH_ARGV[@]} )) fi eerror "Call stack:" while (( n > ${strip} )) ; do funcname=${FUNCNAME[${n} - 1]} sourcefile=$(basename "${BASH_SOURCE[${n}]}") lineno=${BASH_LINENO[${n} - 1]} # Display function arguments args= if [[ ${#BASH_ARGV[@]} -gt 0 ]]; then for (( j = 1 ; j <= ${BASH_ARGC[${n} - 1]} ; ++j )); do newarg=${BASH_ARGV[$(( p - j - 1 ))]} args="${args:+${args} }'${newarg}'" done (( p -= ${BASH_ARGC[${n} - 1]} )) fi eerror " $(printf "%${filespacing}s" "${sourcefile}"), line $(printf "%${linespacing}s" "${lineno}"): Called ${funcname}${args:+ ${args}}" (( n-- )) done } nonfatal() { if ! ___eapi_has_nonfatal; then die "$FUNCNAME() not supported in this EAPI" fi if [[ $# -lt 1 ]]; then die "$FUNCNAME(): Missing argument" fi PORTAGE_NONFATAL=1 "$@" } __bashpid() { # The BASHPID variable is new to bash-4.0, so add a hack for older # versions. This must be used like so: # ${BASHPID:-$(__bashpid)} sh -c 'echo ${PPID}' } __helpers_die() { if ___eapi_helpers_can_die && [[ ${PORTAGE_NONFATAL} != 1 ]]; then die "$@" else echo -e "$@" >&2 fi } die() { # restore PATH since die calls basename & sed # TODO: make it pure bash [[ -n ${_PORTAGE_ORIG_PATH} ]] && PATH=${_PORTAGE_ORIG_PATH} set +x # tracing only produces useless noise here local IFS=$' \t\n' if ___eapi_die_can_respect_nonfatal && [[ $1 == -n ]]; then shift if [[ ${PORTAGE_NONFATAL} == 1 ]]; then [[ $# -gt 0 ]] && eerror "$*" return 1 fi fi set +e if [ -n "${QA_INTERCEPTORS}" ] ; then # die was called from inside inherit. We need to clean up # QA_INTERCEPTORS since sed is called below. unset -f ${QA_INTERCEPTORS} unset QA_INTERCEPTORS fi local n filespacing=0 linespacing=0 # setup spacing to make output easier to read (( n = ${#FUNCNAME[@]} - 1 )) while (( n > 0 )) ; do [ "${FUNCNAME[${n}]}" == "__qa_call" ] && break (( n-- )) done (( n == 0 )) && (( n = ${#FUNCNAME[@]} - 1 )) while (( n > 0 )); do sourcefile=${BASH_SOURCE[${n}]} sourcefile=${sourcefile##*/} lineno=${BASH_LINENO[${n}]} ((filespacing < ${#sourcefile})) && filespacing=${#sourcefile} ((linespacing < ${#lineno})) && linespacing=${#lineno} (( n-- )) done # When a helper binary dies automatically in EAPI 4 and later, we don't # get a stack trace, so at least report the phase that failed. local phase_str= [[ -n $EBUILD_PHASE ]] && phase_str=" ($EBUILD_PHASE phase)" eerror "ERROR: ${CATEGORY}/${PF}::${PORTAGE_REPO_NAME} failed${phase_str}:" eerror " ${*:-(no error message)}" eerror # __dump_trace is useless when the main script is a helper binary local main_index (( main_index = ${#BASH_SOURCE[@]} - 1 )) if has ${BASH_SOURCE[$main_index]##*/} ebuild.sh misc-functions.sh ; then __dump_trace 2 ${filespacing} ${linespacing} eerror " $(printf "%${filespacing}s" "${BASH_SOURCE[1]##*/}"), line $(printf "%${linespacing}s" "${BASH_LINENO[0]}"): Called die" eerror "The specific snippet of code:" # This scans the file that called die and prints out the logic that # ended in the call to die. This really only handles lines that end # with '|| die' and any preceding lines with line continuations (\). # This tends to be the most common usage though, so let's do it. # Due to the usage of appending to the hold space (even when empty), # we always end up with the first line being a blank (thus the 2nd sed). sed -n \ -e "# When we get to the line that failed, append it to the # hold space, move the hold space to the pattern space, # then print out the pattern space and quit immediately ${BASH_LINENO[0]}{H;g;p;q}" \ -e '# If this line ends with a line continuation, append it # to the hold space /\\$/H' \ -e '# If this line does not end with a line continuation, # erase the line and set the hold buffer to it (thus # erasing the hold buffer in the process) /[^\]$/{s:^.*$::;h}' \ "${BASH_SOURCE[1]}" \ | sed -e '1d' -e 's:^:RETAIN-LEADING-SPACE:' \ | while read -r n ; do eerror " ${n#RETAIN-LEADING-SPACE}" ; done eerror fi eerror "If you need support, post the output of \`emerge --info '=${CATEGORY}/${PF}::${PORTAGE_REPO_NAME}'\`," eerror "the complete build log and the output of \`emerge -pqv '=${CATEGORY}/${PF}::${PORTAGE_REPO_NAME}'\`." # Only call die hooks here if we are executed via ebuild.sh or # misc-functions.sh, since those are the only cases where the environment # contains the hook functions. When necessary (like for __helpers_die), die # hooks are automatically called later by a misc-functions.sh invocation. if has ${BASH_SOURCE[$main_index]##*/} ebuild.sh misc-functions.sh && \ [[ ${EBUILD_PHASE} != depend ]] ; then local x for x in $EBUILD_DEATH_HOOKS; do ${x} "$@" >&2 1>&2 done > "$PORTAGE_BUILDDIR/.die_hooks" fi if [[ -n ${PORTAGE_LOG_FILE} ]] ; then eerror "The complete build log is located at '${PORTAGE_LOG_FILE}'." if [[ ${PORTAGE_LOG_FILE} != ${T}/* ]] && \ ! has fail-clean ${FEATURES} ; then # Display path to symlink in ${T}, as requested in bug #412865. local log_ext=log [[ ${PORTAGE_LOG_FILE} != *.log ]] && log_ext+=.${PORTAGE_LOG_FILE##*.} eerror "For convenience, a symlink to the build log is located at '${T}/build.${log_ext}'." fi fi if [ -f "${T}/environment" ] ; then eerror "The ebuild environment file is located at '${T}/environment'." elif [ -d "${T}" ] ; then { set export } > "${T}/die.env" eerror "The ebuild environment file is located at '${T}/die.env'." fi eerror "Working directory: '$(pwd)'" eerror "S: '${S}'" [[ -n $PORTAGE_EBUILD_EXIT_FILE ]] && > "$PORTAGE_EBUILD_EXIT_FILE" [[ -n $PORTAGE_IPC_DAEMON ]] && "$PORTAGE_BIN_PATH"/ebuild-ipc exit 1 # subshell die support [[ ${BASHPID:-$(__bashpid)} == ${EBUILD_MASTER_PID} ]] || kill -s SIGTERM ${EBUILD_MASTER_PID} exit 1 } __quiet_mode() { [[ ${PORTAGE_QUIET} -eq 1 ]] } __vecho() { __quiet_mode || echo "$@" >&2 } # Internal logging function, don't use this in ebuilds __elog_base() { local messagetype [ -z "${1}" -o -z "${T}" -o ! -d "${T}/logging" ] && return 1 case "${1}" in INFO|WARN|ERROR|LOG|QA) messagetype="${1}" shift ;; *) __vecho -e " ${BAD}*${NORMAL} Invalid use of internal function __elog_base(), next message will not be logged" return 1 ;; esac echo -e "$@" | while read -r ; do echo "$messagetype $REPLY" >> \ "${T}/logging/${EBUILD_PHASE:-other}" done return 0 } eqawarn() { __elog_base QA "$*" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo >&2 echo -e "$@" | while read -r ; do echo " $WARN*$NORMAL $REPLY" >&2 done LAST_E_CMD="eqawarn" return 0 } elog() { __elog_base LOG "$*" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo >&2 echo -e "$@" | while read -r ; do echo " $GOOD*$NORMAL $REPLY" >&2 done LAST_E_CMD="elog" return 0 } einfo() { __elog_base INFO "$*" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo >&2 echo -e "$@" | while read -r ; do echo " $GOOD*$NORMAL $REPLY" >&2 done LAST_E_CMD="einfo" return 0 } einfon() { __elog_base INFO "$*" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo >&2 echo -ne " ${GOOD}*${NORMAL} $*" >&2 LAST_E_CMD="einfon" return 0 } ewarn() { __elog_base WARN "$*" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo >&2 echo -e "$@" | while read -r ; do echo " $WARN*$NORMAL $RC_INDENTATION$REPLY" >&2 done LAST_E_CMD="ewarn" return 0 } eerror() { __elog_base ERROR "$*" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo >&2 echo -e "$@" | while read -r ; do echo " $BAD*$NORMAL $RC_INDENTATION$REPLY" >&2 done LAST_E_CMD="eerror" return 0 } ebegin() { local msg="$*" dots spaces=${RC_DOT_PATTERN//?/ } if [[ -n ${RC_DOT_PATTERN} ]] ; then dots=$(printf "%$(( COLS - 3 - ${#RC_INDENTATION} - ${#msg} - 7 ))s" '') dots=${dots//${spaces}/${RC_DOT_PATTERN}} msg="${msg}${dots}" else msg="${msg} ..." fi einfon "${msg}" [[ ${RC_ENDCOL} == "yes" ]] && echo >&2 LAST_E_LEN=$(( 3 + ${#RC_INDENTATION} + ${#msg} )) LAST_E_CMD="ebegin" return 0 } __eend() { local retval=${1:-0} efunc=${2:-eerror} msg shift 2 if [[ ${retval} == "0" ]] ; then msg="${BRACKET}[ ${GOOD}ok${BRACKET} ]${NORMAL}" else if [[ -n $* ]] ; then ${efunc} "$*" fi msg="${BRACKET}[ ${BAD}!!${BRACKET} ]${NORMAL}" fi if [[ ${RC_ENDCOL} == "yes" ]] ; then echo -e "${ENDCOL} ${msg}" >&2 else [[ ${LAST_E_CMD} == ebegin ]] || LAST_E_LEN=0 printf "%$(( COLS - LAST_E_LEN - 7 ))s%b\n" '' "${msg}" >&2 fi return ${retval} } eend() { local retval=${1:-0} shift __eend ${retval} eerror "$*" LAST_E_CMD="eend" return ${retval} } __unset_colors() { COLS=80 ENDCOL= GOOD= WARN= BAD= NORMAL= HILITE= BRACKET= } __set_colors() { COLS=${COLUMNS:-0} # bash's internal COLUMNS variable # Avoid wasteful stty calls during the "depend" phases. # If stdout is a pipe, the parent process can export COLUMNS # if it's relevant. Use an extra subshell for stty calls, in # order to redirect "/dev/tty: No such device or address" # error from bash to /dev/null. [[ $COLS == 0 && $EBUILD_PHASE != depend ]] && \ COLS=$(set -- $( ( stty size /dev/null || echo 24 80 ) ; echo $2) (( COLS > 0 )) || (( COLS = 80 )) # Now, ${ENDCOL} will move us to the end of the # column; irregardless of character width ENDCOL=$'\e[A\e['$(( COLS - 8 ))'C' if [ -n "${PORTAGE_COLORMAP}" ] ; then eval ${PORTAGE_COLORMAP} else GOOD=$'\e[32;01m' WARN=$'\e[33;01m' BAD=$'\e[31;01m' HILITE=$'\e[36;01m' BRACKET=$'\e[34;01m' NORMAL=$'\e[0m' fi } RC_ENDCOL="yes" RC_INDENTATION='' RC_DEFAULT_INDENT=2 RC_DOT_PATTERN='' case "${NOCOLOR:-false}" in yes|true) __unset_colors ;; no|false) __set_colors ;; esac if [[ -z ${USERLAND} ]] ; then case $(uname -s) in *BSD|DragonFly) export USERLAND="BSD" ;; *) export USERLAND="GNU" ;; esac fi if [[ -z ${XARGS} ]] ; then case ${USERLAND} in BSD) if type -P gxargs > /dev/null; then export XARGS="gxargs -r" else export XARGS="xargs" fi ;; *) export XARGS="xargs -r" ;; esac fi ___makeopts_jobs() { # Copied from eutils.eclass:makeopts_jobs() local jobs jobs=$(echo " ${MAKEOPTS} " | \ sed -r -n 's:.*[[:space:]](-j|--jobs[=[:space:]])[[:space:]]*([0-9]+).*:\2:p') || die echo ${jobs:-1} } # Run ${XARGS} in parallel for detected number of CPUs, if supported. # Passes all arguments to xargs, and returns its exit code ___parallel_xargs() { local chunksize=1 jobs xargs=( ${XARGS} ) if "${xargs[@]}" --help | grep -q -- --max-procs=; then jobs=$(___makeopts_jobs) if [[ ${jobs} -gt 1 ]]; then xargs+=("--max-procs=${jobs}" -L "${chunksize}") fi fi "${xargs[@]}" "${@}" } hasq() { has $EBUILD_PHASE prerm postrm || eqawarn \ "QA Notice: The 'hasq' function is deprecated (replaced by 'has')" has "$@" } hasv() { if has "$@" ; then echo "$1" return 0 fi return 1 } has() { local needle=$1 shift local x for x in "$@"; do [ "${x}" = "${needle}" ] && return 0 done return 1 } __repo_attr() { local appropriate_section=0 exit_status=1 line saved_extglob_shopt=$(shopt -p extglob) shopt -s extglob while read line; do [[ ${appropriate_section} == 0 && ${line} == "[$1]" ]] && appropriate_section=1 && continue [[ ${appropriate_section} == 1 && ${line} == "["*"]" ]] && appropriate_section=0 && continue # If a conditional expression like [[ ${line} == $2*( )=* ]] is used # then bash-3.2 produces an error like the following when the file is # sourced: syntax error in conditional expression: unexpected token `(' # Therefore, use a regular expression for compatibility. if [[ ${appropriate_section} == 1 && ${line} =~ ^${2}[[:space:]]*= ]]; then echo "${line##$2*( )=*( )}" exit_status=0 break fi done <<< "${PORTAGE_REPOSITORIES}" eval "${saved_extglob_shopt}" return ${exit_status} } # eqaquote # # outputs parameter escaped for quoting __eqaquote() { local v=${1} esc='' # quote backslashes v=${v//\\/\\\\} # quote the quotes v=${v//\"/\\\"} # quote newlines while read -r; do echo -n "${esc}${REPLY}" esc='\n' done <<<"${v}" } # eqatag [-v] [=...] [/...] # # output (to qa.log): # - tag: # data: # : "" # : "" # files: # - "" # - "" __eqatag() { local tag i filenames=() data=() verbose= if [[ ${1} == -v ]]; then verbose=1 shift fi tag=${1} shift [[ -n ${tag} ]] || die "${FUNCNAME}: no tag specified" # collect data & filenames for i; do if [[ ${i} == /* ]]; then filenames+=( "${i}" ) [[ -n ${verbose} ]] && eqawarn " ${i}" elif [[ ${i} == *=* ]]; then data+=( "${i}" ) else die "${FUNCNAME}: invalid parameter: ${i}" fi done ( echo "- tag: ${tag}" if [[ ${#data[@]} -gt 0 ]]; then echo " data:" for i in "${data[@]}"; do echo " ${i%%=*}: \"$(__eqaquote "${i#*=}")\"" done fi if [[ ${#filenames[@]} -gt 0 ]]; then echo " files:" for i in "${filenames[@]}"; do echo " - \"$(__eqaquote "${i}")\"" done fi ) >> "${T}"/qa.log } if [[ BASH_VERSINFO -gt 4 || (BASH_VERSINFO -eq 4 && BASH_VERSINFO[1] -ge 4) ]] ; then ___is_indexed_array_var() { [[ ${!1@a} == *a* ]] } else ___is_indexed_array_var() { [[ $(declare -p "$1" 2>/dev/null) == 'declare -a'* ]] } fi true