2
0
mirror of https://github.com/boostorg/build.git synced 2026-01-19 16:12:14 +00:00
Files
build/src/engine/make1.cpp
René Ferdinand Rivera Morell fbb7fb175a Implement command database generation (i.e. compile_commands.json). (#399)
This implements obtaining and generating compile commands from toolsets that compile C or C++ sources. I.e. implements both the --command-database=json amd --command-database-out=<filename> CLI options. Although it implements the toolset changes for most compilers, only a few are tested.

Fixes #395
2024-06-02 10:38:38 -05:00

1552 lines
50 KiB
C++

/*
* 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 "events.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 "mod_summary.h"
#include <assert.h>
#include <stdlib.h>
#include <memory>
#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 ];
static std::unique_ptr<b2::summary> make_summary;
static const char * targets_failed = "targets failed";
static const char * targets_skipped = "targets skipped";
/* 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 */
namespace {
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 make_state_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 ) );
make_summary.reset(new b2::summary);
make_summary->group(targets_failed);
make_summary->group(targets_skipped);
{
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 ( DEBUG_MAKE && counts->made )
{
out_printf( "\n...updated %d target%s...\n", counts->made,
counts->made > 1 ? "s" : "" );
}
if ( DEBUG_MAKE && counts->skipped )
{
out_printf( "\n...skipped %d target%s...\n",
make_summary->count(targets_skipped),
make_summary->count(targets_skipped) > 1 ? "s" : "" );
make_summary->print(targets_skipped, " %s\n");
}
if ( counts->failed )
{
out_printf( "\n...failed updating %d target%s...\n",
make_summary->count(targets_failed),
make_summary->count(targets_failed) > 1 ? "s" : "" );
make_summary->print(targets_failed, " %s\n");
}
/* 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;
make_summary->message(targets_skipped, object_str( t->name ));
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;
}
// Signal that we are about to execute a command.
b2::trigger_event_pre_exec_cmd(pState->t);
/* 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. */
list_free( 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); // TODO: Fix this.
/* 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. */
list_free( 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" );
std::string m = object_str( cmd->rule->name );
for (auto i: b2::list_cref(lol_get( (LOL *)&cmd->args, 0 )))
{
m += " ";
m += i->str();
}
make_summary->message(targets_failed, m.c_str());
}
/* 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;
#ifdef 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