diff options
Diffstat (limited to '')
-rw-r--r-- | src/boost/tools/build/src/engine/make1.cpp | 1515 |
1 files changed, 1515 insertions, 0 deletions
diff --git a/src/boost/tools/build/src/engine/make1.cpp b/src/boost/tools/build/src/engine/make1.cpp new file mode 100644 index 000000000..332b91cc5 --- /dev/null +++ b/src/boost/tools/build/src/engine/make1.cpp @@ -0,0 +1,1515 @@ +/* + * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc. + * + * This file is part of Jam - see jam.c for Copyright information. + */ + +/* This file is ALSO: + * Copyright 2022 René Ferdinand Rivera Morell + * Copyright 2001-2004 David Abrahams. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt) + */ + +/* + * make1.c - execute commands to bring targets up to date + * + * This module contains make1(), the entry point called by make() to recursively + * descend the dependency graph executing update actions as marked by make0(). + * + * External routines: + * make1() - execute commands to update a TARGET and all of its dependencies + * + * Internal routines, the recursive/asynchronous command executors: + * make1a() - recursively schedules dependency builds and then goes to + * MAKE1B + * make1b() - if nothing is blocking this target's build, proceed to + * MAKE1C + * make1c() - launch target's next command, or go to parents' MAKE1B + * if none + * make1c_closure() - handle command execution completion and go to MAKE1C + * + * Internal support routines: + * make1cmds() - turn ACTIONS into CMDs, grouping, splitting, etc. + * make1list() - turn a list of targets into a LIST, for $(<) and $(>) + * make1settings() - for vars with bound values, build up replacement lists + * make1bind() - bind targets that weren't bound in dependency analysis + */ + +#include "jam.h" +#include "make.h" + +#include "command.h" +#include "compile.h" +#include "execcmd.h" +#include "headers.h" +#include "lists.h" +#include "object.h" +#include "output.h" +#include "parse.h" +#include "rules.h" +#include "search.h" +#include "variable.h" +#include "output.h" +#include "startup.h" + +#include <assert.h> +#include <stdlib.h> + +#if !defined( NT ) || defined( __GNUC__ ) + #include <unistd.h> /* for unlink */ +#endif + +static CMD * make1cmds ( TARGET * ); +static LIST * make1list ( LIST *, const targets_uptr &, int32_t flags ); +static SETTINGS * make1settings ( struct module_t *, LIST * vars ); +static void make1bind ( TARGET * ); +static void push_cmds( CMDLIST * cmds, int32_t status ); +static int32_t cmd_sem_lock( TARGET * t ); +static void cmd_sem_unlock( TARGET * t ); + +static bool targets_contains( const targets_uptr & l, TARGET * t ); +static bool targets_equal( const targets_uptr & l1, const targets_uptr & l2 ); + +/* Ugly static - it is too hard to carry it through the callbacks. */ + +static struct +{ + int32_t failed; + int32_t skipped; + int32_t total; + int32_t made; +} counts[ 1 ]; + +/* Target state. */ +#define T_STATE_MAKE1A 0 /* make1a() should be called */ +#define T_STATE_MAKE1B 1 /* make1b() should be called */ +#define T_STATE_MAKE1C 2 /* make1c() should be called */ + +typedef struct _state state; +struct _state +{ + state * prev; /* previous state on stack */ + TARGET * t; /* current target */ + TARGET * parent; /* parent argument necessary for MAKE1A */ + int32_t curstate; /* current state */ +}; + +static void make1a( state * const ); +static void make1b( state * const ); +static void make1c( state const * const ); + +static void make1c_closure( void * const closure, int32_t status, + timing_info const * const, char const * const cmd_stdout, + char const * const cmd_stderr, int32_t const cmd_exit_reason ); + +typedef struct _stack +{ + state * stack; +} stack; + +static stack state_stack = { NULL }; + +static state * state_freelist = NULL; + +/* Currently running command counter. */ +static int32_t cmdsrunning; + + +static state * alloc_state() +{ + if ( state_freelist ) + { + state * const pState = state_freelist; + state_freelist = pState->prev; + memset( pState, 0, sizeof( state ) ); + return pState; + } + return (state *)BJAM_MALLOC( sizeof( state ) ); +} + + +static void free_state( state * const pState ) +{ + pState->prev = state_freelist; + state_freelist = pState; +} + + +static void clear_state_freelist() +{ + while ( state_freelist ) + { + state * const pState = state_freelist; + state_freelist = state_freelist->prev; + BJAM_FREE( pState ); + } +} + + +static state * current_state( stack * const pStack ) +{ + return pStack->stack; +} + + +static void pop_state( stack * const pStack ) +{ + if ( pStack->stack ) + { + state * const pState = pStack->stack->prev; + free_state( pStack->stack ); + pStack->stack = pState; + } +} + + +static state * push_state( stack * const pStack, TARGET * const t, + TARGET * const parent, int32_t const curstate ) +{ + state * const pState = alloc_state(); + pState->t = t; + pState->parent = parent; + pState->prev = pStack->stack; + pState->curstate = curstate; + return pStack->stack = pState; +} + + +/* + * Pushes a stack onto another stack, effectively reversing the order. + */ + +static void push_stack_on_stack( stack * const pDest, stack * const pSrc ) +{ + while ( pSrc->stack ) + { + state * const pState = pSrc->stack; + pSrc->stack = pState->prev; + pState->prev = pDest->stack; + pDest->stack = pState; + } +} + + +/* + * make1() - execute commands to update a list of targets and all of their dependencies + */ + +static int32_t intr = 0; +static int32_t quit = 0; + +int32_t make1( LIST * targets ) +{ + state * pState; + int32_t status = 0; + + memset( (char *)counts, 0, sizeof( *counts ) ); + + { + LISTITER iter, end; + stack temp_stack = { NULL }; + for ( iter = list_begin( targets ), end = list_end( targets ); + iter != end; iter = list_next( iter ) ) + push_state( &temp_stack, bindtarget( list_item( iter ) ), NULL, T_STATE_MAKE1A ); + push_stack_on_stack( &state_stack, &temp_stack ); + } + + /* Clear any state left over from the past */ + quit = 0; + + /* Recursively make the target and its dependencies. */ + + while ( 1 ) + { + while ( ( pState = current_state( &state_stack ) ) ) + { + if ( quit ) + pop_state( &state_stack ); + + switch ( pState->curstate ) + { + case T_STATE_MAKE1A: make1a( pState ); break; + case T_STATE_MAKE1B: make1b( pState ); break; + case T_STATE_MAKE1C: make1c( pState ); break; + default: + assert( !"make1(): Invalid state detected." ); + } + } + if ( !cmdsrunning ) + break; + /* Wait for outstanding commands to finish running. */ + exec_wait(); + } + + clear_state_freelist(); + + /* Talk about it. */ + if ( counts->failed ) + out_printf( "...failed updating %d target%s...\n", counts->failed, + counts->failed > 1 ? "s" : "" ); + if ( DEBUG_MAKE && counts->skipped ) + out_printf( "...skipped %d target%s...\n", counts->skipped, + counts->skipped > 1 ? "s" : "" ); + if ( DEBUG_MAKE && counts->made ) + out_printf( "...updated %d target%s...\n", counts->made, + counts->made > 1 ? "s" : "" ); + + /* If we were interrupted, exit now that all child processes + have finished. */ + if ( intr ) + b2::clean_exit( EXITBAD ); + + { + LISTITER iter, end; + for ( iter = list_begin( targets ), end = list_end( targets ); + iter != end; iter = list_next( iter ) ) + { + /* Check that the target was updated and that the + update succeeded. */ + TARGET * t = bindtarget( list_item( iter ) ); + if (t->progress == T_MAKE_DONE) + { + if (t->status != EXEC_CMD_OK) + status = 1; + } + else if ( ! ( t->progress == T_MAKE_NOEXEC_DONE && globs.noexec ) ) + { + status = 1; + } + } + } + return status; +} + + +/* + * make1a() - recursively schedules dependency builds and then goes to MAKE1B + * + * Called to start processing a specified target. Does nothing if the target is + * already being processed or otherwise starts processing all of its + * dependencies. + */ + +static void make1a( state * const pState ) +{ + TARGET * t = pState->t; + TARGET * const scc_root = target_scc( t ); + + if ( !pState->parent || target_scc( pState->parent ) != scc_root ) + pState->t = t = scc_root; + + /* If the parent is the first to try to build this target or this target is + * in the MAKE1C quagmire, arrange for the parent to be notified when this + * target has been built. + */ + if ( pState->parent && t->progress <= T_MAKE_RUNNING ) + { + TARGET * const parent_scc = target_scc( pState->parent ); + if ( t != parent_scc ) + { + targetentry( t->parents, parent_scc ); + ++parent_scc->asynccnt; + } + } + + /* If the target has been previously updated with -n in effect, and we are + * now ignoring -n, update it for real. E.g. if the UPDATE_NOW rule was + * called for it twice - first with the -n option and then without. + */ + if ( !globs.noexec && t->progress == T_MAKE_NOEXEC_DONE ) + t->progress = T_MAKE_INIT; + + /* If this target is already being processed then do nothing. There is no + * need to start processing the same target all over again. + */ + if ( t->progress != T_MAKE_INIT ) + { + pop_state( &state_stack ); + return; + } + + /* Guard against circular dependencies. */ + t->progress = T_MAKE_ONSTACK; + + /* 'asynccnt' counts the dependencies preventing this target from proceeding + * to MAKE1C for actual building. We start off with a count of 1 to prevent + * anything from happening until we can notify all dependencies that they + * are needed. This 1 is then accounted for when we enter MAKE1B ourselves, + * below. Without this if a dependency gets built before we finish + * processing all of our other dependencies our build might be triggered + * prematurely. + */ + t->asynccnt = 1; + + /* Push dependency build requests (to be executed in the natural order). */ + { + stack temp_stack = { NULL }; + targets_ptr c; + for ( c = t->depends.get(); c && !quit; c = c->next.get() ) + push_state( &temp_stack, c->target, t, T_STATE_MAKE1A ); + push_stack_on_stack( &state_stack, &temp_stack ); + } + + t->progress = T_MAKE_ACTIVE; + + /* Once all of our dependencies have started getting processed we can move + * onto MAKE1B. + */ + /* Implementation note: + * In theory this would be done by popping this state before pushing + * dependency target build requests but as a slight optimization we simply + * modify our current state and leave it on the stack instead. + */ + pState->curstate = T_STATE_MAKE1B; +} + + +/* + * make1b() - if nothing is blocking this target's build, proceed to MAKE1C + * + * Called after something stops blocking this target's build, e.g. that all of + * its dependencies have started being processed, one of its dependencies has + * been built or a semaphore this target has been waiting for is free again. + */ + +static void make1b( state * const pState ) +{ + TARGET * const t = pState->t; + 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 ) + { + return; + } + + /* Now ready to build target 't', if dependencies built OK. */ + + /* Collect status from dependencies. If -n was passed then act as though all + * dependencies built correctly (the only way they can fail is if UPDATE_NOW + * was called). If the dependencies can not be found or we got an interrupt, + * we can not get here. + */ + if ( !globs.noexec ) + { + targets_ptr c; + for ( c = t->depends.get(); c; c = c->next.get() ) + if ( c->target->status > t->status && !( c->target->flags & + T_FLAG_NOCARE ) ) + { + failed = c->target; + t->status = c->target->status; + } + } + + /* If an internal header node failed to build, we want to output the target + * that it failed on. + */ + if ( failed ) + failed_name = failed->flags & T_FLAG_INTERNAL + ? failed->failed + : object_str( failed->name ); + t->failed = failed_name; + + /* If actions for building any of the dependencies have failed, bail. + * Otherwise, execute all actions to make the current target. + */ + if ( ( t->status == EXEC_CMD_FAIL ) && t->actions ) + { + ++counts->skipped; + if ( ( t->flags & ( T_FLAG_RMOLD | T_FLAG_NOTFILE ) ) == T_FLAG_RMOLD ) + { + if ( !unlink( object_str( t->boundname ) ) ) + out_printf( "...removing outdated %s\n", object_str( t->boundname ) + ); + } + else + out_printf( "...skipped %s for lack of %s...\n", object_str( t->name ), + failed_name ); + } + + if ( t->status == EXEC_CMD_OK ) + switch ( t->fate ) + { + case T_FATE_STABLE: + case T_FATE_NEWER: + break; + + case T_FATE_CANTFIND: + case T_FATE_CANTMAKE: + t->status = EXEC_CMD_FAIL; + break; + + case T_FATE_ISTMP: + if ( DEBUG_MAKE ) + out_printf( "...using %s...\n", object_str( t->name ) ); + break; + + case T_FATE_TOUCHED: + case T_FATE_MISSING: + case T_FATE_NEEDTMP: + case T_FATE_OUTDATED: + case T_FATE_UPDATE: + case T_FATE_REBUILD: + /* Prepare commands for executing actions scheduled for this target. + * Commands have their embedded variables automatically expanded, + * including making use of any "on target" variables. + */ + if ( t->actions ) + { + ++counts->total; + if ( DEBUG_MAKE && !( counts->total % 100 ) ) + out_printf( "...on %dth target...\n", counts->total ); + + t->cmds = (char *)make1cmds( t ); + /* Update the target's "progress" so MAKE1C processing counts it + * among its successes/failures. + */ + t->progress = T_MAKE_RUNNING; + } + break; + + /* All valid fates should have been accounted for by now. */ + default: + err_printf( "ERROR: %s has bad fate %d", object_str( t->name ), + t->fate ); + b2::clean_exit( b2::exit_result::failure ); + } + + /* 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. + */ + + 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; + out_printf( "Delaying %s %s: %d targets not ready\n", object_str( cmd->rule->name ), object_str( t->boundname ), cmd->asynccnt ); + } +} + + +/* + * make1c() - launch target's next command, or go to parents' MAKE1B if none + * + * If there are (more) commands to run to build this target (and we have not hit + * an error running earlier comands) we launch the command using exec_cmd(). + * Command execution signals its completion in exec_wait() by calling our + * make1c_closure() callback. + * + * If there are no more commands to run, we collect the status from all the + * actions and report our completion to all the parents. + */ + +static void make1c( state const * const pState ) +{ + TARGET * const t = pState->t; + CMD * const cmd = (CMD *)t->cmds; + int32_t exec_flags = 0; + + 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 + * the popped state data after this as the same stack node might have + * been reused internally for some newly pushed state. + */ + 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; + + if ( ( globs.jobs == 1 ) && ( DEBUG_MAKEQ || + ( DEBUG_MAKE && !( cmd->rule->actions->flags & RULE_QUIETLY ) ) ) ) + { + OBJECT * action = cmd->rule->name; + OBJECT * target = list_front( lol_get( (LOL *)&cmd->args, 0 ) ); + + out_printf( "%s %s\n", object_str( action ), object_str( target ) ); + + /* Print out the command executed if given -d+2. */ + if ( DEBUG_EXEC ) + { + out_puts( cmd->buf->value ); + out_putc( '\n' ); + } + + /* We only need to flush the streams if there's likely to + * be a wait before it finishes. + */ + if ( ! globs.noexec && ! cmd->noop ) + { + out_flush(); + err_flush(); + } + } + else + { + exec_flags |= EXEC_CMD_QUIET; + } + + /* Execute the actual build command or fake it if no-op. */ + if ( globs.noexec || cmd->noop ) + { + timing_info time_info = { 0 }; + timestamp_current( &time_info.start ); + timestamp_copy( &time_info.end, &time_info.start ); + make1c_closure( t, EXEC_CMD_OK, &time_info, "", "", EXIT_OK ); + } + else + { + exec_cmd( cmd->buf, exec_flags, make1c_closure, t, cmd->shell ); + + /* Wait until under the concurrent command count limit. */ + /* FIXME: This wait could be skipped here and moved to just before + * trying to execute a command that would cross the command count + * limit. Note though that this might affect the order in which + * unrelated targets get built and would thus require that all + * affected Boost Build tests be updated. + */ + assert( 0 < globs.jobs ); + while ( cmdsrunning >= globs.jobs ) + exec_wait(); + } + } + else + { + /* Tally success/failure for those we tried to update. */ + if ( t->progress == T_MAKE_RUNNING ) + { + /* Invert OK/FAIL target status when FAIL_EXPECTED has been applied. */ + if ( t->flags & T_FLAG_FAIL_EXPECTED && !globs.noexec ) + { + switch ( t->status ) + { + case EXEC_CMD_FAIL: t->status = EXEC_CMD_OK; break; + case EXEC_CMD_OK: t->status = EXEC_CMD_FAIL; break; + } + + /* Printing failure has to be delayed until the last + * action is completed for FAIL_EXPECTED targets. + * Do it here. + */ + if ( t->status == EXEC_CMD_FAIL ) + { + out_printf( "...failed %s ", object_str( t->actions->action->rule->name ) ); + out_printf( "%s", object_str( t->boundname ) ); + out_printf( "...\n" ); + } + + /* Handle -q */ + if ( t->status == EXEC_CMD_FAIL && globs.quitquick ) + ++quit; + + /* Delete the target on failure. */ + if ( !( t->flags & ( T_FLAG_PRECIOUS | T_FLAG_NOTFILE ) ) && + !unlink( object_str( t->boundname ) ) ) + out_printf( "...removing %s\n", object_str( t->boundname ) ); + } + switch ( t->status ) + { + case EXEC_CMD_OK: ++counts->made; break; + case EXEC_CMD_FAIL: ++counts->failed; break; + } + } + + /* Tell parents their dependency has been built. */ + { + targets_ptr c; + stack temp_stack = { NULL }; + TARGET * additional_includes = NULL; + + t->progress = globs.noexec ? T_MAKE_NOEXEC_DONE : T_MAKE_DONE; + + /* Target has been updated so rescan it for dependencies. */ + if ( t->fate >= T_FATE_MISSING && t->status == EXEC_CMD_OK && + !( t->flags & T_FLAG_INTERNAL ) ) + { + TARGET * saved_includes; + SETTINGS * s; + + /* Clean current includes. */ + saved_includes = t->includes; + t->includes = 0; + + s = copysettings( t->settings ); + pushsettings( root_module(), s ); + headers( t ); + popsettings( root_module(), s ); + freesettings( s ); + + if ( t->includes ) + { + /* Tricky. The parents have already been processed, but they + * have not seen the internal node, because it was just + * created. We need to: + * - push MAKE1A states that would have been pushed by the + * parents here + * - make sure all unprocessed parents will pick up the + * new includes + * - make sure processing the additional MAKE1A states is + * done before processing the MAKE1B state for our + * current target (which would mean this target has + * already been built), otherwise the parent would be + * considered built before the additional MAKE1A state + * processing even got a chance to start. + */ + make0( t->includes, t->parents->target, 0, 0, 0, t->includes + ); + /* Link the old includes on to make sure that it gets + * cleaned up correctly. + */ + t->includes->includes = saved_includes; + for ( c = t->dependants.get(); c; c = c->next.get() ) + targetentry( c->target->depends, t->includes ); + /* Will be processed below. */ + additional_includes = t->includes; + } + else + { + t->includes = saved_includes; + } + } + + if ( additional_includes ) + for ( c = t->parents.get(); c; c = c->next.get() ) + push_state( &temp_stack, additional_includes, c->target, + T_STATE_MAKE1A ); + + if ( t->scc_root ) + { + TARGET * const scc_root = target_scc( t ); + assert( scc_root->progress < T_MAKE_DONE ); + for ( c = t->parents.get(); c; c = c->next.get() ) + { + if ( target_scc( c->target ) == scc_root ) + push_state( &temp_stack, c->target, NULL, T_STATE_MAKE1B + ); + else + targetentry( scc_root->parents, c->target ); + } + } + else + { + for ( c = t->parents.get(); c; c = c->next.get() ) + push_state( &temp_stack, c->target, NULL, T_STATE_MAKE1B ); + } + + /* Must pop state before pushing any more. */ + pop_state( &state_stack ); + + /* Using stacks reverses the order of execution. Reverse it back. */ + push_stack_on_stack( &state_stack, &temp_stack ); + } + } +} + + +/* + * call_timing_rule() - Look up the __TIMING_RULE__ variable on the given + * target, and if non-empty, invoke the rule it names, passing the given + * timing_info. + */ + +static void call_timing_rule( TARGET * target, timing_info const * const time ) +{ + LIST * timing_rule; + + pushsettings( root_module(), target->settings ); + timing_rule = var_get( root_module(), constant_TIMING_RULE ); + popsettings( root_module(), target->settings ); + + if ( !list_empty( timing_rule ) ) + { + /* rule timing-rule ( args * : target : start end user system clock ) */ + + /* Prepare the argument list. */ + FRAME frame[ 1 ]; + OBJECT * rulename = list_front( timing_rule ); + frame_init( frame ); + + /* args * :: $(__TIMING_RULE__[2-]) */ + lol_add( frame->args, list_copy_range( timing_rule, list_next( + list_begin( timing_rule ) ), list_end( timing_rule ) ) ); + + /* target :: the name of the target */ + lol_add( frame->args, list_new( object_copy( target->name ) ) ); + + /* start end user system clock :: info about the action command */ + lol_add( frame->args, list_push_back( list_push_back( list_push_back( list_push_back( list_new( + outf_time( &time->start ) ), + outf_time( &time->end ) ), + outf_double( time->user ) ), + outf_double( time->system ) ), + outf_double( timestamp_delta_seconds(&time->start, &time->end) ) ) + ); + + /* Call the rule. */ + evaluate_rule( bindrule( rulename , root_module() ), rulename, frame ); + + /* Clean up. */ + frame_free( frame ); + } +} + + +/* + * call_action_rule() - Look up the __ACTION_RULE__ variable on the given + * target, and if non-empty, invoke the rule it names, passing the given info, + * timing_info, executed command and command output. + */ + +static void call_action_rule +( + TARGET * target, + int32_t status, + timing_info const * time, + char const * executed_command, + char const * command_output +) +{ + LIST * action_rule; + + pushsettings( root_module(), target->settings ); + action_rule = var_get( root_module(), constant_ACTION_RULE ); + popsettings( root_module(), target->settings ); + + if ( !list_empty( action_rule ) ) + { + /* rule action-rule ( + args * : + target : + command status start end user system : + output ? ) */ + + /* Prepare the argument list. */ + FRAME frame[ 1 ]; + OBJECT * rulename = list_front( action_rule ); + frame_init( frame ); + + /* args * :: $(__ACTION_RULE__[2-]) */ + lol_add( frame->args, list_copy_range( action_rule, list_next( + list_begin( action_rule ) ), list_end( action_rule ) ) ); + + /* target :: the name of the target */ + lol_add( frame->args, list_new( object_copy( target->name ) ) ); + + /* command status start end user system :: info about the action command + */ + lol_add( frame->args, + list_push_back( list_push_back( list_push_back( list_push_back( list_push_back( list_new( + object_new( executed_command ) ), + outf_int( status ) ), + outf_time( &time->start ) ), + outf_time( &time->end ) ), + outf_double( time->user ) ), + outf_double( time->system ) ) ); + + /* output ? :: the output of the action command */ + if ( command_output ) + { + OBJECT * command_output_obj = object_new( command_output ); + char * output_i = (char*)object_str(command_output_obj); + /* Clean the output of control characters. */ + for (; *output_i; ++output_i) + { + if (iscntrl(*output_i) && !isspace(*output_i)) *output_i = '?'; + } + lol_add( frame->args, list_new( command_output_obj ) ); + } + else + lol_add( frame->args, L0 ); + + /* Call the rule. */ + evaluate_rule( bindrule( rulename, root_module() ), rulename, frame ); + + /* Clean up. */ + frame_free( frame ); + } +} + + +/* + * make1c_closure() - handle command execution completion and go to MAKE1C. + * + * Internal function passed as a notification callback for when a command + * finishes getting executed by the OS or called directly when faking that a + * command had been executed by the OS. + * + * Now all we need to do is fiddle with the command exit status and push a new + * MAKE1C state to execute the next command scheduled for building this target + * or close up the target's build process in case there are no more commands + * scheduled for it. On interrupts, we bail heavily. + */ + +static void make1c_closure +( + void * const closure, + int32_t status_orig, + timing_info const * const time, + char const * const cmd_stdout, + char const * const cmd_stderr, + int32_t const cmd_exit_reason +) +{ + TARGET * const t = (TARGET *)closure; + CMD * const cmd = (CMD *)t->cmds; + char const * rule_name = 0; + char const * target_name = 0; + + assert( cmd ); + + --cmdsrunning; + + /* Calculate the target's status from the cmd execution result. */ + { + /* Store the target's status. */ + t->status = status_orig; + + /* Ignore failures for actions marked as 'ignore'. */ + if ( t->status == EXEC_CMD_FAIL && cmd->rule->actions->flags & + RULE_IGNORE ) + t->status = EXEC_CMD_OK; + } + + if ( DEBUG_MAKEQ || + ( DEBUG_MAKE && !( cmd->rule->actions->flags & RULE_QUIETLY ) ) ) + { + rule_name = object_str( cmd->rule->name ); + target_name = object_str( list_front( lol_get( (LOL *)&cmd->args, 0 ) ) + ); + } + + if ( rule_name == NULL || globs.jobs > 1 ) + out_action( rule_name, target_name, cmd->buf->value, cmd_stdout, + cmd_stderr, cmd_exit_reason ); + + /* If the process expired, make user aware with an explicit message, but do + * this only for non-quiet actions. + */ + if ( cmd_exit_reason == EXIT_TIMEOUT && target_name ) + out_printf( "%ld second time limit exceeded\n", globs.timeout ); + + out_flush(); + err_flush(); + + if ( !globs.noexec ) + { + call_timing_rule( t, time ); + if ( DEBUG_EXECCMD ) + out_printf( "%f sec system; %f sec user; %f sec clock\n", + time->system, time->user, + timestamp_delta_seconds(&time->start, &time->end) ); + + /* Assume -p0 is in effect, i.e. cmd_stdout contains merged output. */ + call_action_rule( t, status_orig, time, cmd->buf->value, cmd_stdout ); + } + + /* Print command text on failure. */ + if ( t->status == EXEC_CMD_FAIL && DEBUG_MAKE && + ! ( t->flags & T_FLAG_FAIL_EXPECTED ) ) + { + if ( !DEBUG_EXEC ) + out_printf( "%s\n", cmd->buf->value ); + + out_printf( "...failed %s ", object_str( cmd->rule->name ) ); + list_print( lol_get( (LOL *)&cmd->args, 0 ) ); + out_printf( "...\n" ); + } + + /* On interrupt, set quit so _everything_ fails. Do the same for failed + * commands if we were asked to stop the build in case of any errors. + */ + if ( t->status == EXEC_CMD_INTR ) + { + ++intr; + ++quit; + } + if ( t->status == EXEC_CMD_FAIL && globs.quitquick && + ! ( t->flags & T_FLAG_FAIL_EXPECTED ) ) + ++quit; + + /* If the command was not successful remove all of its targets not marked as + * "precious". + */ + if ( t->status != EXEC_CMD_OK ) + { + LIST * const targets = lol_get( (LOL *)&cmd->args, 0 ); + LISTITER iter = list_begin( targets ); + LISTITER const end = list_end( targets ); + for ( ; iter != end; iter = list_next( iter ) ) + { + char const * const filename = object_str( list_item( iter ) ); + TARGET const * const t = bindtarget( list_item( iter ) ); + if ( !( t->flags & T_FLAG_PRECIOUS ) && !unlink( filename ) ) + out_printf( "...removing %s\n", filename ); + } + } + +#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 = NULL; + push_cmds( cmd->next, t->status ); + cmd_free( cmd ); +} + +/* push the next MAKE1C state after a command is run. */ +static void push_cmds( CMDLIST * cmds, int32_t 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 ) ) ); + out_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 ); + } + } +} + + +/* + * swap_settings() - replace the settings from the current module and target + * with those from the new module and target + */ + +static void swap_settings +( + module_t * * current_module, + TARGET * * current_target, + module_t * new_module, + TARGET * new_target +) +{ + if ( ( new_target == *current_target ) && + ( new_module == *current_module ) ) + return; + + if ( *current_target ) + popsettings( *current_module, (*current_target)->settings ); + + if ( new_target ) + pushsettings( new_module, new_target->settings ); + + *current_module = new_module; + *current_target = new_target; +} + + +/* + * make1cmds() - turn ACTIONS into CMDs, grouping, splitting, etc. + * + * Essentially copies a chain of ACTIONs to a chain of CMDs, grouping + * RULE_TOGETHER actions, splitting RULE_PIECEMEAL actions, and handling + * RULE_NEWSRCS actions. The result is a chain of CMDs which has already had all + * of its embedded variable references expanded and can now be executed using + * exec_cmd(). + */ + +static CMD * make1cmds( TARGET * t ) +{ + CMD * cmds = 0; + CMD * last_cmd = 0; + LIST * shell = L0; + module_t * settings_module = 0; + TARGET * settings_target = 0; + ACTIONS * a0 = 0; + int32_t 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_ptr 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 = (CMD *)a0->action->first_cmd; + if( cmds ) + { + last_cmd->next = cmdlist_append_cmd( last_cmd->next, first ); + } + else + { + cmds = first; + } + last_cmd = (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. + */ + { + int32_t const length = list_length( ns ); + int32_t start = 0; + int32_t chunk = length; + int32_t cmd_count = 0; + targets_uptr semaphores; + targets_ptr targets_iter; + int32_t unique_targets; + do + { + CMD * cmd; + int32_t cmd_check_result; + int32_t cmd_error_length; + int32_t cmd_error_max_length; + int32_t retry = 0; + int32_t accept_command = 0; + + /* Build cmd: cmd_new() takes ownership of its lists. */ + cmd = cmd_new( rule, list_copy( nt ), list_sublist( ns, start, + chunk ), list_copy( 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 ); + out_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. */ + out_puts( cmd->buf->value ); + b2::clean_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; + } + } + else + { + 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.get(); targets_iter; targets_iter = targets_iter->next.get() ) + { + 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.get(); targets_iter; targets_iter = targets_iter->next.get() ) + { + TARGET * sem = targets_iter->target->semaphore; + if ( sem ) + { + if ( ! targets_contains( semaphores, sem ) ) + targetentry( semaphores, sem ); + } + } + ( ( CMD * )a0->action->first_cmd )->lock = semaphores.get(); + ( ( CMD * )a0->action->last_cmd )->unlock = std::move(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; +} + + +/* + * make1list() - turn a list of targets into a LIST, for $(<) and $(>) + */ + +static LIST * make1list( LIST * l, const targets_uptr & ts, int32_t flags ) +{ + targets_ptr targets = ts.get(); + for ( ; targets; targets = targets->next.get() ) + { + TARGET * t = targets->target; + + if ( t->binding == T_BIND_UNBOUND ) + make1bind( t ); + + if ( ( flags & RULE_EXISTING ) && ( flags & RULE_NEWSRCS ) ) + { + if ( ( t->binding != T_BIND_EXISTS ) && + ( t->fate <= T_FATE_STABLE ) ) + continue; + } + else if ( flags & RULE_EXISTING ) + { + if ( t->binding != T_BIND_EXISTS ) + continue; + } + else if ( flags & RULE_NEWSRCS ) + { + if ( t->fate <= T_FATE_STABLE ) + continue; + } + + /* Prohibit duplicates for RULE_TOGETHER. */ + if ( flags & RULE_TOGETHER ) + { + LISTITER iter = list_begin( l ); + LISTITER const end = list_end( l ); + for ( ; iter != end; iter = list_next( iter ) ) + if ( object_equal( list_item( iter ), t->boundname ) ) + break; + if ( iter != end ) + continue; + } + + /* Build new list. */ + l = list_push_back( l, object_copy( t->boundname ) ); + } + + return l; +} + + +/* + * make1settings() - for vars with bound values, build up replacement lists + */ + +static SETTINGS * make1settings( struct module_t * module, LIST * vars ) +{ + SETTINGS * settings = 0; + + LISTITER vars_iter = list_begin( vars ); + LISTITER const vars_end = list_end( vars ); + for ( ; vars_iter != vars_end; vars_iter = list_next( vars_iter ) ) + { + LIST * const l = var_get( module, list_item( vars_iter ) ); + LIST * nl = L0; + LISTITER iter = list_begin( l ); + LISTITER const end = list_end( l ); + + for ( ; iter != end; iter = list_next( iter ) ) + { + TARGET * const t = bindtarget( list_item( iter ) ); + + /* Make sure the target is bound. */ + if ( t->binding == T_BIND_UNBOUND ) + make1bind( t ); + + /* Build a new list. */ + nl = list_push_back( nl, object_copy( t->boundname ) ); + } + + /* Add to settings chain. */ + settings = addsettings( settings, VAR_SET, list_item( vars_iter ), nl ); + } + + return settings; +} + + +/* + * make1bind() - bind targets that were not bound during dependency analysis + * + * Spot the kludge! If a target is not in the dependency tree, it did not get + * bound by make0(), so we have to do it here. Ugly. + */ + +static void make1bind( TARGET * t ) +{ + if ( t->flags & T_FLAG_NOTFILE ) + return; + + pushsettings( root_module(), t->settings ); + object_free( t->boundname ); + t->boundname = search( t->name, &t->time, 0, t->flags & T_FLAG_ISFILE ); + t->binding = timestamp_empty( &t->time ) ? T_BIND_MISSING : T_BIND_EXISTS; + popsettings( root_module(), t->settings ); +} + + +static bool targets_contains( const targets_uptr & ts, TARGET * t ) +{ + targets_ptr l = ts.get(); + for ( ; l; l = l->next.get() ) + { + if ( t == l->target ) + { + return true; + } + } + return false; +} + +static bool targets_equal( const targets_uptr & ts1, const targets_uptr & ts2 ) +{ + targets_ptr l1 = ts1.get(); + targets_ptr l2 = ts2.get(); + for ( ; l1 && l2; l1 = l1->next.get(), l2 = l2->next.get() ) + { + if ( l1->target != l2->target ) + return false; + } + return !l1 && !l2; +} + + +#ifdef OPT_SEMAPHORE + +static int32_t cmd_sem_lock( TARGET * t ) +{ + CMD * cmd = (CMD *)t->cmds; + targets_ptr iter; + /* Check whether all the semaphores required for updating + * this target are free. + */ + for ( iter = cmd->lock; iter; iter = iter->next.get() ) + { + if ( iter->target->asynccnt > 0 ) + { + if ( DEBUG_EXECCMD ) + out_printf( "SEM: %s is busy, delaying launch of %s\n", + object_str( iter->target->name ), object_str( t->name ) ); + targetentry( iter->target->parents, t ); + return 0; + } + } + /* Lock the semaphores. */ + for ( iter = cmd->lock; iter; iter = iter->next.get() ) + { + ++iter->target->asynccnt; + if ( DEBUG_EXECCMD ) + out_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_ptr iter; + /* Release the semaphores. */ + for ( iter = cmd->unlock.get(); iter; iter = iter->next.get() ) + { + if ( DEBUG_EXECCMD ) + out_printf( "SEM: %s is now free\n", object_str( + iter->target->name ) ); + --iter->target->asynccnt; + assert( iter->target->asynccnt <= 0 ); + } + for ( iter = cmd->unlock.get(); iter; iter = iter->next.get() ) + { + /* Find a waiting target that's ready */ + while ( iter->target->parents ) + { + TARGET * t1 = iter->target->parents->target; + + iter->target->parents = targets_pop(std::move(iter->target->parents)); + + if ( cmd_sem_lock( t1 ) ) + { + push_state( &state_stack, t1, NULL, T_STATE_MAKE1C ); + break; + } + } + } +} + +#endif |