Boost logo

Boost-Commit :

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",


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk