$include_dir="/home/hyper-archives/boost-commit/include"; include("$include_dir/msg-header.inc") ?>
Subject: [Boost-commit] svn:boost r86561 - in trunk/tools/build/v2: engine test
From: steven_at_[hidden]
Date: 2013-11-04 17:35:15
Author: steven_watanabe
Date: 2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)
New Revision: 86561
URL: http://svn.boost.org/trac/boost/changeset/86561
Log:
Make the handling of actions that produce multiple targets more correct.
Added:
   trunk/tools/build/v2/test/core_multifile_actions.py   (contents, props changed)
Text files modified: 
   trunk/tools/build/v2/engine/command.c               |    37 ++++                                    
   trunk/tools/build/v2/engine/command.h               |    29 +++                                     
   trunk/tools/build/v2/engine/compile.c               |    49 ----                                    
   trunk/tools/build/v2/engine/make.c                  |    29 +++                                     
   trunk/tools/build/v2/engine/make1.c                 |   353 ++++++++++++++++++++++++++++++--------- 
   trunk/tools/build/v2/engine/rules.h                 |     7                                         
   trunk/tools/build/v2/test/core_multifile_actions.py |   202 ++++++++++++++++++++++                  
   trunk/tools/build/v2/test/test_all.py               |     1                                         
   8 files changed, 572 insertions(+), 135 deletions(-)
Modified: trunk/tools/build/v2/engine/command.c
==============================================================================
--- trunk/tools/build/v2/engine/command.c	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/engine/command.c	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -24,6 +24,37 @@
 
 
 /*
+ * cmdlist_append_cmd
+ */
+CMDLIST * cmdlist_append_cmd( CMDLIST * l, CMD * cmd )
+{
+    CMDLIST * result = (CMDLIST *)BJAM_MALLOC( sizeof( CMDLIST ) );
+    result->iscmd = 1;
+    result->next = l;
+    result->impl.cmd = cmd;
+    return result;
+}
+
+CMDLIST * cmdlist_append_target( CMDLIST * l, TARGET * t )
+{
+    CMDLIST * result = (CMDLIST *)BJAM_MALLOC( sizeof( CMDLIST ) );
+    result->iscmd = 0;
+    result->next = l;
+    result->impl.t = t;
+    return result;
+}
+
+void cmdlist_free( CMDLIST * l )
+{
+    while ( l )
+    {
+        CMDLIST * tmp = l->next;
+        BJAM_FREE( l );
+        l = tmp;
+    }
+}
+
+/*
  * cmd_new() - return a new CMD.
  */
 
@@ -37,6 +68,10 @@
     cmd->shell = shell;
     cmd->next = 0;
     cmd->noop = 0;
+    cmd->asynccnt = 1;
+    cmd->status = 0;
+    cmd->lock = NULL;
+    cmd->unlock = NULL;
 
     lol_init( &cmd->args );
     lol_add( &cmd->args, targets );
@@ -62,9 +97,11 @@
 
 void cmd_free( CMD * cmd )
 {
+    cmdlist_free( cmd->next );
     lol_free( &cmd->args );
     list_free( cmd->shell );
     string_free( cmd->buf );
+    freetargets( cmd->unlock );
     BJAM_FREE( (void *)cmd );
 }
 
Modified: trunk/tools/build/v2/engine/command.h
==============================================================================
--- trunk/tools/build/v2/engine/command.h	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/engine/command.h	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -46,14 +46,41 @@
 
 
 typedef struct _cmd CMD;
