Bug #200313 - Detect and report when an ebuild phase
authorZac Medico <zmedico@gentoo.org>
Mon, 26 Nov 2007 11:58:31 +0000 (11:58 -0000)
committerZac Medico <zmedico@gentoo.org>
Mon, 26 Nov 2007 11:58:31 +0000 (11:58 -0000)
exits unexpectedly. This is type of behavior is known
to be triggered by things such as failed variable
assignments (bug #190128) or bad substitution errors
(bug #200313).

We use a EBUILD_EXIT_STATUS_FILE environment variable
to specify a file that the shell code is supposed to
create when it exits in a normal manner. If the file
does not get created like it's supposed to be then we
can conclude that the shell has exited in some
unexpected way.

(trunk r8682)

svn path=/main/branches/2.1.2/; revision=8684

bin/ebuild.sh
bin/isolated-functions.sh
bin/misc-functions.sh
pym/portage.py

index 46651e76aa5ea0be7d0ac6f76a5923249abe8837..a6969c9d86bd108f00b9242723a8f0bd4001a738 100755 (executable)
@@ -1726,7 +1726,7 @@ if [ -n "${EBUILD_SH_ARGS}" ] ; then
                        9>&-
                fi
                set +f
-               #make sure it is writable by our group:
+               touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null
                exit 0
                ;;
        *)
@@ -1737,6 +1737,7 @@ if [ -n "${EBUILD_SH_ARGS}" ] ; then
                exit 1
                ;;
        esac
+       touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null
 fi
 
 # Save the env only for relevant phases.
index ae418389d15871078d54c03e8c55944642009eac..9eaf9bffb61dd4b4e00f37108cd304afb8dd3f41 100644 (file)
@@ -124,6 +124,8 @@ diefunc() {
                done
        fi
 
+       touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null
+
        # subshell die support
        kill -s SIGTERM ${EBUILD_MASTER_PID}
        exit 1
@@ -416,7 +418,8 @@ save_ebuild_env() {
 
                # portage config variables and variables set directly by portage
                unset BAD BRACKET BUILD_PREFIX COLS \
-                       DISTCC_DIR DISTDIR DOC_SYMLINKS_DIR EBUILD_MASTER_PID \
+                       DISTCC_DIR DISTDIR DOC_SYMLINKS_DIR \
+                       EBUILD_EXIT_STATUS_FILE EBUILD_MASTER_PID \
                        ECLASSDIR ECLASS_DEPTH ENDCOL FAKEROOTKEY FEATURES \
                        GOOD HILITE HOME IMAGE \
                        KV LAST_E_CMD LAST_E_LEN LD_PRELOAD MOPREFIX \
index 31ad9d8e8fc06b2bf5fe32b514ea0ae0ef093af2..633b3c37d1408c3170c4fd35b673c7142131b8f2 100755 (executable)
@@ -587,4 +587,6 @@ if [ -n "${MISC_FUNCTIONS_ARGS}" ]; then
        done
 fi
 
+touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null
+
 :
index 31902c7e33b319a4e7737ef5ad4fae09178c9c43..e62355a9a6512415f376f2420e2c4d966ffbb3d8 100644 (file)
@@ -3510,8 +3510,23 @@ def spawnebuild(mydo,actionmap,mysettings,debug,alwaysdep=0,logfile=None):
                                return retval
        kwargs = actionmap[mydo]["args"]
        mysettings["EBUILD_PHASE"] = mydo
+       _doebuild_exit_status_unlink(
+               mysettings.get("EBUILD_EXIT_STATUS_FILE"))
        phase_retval = spawn(actionmap[mydo]["cmd"] % mydo, mysettings, debug=debug, logfile=logfile, **kwargs)
        mysettings["EBUILD_PHASE"] = ""
+       msg = _doebuild_exit_status_check(
+               mydo, mysettings.get("EBUILD_EXIT_STATUS_FILE"))
+       if msg:
+               phase_retval = 1
+               from textwrap import wrap
+               cmd = "source '%s/isolated-functions.sh' ; " % \
+                       PORTAGE_BIN_PATH
+               for l in wrap(msg, 72):
+                       cmd += "eerror \"%s\" ; " % l
+               mysettings["EBUILD_PHASE"] = mydo
+               portage_exec.spawn(["bash", "-c", cmd],
+                       env=mysettings.environ())
+               mysettings["EBUILD_PHASE"] = ""
 
        if "userpriv" in mysettings.features and \
                not kwargs["droppriv"] and secpass >= 2:
@@ -3693,6 +3708,8 @@ def doebuild_environment(myebuild, mydo, myroot, mysettings, debug, use_cache, m
 
        mysettings["PORTAGE_BASHRC"] = os.path.join(
                mysettings["PORTAGE_CONFIGROOT"], EBUILD_SH_ENV_FILE.lstrip(os.path.sep))
+       mysettings["EBUILD_EXIT_STATUS_FILE"] = os.path.join(
+               mysettings["PORTAGE_BUILDDIR"], ".exit_status")
 
        #set up KV variable -- DEP SPEEDUP :: Don't waste time. Keep var persistent.
        if (mydo!="depend") or not mysettings.has_key("KV"):
@@ -3896,6 +3913,37 @@ def prepare_build_dirs(myroot, mysettings, cleanup):
                        mysettings["PORTAGE_LOG_FILE"] = os.path.join(
                                mysettings["T"], "build.log")
 
+def _doebuild_exit_status_check(mydo, exit_status_file):
+       """
+       Returns an error string if the shell appeared
+       to exit unsuccessfully, None otherwise.
+       """
+       if not exit_status_file or \
+               os.path.exists(exit_status_file):
+               return None
+       msg = ("The ebuild phase '%s' has exited " % mydo) + \
+       "unexpectedly. This is type of behavior " + \
+       "is known to be triggered " + \
+       "by things such as failed variable " + \
+       "assignments (bug #190128) or bad substitution " + \
+       "errors (bug #200313)."
+       return msg
+
+def _doebuild_exit_status_unlink(exit_status_file):
+       """
+       Double check to make sure it really doesn't exist
+       and raise an OSError if it still does (it shouldn't).
+       OSError if necessary.
+       """
+       if not exit_status_file:
+               return
+       try:
+               os.unlink(exit_status_file)
+       except OSError:
+               pass
+       if os.path.exists(exit_status_file):
+               os.unlink(exit_status_file)
+
 _doebuild_manifest_exempt_depend = 0
 _doebuild_manifest_checked = None
 
@@ -4038,6 +4086,24 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                                        return 1
                        _doebuild_manifest_checked = manifest_path
 
+       def exit_status_check(retval):
+               if retval != os.EX_OK:
+                       return retval
+               msg = _doebuild_exit_status_check(
+                       mydo, mysettings.get("EBUILD_EXIT_STATUS_FILE"))
+               if msg:
+                       retval = 1
+                       from textwrap import wrap
+                       cmd = "source '%s/isolated-functions.sh' ; " % \
+                               PORTAGE_BIN_PATH
+                       for l in wrap(msg, 72):
+                               cmd += "eerror \"%s\" ; " % l
+                       mysettings["EBUILD_PHASE"] = mydo
+                       portage_exec.spawn(["bash", "-c", cmd],
+                               env=mysettings.environ())
+                       mysettings["EBUILD_PHASE"] = ""
+               return retval
+
        logfile=None
        builddir_lock = None
        tmpdir = None
@@ -4158,6 +4224,12 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                        logfile = mysettings.get("PORTAGE_LOG_FILE")
                        if logfile and not os.access(os.path.dirname(logfile), os.W_OK):
                                logfile = None
+                               
+               if have_build_dirs:
+                       _doebuild_exit_status_unlink(
+                               mysettings.get("EBUILD_EXIT_STATUS_FILE"))
+               else:
+                       mysettings.pop("EBUILD_EXIT_STATUS_FILE", None)
                if mydo == "unmerge":
                        return unmerge(mysettings["CATEGORY"],
                                mysettings["PF"], myroot, mysettings, vartree=vartree)
@@ -4179,6 +4251,7 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                                mysettings.load_infodir(infodir)
                        retval = spawn(EBUILD_SH_BINARY + " " + mydo, mysettings,
                                debug=debug, free=1, logfile=logfile)
+                       retval = exit_status_check(retval)
                        if secpass >= 2:
                                """ Privileged phases may have left files that need to be made
                                writable to a less privileged user."""
@@ -4189,6 +4262,7 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                elif mydo == "preinst":
                        phase_retval = spawn(" ".join((EBUILD_SH_BINARY, mydo)),
                                mysettings, debug=debug, free=1, logfile=logfile)
+                       phase_retval = exit_status_check(phase_retval)
                        if phase_retval == os.EX_OK:
                                # Post phase logic and tasks that have been factored out of
                                # ebuild.sh.
@@ -4206,6 +4280,7 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                        mysettings.load_infodir(mysettings["O"])
                        phase_retval = spawn(" ".join((EBUILD_SH_BINARY, mydo)),
                                mysettings, debug=debug, free=1, logfile=logfile)
+                       phase_retval = exit_status_check(phase_retval)
                        if phase_retval == os.EX_OK:
                                # Post phase logic and tasks that have been factored out of
                                # ebuild.sh.
@@ -4219,8 +4294,10 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                        return phase_retval
                elif mydo in ("prerm", "postrm", "config", "info"):
                        mysettings.load_infodir(mysettings["O"])
-                       return spawn(EBUILD_SH_BINARY + " " + mydo,
+                       retval =  spawn(EBUILD_SH_BINARY + " " + mydo,
                                mysettings, debug=debug, free=1, logfile=logfile)
+                       retval = exit_status_check(retval)
+                       return retval
 
                mycpv = "/".join((mysettings["CATEGORY"], mysettings["PF"]))
 
@@ -4402,7 +4479,9 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                elif mydo=="merge":
                        retval = spawnebuild("install", actionmap, mysettings, debug,
                                alwaysdep=1, logfile=logfile)
-                       if retval != os.EX_OK:
+                       if retval == os.EX_OK:
+                               retval = exit_status_check(retval)
+                       else:
                                # The merge phase handles this already.  Callers don't know how
                                # far this function got, so we have to call elog_process() here
                                # so that it's only called once.