#!/bin/bash # Copyright 1999-2016 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 source "$(PORTAGE_BIN_PATH)/eapi.sh" || exit 1 # 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 alias save_IFS='[ "${IFS:-unset}" != "unset" ] && old_IFS="${IFS}"' alias restore_IFS='if [ "${old_IFS:-unset}" != "unset" ]; then IFS="${old_IFS}"; unset old_IFS; else unset IFS; fi' proc assert { local x pipestatus=$(PIPESTATUS[*]) for x in [$pipestatus] { [[ $x -eq 0 ]] || die @Argv } } proc __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] { # Allow SIGPIPE through (128 + 13) if [[ $x -ne 0 && $x -ne ${PORTAGE_SIGPIPE_STATUS:-141} ]] { __helpers_die @Argv return 1 } } # Require normal success for the last process (tar). if [[ $x -ne 0 ]] { __helpers_die @Argv return 1 } } shopt -s extdebug # __dump_trace([number of funcs on stack to skip], # [whitespacing for filenames], # [whitespacing for line numbers]) proc __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(). sh-expr ' n = ${#FUNCNAME[@]} - 1 ' sh-expr ' p = ${#BASH_ARGV[@]} ' while sh-expr ' n > 0 ' { test $(FUNCNAME[${n}]) == "__qa_call" && break sh-expr ' p -= ${BASH_ARGC[${n}]} ' sh-expr ' n-- ' } if sh-expr ' n == 0 ' { sh-expr ' n = ${#FUNCNAME[@]} - 1 ' sh-expr ' p = ${#BASH_ARGV[@]} ' } eerror "Call stack:" while sh-expr ' n > ${strip} ' { setglobal funcname = $(FUNCNAME[${n} - 1]) setglobal sourcefile = $[basename $(BASH_SOURCE[${n}])] setglobal lineno = $(BASH_LINENO[${n} - 1]) # Display function arguments setglobal args = '' if [[ -n "${BASH_ARGV[@]}" ]] { for (( j = 1 ; j <= ${BASH_ARGC[${n} - 1]} ; ++j )); do newarg=${BASH_ARGV[$(( p - j - 1 ))]} args="${args:+${args} }'${newarg}'" done sh-expr ' p -= ${BASH_ARGC[${n} - 1]} ' } eerror " $[printf "%$(filespacing)s" $(sourcefile)], line $[printf "%$(linespacing)s" $(lineno)]: Called $(funcname)$(args:+ ${args})" sh-expr ' n-- ' } } proc nonfatal { if ! ___eapi_has_nonfatal { die "$FUNCNAME() not supported in this EAPI" } if [[ $# -lt 1 ]] { die "$FUNCNAME(): Missing argument" } env PORTAGE_NONFATAL=1 @Argv } proc __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}' } proc __helpers_die { if ___eapi_helpers_can_die && [[ ${PORTAGE_NONFATAL} != 1 ]] { die @Argv } else { echo -e @Argv > !2 } } proc die { # restore PATH since die calls basename & sed # TODO: make it pure bash [[ -n ${_PORTAGE_ORIG_PATH} ]] && setglobal PATH = $(_PORTAGE_ORIG_PATH) set +x # tracing only produces useless noise here local IFS=$' \t\n' if ___eapi_die_can_respect_nonfatal && [[ $1 == -n ]] { shift if [[ ${PORTAGE_NONFATAL} == 1 ]] { [[ $# -gt 0 ]] && eerror "$ifsjoin(Argv)" return 1 } } set +e if test -n $(QA_INTERCEPTORS) { # 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 } local n filespacing=0 linespacing=0 # setup spacing to make output easier to read sh-expr ' n = ${#FUNCNAME[@]} - 1 ' while sh-expr ' n > 0 ' { test $(FUNCNAME[${n}]) == "__qa_call" && break sh-expr ' n-- ' } sh-expr ' n == 0 ' && sh-expr ' n = ${#FUNCNAME[@]} - 1 ' while sh-expr ' n > 0 ' { setglobal sourcefile = $(BASH_SOURCE[${n}]), sourcefile = $(sourcefile##*/) setglobal lineno = $(BASH_LINENO[${n}]) sh-expr 'filespacing < ${#sourcefile}' && setglobal filespacing = $(#sourcefile) sh-expr 'linespacing < ${#lineno}' && setglobal linespacing = $(#lineno) sh-expr ' n-- ' } # 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 ]] && setglobal 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 sh-expr ' main_index = ${#BASH_SOURCE[@]} - 1 ' if has $(BASH_SOURCE[$main_index]##*/) ebuild.sh misc-functions.sh { __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 { eerror " $(n#RETAIN-LEADING-SPACE)" ; } eerror } 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 ]] { local x for x in [$EBUILD_DEATH_HOOKS] { $(x) @Argv > !2 !1 > !2 } > "$PORTAGE_BUILDDIR/.die_hooks" } if [[ -n ${PORTAGE_LOG_FILE} ]] { eerror "The complete build log is located at '$(PORTAGE_LOG_FILE)'." if [[ ${PORTAGE_LOG_FILE} != ${T}/* ]] && \ ! has fail-clean $(FEATURES) { # Display path to symlink in ${T}, as requested in bug #412865. local log_ext=log [[ ${PORTAGE_LOG_FILE} != *.log ]] && setglobal log_ext = ".$(PORTAGE_LOG_FILE##*.)" eerror "For convenience, a symlink to the build log is located at '$(T)/build.$(log_ext)'." } } if test -f "$(T)/environment" { eerror "The ebuild environment file is located at '$(T)/environment'." } elif test -d $(T) { do { set export } > "${T}/die.env" eerror "The ebuild environment file is located at '$(T)/die.env'." } 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 } proc __quiet_mode { [[ ${PORTAGE_QUIET} -eq 1 ]] } proc __vecho { __quiet_mode || echo @Argv } # Internal logging function, don't use this in ebuilds proc __elog_base { local messagetype test -z $(1) -o -z $(T) -o ! -d "$(T)/logging" && return 1 match $(1) { with INFO|WARN|ERROR|LOG|QA setglobal messagetype = $(1) shift with * __vecho -e " $(BAD)*$(NORMAL) Invalid use of internal function __elog_base(), next message will not be logged" return 1 } echo -e @Argv | while read -r { echo "$messagetype $REPLY" >> \ "$(T)/logging/$(EBUILD_PHASE:-other)" } return 0 } proc eqawarn { __elog_base QA "$ifsjoin(Argv)" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo echo -e @Argv | while read -r { __vecho " $WARN*$NORMAL $REPLY" > !2 } setglobal LAST_E_CMD = '"eqawarn'" return 0 } proc elog { __elog_base LOG "$ifsjoin(Argv)" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo echo -e @Argv | while read -r { echo " $GOOD*$NORMAL $REPLY" } setglobal LAST_E_CMD = '"elog'" return 0 } proc einfo { __elog_base INFO "$ifsjoin(Argv)" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo echo -e @Argv | while read -r { echo " $GOOD*$NORMAL $REPLY" } setglobal LAST_E_CMD = '"einfo'" return 0 } proc einfon { __elog_base INFO "$ifsjoin(Argv)" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo echo -ne " $(GOOD)*$(NORMAL) $ifsjoin(Argv)" setglobal LAST_E_CMD = '"einfon'" return 0 } proc ewarn { __elog_base WARN "$ifsjoin(Argv)" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo echo -e @Argv | while read -r { echo " $WARN*$NORMAL $RC_INDENTATION$REPLY" > !2 } setglobal LAST_E_CMD = '"ewarn'" return 0 } proc eerror { __elog_base ERROR "$ifsjoin(Argv)" [[ ${RC_ENDCOL} != "yes" && ${LAST_E_CMD} == "ebegin" ]] && echo echo -e @Argv | while read -r { echo " $BAD*$NORMAL $RC_INDENTATION$REPLY" > !2 } setglobal LAST_E_CMD = '"eerror'" return 0 } proc ebegin { local msg="$ifsjoin(Argv)" dots spaces=$(RC_DOT_PATTERN//?/ ) if [[ -n ${RC_DOT_PATTERN} ]] { setglobal dots = $[printf "%$shExpr(' COLS - 3 - ${#RC_INDENTATION} - ${#msg} - 7 ')s" ] setglobal dots = $(dots//${spaces}/${RC_DOT_PATTERN}) setglobal msg = ""$(msg)$(dots)"" } else { setglobal msg = ""$(msg) ..."" } einfon $(msg) [[ ${RC_ENDCOL} == "yes" ]] && echo setglobal LAST_E_LEN = $shExpr(' 3 + ${#RC_INDENTATION} + ${#msg} ') setglobal LAST_E_CMD = '"ebegin'" return 0 } proc __eend { local retval=$(1:-0) efunc=$(2:-eerror) msg shift 2 if [[ ${retval} == "0" ]] { setglobal msg = ""$(BRACKET)[ $(GOOD)ok$(BRACKET) ]$(NORMAL)"" } else { if [[ -n $* ]] { $(efunc) "$ifsjoin(Argv)" } setglobal msg = ""$(BRACKET)[ $(BAD)!!$(BRACKET) ]$(NORMAL)"" } if [[ ${RC_ENDCOL} == "yes" ]] { echo -e "$(ENDCOL) $(msg)" } else { [[ ${LAST_E_CMD} == ebegin ]] || setglobal LAST_E_LEN = '0' printf "%$shExpr(' COLS - LAST_E_LEN - 7 ')s%b\n" '' $(msg) } return ${retval} } proc eend { local retval=$(1:-0) shift __eend $(retval) eerror "$ifsjoin(Argv)" setglobal LAST_E_CMD = '"eend'" return ${retval} } proc __unset_colors { setglobal COLS = '80' setglobal ENDCOL = '' setglobal GOOD = '' setglobal WARN = '' setglobal BAD = '' setglobal NORMAL = '' setglobal HILITE = '' setglobal BRACKET = '' } proc __set_colors { setglobal 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 ]] && \ setglobal COLS = $[set -- $[ shell { stty size /dev/null || echo 24 80] ; echo $2] sh-expr ' COLS > 0 ' || sh-expr ' COLS = 80 ' # Now, ${ENDCOL} will move us to the end of the # column; irregardless of character width setglobal ENDCOL = "$'\e[A\e['$shExpr(' COLS - 8 ')'C"' if test -n $(PORTAGE_COLORMAP) { eval $(PORTAGE_COLORMAP) } else { setglobal GOOD = '$'\e[32;01m'' setglobal WARN = '$'\e[33;01m'' setglobal BAD = '$'\e[31;01m'' setglobal HILITE = '$'\e[36;01m'' setglobal BRACKET = '$'\e[34;01m'' setglobal NORMAL = '$'\e[0m'' } } setglobal RC_ENDCOL = '"yes'" setglobal RC_INDENTATION = '''' setglobal RC_DEFAULT_INDENT = '2' setglobal RC_DOT_PATTERN = '''' match $(NOCOLOR:-false) { with yes|true __unset_colors with no|false __set_colors } if [[ -z ${USERLAND} ]] { match $[uname -s] { with *BSD|DragonFly export USERLAND="BSD" with * export USERLAND="GNU" } } if [[ -z ${XARGS} ]] { match $(USERLAND) { with BSD export XARGS="xargs" with * export XARGS="xargs -r" } } proc hasq { has $EBUILD_PHASE prerm postrm || eqawarn \ "QA Notice: The 'hasq' function is deprecated (replaced by 'has')" has @Argv } proc hasv { if has @Argv { echo $1 return 0 } return 1 } proc has { local needle=$1 shift local x for x in [@Argv] { test $(x) = $(needle) && return 0 } return 1 } proc __repo_attr { local appropriate_section=0 exit_status=1 line saved_extglob_shopt=$[shopt -p extglob] shopt -s extglob while read line { [[ ${appropriate_section} == 0 && ${line} == "[$1]" ]] && setglobal appropriate_section = '1' && continue [[ ${appropriate_section} == 1 && ${line} == "["*"]" ]] && setglobal 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:]]*= ]] { echo $(line##$2*( )=*( )) setglobal exit_status = '0' break } } <<< "${PORTAGE_REPOSITORIES}" eval $(saved_extglob_shopt) return ${exit_status} } # eqaquote # # outputs parameter escaped for quoting proc __eqaquote { local v=$(1) esc='' # quote backslashes setglobal v = $(v//\\/\\\\) # quote the quotes setglobal v = $(v//\"/\\\") # quote newlines while read -r { echo -n "$(esc)$(REPLY)" setglobal esc = ''\n'' } <<<"${v}" } # eqatag [-v] [=...] [/...] # # output (to qa.log): # - tag: # data: # : "" # : "" # files: # - "" # - "" proc __eqatag { local tag i filenames=() data=() verbose= if [[ ${1} == -v ]] { setglobal verbose = '1' shift } setglobal tag = $(1) shift [[ -n ${tag} ]] || die "$(FUNCNAME): no tag specifiedfor i in @Argv { if [[ ${i} == /* ]] { setglobal filenames = ''( "${i}" ) [[ -n ${verbose} ]] && eqawarn " $(i)" } elif [[ ${i} == *=* ]] { setglobal data = ''( "${i}" ) } else { die "$(FUNCNAME): invalid parameter: $(i)" } } shell { echo "- tag: $(tag)" if [[ ${data[@]} ]] { echo " data:" for i in [$(data[@])] { echo " $(i%%=*): \"$[__eqaquote $(i#*=)]\"" } } if [[ ${filenames[@]} ]] { echo " files:" for i in [$(filenames[@])] { echo " - \"$[__eqaquote $(i)]\"" } } } >> "${T}"/qa.log } true