+
+/*
+ * A list whose elements are either TARGETS or CMDS.
+ * CMDLIST is used only by CMD.  A TARGET means that
+ * the CMD is the last updating action required to
+ * build the target.  A CMD is the next CMD required
+ * to build the same target.  (Note that a single action
+ * can update more than one target, so the CMDs form
+ * a DAG, not a straight linear list.)
+ */
+typedef struct _cmdlist {
+    struct _cmdlist * next;
+    union {
+        CMD * cmd;
+        TARGET * t;
+    } impl;
+    char iscmd;
+} CMDLIST;
+
+CMDLIST * cmdlist_append_cmd( CMDLIST *, CMD * );
+CMDLIST * cmdlist_append_target( CMDLIST *, TARGET * );
+void cmd_list_free( CMDLIST * );
+
 struct _cmd
 {
-    CMD  * next;
+    CMDLIST * next;
     RULE * rule;      /* rule->actions contains shell script */
     LIST * shell;     /* $(JAMSHELL) value */
     LOL    args;      /* LISTs for $(<), $(>) */
     string buf[ 1 ];  /* actual commands */
     int    noop;      /* no-op commands should be faked instead of executed */
+    int    asynccnt;  /* number of outstanding dependencies */
+    TARGETS * lock;   /* semaphores that are required by this cmd. */
+    TARGETS * unlock; /* semaphores that are released when this cmd finishes. */
+    char   status;    /* the command status */
 };
 
 CMD * cmd_new
Modified: trunk/tools/build/v2/engine/compile.c
==============================================================================
--- trunk/tools/build/v2/engine/compile.c	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/engine/compile.c	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -117,56 +117,17 @@
         action->refs = 1;
 
         /* If we have a group of targets all being built using the same action
-         * then we must not allow any of them to be used as sources unless they
-         * are all up to date and their action does not need to be run or their
-         * action has had a chance to finish its work and build all of them
-         * anew.
-         *
-         * Without this it might be possible, in case of a multi-process build,
-         * for their action, triggered to building one of the targets, to still
-         * be running when another target in the group reports as done in order
-         * to avoid triggering the same action again and gets used prematurely.
-         *
-         * As a quick-fix to achieve this effect we make all the targets list
-         * each other as 'included targets'. More precisely, we mark the first
-         * listed target as including all the other targets in the list and vice
-         * versa. This makes anyone depending on any of those targets implicitly
-         * depend on all of them, thus making sure none of those targets can be
-         * used as sources until all of them have been built. Note that direct
-         * dependencies could not have been used due to the 'circular
-         * dependency' issue.
-         *
-         * TODO: Although the current implementation solves the problem of one
-         * of the targets getting used before its action completes its work, it
-         * also forces the action to run whenever any of the targets in the
-         * group is not up to date even though some of them might not actually
-         * be used by the targets being built. We should see how we can
-         * correctly recognize such cases and use that to avoid running the
-         * action if possible and not rebuild targets not actually depending on
-         * targets that are not up to date.
-         *
-         * TODO: Current solution using fake INCLUDES relations may cause
-         * actions to be run when the affected targets are built by multiple
-         * actions. E.g. if we have the following actions registered in the
-         * order specified:
-         *     (I) builds targets A & B
-         *     (II) builds target B
-         * and we want to build a target depending on target A, then both
-         * actions (I) & (II) will be run, even though the second one does not
-         * have any direct relationship to target A. Consider whether this is
-         * desired behaviour or not. It could be that Boost Build should (or
-         * possibly already does) run all actions registered for a given target
-         * if any of them needs to be run in which case our INCLUDES relations
-         * are not actually causing any actions to be run that would not have
-         * been run without them.
+         * and any of these targets is updated, then we have to consider them
+         * all to be out-dated.  We do this by adding a REBUILDS in both directions
+         * between the first target and all the other targets.
          */
         if ( action->targets )
         {
             TARGET * const t0 = action->targets->target;
             for ( t = action->targets->next; t; t = t->next )
             {
-                target_include( t->target, t0 );
-                target_include( t0, t->target );
+                t->target->rebuilds = targetentry( t->target->rebuilds, t0 );
+                t0->rebuilds = targetentry( t0->rebuilds, t->target );
             }
         }
 
Modified: trunk/tools/build/v2/engine/make.c
==============================================================================
--- trunk/tools/build/v2/engine/make.c	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/engine/make.c	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -161,6 +161,8 @@
  * make0() to be updated.
  */
 
+static void force_rebuilds( TARGET * t );
+
 static void update_dependants( TARGET * t )
 {
     TARGETS * q;
@@ -190,6 +192,8 @@
                 update_dependants( p );
         }
     }
