static CMD * make1cmds( TARGET * t ) { CMD * cmds = 0; CMD * * cmds_next = &cmds; 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. */ for ( a0 = t->actions; a0; a0 = a0->next ) { RULE * rule = a0->action->rule; rule_actions * actions = rule->actions; SETTINGS * boundvars; LIST * nt; LIST * ns; ACTIONS * a1; /* Only do rules with commands to execute. If this action has already * been executed, use saved status. */ if ( !actions || a0->action->running >= running_flag ) continue; a0->action->running = running_flag; /* Make LISTS of targets and sources. If `execute together` has been * specified for this rule, tack on sources from each instance of this * rule for this target. */ nt = make1list( L0, a0->action->targets, 0 ); ns = make1list( L0, a0->action->sources, actions->flags ); if ( actions->flags & RULE_TOGETHER ) for ( a1 = a0->next; a1; a1 = a1->next ) if ( a1->action->rule == rule && a1->action->running < running_flag ) { ns = make1list( ns, a1->action->sources, actions->flags ); a1->action->running = running_flag; } /* If doing only updated (or existing) sources, but none have been * updated (or exist), skip this action. */ if ( list_empty( ns ) && ( actions->flags & ( RULE_NEWSRCS | RULE_EXISTING ) ) ) { list_free( nt ); continue; } swap_settings( &settings_module, &settings_target, rule->module, t ); if ( list_empty( shell ) ) { /* shell is per-target */ shell = var_get( rule->module, constant_JAMSHELL ); } /* If we had 'actions xxx bind vars' we bind the vars now. */ boundvars = make1settings( rule->module, actions->bindlist ); pushsettings( rule->module, boundvars ); /* * Build command, starting with all source args. * * For actions that allow PIECEMEAL commands, if the constructed command * string is too long, we retry constructing it with a reduced number of * source arguments presented. * * While reducing slowly takes a bit of compute time to get things just * right, it is worth it to get as close to maximum allowed command * string length as possible, because launching the commands we are * executing is likely to be much more compute intensive. * * Note that we loop through at least once, for sourceless actions. */ { int const length = list_length( ns ); int start = 0; int chunk = length; LIST * cmd_targets = L0; LIST * cmd_shell = L0; do { CMD * cmd; int cmd_check_result; int cmd_error_length; int cmd_error_max_length; int retry = 0; int accept_command = 0; /* Build cmd: cmd_new() takes ownership of its lists. */ if ( list_empty( cmd_targets ) ) cmd_targets = list_copy( nt ); if ( list_empty( cmd_shell ) ) cmd_shell = list_copy( shell ); cmd = cmd_new( rule, cmd_targets, list_sublist( ns, start, chunk ), cmd_shell ); cmd_check_result = exec_check( cmd->buf, &cmd->shell, &cmd_error_length, &cmd_error_max_length ); if ( cmd_check_result == EXEC_CHECK_OK ) { accept_command = 1; } else if ( cmd_check_result == EXEC_CHECK_NOOP ) { accept_command = 1; cmd->noop = 1; } else if ( ( actions->flags & RULE_PIECEMEAL ) && ( chunk > 1 ) ) { /* Too long but splittable. Reduce chunk size slowly and * retry. */ assert( cmd_check_result == EXEC_CHECK_TOO_LONG || cmd_check_result == EXEC_CHECK_LINE_TOO_LONG ); chunk = chunk * 9 / 10; retry = 1; } else { /* Too long and not splittable. */ char const * const error_message = cmd_check_result == EXEC_CHECK_TOO_LONG ? "is too long" : "contains a line that is too long"; assert( cmd_check_result == EXEC_CHECK_TOO_LONG || cmd_check_result == EXEC_CHECK_LINE_TOO_LONG ); printf( "%s action %s (%d, max %d):\n", object_str( rule->name ), error_message, cmd_error_length, cmd_error_max_length ); /* Tell the user what did not fit. */ fputs( cmd->buf->value, stdout ); exit( EXITBAD ); } assert( !retry || !accept_command ); if ( accept_command ) { /* Chain it up. */ *cmds_next = cmd; cmds_next = &cmd->next; /* Mark lists we need recreated for the next command since * they got consumed by the cmd object. */ cmd_targets = L0; cmd_shell = L0; } else { /* We can reuse targets & shell lists for the next command * if we do not let them die with this cmd object. */ cmd_release_targets_and_shell( cmd ); cmd_free( cmd ); } if ( !retry ) start += chunk; } while ( start < length ); } /* These were always copied when used. */ list_free( nt ); list_free( ns ); /* Free variables with values bound by 'actions xxx bind vars'. */ popsettings( rule->module, boundvars ); freesettings( boundvars ); } swap_settings( &settings_module, &settings_target, 0, 0 ); return cmds; }
static CMD * make1cmds( TARGET *t ) { CMD *cmds = 0; LIST *shell = 0; module_t *settings_module = 0; TARGET *settings_target = 0; /* Step through actions */ /* Actions may be shared with other targets or grouped with */ /* RULE_TOGETHER, so actions already seen are skipped. */ ACTIONS* a0; for(a0 = t->actions ; a0; a0 = a0->next ) { RULE *rule = a0->action->rule; rule_actions *actions = rule->actions; SETTINGS *boundvars; LIST *nt, *ns; ACTIONS *a1; int start, chunk, length; /* Only do rules with commands to execute. */ /* If this action has already been executed, use saved status */ if( !actions || a0->action->running ) continue; a0->action->running = 1; /* Make LISTS of targets and sources */ /* If `execute together` has been specified for this rule, tack */ /* on sources from each instance of this rule for this target. */ nt = make1list( L0, a0->action->targets, 0 ); ns = make1list( L0, a0->action->sources, actions->flags ); if( actions->flags & RULE_TOGETHER ) for( a1 = a0->next; a1; a1 = a1->next ) if( a1->action->rule == rule && !a1->action->running ) { ns = make1list( ns, a1->action->sources, actions->flags ); a1->action->running = 1; } /* If doing only updated (or existing) sources, but none have */ /* been updated (or exist), skip this action. */ if( !ns && ( actions->flags & ( RULE_NEWSRCS | RULE_EXISTING ) ) ) { list_free( nt ); continue; } swap_settings( &settings_module, &settings_target, rule->module, t ); if (!shell) shell = var_get( "JAMSHELL" ); /* shell is per-target */ /* If we had 'actions xxx bind vars' we bind the vars now */ boundvars = make1settings( actions->bindlist ); pushsettings( boundvars ); /* * Build command, starting with all source args. * * If cmd_new returns 0, it's because the resulting command * length is > MAXLINE. In this case, we'll slowly reduce * the number of source arguments presented until it does * fit. This only applies to actions that allow PIECEMEAL * commands. * * While reducing slowly takes a bit of compute time to get * things just right, it's worth it to get as close to MAXLINE * as possible, because launching the commands we're executing * is likely to be much more compute intensive! * * Note we loop through at least once, for sourceless actions. */ start = 0; chunk = length = list_length( ns ); do { /* Build cmd: cmd_new consumes its lists. */ CMD *cmd = cmd_new( rule, list_copy( L0, nt ), list_sublist( ns, start, chunk ), list_copy( L0, shell ) ); if( cmd ) { /* It fit: chain it up. */ if( !cmds ) cmds = cmd; else cmds->tail->next = cmd; cmds->tail = cmd; start += chunk; } else if( ( actions->flags & RULE_PIECEMEAL ) && chunk > 1 ) { /* Reduce chunk size slowly. */ chunk = chunk * 9 / 10; } else { /* Too long and not splittable. */ printf( "%s actions too long (max %d):\n", rule->name, MAXLINE ); /* Tell the user what didn't fit */ cmd = cmd_new( rule, list_copy( L0, nt ), list_sublist( ns, start, chunk ), list_new( L0, newstr( "%" ) ) ); printf( cmd->buf ); exit( EXITBAD ); } } while( start < length ); /* These were always copied when used. */ list_free( nt ); list_free( ns ); /* Free the variables whose values were bound by */ /* 'actions xxx bind vars' */ popsettings( boundvars ); freesettings( boundvars ); } swap_settings( &settings_module, &settings_target, 0, 0 ); return cmds; }
static CMD * make1cmds( TARGET * t ) { CMD * cmds = 0; 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. */ for ( a0 = t->actions; a0; a0 = a0->next ) { RULE * rule = a0->action->rule; rule_actions * actions = rule->actions; SETTINGS * boundvars; LIST * nt; LIST * ns; ACTIONS * a1; /* Only do rules with commands to execute. */ 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; /* Make LISTS of targets and sources. If `execute together` has been * specified for this rule, tack on sources from each instance of this * rule for this target. */ nt = make1list( L0, a0->action->targets, 0 ); ns = make1list( L0, a0->action->sources, actions->flags ); if ( actions->flags & RULE_TOGETHER ) for ( a1 = a0->next; a1; a1 = a1->next ) if ( a1->action->rule == rule && 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; } /* If doing only updated (or existing) sources, but none have been * updated (or exist), skip this action. */ if ( list_empty( ns ) && ( actions->flags & ( RULE_NEWSRCS | RULE_EXISTING ) ) ) { list_free( nt ); continue; } swap_settings( &settings_module, &settings_target, rule->module, t ); if ( list_empty( shell ) ) { /* shell is per-target */ shell = var_get( rule->module, constant_JAMSHELL ); } /* If we had 'actions xxx bind vars' we bind the vars now. */ boundvars = make1settings( rule->module, actions->bindlist ); pushsettings( rule->module, boundvars ); /* * Build command, starting with all source args. * * For actions that allow PIECEMEAL commands, if the constructed command * string is too long, we retry constructing it with a reduced number of * source arguments presented. * * While reducing slowly takes a bit of compute time to get things just * right, it is worth it to get as close to maximum allowed command * string length as possible, because launching the commands we are * executing is likely to be much more compute intensive. * * Note that we loop through at least once, for sourceless actions. */ { 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; int cmd_check_result; int cmd_error_length; int cmd_error_max_length; int retry = 0; int accept_command = 0; /* Build cmd: cmd_new() takes ownership of its lists. */ if ( list_empty( cmd_targets ) ) cmd_targets = list_copy( nt ); if ( list_empty( cmd_shell ) ) cmd_shell = list_copy( shell ); cmd = cmd_new( rule, cmd_targets, list_sublist( ns, start, chunk ), cmd_shell ); cmd_check_result = exec_check( cmd->buf, &cmd->shell, &cmd_error_length, &cmd_error_max_length ); if ( cmd_check_result == EXEC_CHECK_OK ) { accept_command = 1; } else if ( cmd_check_result == EXEC_CHECK_NOOP ) { accept_command = 1; cmd->noop = 1; } else if ( ( actions->flags & RULE_PIECEMEAL ) && ( chunk > 1 ) ) { /* Too long but splittable. Reduce chunk size slowly and * retry. */ assert( cmd_check_result == EXEC_CHECK_TOO_LONG || cmd_check_result == EXEC_CHECK_LINE_TOO_LONG ); chunk = chunk * 9 / 10; retry = 1; } else { /* Too long and not splittable. */ char const * const error_message = cmd_check_result == EXEC_CHECK_TOO_LONG ? "is too long" : "contains a line that is too long"; assert( cmd_check_result == EXEC_CHECK_TOO_LONG || cmd_check_result == EXEC_CHECK_LINE_TOO_LONG ); printf( "%s action %s (%d, max %d):\n", object_str( rule->name ), error_message, cmd_error_length, cmd_error_max_length ); /* Tell the user what did not fit. */ fputs( cmd->buf->value, stdout ); exit( EXITBAD ); } assert( !retry || !accept_command ); if ( accept_command ) { /* Chain it up. */ 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. */ cmd_targets = L0; cmd_shell = L0; } else { /* We can reuse targets & shell lists for the next command * if we do not let them die with this cmd object. */ cmd_release_targets_and_shell( cmd ); cmd_free( cmd ); } if ( !retry ) 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. */ list_free( nt ); list_free( ns ); /* Free variables with values bound by 'actions xxx bind vars'. */ popsettings( rule->module, boundvars ); freesettings( boundvars ); } if ( cmds ) { last_cmd->next = cmdlist_append_target( last_cmd->next, t ); } swap_settings( &settings_module, &settings_target, 0, 0 ); return cmds; }