<pre style='margin:0'>
Landon Fuller (landonf) pushed a commit to branch master
in repository macports-base.

</pre>
<p><a href="https://github.com/macports/macports-base/commit/da3524e8a3006a9b9a2370b66fa91bb554dd768b">https://github.com/macports/macports-base/commit/da3524e8a3006a9b9a2370b66fa91bb554dd768b</a></p>
<pre style="white-space: pre; background: #F8F8F8">The following commit(s) were added to refs/heads/master by this push:
<span style='display:block; white-space:pre;color:#404040;'>     new da3524e  Basic target progress bar support (#150)
</span>da3524e is described below

<span style='display:block; white-space:pre;color:#808000;'>commit da3524e8a3006a9b9a2370b66fa91bb554dd768b
</span>Author: Landon Fuller <landonf@macports.org>
AuthorDate: Tue Feb 11 10:35:44 2020 -0700

<span style='display:block; white-space:pre;color:#404040;'>    Basic target progress bar support (#150)
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    - Add callback event support for monitoring the progress of SystemCmd.
</span><span style='display:block; white-space:pre;color:#404040;'>    - Use SystemCmd events to implement basic progress bar support for a subset of targets (including build).
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    Closes: https://trac.macports.org/ticket/15939
</span>---
 src/macports1.0/macports.tcl        |  10 +-
 src/package1.0/portarchivefetch.tcl |   2 +-
 src/pextlib1.0/system.c             | 364 +++++++++++++++++++++++++++++++++---
 src/port1.0/Makefile.in             |   2 +-
 src/port1.0/portbuild.tcl           |   3 +-
 src/port1.0/portconfigure.tcl       |  15 +-
 src/port1.0/portdestroot.tcl        |   2 +-
 src/port1.0/portfetch.tcl           |   2 +-
 src/port1.0/portprogress.tcl        | 117 ++++++++++++
 src/port1.0/porttest.tcl            |   3 +-
 src/port1.0/portutil.tcl            |  56 ++++--
 11 files changed, 516 insertions(+), 60 deletions(-)

<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/macports1.0/macports.tcl b/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 620b21b..62841c1 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -1440,9 +1440,13 @@ proc macports::worker_init {workername portpath porturl portbuildpath options va
</span>     foreach priority $macports::ui_priorities {
         $workername alias ui_$priority ui_$priority
     }
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    # add the UI progress call-back
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    if {[info exists macports::ui_options(progress_download)]} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        $workername alias ui_progress_download $macports::ui_options(progress_download)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # add the UI progress call-backs (or a no-op alias, if unavailable)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach pname {progress_download progress_generic} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[info exists macports::ui_options($pname)]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            $workername alias ui_$pname $macports::ui_options($pname)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            $workername alias ui_$pname return -level 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span>     }
 
     # notifications callback
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/package1.0/portarchivefetch.tcl b/src/package1.0/portarchivefetch.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 4791134..2bf4945 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/package1.0/portarchivefetch.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/package1.0/portarchivefetch.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -196,7 +196,7 @@ proc portarchivefetch::fetchfiles {args} {
</span>     if {$portverbose eq "yes"} {
         lappend fetch_options "--progress"
         lappend fetch_options "builtin"
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    } elseif {[llength [info commands ui_progress_download]] > 0} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    } else {
</span>         lappend fetch_options "--progress"
         lappend fetch_options "ui_progress_download"
     }
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/pextlib1.0/system.c b/src/pextlib1.0/system.c
</span><span style='display:block; white-space:pre;color:#808080;'>index c043dd3..6f32bb8 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/pextlib1.0/system.c
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/pextlib1.0/system.c
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -49,7 +49,9 @@
</span> #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/resource.h>
<span style='display:block; white-space:pre;background:#e0ffe0;'>+#include <assert.h>
</span> #include <fcntl.h>
<span style='display:block; white-space:pre;background:#e0ffe0;'>+#include <stdbool.h>
</span> #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -79,6 +81,40 @@ extern char **environ;
</span> #define _PATH_DEVNULL "/dev/null"
 #endif
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_TYPE_KEY               "type"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_PID_KEY                "pid"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_TYPE_EXEC              "exec"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_TYPE_EXIT              "exit"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_TYPE_STDIN             "stdin"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_STDIN_LINE_KEY         "line"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_EXIT_STATUS_KEY        "exit_status"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_SIGNALED               "signaled"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_SIGNAL_ID_KEY          "signal_id"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_SIGNAL_MSG_KEY         "signal_msg"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_EXITED                 "exited"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_EXITED_CODE_KEY        "exit_code"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_IOERR                  "ioerr"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_EXIT_IOERR_ERRNO_KEY   "io_errno"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#define SYSEVENT_EXIT_IOERR_MSG_KEY     "io_message"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+typedef struct SystemCmd_Callback {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Interp  *interp;            /**< interpreter */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj     *procs;             /**< list of callback proc(s) */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj     *pid;               /**< child process pid, or NULL if child has not yet been executed */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    /* Commonly used event keys and values */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj     *type_key;          /**< cached event type dictionary key */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj     *pid_key;           /**< cached event pid dictionary key */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj     *stdin_type;        /**< cached stdin event type */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj     *stdin_line_key;    /**< cached stdin line dictionary key */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+} SystemCmd_Callback;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> static int check_sandboxing(Tcl_Interp *interp, char **sandbox_exec_path, char **profilestr)
 {
     Tcl_Obj *tcl_result;
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -104,12 +140,150 @@ static int check_sandboxing(Tcl_Interp *interp, char **sandbox_exec_path, char *
</span>     return 1;
 }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+static int SystemCmd_Callback_Create(Tcl_Interp *interp, SystemCmd_Callback **cb)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    SystemCmd_Callback *result;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if ((result = malloc(sizeof(*result))) == NULL) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_SetResult(interp, strerror(errno), TCL_STATIC);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    *result = (struct SystemCmd_Callback) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .interp = interp,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .procs = Tcl_NewListObj(0, NULL),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .type_key = Tcl_NewStringObj(SYSEVENT_TYPE_KEY, -1),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .pid_key = Tcl_NewStringObj(SYSEVENT_PID_KEY, -1),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .pid = NULL,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .stdin_type = Tcl_NewStringObj(SYSEVENT_TYPE_STDIN, -1),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        .stdin_line_key = Tcl_NewStringObj(SYSEVENT_STDIN_LINE_KEY, -1)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    };
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(result->procs);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(result->type_key);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(result->pid_key);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(result->stdin_type);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(result->stdin_line_key);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    *cb = result;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return TCL_OK;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Callback_Free(SystemCmd_Callback *cb)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DecrRefCount(cb->procs);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DecrRefCount(cb->type_key);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DecrRefCount(cb->pid_key);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DecrRefCount(cb->stdin_type);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DecrRefCount(cb->stdin_line_key);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (cb->pid != NULL)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_DecrRefCount(cb->pid);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    free(cb);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static int SystemCmd_Callback_Append(SystemCmd_Callback *cb, Tcl_Obj *callbackProc)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return Tcl_ListObjAppendElement(cb->interp, cb->procs, callbackProc);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static int SystemCmd_Callback_NumProcs(SystemCmd_Callback *cb)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    int status;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    int len;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if ((status = Tcl_ListObjLength(cb->interp, cb->procs, &len)) != TCL_OK) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        /* We allocate this explicitly as a list; the type should not change */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_Panic("SystemCmd: callbacks has non-list type");
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return len;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static bool SystemCmd_Callback_Enabled(SystemCmd_Callback *cb)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return (SystemCmd_Callback_NumProcs(cb) > 0);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Callback_SetPid(SystemCmd_Callback *cb, pid_t pid)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (cb->pid != NULL)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_DecrRefCount(cb->pid);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    cb->pid = Tcl_NewWideIntObj(pid);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(cb->pid);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static int SystemCmd_Callback_Invoke(SystemCmd_Callback *cb, Tcl_Obj *event)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj **procs;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    int numProcs;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_ListObjGetElements(cb->interp, cb->procs, &numProcs, &procs);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    for (int i = 0; i < numProcs; i++) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_Obj *objv[] = { procs[i], event };
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        int status;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        status = Tcl_EvalObjv(cb->interp, (sizeof(objv)/sizeof(objv[0])), objv, TCL_EVAL_GLOBAL);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if (status != TCL_OK)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return status;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return TCL_OK;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static Tcl_Obj *SystemCmd_Event_Create(SystemCmd_Callback *cb, Tcl_Obj *type)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_Obj *event = Tcl_NewDictObj();
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    assert(cb->pid != NULL);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, cb->type_key, type);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, cb->pid_key, cb->pid);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_IncrRefCount(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return event;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Event_Release(Tcl_Obj *event)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DecrRefCount(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Event_SetExitType(SystemCmd_Callback *cb, Tcl_Obj *event, const char *exit_type)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, Tcl_NewStringObj(SYSEVENT_EXIT_STATUS_KEY,-1),        Tcl_NewStringObj(exit_type, -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Event_SetExitCode(SystemCmd_Callback *cb, Tcl_Obj *event, int exit_code)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, Tcl_NewStringObj(SYSEVENT_EXITED_CODE_KEY,-1),        Tcl_NewIntObj(exit_code));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Event_SetLine(SystemCmd_Callback *cb, Tcl_Obj *event, Tcl_Obj *line)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, cb->stdin_line_key, line);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Event_SetIOError(SystemCmd_Callback *cb, Tcl_Obj *event, int error)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, Tcl_NewStringObj(SYSEVENT_EXIT_IOERR_ERRNO_KEY, -1),  Tcl_NewIntObj(error));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, Tcl_NewStringObj(SYSEVENT_EXIT_IOERR_MSG_KEY, -1),    Tcl_NewStringObj(strerror(error), -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+static void SystemCmd_Event_SetSignaled(SystemCmd_Callback *cb, Tcl_Obj *event, int signal)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+{
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, Tcl_NewStringObj(SYSEVENT_SIGNAL_ID_KEY,-1),          Tcl_NewStringObj(Tcl_SignalId(signal), -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_DictObjPut(cb->interp, event, Tcl_NewStringObj(SYSEVENT_SIGNAL_MSG_KEY,-1),         Tcl_NewStringObj(Tcl_SignalMsg(signal), -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> static volatile sig_atomic_t interrupted_by = 0;
 static void handle_sigint(int s) {
     interrupted_by = s;
 }
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-/* usage: system ?-notty? ?-nodup? ?-nice value? ?-W path? command */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+/* usage: system ?-callback proc? ?-notty? ?-nodup? ?-nice value? ?-W path? command */
</span> int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
 {
     char *args[7];
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -118,7 +292,8 @@ int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Ob
</span>     char *sandbox_exec_path = NULL;
     char *profilestr = NULL;
     FILE *pdes;
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    int fdset[2], nullfd;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    int fdset[2] = { -1, -1 };
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    int nullfd;
</span>     int ret;
     int osetsid = 0;
     int odup = 1; /* redirect stdin/stdout/stderr by default */
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -127,39 +302,69 @@ int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Ob
</span>     pid_t pid;
     uid_t euid;
     Tcl_Obj *tcl_result;
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    SystemCmd_Callback *callback;
</span>     int read_failed = 0;
     int status;
     int i;
 
     if (objc < 2) {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        Tcl_WrongNumArgs(interp, 1, objv, "?-notty? ?-nice value? ?-W path? command");
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_WrongNumArgs(interp, 1, objv, "?-callback proc? ?-notty? ?-nice value? ?-W path? command");
</span>         return TCL_ERROR;
     }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    if ((status = SystemCmd_Callback_Create(interp, &callback)) != TCL_OK)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return status;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     cmdstring = Tcl_GetString(objv[objc - 1]);
 
     for (i = 1; i < objc - 1; i++) {
         char *arg = Tcl_GetString(objv[i]);
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if (strcmp(arg, "-notty") == 0) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if (strcmp(arg, "-callback") == 0) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (++i >= objc) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_WrongNumArgs(interp, 1, objv, "proc");
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Callback_Free(callback);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if ((status = SystemCmd_Callback_Append(callback, objv[i])) != TCL_OK) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Callback_Free(callback);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return status;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } else if (strcmp(arg, "-notty") == 0) {
</span>             osetsid = 1;
         } else if (strcmp(arg, "-nodup") == 0) {
             odup = 0;
         } else if (strcmp(arg, "-nice") == 0) {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            i++;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (++i >= objc) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_WrongNumArgs(interp, 1, objv, "value");
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Callback_Free(callback);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>             if (Tcl_GetIntFromObj(interp, objv[i], &oniceval) != TCL_OK) {
                 Tcl_SetResult(interp, "invalid value for -nice", TCL_STATIC);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Callback_Free(callback);
</span>                 return TCL_ERROR;
             }
         } else if (strcmp(arg, "-W") == 0) {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            i++;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (++i >= objc) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_WrongNumArgs(interp, 1, objv, "path");
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Callback_Free(callback);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>             if ((path = Tcl_GetString(objv[i])) == NULL) {
                 Tcl_SetResult(interp, "invalid value for -W", TCL_STATIC);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Callback_Free(callback);
</span>                 return TCL_ERROR;
             }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        } else if (strcmp(arg, "--") == 0) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            break;
</span>         } else {
             tcl_result = Tcl_NewStringObj("bad option ", -1);
             Tcl_AppendObjToObj(tcl_result, Tcl_NewStringObj(arg, -1));
             Tcl_SetObjResult(interp, tcl_result);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            SystemCmd_Callback_Free(callback);
</span>             return TCL_ERROR;
         }
     }
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -181,6 +386,7 @@ int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Ob
</span>     if (odup) {
         if (pipe(fdset) != 0) {
             Tcl_SetResult(interp, strerror(errno), TCL_STATIC);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            SystemCmd_Callback_Free(callback);
</span>             return TCL_ERROR;
         }
     }
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -213,6 +419,7 @@ int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Ob
</span>     case 0: /* child */
         if (odup) {
             close(fdset[0]);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            fdset[0] = -1;
</span> 
             if ((nullfd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
                 _exit(1);
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -278,11 +485,26 @@ int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Ob
</span>         exit(128);
         /*NOTREACHED*/
     default: /* parent */
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        /* Must be done before creating any events from the callback context */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        SystemCmd_Callback_SetPid(callback, pid);
</span>         break;
     }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    /* Inform the callback of our exec event */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (SystemCmd_Callback_Enabled(callback)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_Obj *event;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        event = SystemCmd_Event_Create(callback, Tcl_NewStringObj(SYSEVENT_TYPE_EXEC,-1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        status = SystemCmd_Callback_Invoke(callback, event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        SystemCmd_Event_Release(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if (status != TCL_OK)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            goto cleanup;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     if (odup) {
         close(fdset[1]);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        fdset[1] = -1;
</span> 
         /* read from simulated popen() pipe */
         read_failed = 0;
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -292,64 +514,147 @@ int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Ob
</span>             size_t linesz = 0;
             ssize_t linelen;
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            status = TCL_OK;
</span>             while ((linelen = getline(&line, &linesz, pdes)) > 0) {
                 /* replace '\n' if it exists */
                 if (line[linelen - 1] == '\n') {
                     line[linelen - 1] = '\0';
                 }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+                /* Provide the line event to our callback */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if (SystemCmd_Callback_Enabled(callback)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    Tcl_Obj *event = SystemCmd_Event_Create(callback, callback->stdin_type);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    SystemCmd_Event_SetLine(callback, event, Tcl_NewStringObj(line,linelen));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    status = SystemCmd_Callback_Invoke(callback, event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    SystemCmd_Event_Release(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    if (status != TCL_OK) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        free(line);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        fclose(pdes);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        goto cleanup;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>                 ui_info(interp, "%s", line);
             }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>             free(line);
             fclose(pdes);
         } else {
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            int error = errno;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* Provide the exit event to our callback */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (SystemCmd_Callback_Enabled(callback)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_Obj *event = SystemCmd_Event_Create(callback, Tcl_NewStringObj(SYSEVENT_EXITED,-1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Event_SetExitType(callback, event, SYSEVENT_IOERR);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Event_SetIOError(callback, event, error);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                status = SystemCmd_Callback_Invoke(callback, event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Event_Release(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if (status != TCL_OK)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    goto cleanup;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_SetResult(interp, strerror(error), TCL_STATIC);
</span>             read_failed = 1;
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            Tcl_SetResult(interp, strerror(errno), TCL_STATIC);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            status = TCL_ERROR;
</span>         }
     }
 
     status = TCL_ERROR;
<span style='display:block; white-space:pre;background:#ffe0e0;'>-
</span>     if (wait(&ret) == pid && (WIFEXITED(ret) || WIFSIGNALED(ret)) && !read_failed) {
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_Obj *event = NULL;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        /* Populate common exit event fields */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if (SystemCmd_Callback_Enabled(callback)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* Determine the exit status type */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            const char *exit_type = NULL;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (WIFEXITED(ret)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                exit_type = SYSEVENT_EXITED;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else if (WIFSIGNALED(ret) || interrupted_by != 0) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                exit_type = SYSEVENT_SIGNALED;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_SetObjResult(interp, Tcl_ObjPrintf("unhandled exit status: %d", ret));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                status = TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                goto cleanup;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* Populate a new exit event with exit_status and exit_code */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            event = SystemCmd_Event_Create(callback, Tcl_NewStringObj(SYSEVENT_TYPE_EXIT,-1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            SystemCmd_Event_SetExitType(callback, event, exit_type);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            SystemCmd_Event_SetExitCode(callback, event, WEXITSTATUS(ret));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>         /* Normal exit, and reading from the pipe didn't fail. */
         if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* Report the exit event to our callback */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (event != NULL) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                status = SystemCmd_Callback_Invoke(callback, event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Event_Release(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if (status != TCL_OK)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    goto cleanup;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>             status = TCL_OK;
         } else {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            Tcl_Obj* errorCode;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_Obj *errorCode = Tcl_NewListObj(0, NULL);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_IncrRefCount(errorCode);
</span> 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            /* print error */
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            ui_info(interp, "Command failed: %s", cmdstring);
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            if (WIFEXITED(ret)) {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                ui_info(interp, "Exit code: %d", WEXITSTATUS(ret));
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            } else if(WIFSIGNALED(ret)) {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                ui_info(interp, "Killed by signal: %d", WTERMSIG(ret));
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            }
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            errorCode = Tcl_NewListObj(0, NULL);
</span>             if (interrupted_by != 0) {
<span style='display:block; white-space:pre;background:#e0ffe0;'>+                /* Add signal keys to our exit event */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if (event != NULL)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    SystemCmd_Event_SetSignaled(callback, event, interrupted_by);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>                 /* set errorCode [list POSIX SIG <SIGNAME> <signal descripton>] */
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("POSIX", -1));
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("SIG", -1));
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj(Tcl_SignalId(interrupted_by), -1));
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj(Tcl_SignalMsg(interrupted_by), -1));
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_SetObjErrorCode(interp, errorCode);
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_SetObjResult(interp, Tcl_NewStringObj("interrupted by signal", -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                tcl_result = Tcl_NewStringObj("interrupted by signal", -1);
</span>             } else if (WIFEXITED(ret)) {
                 /* set errorCode [list CHILDSTATUS <pid> <code>] */
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDSTATUS", -1));
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewWideIntObj(pid));
</span>                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(WEXITSTATUS(ret)));
                 Tcl_SetObjErrorCode(interp, errorCode);
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_SetObjResult(interp, Tcl_NewStringObj("command execution failed", -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                tcl_result = Tcl_NewStringObj("command execution failed", -1);
</span>             } else if (WIFSIGNALED(ret)) {
<span style='display:block; white-space:pre;background:#e0ffe0;'>+                /* Add signal keys to our exit event */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if (event != NULL)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    SystemCmd_Event_SetSignaled(callback, event, WTERMSIG(ret));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>                 /* set errorCode [list CHILDKILLED <pid> <SIGNAME> <signal descripton>] */
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDKILLED", -1));
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewWideIntObj(pid));
</span>                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj(Tcl_SignalId(WTERMSIG(ret)), -1));
                 Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj(Tcl_SignalMsg(WTERMSIG(ret)), -1));
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_SetObjErrorCode(interp, errorCode);
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                Tcl_SetObjResult(interp, Tcl_NewStringObj("command execution failed", -1));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                tcl_result = Tcl_NewStringObj("command execution failed", -1);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* Report the exit event to our callback */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (event != NULL) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                status = SystemCmd_Callback_Invoke(callback, event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                SystemCmd_Event_Release(event);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if (status != TCL_OK)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    goto cleanup;
</span>             }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* print error */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ui_info(interp, "Command failed: %s", cmdstring);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if (WIFEXITED(ret)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ui_info(interp, "Exit code: %d", WEXITSTATUS(ret));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else if(WIFSIGNALED(ret)) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ui_info(interp, "Killed by signal: %d", WTERMSIG(ret));
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            /* Set the error result */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_SetObjErrorCode(interp, errorCode);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_SetObjResult(interp, tcl_result);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            Tcl_DecrRefCount(errorCode);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            status = TCL_ERROR;
</span>         }
     }
 
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -358,10 +663,13 @@ cleanup:
</span>     sigaction(SIGINT, &old_sa_int, NULL);
     sigaction(SIGQUIT, &old_sa_quit, NULL);
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    if (odup) {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        /* Cleanup. */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    /* Cleanup. */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (fdset[0] >= 0)
</span>         close(fdset[0]);
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    }
</span> 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (fdset[1] >= 0)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        close(fdset[1]);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    SystemCmd_Callback_Free(callback);
</span>     return status;
 }
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/Makefile.in b/src/port1.0/Makefile.in
</span><span style='display:block; white-space:pre;color:#808080;'>index 3ae95fc..f840d98 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/Makefile.in
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/Makefile.in
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -7,7 +7,7 @@ INSTALLDIR= ${TCL_PACKAGE_PATH}/port1.0
</span> 
 SRCS_AUTOCONF= port_autoconf.tcl
 SRCS=  port.tcl portchecksum.tcl portconfigure.tcl portextract.tcl         \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        portfetch.tcl portmain.tcl portbuild.tcl portpatch.tcl portutil.tcl \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+   portfetch.tcl portmain.tcl portbuild.tcl portpatch.tcl portprogress.tcl portutil.tcl \
</span>   portinstall.tcl portuninstall.tcl portdepends.tcl portdestroot.tcl \
        portlint.tcl portclean.tcl porttest.tcl portactivate.tcl portbump.tcl \
        portdeactivate.tcl portstartupitem.tcl porttrace.tcl portlivecheck.tcl \
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portbuild.tcl b/src/port1.0/portbuild.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 38e1f6c..c00b8a2 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/portbuild.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portbuild.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -32,6 +32,7 @@
</span> 
 package provide portbuild 1.0
 package require portutil 1.0
<span style='display:block; white-space:pre;background:#e0ffe0;'>+package require portprogress 1.0
</span> 
 set org.macports.build [target_new org.macports.build portbuild::build_main]
 target_provides ${org.macports.build} build
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -199,7 +200,7 @@ proc portbuild::build_main {args} {
</span> 
     set realcmd ${build.cmd}
     set build.cmd "${build.cmd}$jobs_suffix"
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    command_exec build
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    command_exec -callback portprogress::target_progress_callback build
</span>     set build.cmd ${realcmd}
     return 0
 }
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portconfigure.tcl b/src/port1.0/portconfigure.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 8b544c4..6d3dac3 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/portconfigure.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portconfigure.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -32,6 +32,7 @@
</span> 
 package provide portconfigure 1.0
 package require portutil 1.0
<span style='display:block; white-space:pre;background:#e0ffe0;'>+package require portprogress 1.0
</span> 
 set org.macports.configure [target_new org.macports.configure portconfigure::configure_main]
 target_provides ${org.macports.configure} configure
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -1522,32 +1523,34 @@ proc portconfigure::configure_main {args} {
</span>         global configure.${flags} configure.universal_${flags}
     }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    set callback [list "-callback" portprogress::target_progress_callback]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     if {[tbool use_autoreconf]} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[catch {command_exec autoreconf} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[catch {command_exec {*}${callback} autoreconf} result]} {
</span>             return -code error "[format [msgcat::mc "%s failure: %s"] autoreconf $result]"
         }
     }
 
     if {[tbool use_automake]} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[catch {command_exec automake} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[catch {command_exec {*}${callback} automake} result]} {
</span>             return -code error "[format [msgcat::mc "%s failure: %s"] automake $result]"
         }
     }
 
     if {[tbool use_autoconf]} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[catch {command_exec autoconf} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[catch {command_exec {*}${callback} autoconf} result]} {
</span>             return -code error "[format [msgcat::mc "%s failure: %s"] autoconf $result]"
         }
     }
 
     if {[tbool use_xmkmf]} {
         parse_environment xmkmf
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[catch {command_exec xmkmf} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[catch {command_exec {*}${callback} xmkmf} result]} {
</span>             return -code error "[format [msgcat::mc "%s failure: %s"] xmkmf $result]"
         }
 
         parse_environment xmkmf
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[catch {command_exec "cd ${worksrcpath} && make Makefiles" -varprefix xmkmf} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[catch {command_exec {*}${callback} "cd ${worksrcpath} && make Makefiles" -varprefix xmkmf} result]} {
</span>             return -code error "[format [msgcat::mc "%s failure: %s"] "make Makefiles" $result]"
         }
     } elseif {[tbool use_configure]} {
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -1637,7 +1640,7 @@ proc portconfigure::configure_main {args} {
</span>         }
 
         # Execute the command (with the new environment).
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[catch {command_exec configure} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[catch {command_exec {*}${callback} configure} result]} {
</span>             global configure.dir
             if {[file exists ${configure.dir}/config.log]} {
                 ui_error "[format [msgcat::mc "Failed to configure %s, consult %s/config.log"] [option subport] ${configure.dir}]"
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portdestroot.tcl b/src/port1.0/portdestroot.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 35c5f80..c863dce 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/portdestroot.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portdestroot.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -121,7 +121,7 @@ proc portdestroot::destroot_start {args} {
</span> }
 
 proc portdestroot::destroot_main {args} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    command_exec destroot
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    command_exec -callback portprogress::target_progress_callback destroot
</span>     return 0
 }
 
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portfetch.tcl b/src/port1.0/portfetch.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 7abac19..6eccec7 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/portfetch.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portfetch.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -536,7 +536,7 @@ proc portfetch::fetchfiles {args} {
</span>     if {$portverbose eq "yes"} {
         lappend fetch_options "--progress"
         lappend fetch_options "builtin"
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    } elseif {[llength [info commands ui_progress_download]] > 0} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    } else {
</span>         lappend fetch_options "--progress"
         lappend fetch_options "ui_progress_download"
     }
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portprogress.tcl b/src/port1.0/portprogress.tcl
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..f0165e7
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portprogress.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,117 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Copyright (c) 2019-2020 The MacPorts Project
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# All rights reserved.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Redistribution and use in source and binary forms, with or without
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# modification, are permitted provided that the following conditions
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# are met:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# 1. Redistributions of source code must retain the above copyright
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#    notice, this list of conditions and the following disclaimer.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# 2. Redistributions in binary form must reproduce the above copyright
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#    notice, this list of conditions and the following disclaimer in the
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#    documentation and/or other materials provided with the distribution.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# 3. Neither the name of Apple Inc. nor the names of its contributors
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#    may be used to endorse or promote products derived from this software
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#    without specific prior written permission.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# POSSIBILITY OF SUCH DAMAGE.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+package provide portprogress 1.0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+package require portutil 1.0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+package require Pextlib 1.0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+namespace eval portprogress {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # The time in milliseconds to wait before we switch our progress bar from
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # determinate to indeterminate
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable indeterminate_threshold    10000
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # The time (in milliseconds since epoch) since our progress callback last
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # produced a determinate progress update.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable indeterminate_timer        0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # If our progress callback should issue indeterminate progress updates
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable indeterminate              yes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # ninja ([<completed tasks>/<pending tasks>])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable ninja_line_re              {^\[([1-9][0-9]*)/([1-9][0-9]*)\].*}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # cmake makefiles ([<percentage>%])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable cmake_line_re              {^\[\s*([1-9][0-9]*)%\].*}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# A SystemCmd callback that parses common target progress formats to display
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# a progress bar
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc portprogress::target_progress_callback {event} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    global portverbose
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable indeterminate
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable indeterminate_timer
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable indeterminate_threshold
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable ninja_line_re
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable cmake_line_re
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {${portverbose}} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    switch -- [dict get $event type] {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        exec {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set indeterminate yes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set indeterminate_timer 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ui_progress_generic start
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        stdin {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set line [dict get $event line]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Try to parse the build line
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set determinate_match no
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set cur 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set total 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # ninja ([<completed tasks>/<pending tasks>])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {[regexp $ninja_line_re ${line} -> cur total]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set determinate_match yes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # cmake makefiles ([<percentage>%])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } elseif {[regexp $cmake_line_re ${line} -> cur]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set total 100
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set determinate_match yes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # No match
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set cur 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set total 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {${determinate_match}} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set indeterminate no
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set indeterminate_timer [clock milliseconds]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } elseif {!${indeterminate}} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set time_last $indeterminate_timer
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set time_now [clock milliseconds]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set time_diff [expr { $time_now - $time_last }]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if {${time_diff} >= ${indeterminate_threshold}} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    set indeterminate yes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {${determinate_match} || ${indeterminate}} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ui_progress_generic update ${cur} ${total}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        exit {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ui_progress_generic finish
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/porttest.tcl b/src/port1.0/porttest.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 816c158..c8ce4b6 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/porttest.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/porttest.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -3,6 +3,7 @@
</span> 
 package provide porttest 1.0
 package require portutil 1.0
<span style='display:block; white-space:pre;background:#e0ffe0;'>+package require portprogress 1.0
</span> 
 set org.macports.test [target_new org.macports.test porttest::test_main]
 target_provides ${org.macports.test} test
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -32,7 +33,7 @@ proc porttest::test_start {args} {
</span> proc porttest::test_main {args} {
     global subport test.run
     if {[tbool test.run]} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        command_exec test
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        command_exec -callback portprogress::target_progress_callback test
</span>     } else {
     return -code error [format [msgcat::mc "%s has no tests turned on. see 'test.run' in portfile(7)"] $subport]
     }
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portutil.tcl b/src/port1.0/portutil.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index d5c4ec4..2883a66 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/portutil.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portutil.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -368,33 +368,55 @@ proc command_string {command} {
</span> }
 
 # Given a command name, execute it with the options.
<span style='display:block; white-space:pre;background:#ffe0e0;'>-# command_exec command [-notty] [-varprefix variable_prefix] [command_prefix [command_suffix]]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# command_exec [-notty] [-callback proc] [-varprefix variable_prefix] command [command_prefix [command_suffix]]
</span> # command           name of the command
 # variable_prefix   name of the variable prefix to use (defaults to command)
 # command_prefix    additional command prefix (typically pipe command)
 # command_suffix    additional command suffix (typically redirection)
<span style='display:block; white-space:pre;background:#ffe0e0;'>-proc command_exec {command args} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    set varprefix "${command}"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc command_exec {args} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set callback ""
</span>     set notty ""
     set command_prefix ""
     set command_suffix ""
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    if {[llength $args] > 0} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[lindex $args 0] eq "-notty"} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set notty "-notty"
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set args [lrange $args 1 end]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    while {[llength $args] > 0} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        switch -glob -- [lindex $args 0] {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            -notty {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set notty "-notty"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set args [lrange $args 1 end]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            -callback {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set callback [lrange $args 0 1]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set args [lrange $args 2 end]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            -varprefix {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set varprefix [lindex $args 1]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set args [lrange $args 2 end]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            -* {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return -code error "unknown option [lindex $args 0]"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            -- {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set args [lrange $args 1 end]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                break
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            default {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                break
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span>         }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span> 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[lindex $args 0] eq "-varprefix"} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set varprefix [lindex $args 1]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set args [lrange $args 2 end]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {[llength $args] == 0} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return -code error "Missing command argument"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span> 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[llength $args] > 0} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set command_prefix [lindex $args 0]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            if {[llength $args] > 1} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                set command_suffix [lindex $args 1]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set command [lindex $args 0]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set varprefix "${command}"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {[llength $args] > 1} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set command_prefix [lindex $args 1]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[llength $args] > 2} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set command_suffix [lindex $args 2]
</span>         }
     }
 
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -453,7 +475,7 @@ proc command_exec {command args} {
</span>     # Call the command.
     set fullcmdstring "$command_prefix $cmdstring $command_suffix"
     ui_info "Executing: $fullcmdstring"
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    set code [catch {system {*}$notty {*}$nice $fullcmdstring} result]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set code [catch {system {*}$notty {*}$callback {*}$nice $fullcmdstring} result]
</span>     # Save variables in order to re-throw the same error code.
     set errcode $::errorCode
     set errinfo $::errorInfo
</pre><pre style='margin:0'>

</pre>