+    /* Make sure that rebuilds can be chained. */
+    force_rebuilds( t );
 }
 
 
@@ -676,7 +680,30 @@
     else
         fate = t->fate;
 
-    /* Step 4g: If this target needs to be built, force rebuild everything in
+    /*
+     * Step 4g: If this target needs to be built, make0 all targets
+     * that are updated by the same actions used to update this target.
+     * These have already been marked as REBUILDS, and make1 has
+     * special handling for them.  We just need to make sure that
+     * they get make0ed.
+     */
+    if ( ( fate >= T_FATE_BUILD ) && ( fate < T_FATE_BROKEN ) )
+    {
+        ACTIONS * a;
+        TARGETS * c;
+        for ( a = t->actions; a; a = a->next )
+        {
+            for ( c = a->action->targets; c; c = c->next )
+            {
+                if ( c->target->fate == T_FATE_INIT )
+                {
+                    make0( c->target, ptime, depth + 1, counts, anyhow, rescanning );
+                }
+            }
+        }
+    }
+
+    /* Step 4h: If this target needs to be built, force rebuild everything in
      * its rebuilds list.
      */
     if ( ( fate >= T_FATE_BUILD ) && ( fate < T_FATE_BROKEN ) )
Modified: trunk/tools/build/v2/engine/make1.c
==============================================================================
--- trunk/tools/build/v2/engine/make1.c	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/engine/make1.c	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -63,6 +63,12 @@
 static void       make1bind      ( TARGET * );
 static TARGET   * make1findcycle ( TARGET * );
 static void       make1breakcycle( TARGET *, TARGET * cycle_root );
+static void       push_cmds( CMDLIST * cmds, int status );
+static int        cmd_sem_lock( TARGET * t );
+static void       cmd_sem_unlock( TARGET * t );
+
+static int targets_contains( TARGETS * l, TARGET * t );
+static int targets_equal( TARGETS * l1, TARGETS * l2 );
 
 /* Ugly static - it is too hard to carry it through the callbacks. */
 
@@ -372,33 +378,16 @@
     TARGET * failed = 0;
     char const * failed_name = "dependencies";
 
+    pop_state( &state_stack );
+
     /* If any dependencies are still outstanding, wait until they signal their
      * completion by pushing this same state for their parent targets.
      */
     if ( --t->asynccnt )
     {
-        pop_state( &state_stack );
         return;
     }
 
-    /* Try to aquire a semaphore. If it is locked, wait until the target that
-     * locked it is built and signals completition.
-     */
-#ifdef OPT_SEMAPHORE
-    if ( t->semaphore && t->semaphore->asynccnt )
-    {
-        /* Append 't' to the list of targets waiting on semaphore. */
-        t->semaphore->parents = targetentry( t->semaphore->parents, t );
-        t->asynccnt++;
-
-        if ( DEBUG_EXECCMD )
-            printf( "SEM: %s is busy, delaying launch of %s\n",
-                object_str( t->semaphore->name ), object_str( t->name ) );
-        pop_state( &state_stack );
-        return;
-    }
-#endif
-
     /* Now ready to build target 't', if dependencies built OK. */
 
     /* Collect status from dependencies. If -n was passed then act as though all
@@ -492,28 +481,19 @@
             abort();
         }
 
-#ifdef OPT_SEMAPHORE
-    /* If there is a semaphore, indicate that it is in use. */
-    if ( t->semaphore )
-    {
-        ++t->semaphore->asynccnt;
-        if ( DEBUG_EXECCMD )
-            printf( "SEM: %s now used by %s\n", object_str( t->semaphore->name
-                ), object_str( t->name ) );
-    }
-#endif
-
     /* Proceed to MAKE1C to begin executing the chain of commands prepared for
      * building the target. If we are not going to build the target (e.g. due to
      * dependency failures or no commands needing to be run) the chain will be
      * empty and MAKE1C processing will directly signal the target's completion.
      */
