From 5539d6e2b9034a01a80936b3bd28c517791900fc Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 12 Feb 2005 02:30:18 +0000 Subject: [PATCH] jam_src/ builtins.{c,h} Support for the REBUILDS rule remove unused variable rules.h make.c Support for the REBUILDS rule make1.c Support for the REBUILDS rule Support for recording timing information Restructured a case statement because it was masking a bug I introduced. execcmd.h execnt.c execunix.c Support for recording timing information Also removed NT-specific stuff from execunix expand.c Removed tabs from critical comment Added tab-width variable setting comment for emacs. v2/test rebuilds.py, timedata.py, test_all.py Tests for REBUILDS and timing. [SVN r27334] --- historic/jam/src/builtins.c | 40 +++++++- historic/jam/src/builtins.h | 1 + historic/jam/src/execcmd.h | 9 +- historic/jam/src/execnt.c | 186 ++++++++++++++++++++++++++++-------- historic/jam/src/execunix.c | 100 ++++--------------- historic/jam/src/expand.c | 36 ++++--- historic/jam/src/make.c | 78 ++++++++++++++- historic/jam/src/make1.c | 68 ++++++++++++- historic/jam/src/rules.h | 17 ++-- jam_src/builtins.c | 40 +++++++- jam_src/builtins.h | 1 + jam_src/execcmd.h | 9 +- jam_src/execnt.c | 186 ++++++++++++++++++++++++++++-------- jam_src/execunix.c | 100 ++++--------------- jam_src/expand.c | 36 ++++--- jam_src/make.c | 78 ++++++++++++++- jam_src/make1.c | 68 ++++++++++++- jam_src/rules.h | 17 ++-- v2/test/rebuilds.py | 48 ++++++++++ v2/test/test_all.py | 4 +- v2/test/timedata.py | 54 +++++++++++ 21 files changed, 871 insertions(+), 305 deletions(-) create mode 100644 v2/test/rebuilds.py create mode 100644 v2/test/timedata.py diff --git a/historic/jam/src/builtins.c b/historic/jam/src/builtins.c index 2d41a74fb..8049c2f45 100644 --- a/historic/jam/src/builtins.c +++ b/historic/jam/src/builtins.c @@ -113,6 +113,12 @@ load_builtins() bind_builtin( "INCLUDES" , builtin_depends, 1, 0 ) ); + { + char * args[] = { "targets", "*", ":", "targets-to-rebuild", "*", 0 }; + bind_builtin( "REBUILDS" , + builtin_rebuilds, 0, args ); + } + duplicate_rule( "Leaves" , bind_builtin( "LEAVES" , builtin_flags, T_FLAG_LEAVES, 0 ) ); @@ -376,7 +382,6 @@ builtin_depends( { LIST *targets = lol_get( frame->args, 0 ); LIST *sources = lol_get( frame->args, 1 ); - int which = parse->num; LIST *l; for( l = targets; l; l = list_next( l ) ) @@ -399,6 +404,39 @@ builtin_depends( t->depends = targetlist( t->depends, sources ); } + /* Enter reverse links */ + for( l = sources; l; l = list_next( l ) ) + { + TARGET *s = bindtarget( l->string ); + s->dependents = targetlist( s->dependents, targets ); + } + + return L0; +} + +/* + * builtin_rebuilds() - REBUILDS rule + * + * The REBUILDS builtin rule appends each of the listed + * rebuild-targets in its 2nd argument on the rebuilds list of each of + * the listed targets in its first argument. + */ + +LIST * +builtin_rebuilds( + PARSE *parse, + FRAME *frame ) +{ + LIST *targets = lol_get( frame->args, 0 ); + LIST *rebuilds = lol_get( frame->args, 1 ); + LIST *l; + + for( l = targets; l; l = list_next( l ) ) + { + TARGET *t = bindtarget( l->string ); + t->rebuilds = targetlist( t->rebuilds, rebuilds ); + } + return L0; } diff --git a/historic/jam/src/builtins.h b/historic/jam/src/builtins.h index c5b996f26..c94b00a1e 100644 --- a/historic/jam/src/builtins.h +++ b/historic/jam/src/builtins.h @@ -17,6 +17,7 @@ void load_builtins(); LIST *builtin_calc( PARSE *parse, FRAME *args ); LIST *builtin_depends( PARSE *parse, FRAME *args ); +LIST *builtin_rebuilds( PARSE *parse, FRAME *args ); LIST *builtin_echo( PARSE *parse, FRAME *args ); LIST *builtin_exit( PARSE *parse, FRAME *args ); LIST *builtin_flags( PARSE *parse, FRAME *args ); diff --git a/historic/jam/src/execcmd.h b/historic/jam/src/execcmd.h index 1e7a60835..9440dab7f 100644 --- a/historic/jam/src/execcmd.h +++ b/historic/jam/src/execcmd.h @@ -10,9 +10,16 @@ * 05/04/94 (seiwald) - async multiprocess interface */ +typedef struct timing_info +{ + /* double elapsed; */ /* We don't know how to get this number on Unix */ + double system; + double user; +} timing_info; + void execcmd( char *string, - void (*func)( void *closure, int status ), + void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ); diff --git a/historic/jam/src/execnt.c b/historic/jam/src/execnt.c index 97132faa6..d1741809f 100644 --- a/historic/jam/src/execnt.c +++ b/historic/jam/src/execnt.c @@ -16,6 +16,7 @@ # include # include # include +# include # ifdef USE_EXECNT @@ -71,7 +72,7 @@ static int is_win95_defined = 0; static struct { int pid; /* on win32, a real process handle */ - void (*func)( void *closure, int status ); + void (*func)( void *closure, int status, timing_info* ); void *closure; char *tempfile; @@ -446,6 +447,65 @@ static const char *getTempDir(void) return pTempPath; } +/* 64-bit arithmetic helpers */ + +/* Compute the carry bit from the addition of two 32-bit unsigned numbers */ +#define add_carry_bit(a, b) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 ) + +/* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2 */ +#define add_64_hi(h1, l1, h2, l2) ((h1) + (h2) + add_carry_bit(l1, l2)) + +/* Add two 64-bit unsigned numbers, h1l1 and h2l2 */ +static FILETIME add_64( + unsigned long h1, unsigned long l1, + unsigned long h2, unsigned long l2) +{ + FILETIME result; + result.dwLowDateTime = l1 + l2; + result.dwHighDateTime = add_64_hi(h1, l1, h2, l2); + + return result; +} + +static FILETIME add_FILETIME(FILETIME t1, FILETIME t2) +{ + return add_64( + t1.dwHighDateTime, t1.dwLowDateTime + , t2.dwHighDateTime, t2.dwLowDateTime); +} +static FILETIME negate_FILETIME(FILETIME t) +{ + /* 2s complement negation */ + return add_64(~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1); +} + +/* COnvert a FILETIME to a number of seconds */ +static double filetime_seconds(FILETIME t) +{ + return t.dwHighDateTime * (double)(1UL << 31) * 2 + t.dwLowDateTime * 1.0e-7; +} + +static void +record_times(int pid, timing_info* time) +{ + FILETIME creation, exit, kernel, user; + if (GetProcessTimes((HANDLE)pid, &creation, &exit, &kernel, &user)) + { + /* Compute the elapsed time */ +#if 0 /* We don't know how to get this number this on Unix */ + time->elapsed = filetime_seconds( + add_FILETIME( exit, negate_FILETIME(creation) ) + ); +#endif + + time->system = filetime_seconds(kernel); + time->user = filetime_seconds(user); + } + + CloseHandle((HANDLE)pid); +} + + /* * execcmd() - launch an async command execution */ @@ -453,7 +513,7 @@ static const char *getTempDir(void) void execcmd( char *string, - void (*func)( void *closure, int status ), + void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ) { @@ -613,6 +673,7 @@ execcmd( const char** keyword; int len, spawn = 1; int result; + timing_info time = {0,0}; for ( keyword = hard_coded; keyword[0]; keyword++ ) { @@ -654,12 +715,13 @@ execcmd( fprintf( stderr, "\n" ); #endif result = spawnvp( P_WAIT, args[0], args ); + record_times(result, &time); free_args( args ); } else result = 1; } - func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK ); + func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK, &time ); return; } @@ -718,19 +780,20 @@ execwait() int i; int status, w; int rstat; + timing_info time; /* Handle naive make1() which doesn't know if cmds are running. */ if( !cmdsrunning ) return 0; - if ( is_win95 ) - return 0; + if ( is_win95 ) + return 0; /* Pick up process pid and status */ - while( ( w = wait( &status ) ) == -1 && errno == EINTR ) - ; + while( ( w = wait( &status ) ) == -1 && errno == EINTR ) + ; if( w == -1 ) { @@ -751,6 +814,8 @@ execwait() exit( EXITBAD ); } + record_times(cmdtab[i].pid, &time); + /* Clear the temp file */ if ( cmdtab[i].tempfile ) unlink( cmdtab[ i ].tempfile ); @@ -774,62 +839,107 @@ execwait() free(cmdtab[i].tempfile); cmdtab[i].tempfile = NULL; } - (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat ); + (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); return 1; } # if !defined( __BORLANDC__ ) +/* The possible result codes from check_process_exit, below */ +typedef enum { process_error, process_active, process_finished } process_state; + +/* Helper for my_wait() below. Checks to see whether the process has + * exited and if so, records timing information. + */ +static process_state +check_process_exit( + HANDLE process /* The process we're looking at */ + + , int* status /* Storage for the finished process' exit + * code. If the process is still active + * this location is left untouched. */ + + , HANDLE* active_handles /* Storage for the process handle if it is + * found to be still active, or NULL. The + * process is treated as though it is + * complete. */ + + , int* num_active /* The current length of active_handles */ +) +{ + DWORD exitcode; + process_state result; + + /* Try to get the process exit code */ + if (!GetExitCodeProcess(process, &exitcode)) + { + result = process_error; /* signal an error */ + } + else if ( + exitcode == STILL_ACTIVE /* If the process is still active */ + && active_handles != 0 /* and we've been passed a place to buffer it */ + ) + { + active_handles[(*num_active)++] = process; /* push it onto the active stack */ + result = process_active; + } + else + { + *status = (int)((exitcode & 0xff) << 8); + result = process_finished; + } + + return result; +} + static int my_wait( int *status ) { int i, num_active = 0; DWORD exitcode, waitcode; - static HANDLE *active_handles = 0; - - if (!active_handles) - active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) ); + HANDLE active_handles[MAXJOBS]; /* first see if any non-waited-for processes are dead, * and return if so. */ - for ( i = 0; i < globs.jobs; i++ ) { - if ( cmdtab[i].pid ) { - if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) { - if ( exitcode == STILL_ACTIVE ) - active_handles[num_active++] = (HANDLE)cmdtab[i].pid; - else { - CloseHandle((HANDLE)cmdtab[i].pid); - *status = (int)((exitcode & 0xff) << 8); - return cmdtab[i].pid; - } - } - else - goto FAILED; + for ( i = 0; i < globs.jobs; i++ ) + { + int pid = cmdtab[i].pid; + + if ( pid ) + { + process_state state + = check_process_exit((HANDLE)pid, status, active_handles, &num_active); + + if ( state == process_error ) + goto FAILED; + else if ( state == process_finished ) + return pid; } } /* if a child exists, wait for it to die */ - if ( !num_active ) { + if ( !num_active ) + { errno = ECHILD; return -1; } + waitcode = WaitForMultipleObjects( num_active, - active_handles, - FALSE, - INFINITE ); - if ( waitcode != WAIT_FAILED ) { + active_handles, + FALSE, + INFINITE ); + if ( waitcode != WAIT_FAILED ) + { if ( waitcode >= WAIT_ABANDONED_0 - && waitcode < WAIT_ABANDONED_0 + num_active ) - i = waitcode - WAIT_ABANDONED_0; + && waitcode < WAIT_ABANDONED_0 + num_active ) + i = waitcode - WAIT_ABANDONED_0; else - i = waitcode - WAIT_OBJECT_0; - if ( GetExitCodeProcess(active_handles[i], &exitcode) ) { - CloseHandle(active_handles[i]); - *status = (int)((exitcode & 0xff) << 8); - return (int)active_handles[i]; - } + i = waitcode - WAIT_OBJECT_0; + + if ( check_process_exit(active_handles[i], status, 0, 0) == process_finished ) + return (int)active_handles[i]; } FAILED: diff --git a/historic/jam/src/execunix.c b/historic/jam/src/execunix.c index e450fdd12..13da13431 100644 --- a/historic/jam/src/execunix.c +++ b/historic/jam/src/execunix.c @@ -8,6 +8,7 @@ # include "lists.h" # include "execcmd.h" # include +# include #if defined(sun) || defined(__sun) #include /* need to include unistd.h on sun for the vfork prototype*/ @@ -15,24 +16,12 @@ #endif # ifdef USE_EXECUNIX +# include # ifdef NO_VFORK # define vfork() fork() # endif -# if defined( OS_NT ) || defined( OS_OS2 ) - -# define USE_EXECNT - -# include - -# if !defined( __BORLANDC__ ) && !defined( OS_OS2 ) -# define wait my_wait -static int my_wait( int *status ); -# endif - -# endif - /* * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS * @@ -71,13 +60,8 @@ static void (*istat)( int ); static struct { int pid; /* on win32, a real process handle */ - void (*func)( void *closure, int status ); + void (*func)( void *closure, int status, timing_info* ); void *closure; - -# ifdef USE_EXECNT - char *tempfile; -# endif - } cmdtab[ MAXJOBS ] = {{0}}; /* @@ -98,7 +82,7 @@ onintr( int disp ) void execcmd( char *string, - void (*func)( void *closure, int status ), + void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ) { @@ -106,10 +90,6 @@ execcmd( int slot; char *argv[ MAXARGC + 1 ]; /* +1 for NULL */ -# ifdef USE_EXECNT - char *p; -# endif - /* Find a slot in the running commands table for this one. */ for( slot = 0; slot < MAXJOBS; slot++ ) @@ -122,49 +102,6 @@ execcmd( exit( EXITBAD ); } -# ifdef USE_EXECNT - if( !cmdtab[ slot ].tempfile ) - { - char *tempdir; - - if( !( tempdir = getenv( "TEMP" ) ) && - !( tempdir = getenv( "TMP" ) ) ) - tempdir = "\\temp"; - - cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 14 ); - - sprintf( cmdtab[ slot ].tempfile, "%s\\jamtmp%02d.bat", - tempdir, slot ); - } - - /* Trim leading, ending white space */ - - while( isspace( *string ) ) - ++string; - - p = strchr( string, '\n' ); - - while( p && isspace( *p ) ) - ++p; - - /* If multi line, or too long, or JAMSHELL is set, write to bat file. */ - /* Otherwise, exec directly. */ - /* Frankly, if it is a single long line I don't think the */ - /* command interpreter will do any better -- it will fail. */ - - if( p && *p || strlen( string ) > MAXLINE || shell ) - { - FILE *f; - - /* Write command to bat file. */ - - f = fopen( cmdtab[ slot ].tempfile, "w" ); - fputs( string, f ); - fclose( f ); - - string = cmdtab[ slot ].tempfile; - } -# endif /* Forumulate argv */ /* If shell was defined, be prepared for % and ! subs. */ @@ -197,13 +134,8 @@ execcmd( } else { -# ifdef USE_EXECNT - argv[0] = "cmd.exe"; - argv[1] = "/Q/C"; /* anything more is non-portable */ -# else argv[0] = "/bin/sh"; argv[1] = "-c"; -# endif argv[2] = string; argv[3] = 0; } @@ -215,13 +147,6 @@ execcmd( /* Start the command */ -# ifdef USE_EXECNT - if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) - { - perror( "spawn" ); - exit( EXITBAD ); - } -# else if ((pid = vfork()) == 0) { execvp( argv[0], argv ); @@ -233,7 +158,7 @@ execcmd( perror( "vfork" ); exit( EXITBAD ); } -# endif + /* Save the operation for execwait() to find. */ cmdtab[ slot ].pid = pid; @@ -258,14 +183,17 @@ execwait() int i; int status, w; int rstat; - + timing_info time; + struct tms old_time, new_time; + /* Handle naive make1() which doesn't know if cmds are running. */ if( !cmdsrunning ) return 0; - /* Pick up process pid and status */ + times(&old_time); + /* Pick up process pid and status */ while( ( w = wait( &status ) ) == -1 && errno == EINTR ) ; @@ -276,6 +204,11 @@ execwait() exit( EXITBAD ); } + times(&new_time); + + time.system = (double)(new_time.tms_cstime - old_time.tms_cstime) / CLOCKS_PER_SEC; + time.user = (double)(new_time.tms_cutime - old_time.tms_cutime) / CLOCKS_PER_SEC; + /* Find the process in the cmdtab. */ for( i = 0; i < MAXJOBS; i++ ) @@ -288,6 +221,7 @@ execwait() exit( EXITBAD ); } + /* Drive the completion */ if( !--cmdsrunning ) @@ -302,7 +236,7 @@ execwait() cmdtab[ i ].pid = 0; - (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat ); + (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); return 1; } diff --git a/historic/jam/src/expand.c b/historic/jam/src/expand.c index fbe1f0ec6..87c14c4a2 100644 --- a/historic/jam/src/expand.c +++ b/historic/jam/src/expand.c @@ -136,14 +136,14 @@ expand: /* * Input so far (ignore blanks): * - * stuff-in-outbuf $(variable) remainder - * ^ ^ - * in end + * stuff-in-outbuf $(variable) remainder + * ^ ^ + * in end * Output so far: * - * stuff-in-outbuf $ - * ^ ^ - * out_buf out + * stuff-in-outbuf $ + * ^ ^ + * out_buf out * * * We just copied the $ of $(...), so back up one on the output. @@ -169,9 +169,9 @@ expand: /* * Input so far (ignore blanks): * - * stuff-in-outbuf $(variable) remainder - * ^ ^ ^ - * inp in end + * stuff-in-outbuf $(variable) remainder + * ^ ^ ^ + * inp in end */ prefix_length = buf->size; string_append_range( buf, inp, in - 1 ); @@ -190,14 +190,14 @@ expand: /* * Input so far (ignore blanks): * - * stuff-in-outbuf $(variable) remainder - * ^ ^ - * in end + * stuff-in-outbuf $(variable) remainder + * ^ ^ + * in end * Output so far: * - * stuff-in-outbuf variable - * ^ ^ ^ - * out_buf out ov + * stuff-in-outbuf variable + * ^ ^ ^ + * out_buf out ov * * Later we will overwrite 'variable' in out_buf, but we'll be * done with it by then. 'variable' may be a multi-element list, @@ -714,3 +714,9 @@ void var_expand_unit_test() lol_free(lol); } #endif + +/* + Local Variables: + tab-width: 8 + End: + */ diff --git a/historic/jam/src/make.c b/historic/jam/src/make.c index 83779eb09..a112855af 100644 --- a/historic/jam/src/make.c +++ b/historic/jam/src/make.c @@ -82,6 +82,7 @@ static const char *target_fate[] = "newer", /* T_FATE_NEWER */ "temp", /* T_FATE_ISTMP */ "touched", /* T_FATE_TOUCHED */ + "rebuild", /* T_FATE_REBUILD */ "missing", /* T_FATE_MISSING */ "needtmp", /* T_FATE_NEEDTMP */ "old", /* T_FATE_OUTDATED */ @@ -174,6 +175,63 @@ make( return status; } +/* Force any dependents of t that have already at least begun being + * visited by make0 to be updated. + */ +static void update_dependents(TARGET* t) +{ + TARGETS *q; + + for (q = t->dependents; q; q = q->next) + { + TARGET* p = q->target; + char fate0 = p->fate; + + /* If we've already at least begun visiting it and + * we're not already rebuilding it for other reasons + */ + if (fate0 != T_FATE_INIT && fate0 < T_FATE_BUILD) + { + p->fate = T_FATE_UPDATE; + + if (DEBUG_FATE) + { + printf( "fate change %s from %s to %s (as dependent of %s)\n", + p->name, target_fate[fate0], target_fate[p->fate], t->name); + } + + /* If we're done visiting it, go back and make sure its + * dependents get rebuilt. + */ + if (fate0 > T_FATE_MAKING) + update_dependents(p); + } + } +} + +/* Make sure that all of t's rebuilds get rebuilt */ +static void force_rebuilds(TARGET* t) +{ + TARGETS* d; + for (d = t->rebuilds; d; d = d->next) + { + TARGET* r = d->target; + + /* If it's not already being rebuilt for other reasons */ + if (r->fate < T_FATE_BUILD) + { + if (DEBUG_FATE) + printf( "fate change %s from %s to %s (by rebuild)\n", + r->name, target_fate[r->fate], target_fate[T_FATE_REBUILD]); + + /* Force rebuild it */ + r->fate = T_FATE_REBUILD; + + /* And make sure its dependents are updated too */ + update_dependents(r); + } + } +} /* * make0() - bind and scan everything to make a TARGET * @@ -362,7 +420,7 @@ make0( last = 0; leaf = 0; - fate = T_FATE_STABLE; + fate = T_FATE_STABLE; for( c = t->depends; c; c = c->next ) { @@ -515,7 +573,7 @@ make0( t->name, target_fate[fate], oldTimeStamp ? " (by timestamp)" : "" ); else - printf( "fate change %s adjusted from %s to %s%s\n", + printf( "fate change %s from %s to %s%s\n", t->name, target_fate[savedFate], target_fate[fate], oldTimeStamp ? " (by timestamp)" : "" ); #endif @@ -551,8 +609,19 @@ make0( t->time = max( t->time, last ); t->leaf = leaf ? leaf : t->time ; - t->fate = fate; + /* This target's fate may have been updated by virtue of following + * some target's rebuilds list, so only allow it to be increased + * to the fate we've calculated. Otherwise, grab its new fate. + */ + if (fate > t->fate) + t->fate = fate; + else + fate = t->fate; + /* Step 4g: if this target needs to be built, force rebuild + * everything in this target's rebuilds list */ + if (fate >= T_FATE_BUILD && fate < T_FATE_BROKEN) + force_rebuilds(t); /* * Step 5: sort dependents by their update time. */ @@ -674,6 +743,9 @@ dependGraphOutput( TARGET *t, int depth ) case T_FATE_OUTDATED: printf( " %s : Outdated, updating it\n", spaces(depth) ); break; + case T_FATE_REBUILD: + printf( " %s : Rebuild, Updating it\n", spaces(depth) ); + break; case T_FATE_UPDATE: printf( " %s : Updating it\n", spaces(depth) ); break; diff --git a/historic/jam/src/make1.c b/historic/jam/src/make1.c index 0d7423ea8..a8d04c5fb 100644 --- a/historic/jam/src/make1.c +++ b/historic/jam/src/make1.c @@ -61,6 +61,8 @@ # include "command.h" # include "execcmd.h" +# include + #if defined(sun) || defined(__sun) #include /* for unlink */ #endif @@ -101,7 +103,7 @@ static void make1atail(state *pState); static void make1b( state *pState ); static void make1c( state *pState ); static void make1d( state *pState ); -static void make_closure(void *closure, int status); +static void make_closure(void *closure, int status, timing_info*); typedef struct _stack { @@ -410,9 +412,10 @@ make1b( state *pState ) if( pState->t->status == EXEC_CMD_OK ) switch( pState->t->fate ) { + /* These are handled by the default case below now case T_FATE_INIT: case T_FATE_MAKING: - /* shouldn't happen */ + */ case T_FATE_STABLE: case T_FATE_NEWER: @@ -433,6 +436,7 @@ make1b( state *pState ) case T_FATE_NEEDTMP: case T_FATE_OUTDATED: case T_FATE_UPDATE: + case T_FATE_REBUILD: /* Set "on target" vars, build actions, unset vars */ /* Set "progress" so that make1c() counts this target among */ @@ -449,6 +453,11 @@ make1b( state *pState ) } break; + + /* All possible fates should have been accounted for by now */ + default: + printf("ERROR: %s has bad fate %d", pState->t->name, pState->t->fate); + abort(); } /* Call make1c() to begin the execution of the chain of commands */ @@ -655,9 +664,60 @@ make1c( state *pState ) } } -static void make_closure(void *closure, int status) +/* To l, append a 1-element list containing the string representation + * of x + */ +static void append_double_string( LOL *l, double x ) { - push_state(&state_stack, (TARGET *)closure, NULL, T_STATE_MAKE1D)->status = status; + char buffer[50]; + sprintf(buffer, "%f", x); + lol_add( l, list_new( L0, newstr( buffer ) ) ); +} + +/* 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* time) +{ + LIST* timing_rule; + + pushsettings(target->settings); + timing_rule = var_get( "__TIMING_RULE__" ); + popsettings(target->settings); + + if (timing_rule) + { + /* We'll prepend $(__TIMING_RULE__[2-]) to the first argument */ + LIST* initial_args = list_copy( L0, timing_rule->next ); + + /* Prepare the argument list */ + FRAME frame[1]; + frame_init( frame ); + + /* First argument is the name of the timed target */ + lol_add( frame->args, list_new( initial_args, target->name ) ); + append_double_string(frame->args, time->user); + append_double_string(frame->args, time->system); + + if( lol_get( frame->args, 2 ) ) + evaluate_rule( timing_rule->string, frame ); + + /* Clean up */ + frame_free( frame ); + } +} + +static void make_closure( + void *closure, int status, timing_info* time) +{ + TARGET* built = (TARGET*)closure; + + call_timing_rule(built, time); + if (DEBUG_EXECCMD) + printf("%f sec system; %f sec user\n", time->system, time->user); + + push_state(&state_stack, built, NULL, T_STATE_MAKE1D)->status = status; } /* diff --git a/historic/jam/src/rules.h b/historic/jam/src/rules.h index 833a8e84f..3dc21a3f1 100644 --- a/historic/jam/src/rules.h +++ b/historic/jam/src/rules.h @@ -178,6 +178,8 @@ struct _target { # define T_BIND_EXISTS 3 /* real file, timestamp valid */ TARGETS *depends; /* dependencies */ + TARGETS *dependents;/* the inverse of dependencies */ + TARGETS *rebuilds; /* targets that should be force-rebuilt whenever this one is */ TARGET *includes; /* includes */ TARGET *original_target; /* original_target->includes = this */ char rescanned; @@ -198,14 +200,15 @@ struct _target { # define T_FATE_BUILD 5 /* >= BUILD rebuilds target */ # define T_FATE_TOUCHED 5 /* manually touched with -t */ -# define T_FATE_MISSING 6 /* is missing, needs updating */ -# define T_FATE_NEEDTMP 7 /* missing temp that must be rebuild */ -# define T_FATE_OUTDATED 8 /* is out of date, needs updating */ -# define T_FATE_UPDATE 9 /* deps updated, needs updating */ +# define T_FATE_REBUILD 6 +# define T_FATE_MISSING 7 /* is missing, needs updating */ +# define T_FATE_NEEDTMP 8 /* missing temp that must be rebuild */ +# define T_FATE_OUTDATED 9 /* is out of date, needs updating */ +# define T_FATE_UPDATE 10 /* deps updated, needs updating */ -# define T_FATE_BROKEN 10 /* >= BROKEN ruins parents */ -# define T_FATE_CANTFIND 10 /* no rules to make missing target */ -# define T_FATE_CANTMAKE 11 /* can't find dependents */ +# define T_FATE_BROKEN 11 /* >= BROKEN ruins parents */ +# define T_FATE_CANTFIND 11 /* no rules to make missing target */ +# define T_FATE_CANTMAKE 12 /* can't find dependents */ char progress; /* tracks make1() progress */ diff --git a/jam_src/builtins.c b/jam_src/builtins.c index 2d41a74fb..8049c2f45 100644 --- a/jam_src/builtins.c +++ b/jam_src/builtins.c @@ -113,6 +113,12 @@ load_builtins() bind_builtin( "INCLUDES" , builtin_depends, 1, 0 ) ); + { + char * args[] = { "targets", "*", ":", "targets-to-rebuild", "*", 0 }; + bind_builtin( "REBUILDS" , + builtin_rebuilds, 0, args ); + } + duplicate_rule( "Leaves" , bind_builtin( "LEAVES" , builtin_flags, T_FLAG_LEAVES, 0 ) ); @@ -376,7 +382,6 @@ builtin_depends( { LIST *targets = lol_get( frame->args, 0 ); LIST *sources = lol_get( frame->args, 1 ); - int which = parse->num; LIST *l; for( l = targets; l; l = list_next( l ) ) @@ -399,6 +404,39 @@ builtin_depends( t->depends = targetlist( t->depends, sources ); } + /* Enter reverse links */ + for( l = sources; l; l = list_next( l ) ) + { + TARGET *s = bindtarget( l->string ); + s->dependents = targetlist( s->dependents, targets ); + } + + return L0; +} + +/* + * builtin_rebuilds() - REBUILDS rule + * + * The REBUILDS builtin rule appends each of the listed + * rebuild-targets in its 2nd argument on the rebuilds list of each of + * the listed targets in its first argument. + */ + +LIST * +builtin_rebuilds( + PARSE *parse, + FRAME *frame ) +{ + LIST *targets = lol_get( frame->args, 0 ); + LIST *rebuilds = lol_get( frame->args, 1 ); + LIST *l; + + for( l = targets; l; l = list_next( l ) ) + { + TARGET *t = bindtarget( l->string ); + t->rebuilds = targetlist( t->rebuilds, rebuilds ); + } + return L0; } diff --git a/jam_src/builtins.h b/jam_src/builtins.h index c5b996f26..c94b00a1e 100644 --- a/jam_src/builtins.h +++ b/jam_src/builtins.h @@ -17,6 +17,7 @@ void load_builtins(); LIST *builtin_calc( PARSE *parse, FRAME *args ); LIST *builtin_depends( PARSE *parse, FRAME *args ); +LIST *builtin_rebuilds( PARSE *parse, FRAME *args ); LIST *builtin_echo( PARSE *parse, FRAME *args ); LIST *builtin_exit( PARSE *parse, FRAME *args ); LIST *builtin_flags( PARSE *parse, FRAME *args ); diff --git a/jam_src/execcmd.h b/jam_src/execcmd.h index 1e7a60835..9440dab7f 100644 --- a/jam_src/execcmd.h +++ b/jam_src/execcmd.h @@ -10,9 +10,16 @@ * 05/04/94 (seiwald) - async multiprocess interface */ +typedef struct timing_info +{ + /* double elapsed; */ /* We don't know how to get this number on Unix */ + double system; + double user; +} timing_info; + void execcmd( char *string, - void (*func)( void *closure, int status ), + void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ); diff --git a/jam_src/execnt.c b/jam_src/execnt.c index 97132faa6..d1741809f 100644 --- a/jam_src/execnt.c +++ b/jam_src/execnt.c @@ -16,6 +16,7 @@ # include # include # include +# include # ifdef USE_EXECNT @@ -71,7 +72,7 @@ static int is_win95_defined = 0; static struct { int pid; /* on win32, a real process handle */ - void (*func)( void *closure, int status ); + void (*func)( void *closure, int status, timing_info* ); void *closure; char *tempfile; @@ -446,6 +447,65 @@ static const char *getTempDir(void) return pTempPath; } +/* 64-bit arithmetic helpers */ + +/* Compute the carry bit from the addition of two 32-bit unsigned numbers */ +#define add_carry_bit(a, b) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 ) + +/* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2 */ +#define add_64_hi(h1, l1, h2, l2) ((h1) + (h2) + add_carry_bit(l1, l2)) + +/* Add two 64-bit unsigned numbers, h1l1 and h2l2 */ +static FILETIME add_64( + unsigned long h1, unsigned long l1, + unsigned long h2, unsigned long l2) +{ + FILETIME result; + result.dwLowDateTime = l1 + l2; + result.dwHighDateTime = add_64_hi(h1, l1, h2, l2); + + return result; +} + +static FILETIME add_FILETIME(FILETIME t1, FILETIME t2) +{ + return add_64( + t1.dwHighDateTime, t1.dwLowDateTime + , t2.dwHighDateTime, t2.dwLowDateTime); +} +static FILETIME negate_FILETIME(FILETIME t) +{ + /* 2s complement negation */ + return add_64(~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1); +} + +/* COnvert a FILETIME to a number of seconds */ +static double filetime_seconds(FILETIME t) +{ + return t.dwHighDateTime * (double)(1UL << 31) * 2 + t.dwLowDateTime * 1.0e-7; +} + +static void +record_times(int pid, timing_info* time) +{ + FILETIME creation, exit, kernel, user; + if (GetProcessTimes((HANDLE)pid, &creation, &exit, &kernel, &user)) + { + /* Compute the elapsed time */ +#if 0 /* We don't know how to get this number this on Unix */ + time->elapsed = filetime_seconds( + add_FILETIME( exit, negate_FILETIME(creation) ) + ); +#endif + + time->system = filetime_seconds(kernel); + time->user = filetime_seconds(user); + } + + CloseHandle((HANDLE)pid); +} + + /* * execcmd() - launch an async command execution */ @@ -453,7 +513,7 @@ static const char *getTempDir(void) void execcmd( char *string, - void (*func)( void *closure, int status ), + void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ) { @@ -613,6 +673,7 @@ execcmd( const char** keyword; int len, spawn = 1; int result; + timing_info time = {0,0}; for ( keyword = hard_coded; keyword[0]; keyword++ ) { @@ -654,12 +715,13 @@ execcmd( fprintf( stderr, "\n" ); #endif result = spawnvp( P_WAIT, args[0], args ); + record_times(result, &time); free_args( args ); } else result = 1; } - func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK ); + func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK, &time ); return; } @@ -718,19 +780,20 @@ execwait() int i; int status, w; int rstat; + timing_info time; /* Handle naive make1() which doesn't know if cmds are running. */ if( !cmdsrunning ) return 0; - if ( is_win95 ) - return 0; + if ( is_win95 ) + return 0; /* Pick up process pid and status */ - while( ( w = wait( &status ) ) == -1 && errno == EINTR ) - ; + while( ( w = wait( &status ) ) == -1 && errno == EINTR ) + ; if( w == -1 ) { @@ -751,6 +814,8 @@ execwait() exit( EXITBAD ); } + record_times(cmdtab[i].pid, &time); + /* Clear the temp file */ if ( cmdtab[i].tempfile ) unlink( cmdtab[ i ].tempfile ); @@ -774,62 +839,107 @@ execwait() free(cmdtab[i].tempfile); cmdtab[i].tempfile = NULL; } - (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat ); + (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); return 1; } # if !defined( __BORLANDC__ ) +/* The possible result codes from check_process_exit, below */ +typedef enum { process_error, process_active, process_finished } process_state; + +/* Helper for my_wait() below. Checks to see whether the process has + * exited and if so, records timing information. + */ +static process_state +check_process_exit( + HANDLE process /* The process we're looking at */ + + , int* status /* Storage for the finished process' exit + * code. If the process is still active + * this location is left untouched. */ + + , HANDLE* active_handles /* Storage for the process handle if it is + * found to be still active, or NULL. The + * process is treated as though it is + * complete. */ + + , int* num_active /* The current length of active_handles */ +) +{ + DWORD exitcode; + process_state result; + + /* Try to get the process exit code */ + if (!GetExitCodeProcess(process, &exitcode)) + { + result = process_error; /* signal an error */ + } + else if ( + exitcode == STILL_ACTIVE /* If the process is still active */ + && active_handles != 0 /* and we've been passed a place to buffer it */ + ) + { + active_handles[(*num_active)++] = process; /* push it onto the active stack */ + result = process_active; + } + else + { + *status = (int)((exitcode & 0xff) << 8); + result = process_finished; + } + + return result; +} + static int my_wait( int *status ) { int i, num_active = 0; DWORD exitcode, waitcode; - static HANDLE *active_handles = 0; - - if (!active_handles) - active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) ); + HANDLE active_handles[MAXJOBS]; /* first see if any non-waited-for processes are dead, * and return if so. */ - for ( i = 0; i < globs.jobs; i++ ) { - if ( cmdtab[i].pid ) { - if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) { - if ( exitcode == STILL_ACTIVE ) - active_handles[num_active++] = (HANDLE)cmdtab[i].pid; - else { - CloseHandle((HANDLE)cmdtab[i].pid); - *status = (int)((exitcode & 0xff) << 8); - return cmdtab[i].pid; - } - } - else - goto FAILED; + for ( i = 0; i < globs.jobs; i++ ) + { + int pid = cmdtab[i].pid; + + if ( pid ) + { + process_state state + = check_process_exit((HANDLE)pid, status, active_handles, &num_active); + + if ( state == process_error ) + goto FAILED; + else if ( state == process_finished ) + return pid; } } /* if a child exists, wait for it to die */ - if ( !num_active ) { + if ( !num_active ) + { errno = ECHILD; return -1; } + waitcode = WaitForMultipleObjects( num_active, - active_handles, - FALSE, - INFINITE ); - if ( waitcode != WAIT_FAILED ) { + active_handles, + FALSE, + INFINITE ); + if ( waitcode != WAIT_FAILED ) + { if ( waitcode >= WAIT_ABANDONED_0 - && waitcode < WAIT_ABANDONED_0 + num_active ) - i = waitcode - WAIT_ABANDONED_0; + && waitcode < WAIT_ABANDONED_0 + num_active ) + i = waitcode - WAIT_ABANDONED_0; else - i = waitcode - WAIT_OBJECT_0; - if ( GetExitCodeProcess(active_handles[i], &exitcode) ) { - CloseHandle(active_handles[i]); - *status = (int)((exitcode & 0xff) << 8); - return (int)active_handles[i]; - } + i = waitcode - WAIT_OBJECT_0; + + if ( check_process_exit(active_handles[i], status, 0, 0) == process_finished ) + return (int)active_handles[i]; } FAILED: diff --git a/jam_src/execunix.c b/jam_src/execunix.c index e450fdd12..13da13431 100644 --- a/jam_src/execunix.c +++ b/jam_src/execunix.c @@ -8,6 +8,7 @@ # include "lists.h" # include "execcmd.h" # include +# include #if defined(sun) || defined(__sun) #include /* need to include unistd.h on sun for the vfork prototype*/ @@ -15,24 +16,12 @@ #endif # ifdef USE_EXECUNIX +# include # ifdef NO_VFORK # define vfork() fork() # endif -# if defined( OS_NT ) || defined( OS_OS2 ) - -# define USE_EXECNT - -# include - -# if !defined( __BORLANDC__ ) && !defined( OS_OS2 ) -# define wait my_wait -static int my_wait( int *status ); -# endif - -# endif - /* * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS * @@ -71,13 +60,8 @@ static void (*istat)( int ); static struct { int pid; /* on win32, a real process handle */ - void (*func)( void *closure, int status ); + void (*func)( void *closure, int status, timing_info* ); void *closure; - -# ifdef USE_EXECNT - char *tempfile; -# endif - } cmdtab[ MAXJOBS ] = {{0}}; /* @@ -98,7 +82,7 @@ onintr( int disp ) void execcmd( char *string, - void (*func)( void *closure, int status ), + void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ) { @@ -106,10 +90,6 @@ execcmd( int slot; char *argv[ MAXARGC + 1 ]; /* +1 for NULL */ -# ifdef USE_EXECNT - char *p; -# endif - /* Find a slot in the running commands table for this one. */ for( slot = 0; slot < MAXJOBS; slot++ ) @@ -122,49 +102,6 @@ execcmd( exit( EXITBAD ); } -# ifdef USE_EXECNT - if( !cmdtab[ slot ].tempfile ) - { - char *tempdir; - - if( !( tempdir = getenv( "TEMP" ) ) && - !( tempdir = getenv( "TMP" ) ) ) - tempdir = "\\temp"; - - cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 14 ); - - sprintf( cmdtab[ slot ].tempfile, "%s\\jamtmp%02d.bat", - tempdir, slot ); - } - - /* Trim leading, ending white space */ - - while( isspace( *string ) ) - ++string; - - p = strchr( string, '\n' ); - - while( p && isspace( *p ) ) - ++p; - - /* If multi line, or too long, or JAMSHELL is set, write to bat file. */ - /* Otherwise, exec directly. */ - /* Frankly, if it is a single long line I don't think the */ - /* command interpreter will do any better -- it will fail. */ - - if( p && *p || strlen( string ) > MAXLINE || shell ) - { - FILE *f; - - /* Write command to bat file. */ - - f = fopen( cmdtab[ slot ].tempfile, "w" ); - fputs( string, f ); - fclose( f ); - - string = cmdtab[ slot ].tempfile; - } -# endif /* Forumulate argv */ /* If shell was defined, be prepared for % and ! subs. */ @@ -197,13 +134,8 @@ execcmd( } else { -# ifdef USE_EXECNT - argv[0] = "cmd.exe"; - argv[1] = "/Q/C"; /* anything more is non-portable */ -# else argv[0] = "/bin/sh"; argv[1] = "-c"; -# endif argv[2] = string; argv[3] = 0; } @@ -215,13 +147,6 @@ execcmd( /* Start the command */ -# ifdef USE_EXECNT - if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) - { - perror( "spawn" ); - exit( EXITBAD ); - } -# else if ((pid = vfork()) == 0) { execvp( argv[0], argv ); @@ -233,7 +158,7 @@ execcmd( perror( "vfork" ); exit( EXITBAD ); } -# endif + /* Save the operation for execwait() to find. */ cmdtab[ slot ].pid = pid; @@ -258,14 +183,17 @@ execwait() int i; int status, w; int rstat; - + timing_info time; + struct tms old_time, new_time; + /* Handle naive make1() which doesn't know if cmds are running. */ if( !cmdsrunning ) return 0; - /* Pick up process pid and status */ + times(&old_time); + /* Pick up process pid and status */ while( ( w = wait( &status ) ) == -1 && errno == EINTR ) ; @@ -276,6 +204,11 @@ execwait() exit( EXITBAD ); } + times(&new_time); + + time.system = (double)(new_time.tms_cstime - old_time.tms_cstime) / CLOCKS_PER_SEC; + time.user = (double)(new_time.tms_cutime - old_time.tms_cutime) / CLOCKS_PER_SEC; + /* Find the process in the cmdtab. */ for( i = 0; i < MAXJOBS; i++ ) @@ -288,6 +221,7 @@ execwait() exit( EXITBAD ); } + /* Drive the completion */ if( !--cmdsrunning ) @@ -302,7 +236,7 @@ execwait() cmdtab[ i ].pid = 0; - (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat ); + (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); return 1; } diff --git a/jam_src/expand.c b/jam_src/expand.c index fbe1f0ec6..87c14c4a2 100644 --- a/jam_src/expand.c +++ b/jam_src/expand.c @@ -136,14 +136,14 @@ expand: /* * Input so far (ignore blanks): * - * stuff-in-outbuf $(variable) remainder - * ^ ^ - * in end + * stuff-in-outbuf $(variable) remainder + * ^ ^ + * in end * Output so far: * - * stuff-in-outbuf $ - * ^ ^ - * out_buf out + * stuff-in-outbuf $ + * ^ ^ + * out_buf out * * * We just copied the $ of $(...), so back up one on the output. @@ -169,9 +169,9 @@ expand: /* * Input so far (ignore blanks): * - * stuff-in-outbuf $(variable) remainder - * ^ ^ ^ - * inp in end + * stuff-in-outbuf $(variable) remainder + * ^ ^ ^ + * inp in end */ prefix_length = buf->size; string_append_range( buf, inp, in - 1 ); @@ -190,14 +190,14 @@ expand: /* * Input so far (ignore blanks): * - * stuff-in-outbuf $(variable) remainder - * ^ ^ - * in end + * stuff-in-outbuf $(variable) remainder + * ^ ^ + * in end * Output so far: * - * stuff-in-outbuf variable - * ^ ^ ^ - * out_buf out ov + * stuff-in-outbuf variable + * ^ ^ ^ + * out_buf out ov * * Later we will overwrite 'variable' in out_buf, but we'll be * done with it by then. 'variable' may be a multi-element list, @@ -714,3 +714,9 @@ void var_expand_unit_test() lol_free(lol); } #endif + +/* + Local Variables: + tab-width: 8 + End: + */ diff --git a/jam_src/make.c b/jam_src/make.c index 83779eb09..a112855af 100644 --- a/jam_src/make.c +++ b/jam_src/make.c @@ -82,6 +82,7 @@ static const char *target_fate[] = "newer", /* T_FATE_NEWER */ "temp", /* T_FATE_ISTMP */ "touched", /* T_FATE_TOUCHED */ + "rebuild", /* T_FATE_REBUILD */ "missing", /* T_FATE_MISSING */ "needtmp", /* T_FATE_NEEDTMP */ "old", /* T_FATE_OUTDATED */ @@ -174,6 +175,63 @@ make( return status; } +/* Force any dependents of t that have already at least begun being + * visited by make0 to be updated. + */ +static void update_dependents(TARGET* t) +{ + TARGETS *q; + + for (q = t->dependents; q; q = q->next) + { + TARGET* p = q->target; + char fate0 = p->fate; + + /* If we've already at least begun visiting it and + * we're not already rebuilding it for other reasons + */ + if (fate0 != T_FATE_INIT && fate0 < T_FATE_BUILD) + { + p->fate = T_FATE_UPDATE; + + if (DEBUG_FATE) + { + printf( "fate change %s from %s to %s (as dependent of %s)\n", + p->name, target_fate[fate0], target_fate[p->fate], t->name); + } + + /* If we're done visiting it, go back and make sure its + * dependents get rebuilt. + */ + if (fate0 > T_FATE_MAKING) + update_dependents(p); + } + } +} + +/* Make sure that all of t's rebuilds get rebuilt */ +static void force_rebuilds(TARGET* t) +{ + TARGETS* d; + for (d = t->rebuilds; d; d = d->next) + { + TARGET* r = d->target; + + /* If it's not already being rebuilt for other reasons */ + if (r->fate < T_FATE_BUILD) + { + if (DEBUG_FATE) + printf( "fate change %s from %s to %s (by rebuild)\n", + r->name, target_fate[r->fate], target_fate[T_FATE_REBUILD]); + + /* Force rebuild it */ + r->fate = T_FATE_REBUILD; + + /* And make sure its dependents are updated too */ + update_dependents(r); + } + } +} /* * make0() - bind and scan everything to make a TARGET * @@ -362,7 +420,7 @@ make0( last = 0; leaf = 0; - fate = T_FATE_STABLE; + fate = T_FATE_STABLE; for( c = t->depends; c; c = c->next ) { @@ -515,7 +573,7 @@ make0( t->name, target_fate[fate], oldTimeStamp ? " (by timestamp)" : "" ); else - printf( "fate change %s adjusted from %s to %s%s\n", + printf( "fate change %s from %s to %s%s\n", t->name, target_fate[savedFate], target_fate[fate], oldTimeStamp ? " (by timestamp)" : "" ); #endif @@ -551,8 +609,19 @@ make0( t->time = max( t->time, last ); t->leaf = leaf ? leaf : t->time ; - t->fate = fate; + /* This target's fate may have been updated by virtue of following + * some target's rebuilds list, so only allow it to be increased + * to the fate we've calculated. Otherwise, grab its new fate. + */ + if (fate > t->fate) + t->fate = fate; + else + fate = t->fate; + /* Step 4g: if this target needs to be built, force rebuild + * everything in this target's rebuilds list */ + if (fate >= T_FATE_BUILD && fate < T_FATE_BROKEN) + force_rebuilds(t); /* * Step 5: sort dependents by their update time. */ @@ -674,6 +743,9 @@ dependGraphOutput( TARGET *t, int depth ) case T_FATE_OUTDATED: printf( " %s : Outdated, updating it\n", spaces(depth) ); break; + case T_FATE_REBUILD: + printf( " %s : Rebuild, Updating it\n", spaces(depth) ); + break; case T_FATE_UPDATE: printf( " %s : Updating it\n", spaces(depth) ); break; diff --git a/jam_src/make1.c b/jam_src/make1.c index 0d7423ea8..a8d04c5fb 100644 --- a/jam_src/make1.c +++ b/jam_src/make1.c @@ -61,6 +61,8 @@ # include "command.h" # include "execcmd.h" +# include + #if defined(sun) || defined(__sun) #include /* for unlink */ #endif @@ -101,7 +103,7 @@ static void make1atail(state *pState); static void make1b( state *pState ); static void make1c( state *pState ); static void make1d( state *pState ); -static void make_closure(void *closure, int status); +static void make_closure(void *closure, int status, timing_info*); typedef struct _stack { @@ -410,9 +412,10 @@ make1b( state *pState ) if( pState->t->status == EXEC_CMD_OK ) switch( pState->t->fate ) { + /* These are handled by the default case below now case T_FATE_INIT: case T_FATE_MAKING: - /* shouldn't happen */ + */ case T_FATE_STABLE: case T_FATE_NEWER: @@ -433,6 +436,7 @@ make1b( state *pState ) case T_FATE_NEEDTMP: case T_FATE_OUTDATED: case T_FATE_UPDATE: + case T_FATE_REBUILD: /* Set "on target" vars, build actions, unset vars */ /* Set "progress" so that make1c() counts this target among */ @@ -449,6 +453,11 @@ make1b( state *pState ) } break; + + /* All possible fates should have been accounted for by now */ + default: + printf("ERROR: %s has bad fate %d", pState->t->name, pState->t->fate); + abort(); } /* Call make1c() to begin the execution of the chain of commands */ @@ -655,9 +664,60 @@ make1c( state *pState ) } } -static void make_closure(void *closure, int status) +/* To l, append a 1-element list containing the string representation + * of x + */ +static void append_double_string( LOL *l, double x ) { - push_state(&state_stack, (TARGET *)closure, NULL, T_STATE_MAKE1D)->status = status; + char buffer[50]; + sprintf(buffer, "%f", x); + lol_add( l, list_new( L0, newstr( buffer ) ) ); +} + +/* 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* time) +{ + LIST* timing_rule; + + pushsettings(target->settings); + timing_rule = var_get( "__TIMING_RULE__" ); + popsettings(target->settings); + + if (timing_rule) + { + /* We'll prepend $(__TIMING_RULE__[2-]) to the first argument */ + LIST* initial_args = list_copy( L0, timing_rule->next ); + + /* Prepare the argument list */ + FRAME frame[1]; + frame_init( frame ); + + /* First argument is the name of the timed target */ + lol_add( frame->args, list_new( initial_args, target->name ) ); + append_double_string(frame->args, time->user); + append_double_string(frame->args, time->system); + + if( lol_get( frame->args, 2 ) ) + evaluate_rule( timing_rule->string, frame ); + + /* Clean up */ + frame_free( frame ); + } +} + +static void make_closure( + void *closure, int status, timing_info* time) +{ + TARGET* built = (TARGET*)closure; + + call_timing_rule(built, time); + if (DEBUG_EXECCMD) + printf("%f sec system; %f sec user\n", time->system, time->user); + + push_state(&state_stack, built, NULL, T_STATE_MAKE1D)->status = status; } /* diff --git a/jam_src/rules.h b/jam_src/rules.h index 833a8e84f..3dc21a3f1 100644 --- a/jam_src/rules.h +++ b/jam_src/rules.h @@ -178,6 +178,8 @@ struct _target { # define T_BIND_EXISTS 3 /* real file, timestamp valid */ TARGETS *depends; /* dependencies */ + TARGETS *dependents;/* the inverse of dependencies */ + TARGETS *rebuilds; /* targets that should be force-rebuilt whenever this one is */ TARGET *includes; /* includes */ TARGET *original_target; /* original_target->includes = this */ char rescanned; @@ -198,14 +200,15 @@ struct _target { # define T_FATE_BUILD 5 /* >= BUILD rebuilds target */ # define T_FATE_TOUCHED 5 /* manually touched with -t */ -# define T_FATE_MISSING 6 /* is missing, needs updating */ -# define T_FATE_NEEDTMP 7 /* missing temp that must be rebuild */ -# define T_FATE_OUTDATED 8 /* is out of date, needs updating */ -# define T_FATE_UPDATE 9 /* deps updated, needs updating */ +# define T_FATE_REBUILD 6 +# define T_FATE_MISSING 7 /* is missing, needs updating */ +# define T_FATE_NEEDTMP 8 /* missing temp that must be rebuild */ +# define T_FATE_OUTDATED 9 /* is out of date, needs updating */ +# define T_FATE_UPDATE 10 /* deps updated, needs updating */ -# define T_FATE_BROKEN 10 /* >= BROKEN ruins parents */ -# define T_FATE_CANTFIND 10 /* no rules to make missing target */ -# define T_FATE_CANTMAKE 11 /* can't find dependents */ +# define T_FATE_BROKEN 11 /* >= BROKEN ruins parents */ +# define T_FATE_CANTFIND 11 /* no rules to make missing target */ +# define T_FATE_CANTMAKE 12 /* can't find dependents */ char progress; /* tracks make1() progress */ diff --git a/v2/test/rebuilds.py b/v2/test/rebuilds.py new file mode 100644 index 000000000..ce63117f2 --- /dev/null +++ b/v2/test/rebuilds.py @@ -0,0 +1,48 @@ +#!/usr/bin/python + +# This tests the typechecking facilities. + +import BoostBuild + +t = BoostBuild.Tester(pass_toolset=0) + +t.write('file.jam', ''' +rule make +{ + DEPENDS $(<) : $(>) ; + DEPENDS all : $(<) ; +} +actions make +{ + echo "******" making $(<) from $(>) "******" + echo made from $(>) >> $(<) +} + +make aux1 : bar ; +make foo : bar ; +REBUILDS foo : bar ; +make bar : baz ; +make aux2 : bar ; +''') + +t.write('baz', 'nothing\n') + +t.run_build_system('-ffile.jam bar') +t.expect_addition('bar') +t.expect_nothing_more() + +t.run_build_system('-ffile.jam foo') +t.expect_touch('bar') +t.expect_addition('foo') +t.expect_nothing_more() + +t.run_build_system('-ffile.jam') +t.expect_addition(['aux1', 'aux2']) +t.expect_nothing_more() + +t.touch('bar') +t.run_build_system('-ffile.jam') +t.expect_touch(['foo', 'aux1', 'aux2', 'bar']) +t.expect_nothing_more() + +t.cleanup() diff --git a/v2/test/test_all.py b/v2/test/test_all.py index bcb1fe28d..0c88d6237 100644 --- a/v2/test/test_all.py +++ b/v2/test/test_all.py @@ -73,7 +73,9 @@ critical_tests = ["unit_tests", "module_actions", "startup_v1", "startup_v2"] critical_tests += ["core_d12", "core_typecheck", "core_delete_module", "core_varnames", "core_import_module"] -tests = [ "project_test3", +tests = [ "rebuilds", + "timedata", + "project_test3", "project_test4", "generators_test", "dependency_test", diff --git a/v2/test/timedata.py b/v2/test/timedata.py new file mode 100644 index 000000000..635bf4ba1 --- /dev/null +++ b/v2/test/timedata.py @@ -0,0 +1,54 @@ +#!/usr/bin/python + +# This tests the typechecking facilities. + +import BoostBuild + +t = BoostBuild.Tester(pass_toolset=0) + +t.write('file.jam', ''' +rule time +{ + DEPENDS $(<) : $(>) ; + __TIMING_RULE__ on $(>) = record_time $(<) ; + DEPENDS all : $(<) ; +} +actions time +{ + echo $(>) user: $(__USER_TIME__) system: $(__SYSTEM_TIME__) + echo timed from $(>) >> $(<) +} + +rule record_time ( target source : user : system ) +{ + ECHO record_time called: $(target) / $(source) / $(user) / $(system) ; + __USER_TIME__ on $(target) = $(user) ; + __SYSTEM_TIME__ on $(target) = $(system) ; +} + +rule make +{ + DEPENDS $(<) : $(>) ; +} +actions make +{ + echo made from $(>) >> $(<) +} + + +time foo : bar ; +make bar : baz ; +''') + +import re +t.write('baz', 'nothing\n') +t.run_build_system( + '-ffile.jam', + stdout=r'bar +user: [0-9\.]+ +system: +[0-9\.]+ *$', + match = lambda actual,expected: re.search(expected,actual,re.DOTALL) + ) +t.expect_addition('foo') +t.expect_addition('bar') +t.expect_nothing_more() + +t.cleanup()