-    /* Implementation note:
-     *   Morfing the current state on the stack instead of popping it and
-     * pushing a new one is a slight optimization with no side-effects since we
-     * pushed no other states while processing this one.
-     */
-    pState->curstate = T_STATE_MAKE1C;
+
+    if ( t->cmds == NULL || --( ( CMD * )t->cmds )->asynccnt == 0 )
+        push_state( &state_stack, t, NULL, T_STATE_MAKE1C );
+    else if ( DEBUG_EXECCMD )
+    {
+        CMD * cmd = ( CMD * )t->cmds;
+        printf( "Delaying %s %s: %d targets not ready\n", object_str( cmd->rule->name ), object_str( t->boundname ), cmd->asynccnt );
+    }
 }
 
 
@@ -534,7 +514,7 @@
     TARGET * const t = pState->t;
     CMD * const cmd = (CMD *)t->cmds;
 
-    if ( cmd && t->status == EXEC_CMD_OK )
+    if ( cmd )
     {
         /* Pop state first in case something below (e.g. exec_cmd(), exec_wait()
          * or make1c_closure()) pushes a new state. Note that we must not access
@@ -543,6 +523,21 @@
          */
         pop_state( &state_stack );
 
+        if ( cmd->status != EXEC_CMD_OK )
+        {
+            t->cmds = NULL;
+            push_cmds( cmd->next, cmd->status );
+            cmd_free( cmd );
+            return;
+        }
+
+#ifdef OPT_SEMAPHORE
+        if ( ! cmd_sem_lock( t ) )
+        {
+            return;
+        }
+#endif
+
         /* Increment the jobs running counter. */
         ++cmdsrunning;
 
@@ -575,14 +570,6 @@
     {
         ACTIONS * actions;
 
-        /* Collect status from actions, and distribute it as well. */
-        for ( actions = t->actions; actions; actions = actions->next )
-            if ( actions->action->status > t->status )
-                t->status = actions->action->status;
-        for ( actions = t->actions; actions; actions = actions->next )
-            if ( t->status > actions->action->status )
-                actions->action->status = t->status;
-
         /* Tally success/failure for those we tried to update. */
         if ( t->progress == T_MAKE_RUNNING )
             switch ( t->status )
@@ -677,38 +664,6 @@
                     push_state( &temp_stack, c->target, NULL, T_STATE_MAKE1B );
             }
 
-#ifdef OPT_SEMAPHORE
-            /* If there is a semaphore, it is now free. */
-            if ( t->semaphore )
-            {
-                assert( t->semaphore->asynccnt == 1 );
-                --t->semaphore->asynccnt;
-
-                if ( DEBUG_EXECCMD )
-                    printf( "SEM: %s is now free\n", object_str(
-                        t->semaphore->name ) );
-
-                /* If anything is waiting, notify the next target. There is no
-                 * point in notifying all waiting targets, since they will be
-                 * notified again.
-                 */
-                if ( t->semaphore->parents )
-                {
-                    TARGETS * first = t->semaphore->parents;
-                    t->semaphore->parents = first->next;
-                    if ( first->next )
-                        first->next->tail = first->tail;
-
-                    if ( DEBUG_EXECCMD )
-                        printf( "SEM: placing %s on stack\n", object_str(
-                            first->target->name ) );
-                    push_state( &temp_stack, first->target, NULL, T_STATE_MAKE1B
-                        );
-                    BJAM_FREE( first );
-                }
-            }
-#endif
-
             /* Must pop state before pushing any more. */
             pop_state( &state_stack );
 
@@ -945,12 +900,57 @@
         }
     }
 
+#ifdef OPT_SEMAPHORE
+    /* Release any semaphores used by this action. */
+    cmd_sem_unlock( t );
+#endif
+
     /* Free this command and push the MAKE1C state to execute the next one
      * scheduled for building this same target.
      */
-    t->cmds = (char *)cmd_next( cmd );
+    t->cmds = NULL;
+    push_cmds( cmd->next, t->status );
     cmd_free( cmd );
-    push_state( &state_stack, t, NULL, T_STATE_MAKE1C );
+}
+
+/* push the next MAKE1C state after a command is run. */
+static void push_cmds( CMDLIST * cmds, int status )
+{
+    CMDLIST * cmd_iter;
+    for( cmd_iter = cmds; cmd_iter; cmd_iter = cmd_iter->next )
+    {
+        if ( cmd_iter->iscmd )
+        {
+            CMD * next_cmd = cmd_iter->impl.cmd;
+            /* Propagate the command status. */
+            if ( next_cmd->status < status )
+                next_cmd->status = status;
+            if ( --next_cmd->asynccnt == 0 )
+            {
+                /* Select the first target associated with the action.
+                 * This is safe because sibling CMDs cannot have targets
+                 * in common.
+                 */
+                TARGET * first_target = bindtarget( list_front( lol_get( &next_cmd->args, 0 ) ) );
+                first_target->cmds = (char *)next_cmd;
+                push_state( &state_stack, first_target, NULL, T_STATE_MAKE1C );
+            }
+            else if ( DEBUG_EXECCMD )
+            {
+                TARGET * first_target = bindtarget( list_front( lol_get( &next_cmd->args, 0 ) ) );
+                printf( "Delaying %s %s: %d targets not ready\n", object_str( next_cmd->rule->name ), object_str( first_target->boundname ), next_cmd->asynccnt );
+            }
+        }
+        else
+        {
+            /* This is a target that we're finished updating */
+            TARGET * updated_target = cmd_iter->impl.t;
+            if ( updated_target->status < status )
+                updated_target->status = status;
+            updated_target->cmds = NULL;
+            push_state( &state_stack, updated_target, NULL, T_STATE_MAKE1C );
+        }
+    }
 }
 
 
@@ -995,15 +995,14 @@
 static CMD * make1cmds( TARGET * t )
 {
     CMD * cmds = 0;
-    CMD * * cmds_next = &cmds;
+    CMD * last_cmd;
     LIST * shell = L0;
     module_t * settings_module = 0;
     TARGET * settings_target = 0;
     ACTIONS * a0;
     int const running_flag = globs.noexec ? A_RUNNING_NOEXEC : A_RUNNING;
 
-    /* Step through actions. Actions may be shared with other targets or grouped
-     * using RULE_TOGETHER, so actions already seen are skipped.
+    /* Step through actions.
      */
     for ( a0 = t->actions; a0; a0 = a0->next )
     {
@@ -1014,11 +1013,36 @@
         LIST         * ns;
         ACTIONS      * a1;
 
-        /* Only do rules with commands to execute. If this action has already
-         * been executed, use saved status.
+        /* Only do rules with commands to execute.
          */
-        if ( !actions || a0->action->running >= running_flag )
+        if ( !actions )
+            continue;
+
+        if ( a0->action->running >= running_flag )
+        {
+            CMD * first;
+            /* If this action was skipped either because it was
+             * combined with another action by RULE_TOGETHER, or
+             * because all of its sources were filtered out,
+             * then we don't have anything to do here.
+             */
+            if ( a0->action->first_cmd == NULL )
+                continue;
+            /* This action has already been processed for another target.
+             * Just set up the dependency graph correctly and move on.
+             */
+            first = a0->action->first_cmd;
+            if( cmds )
+            {
+                last_cmd->next = cmdlist_append_cmd( last_cmd->next, first );
+            }
+            else
+            {
+                cmds = first;
+            }
+            last_cmd = a0->action->last_cmd;
             continue;
+        }
 
         a0->action->running = running_flag;
 
@@ -1031,7 +1055,8 @@
         if ( actions->flags & RULE_TOGETHER )
             for ( a1 = a0->next; a1; a1 = a1->next )
                 if ( a1->action->rule == rule &&
-                    a1->action->running < running_flag )
+                    a1->action->running < running_flag &&
+                    targets_equal( a0->action->targets, a1->action->targets ) )
                 {
                     ns = make1list( ns, a1->action->sources, actions->flags );
                     a1->action->running = running_flag;
@@ -1076,8 +1101,12 @@
             int const length = list_length( ns );
             int start = 0;
             int chunk = length;
+            int cmd_count = 0;
             LIST * cmd_targets = L0;
             LIST * cmd_shell = L0;
+            TARGETS * semaphores = NULL;
+            TARGETS * targets_iter;
+            int unique_targets;
             do
             {
                 CMD * cmd;
@@ -1138,8 +1167,20 @@
                 if ( accept_command )
                 {
                     /* Chain it up. */
-                    *cmds_next = cmd;
-                    cmds_next = &cmd->next;
+                    if ( cmds )
+                    {
+                        last_cmd->next = cmdlist_append_cmd( last_cmd->next, cmd );
+                        last_cmd = cmd;
+                    }
+                    else
+                    {
+                        cmds = last_cmd = cmd;
+                    }
+
+                    if ( cmd_count++ == 0 )
+                    {
+                        a0->action->first_cmd = cmd;
+                    }
 
                     /* Mark lists we need recreated for the next command since
                      * they got consumed by the cmd object.
@@ -1160,6 +1201,39 @@
                     start += chunk;
             }
             while ( start < length );
+
+            /* Record the end of the actions cmds */
+            a0->action->last_cmd = last_cmd;
+
+            unique_targets = 0;
+            for ( targets_iter = a0->action->targets; targets_iter; targets_iter = targets_iter->next )
+            {
+                if ( targets_contains( targets_iter->next, targets_iter->target ) )
+                    continue;
+                /* Add all targets produced by the action to the update list. */
+                push_state( &state_stack, targets_iter->target, NULL, T_STATE_MAKE1A );
+                ++unique_targets;
+            }
+            /* We need to wait until all the targets agree that
+             * it's okay to run this action.
+             */
+            ( ( CMD * )a0->action->first_cmd )->asynccnt = unique_targets;
+
+#if OPT_SEMAPHORE
+            /* Collect semaphores */
+            for ( targets_iter = a0->action->targets; targets_iter; targets_iter = targets_iter->next )
+            {
+                TARGET * sem = targets_iter->target->semaphore;
+                if ( sem )
+                {
+                    TARGETS * semiter;
+                    if ( ! targets_contains( semaphores, sem ) )
+                        semaphores = targetentry( semaphores, sem );
+                }
+            }
+            ( ( CMD * )a0->action->first_cmd )->lock = semaphores;
+            ( ( CMD * )a0->action->last_cmd )->unlock = semaphores;
+#endif
         }
 
         /* These were always copied when used. */
@@ -1171,6 +1245,11 @@
         freesettings( boundvars );
     }
 
+    if ( cmds )
+    {
+        last_cmd->next = cmdlist_append_target( last_cmd->next, t );
+    }
+
     swap_settings( &settings_module, &settings_target, 0, 0 );
     return cmds;
 }
@@ -1281,3 +1360,101 @@
     t->binding = timestamp_empty( &t->time ) ? T_BIND_MISSING : T_BIND_EXISTS;
     popsettings( root_module(), t->settings );
 }
+
+
+static int targets_contains( TARGETS * l, TARGET * t )
+{
+    for ( ; l; l = l->next )
+    {
+        if ( t == l->target )
+        {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int targets_equal( TARGETS * l1, TARGETS * l2 )
+{
+    for ( ; l1 && l2; l1 = l1->next, l2 = l2->next )
+    {
+        if ( l1->target != l2->target )
+            return 0;
+    }
+    return !l1 && !l2;
+}
+
+
+#ifdef OPT_SEMAPHORE
+
+static int cmd_sem_lock( TARGET * t )
+{
+    CMD * cmd = (CMD *)t->cmds;
+    TARGETS * iter;
+    /* Check whether all the semaphores required for updating
+     * this target are free.
+     */
+    for ( iter = cmd->lock; iter; iter = iter->next )
+    {
+        if ( iter->target->asynccnt > 0 )
+        {
+            if ( DEBUG_EXECCMD )
+                printf( "SEM: %s is busy, delaying launch of %s\n",
+                    object_str( iter->target->name ), object_str( t->name ) );
+            iter->target->parents = targetentry( iter->target->parents, t );
+            return 0;
+        }
+    }
+    /* Lock the semaphores. */
+    for ( iter = cmd->lock; iter; iter = iter->next )
+    {
+        ++iter->target->asynccnt;
+        if ( DEBUG_EXECCMD )
+            printf( "SEM: %s now used by %s\n", object_str( iter->target->name
+                ), object_str( t->name ) );
+    }
+    /* A cmd only needs to be locked around its execution.
+     * clearing cmd->lock here makes it safe to call cmd_sem_lock
+     * twice.
+     */
+    cmd->lock = NULL;
+    return 1;
+}
+
+static void cmd_sem_unlock( TARGET * t )
+{
+    CMD * cmd = ( CMD * )t->cmds;
+    TARGETS * iter;
+    /* Release the semaphores. */
+    for ( iter = cmd->unlock; iter; iter = iter->next )
+    {
+        if ( DEBUG_EXECCMD )
+            printf( "SEM: %s is now free\n", object_str(
+                iter->target->name ) );
+        --iter->target->asynccnt;
+        assert( iter->target->asynccnt <= 0 );
+    }
+    for ( iter = cmd->unlock; iter; iter = iter->next )
+    {
+        /* Find a waiting target that's ready */
+        while ( iter->target->parents )
+        {
+            TARGETS * first = iter->target->parents;
+            TARGET * t1 = first->target;
+
+            /* Pop the first waiting CMD */
+            if ( first->next )
+                first->next->tail = first->tail;
+            iter->target->parents = first->next;
+            BJAM_FREE( first );
+
+            if ( cmd_sem_lock( t1 ) )
+            {
+                push_state( &state_stack, t1, NULL, T_STATE_MAKE1C );
+                break;
+            }
+        }
+    }
+}
+
+#endif
Modified: trunk/tools/build/v2/engine/rules.h
==============================================================================
--- trunk/tools/build/v2/engine/rules.h	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/engine/rules.h	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -92,8 +92,13 @@
 #define A_INIT           0
 #define A_RUNNING_NOEXEC 1
 #define A_RUNNING        2
-    char      status;         /* see TARGET status */
     int       refs;
+
+    /* WARNING: These variables are used to pass state required by make1cmds and
+     * are not valid anywhere else.
+     */
+    void    * first_cmd;      /* Pointer to the first CMD created by this action */
+    void    * last_cmd;       /* Pointer to the last CMD created by this action */
 };
 
 /* SETTINGS - variables to set when executing a TARGET's ACTIONS. */
Added: trunk/tools/build/v2/test/core_multifile_actions.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/tools/build/v2/test/core_multifile_actions.py	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -0,0 +1,202 @@
+#!/usr/bin/python
+
+# Copyright 2013 Steven Watanabe
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
+
+#   Tests that actions that produce multiple targets are handled
+# correctly.  The rules are as follows:
+#
+# - If any action that updates a target is run, then the target
+#   is considered to be out-of-date and all of its updating actions
+#   are run in order.
+# - A target is considered updated when all of its updating actions
+#   have completed successfully.
+# - If any updating action for a target fails, then the remaining
+#   actions are skipped and the target is marked as failed.
+#
+# Note that this is a more thorough test case for the same
+# problem that core_parallel_multifile_actions_N.py checks for.
+
+import BoostBuild
+
+t = BoostBuild.Tester(pass_toolset=0, pass_d0=False)
+
+t.write("file.jam", """
+actions update
+{
+    echo updating $(<)
+}
+
+update x1 x2 ;
+update x2 x3 ;
+""")
+
+# Updating x1 should force x2 to update as well.
+t.run_build_system(["-ffile.jam", "x1"], stdout="""\
+...found 3 targets...
+...updating 3 targets...
+update x1
+updating x1 x2
+update x2
+updating x2 x3
+...updated 3 targets...
+""")
+
+# If x1 is up-to-date, we don't need to update x2,
+# even though x2 is missing.
+t.write("x1", "")
+t.run_build_system(["-ffile.jam", "x1"], stdout="""\
+...found 1 target...
+""")
+
+# Building x3 should update x1 and x2, even though
+# x1 would be considered up-to-date, taken alone.
+t.run_build_system(["-ffile.jam", "x3"], stdout="""\
+...found 3 targets...
+...updating 2 targets...
+update x1
+updating x1 x2
+update x2
+updating x2 x3
+...updated 3 targets...
+""")
+
+# Updating x2 should succeed, but x3 should be skipped
+t.rm("x1")
+t.write("file.jam", """\
+actions update
+{
+    echo updating $(<)
+}
+actions fail
+{
+    echo failed $(<)
+    exit 1
+}
+
+update x1 x2 ;
+fail x1 ;
+update x1 x3 ;
+update x2 ;
+update x3 ;
+""")
+
+t.run_build_system(["-ffile.jam", "x3"], status=1, stdout="""\
+...found 3 targets...
+...updating 3 targets...
+update x1
+updating x1 x2
+fail x1
+failed x1
+
+    echo failed x1
+    exit 1
+
+...failed fail x1...
+update x2
+updating x2
+...failed updating 2 targets...
+...updated 1 target...
+""")
+
+# Make sure that dependencies of targets that are
+# updated as a result of a multifile action are
+# processed correctly.
+t.rm("x1")
+t.write("file.jam", """\
+actions update
+{
+    echo updating $(<)
+}
+
+update x1 ;
+update x2 ;
+DEPENDS x2 : x1 ;
+update x2 x3 ;
+""")
+t.run_build_system(["-ffile.jam", "x3"], stdout="""\
+...found 3 targets...
+...updating 3 targets...
+update x1
+updating x1
+update x2
+updating x2
+update x2
+updating x2 x3
+...updated 3 targets...
+""")
+
+# JAM_SEMAPHORE rules:
+#
+# - if two updating actions have targets that share a semaphore,
+#   these actions cannot be run in parallel.
+#
+t.write("file.jam", """\
+actions update
+{
+    echo updating $(<)
+}
+
+targets = x1 x2 ;
+JAM_SEMAPHORE on $(targets) = <s>update_sem ;
+update x1 x2 ;
+""")
+t.run_build_system(["-ffile.jam", "x1"], stdout="""\
+...found 2 targets...
+...updating 2 targets...
+update x1
+updating x1 x2
+...updated 2 targets...
+""")
+
+# A target can appear multiple times in an action
+t.write("file.jam", """\
+actions update
+{
+    echo updating $(<)
+}
+
+update x1 x1 ;
+""")
+t.run_build_system(["-ffile.jam", "x1"], stdout="""\
+...found 1 target...
+...updating 1 target...
+update x1
+updating x1 x1
+...updated 1 target...
+""")
+
+# Together actions should check that all the targets are the same
+# before combining.
+t.write("file.jam", """\
+actions together update
+{
+    echo updating $(<) : $(>)
+}
+
+update x1 x2 : s1 ;
+update x1 x2 : s2 ;
+
+update x3 : s3 ;
+update x3 x4 : s4 ;
+update x4 x3 : s5 ;
+DEPENDS all : x1 x2 x3 x4 ;
+""")
+t.run_build_system(["-ffile.jam"], stdout="""\
+...found 5 targets...
+...updating 4 targets...
+update x1
+updating x1 x2 : s1 s2
+update x3
+updating x3 : s3
+update x3
+updating x3 x4 : s4
+update x4
+updating x4 x3 : s5
+...updated 4 targets...
+""")
+
+
+
+t.cleanup()
Modified: trunk/tools/build/v2/test/test_all.py
==============================================================================
--- trunk/tools/build/v2/test/test_all.py	Mon Nov  4 07:53:53 2013	(r86560)
+++ trunk/tools/build/v2/test/test_all.py	2013-11-04 17:35:14 EST (Mon, 04 Nov 2013)	(r86561)
@@ -182,6 +182,7 @@
          "core_actions_quietly",
          "core_at_file",
          "core_bindrule",
+         "core_multifile_actions",
          "core_nt_cmd_line",
          "core_option_d2",
          "core_option_l",