static void reportWindowsError( char const * const apiName, int slot ) { char * errorMessage; char buf[24]; string * err_buf; timing_info time; DWORD const errorCode = GetLastError(); DWORD apiResult = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | /* __in DWORD dwFlags */ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, /* __in_opt LPCVOID lpSource */ errorCode, /* __in DWORD dwMessageId */ 0, /* __in DWORD dwLanguageId */ (LPSTR)&errorMessage, /* __out LPTSTR lpBuffer */ 0, /* __in DWORD nSize */ 0 ); /* __in_opt va_list * Arguments */ /* Build a message as if the process had written to stderr. */ if ( globs.pipe_action ) err_buf = cmdtab[ slot ].buffer_err; else err_buf = cmdtab[ slot ].buffer_out; string_append( err_buf, apiName ); string_append( err_buf, "() Windows API failed: " ); sprintf( buf, "%d", errorCode ); string_append( err_buf, buf ); if ( !apiResult ) string_append( err_buf, ".\n" ); else { string_append( err_buf, " - " ); string_append( err_buf, errorMessage ); /* Make sure that the buffer is terminated with a newline */ if( err_buf->value[ err_buf->size - 1 ] != '\n' ) string_push_back( err_buf, '\n' ); LocalFree( errorMessage ); } /* Since the process didn't actually start, use a blank timing_info. */ time.system = 0; time.user = 0; timestamp_current( &time.start ); timestamp_current( &time.end ); /* Invoke the callback with a failure status. */ (*cmdtab[ slot ].func)( cmdtab[ slot ].closure, EXEC_CMD_FAIL, &time, cmdtab[ slot ].buffer_out->value, cmdtab[ slot ].buffer_err->value, EXIT_OK ); /* Clean up any handles that were opened. */ closeWinHandle( &cmdtab[ slot ].pi.hProcess ); closeWinHandle( &cmdtab[ slot ].pi.hThread ); closeWinHandle( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ] ); closeWinHandle( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ] ); closeWinHandle( &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_READ ] ); closeWinHandle( &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ] ); string_renew( cmdtab[ slot ].buffer_out ); string_renew( cmdtab[ slot ].buffer_err ); }
void exec_wait() { int finished = 0; /* Process children that signaled. */ while ( !finished ) { int i; struct timeval tv; struct timeval * ptv = NULL; int select_timeout = globs.timeout; /* Check for timeouts: * - kill children that already timed out * - decide how long until the next one times out */ if ( globs.timeout > 0 ) { struct tms buf; clock_t const current = times( &buf ); for ( i = 0; i < globs.jobs; ++i ) if ( cmdtab[ i ].pid ) { clock_t const consumed = ( current - cmdtab[ i ].start_time ) / tps; if ( consumed >= globs.timeout ) { killpg( cmdtab[ i ].pid, SIGKILL ); cmdtab[ i ].exit_reason = EXIT_TIMEOUT; } else if ( globs.timeout - consumed < select_timeout ) select_timeout = globs.timeout - consumed; } /* If nothing else causes our select() call to exit, force it after * however long it takes for the next one of our child processes to * crossed its alloted processing time so we can terminate it. */ tv.tv_sec = select_timeout; tv.tv_usec = 0; ptv = &tv; } /* select() will wait for I/O on a descriptor, a signal, or timeout. */ { /* disable child termination signals while in select */ int ret; sigset_t sigmask; sigemptyset(&sigmask); sigaddset(&sigmask, SIGCHLD); sigprocmask(SIG_BLOCK, &sigmask, NULL); while ( ( ret = poll( wait_fds, WAIT_FDS_SIZE, select_timeout * 1000 ) ) == -1 ) if ( errno != EINTR ) break; /* restore original signal mask by unblocking sigchld */ sigprocmask(SIG_UNBLOCK, &sigmask, NULL); if ( ret <= 0 ) continue; } for ( i = 0; i < globs.jobs; ++i ) { int out_done = 0; int err_done = 0; if ( GET_WAIT_FD( i )[ OUT ].revents ) out_done = read_descriptor( i, OUT ); if ( globs.pipe_action && ( GET_WAIT_FD( i )[ ERR ].revents ) ) err_done = read_descriptor( i, ERR ); /* If feof on either descriptor, we are done. */ if ( out_done || err_done ) { int pid; int status; int rstat; timing_info time_info; struct rusage cmd_usage; /* We found a terminated child process - our search is done. */ finished = 1; /* Close the stream and pipe descriptors. */ close_streams( i, OUT ); if ( globs.pipe_action ) close_streams( i, ERR ); /* Reap the child and release resources. */ while ( ( pid = wait4( cmdtab[ i ].pid, &status, 0, &cmd_usage ) ) == -1 ) if ( errno != EINTR ) break; if ( pid != cmdtab[ i ].pid ) { err_printf( "unknown pid %d with errno = %d\n", pid, errno ); exit( EXITBAD ); } /* Set reason for exit if not timed out. */ if ( WIFEXITED( status ) ) cmdtab[ i ].exit_reason = WEXITSTATUS( status ) ? EXIT_FAIL : EXIT_OK; { time_info.system = ((double)(cmd_usage.ru_stime.tv_sec)*1000000.0+(double)(cmd_usage.ru_stime.tv_usec))/1000000.0; time_info.user = ((double)(cmd_usage.ru_utime.tv_sec)*1000000.0+(double)(cmd_usage.ru_utime.tv_usec))/1000000.0; timestamp_copy( &time_info.start, &cmdtab[ i ].start_dt ); timestamp_current( &time_info.end ); } /* Drive the completion. */ if ( interrupted() ) rstat = EXEC_CMD_INTR; else if ( status ) rstat = EXEC_CMD_FAIL; else rstat = EXEC_CMD_OK; /* Call the callback, may call back to jam rule land. */ (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time_info, cmdtab[ i ].buffer[ OUT ], cmdtab[ i ].buffer[ ERR ], cmdtab[ i ].exit_reason ); /* Clean up the command's running commands table slot. */ BJAM_FREE( cmdtab[ i ].buffer[ OUT ] ); cmdtab[ i ].buffer[ OUT ] = 0; cmdtab[ i ].buf_size[ OUT ] = 0; BJAM_FREE( cmdtab[ i ].buffer[ ERR ] ); cmdtab[ i ].buffer[ ERR ] = 0; cmdtab[ i ].buf_size[ ERR ] = 0; cmdtab[ i ].pid = 0; cmdtab[ i ].func = 0; cmdtab[ i ].closure = 0; cmdtab[ i ].start_time = 0; } } } }
void exec_cmd ( string const * command, int flags, ExecCmdCallback func, void * closure, LIST * shell ) { struct sigaction ignore, saveintr, savequit; sigset_t chldmask, savemask; int const slot = get_free_cmdtab_slot(); int out[ 2 ]; int err[ 2 ]; int len; char const * argv[ MAXARGC + 1 ]; /* +1 for NULL */ /* Initialize default shell. */ static LIST * default_shell; if ( !default_shell ) default_shell = list_push_back( list_new( object_new( "/bin/sh" ) ), object_new( "-c" ) ); if ( list_empty( shell ) ) shell = default_shell; /* Forumulate argv. If shell was defined, be prepared for % and ! subs. * Otherwise, use stock /bin/sh. */ argv_from_shell( argv, shell, command->value, slot ); if ( DEBUG_EXECCMD ) { int i; out_printf( "Using shell: " ); list_print( shell ); out_printf( "\n" ); for ( i = 0; argv[ i ]; ++i ) out_printf( " argv[%d] = '%s'\n", i, argv[ i ] ); } /* Create pipes for collecting child output. */ if ( pipe( out ) < 0 || ( globs.pipe_action && pipe( err ) < 0 ) ) { perror( "pipe" ); exit( EXITBAD ); } /* Start the command */ timestamp_current( &cmdtab[ slot ].start_dt ); if ( 0 < globs.timeout ) { /* Handle hung processes by manually tracking elapsed time and signal * process when time limit expires. */ struct tms buf; cmdtab[ slot ].start_time = times( &buf ); /* Make a global, only do this once. */ if ( !tps ) tps = sysconf( _SC_CLK_TCK ); } /* Child does not need the read pipe ends used by the parent. */ fcntl( out[ EXECCMD_PIPE_READ ], F_SETFD, FD_CLOEXEC ); if ( globs.pipe_action ) fcntl( err[ EXECCMD_PIPE_READ ], F_SETFD, FD_CLOEXEC ); /* ignore SIGINT and SIGQUIT */ ignore.sa_handler = SIG_IGN; sigemptyset(&ignore.sa_mask); ignore.sa_flags = 0; if (sigaction(SIGINT, &ignore, &saveintr) < 0) return; if (sigaction(SIGQUIT, &ignore, &savequit) < 0) return; /* block SIGCHLD */ sigemptyset(&chldmask); sigaddset(&chldmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0) return; if ( ( cmdtab[ slot ].pid = vfork() ) == -1 ) { perror( "vfork" ); exit( EXITBAD ); } if ( cmdtab[ slot ].pid == 0 ) { /*****************/ /* Child process */ /*****************/ int const pid = getpid(); /* restore previous signals */ sigaction(SIGINT, &saveintr, NULL); sigaction(SIGQUIT, &savequit, NULL); sigprocmask(SIG_SETMASK, &savemask, NULL); /* Redirect stdout and stderr to pipes inherited from the parent. */ dup2( out[ EXECCMD_PIPE_WRITE ], STDOUT_FILENO ); dup2( globs.pipe_action ? err[ EXECCMD_PIPE_WRITE ] : out[ EXECCMD_PIPE_WRITE ], STDERR_FILENO ); close( out[ EXECCMD_PIPE_WRITE ] ); if ( globs.pipe_action ) close( err[ EXECCMD_PIPE_WRITE ] ); /* Make this process a process group leader so that when we kill it, all * child processes of this process are terminated as well. We use * killpg( pid, SIGKILL ) to kill the process group leader and all its * children. */ if ( 0 < globs.timeout ) { struct rlimit r_limit; r_limit.rlim_cur = globs.timeout; r_limit.rlim_max = globs.timeout; setrlimit( RLIMIT_CPU, &r_limit ); } if (0 != setpgid( pid, pid )) { perror("setpgid(child)"); /* exit( EXITBAD ); */ } execvp( argv[ 0 ], (char * *)argv ); perror( "execvp" ); _exit( 127 ); } /******************/ /* Parent process */ /******************/ /* redundant call, ignore return value */ setpgid(cmdtab[ slot ].pid, cmdtab[ slot ].pid); /* Parent not need the write pipe ends used by the child. */ close( out[ EXECCMD_PIPE_WRITE ] ); if ( globs.pipe_action ) close( err[ EXECCMD_PIPE_WRITE ] ); /* Set both pipe read file descriptors to non-blocking. */ fcntl( out[ EXECCMD_PIPE_READ ], F_SETFL, O_NONBLOCK ); if ( globs.pipe_action ) fcntl( err[ EXECCMD_PIPE_READ ], F_SETFL, O_NONBLOCK ); /* Parent reads from out[ EXECCMD_PIPE_READ ]. */ cmdtab[ slot ].fd[ OUT ] = out[ EXECCMD_PIPE_READ ]; cmdtab[ slot ].stream[ OUT ] = fdopen( cmdtab[ slot ].fd[ OUT ], "rb" ); if ( !cmdtab[ slot ].stream[ OUT ] ) { perror( "fdopen" ); exit( EXITBAD ); } /* Parent reads from err[ EXECCMD_PIPE_READ ]. */ if ( globs.pipe_action ) { cmdtab[ slot ].fd[ ERR ] = err[ EXECCMD_PIPE_READ ]; cmdtab[ slot ].stream[ ERR ] = fdopen( cmdtab[ slot ].fd[ ERR ], "rb" ); if ( !cmdtab[ slot ].stream[ ERR ] ) { perror( "fdopen" ); exit( EXITBAD ); } } GET_WAIT_FD( slot )[ OUT ].fd = out[ EXECCMD_PIPE_READ ]; if ( globs.pipe_action ) GET_WAIT_FD( slot )[ ERR ].fd = err[ EXECCMD_PIPE_READ ]; cmdtab[ slot ].flags = flags; /* Save input data into the selected running commands table slot. */ cmdtab[ slot ].func = func; cmdtab[ slot ].closure = closure; /* restore previous signals */ sigaction(SIGINT, &saveintr, NULL); sigaction(SIGQUIT, &savequit, NULL); sigprocmask(SIG_SETMASK, &savemask, NULL); }
static void make1c( state const * const pState ) { TARGET * const t = pState->t; CMD * const cmd = (CMD *)t->cmds; if ( cmd && t->status == EXEC_CMD_OK ) { /* 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 ); /* Increment the jobs running counter. */ ++cmdsrunning; /* 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, 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 ); assert( globs.jobs <= MAXJOBS ); while ( cmdsrunning >= globs.jobs ) exec_wait(); } } else { ACTIONS * actions; /* Collect status from actions, and distribute it as well. */ for ( actions = t->actions; actions; actions = actions->next ) if ( actions->action->status > t->status ) t->status = actions->action->status; for ( actions = t->actions; actions; actions = actions->next ) if ( t->status > actions->action->status ) actions->action->status = t->status; /* Tally success/failure for those we tried to update. */ if ( t->progress == T_MAKE_RUNNING ) switch ( t->status ) { case EXEC_CMD_OK: ++counts->made; break; case EXEC_CMD_FAIL: ++counts->failed; break; } /* Tell parents their dependency has been built. */ { TARGETS * 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; t->rescanned = 1; /* 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; c; c = c->next ) c->target->depends = 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; c; c = c->next ) 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; c; c = c->next ) { if ( target_scc( c->target ) == scc_root ) push_state( &temp_stack, c->target, NULL, T_STATE_MAKE1B ); else scc_root->parents = targetentry( scc_root->parents, c->target ); } } else { for ( c = t->parents; c; c = c->next ) push_state( &temp_stack, c->target, NULL, T_STATE_MAKE1B ); } #ifdef OPT_SEMAPHORE /* If there is a semaphore, it is now free. */ if ( t->semaphore ) { assert( t->semaphore->asynccnt == 1 ); --t->semaphore->asynccnt; if ( DEBUG_EXECCMD ) printf( "SEM: %s is now free\n", object_str( t->semaphore->name ) ); /* If anything is waiting, notify the next target. There is no * point in notifying all waiting targets, since they will be * notified again. */ if ( t->semaphore->parents ) { TARGETS * first = t->semaphore->parents; t->semaphore->parents = first->next; if ( first->next ) first->next->tail = first->tail; if ( DEBUG_EXECCMD ) printf( "SEM: placing %s on stack\n", object_str( first->target->name ) ); push_state( &temp_stack, first->target, NULL, T_STATE_MAKE1B ); BJAM_FREE( first ); } } #endif /* Must pop state before pushing any more. */ pop_state( &state_stack ); /* Using stacks reverses the order of execution. Reverse it back. */ push_stack_on_stack( &state_stack, &temp_stack ); } } }
void exec_wait() { int finished = 0; /* Process children that signaled. */ while ( !finished ) { int i; struct timeval tv; struct timeval * ptv = NULL; int select_timeout = globs.timeout; /* Prepare file descriptor information for use in select(). */ fd_set fds; int const fd_max = populate_file_descriptors( &fds ); /* Check for timeouts: * - kill children that already timed out * - decide how long until the next one times out */ if ( globs.timeout > 0 ) { struct tms buf; clock_t const current = times( &buf ); for ( i = 0; i < globs.jobs; ++i ) if ( cmdtab[ i ].pid ) { clock_t const consumed = ( current - cmdtab[ i ].start_time ) / tps; if ( consumed >= globs.timeout ) { killpg( cmdtab[ i ].pid, SIGKILL ); cmdtab[ i ].exit_reason = EXIT_TIMEOUT; } else if ( globs.timeout - consumed < select_timeout ) select_timeout = globs.timeout - consumed; } /* If nothing else causes our select() call to exit, force it after * however long it takes for the next one of our child processes to * crossed its alloted processing time so we can terminate it. */ tv.tv_sec = select_timeout; tv.tv_usec = 0; ptv = &tv; } /* select() will wait for I/O on a descriptor, a signal, or timeout. */ { int ret; while ( ( ret = select( fd_max + 1, &fds, 0, 0, ptv ) ) == -1 ) if ( errno != EINTR ) break; if ( ret <= 0 ) continue; } for ( i = 0; i < globs.jobs; ++i ) { int out_done = 0; int err_done = 0; if ( FD_ISSET( cmdtab[ i ].fd[ OUT ], &fds ) ) out_done = read_descriptor( i, OUT ); if ( globs.pipe_action && FD_ISSET( cmdtab[ i ].fd[ ERR ], &fds ) ) err_done = read_descriptor( i, ERR ); /* If feof on either descriptor, we are done. */ if ( out_done || err_done ) { int pid; int status; int rstat; timing_info time_info; /* We found a terminated child process - our search is done. */ finished = 1; /* Close the stream and pipe descriptors. */ close_streams( i, OUT ); if ( globs.pipe_action ) close_streams( i, ERR ); /* Reap the child and release resources. */ while ( ( pid = waitpid( cmdtab[ i ].pid, &status, 0 ) ) == -1 ) if ( errno != EINTR ) break; if ( pid != cmdtab[ i ].pid ) { printf( "unknown pid %d with errno = %d\n", pid, errno ); exit( EXITBAD ); } /* Set reason for exit if not timed out. */ if ( WIFEXITED( status ) ) cmdtab[ i ].exit_reason = WEXITSTATUS( status ) ? EXIT_FAIL : EXIT_OK; { struct tms new_time; times( &new_time ); time_info.system = (double)( new_time.tms_cstime - old_time.tms_cstime ) / CLOCKS_PER_SEC; time_info.user = (double)( new_time.tms_cutime - old_time.tms_cutime ) / CLOCKS_PER_SEC; timestamp_copy( &time_info.start, &cmdtab[ i ].start_dt ); timestamp_current( &time_info.end ); old_time = new_time; } /* Drive the completion. */ if ( interrupted() ) rstat = EXEC_CMD_INTR; else if ( status ) rstat = EXEC_CMD_FAIL; else rstat = EXEC_CMD_OK; /* Call the callback, may call back to jam rule land. */ (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time_info, cmdtab[ i ].buffer[ OUT ], cmdtab[ i ].buffer[ ERR ], cmdtab[ i ].exit_reason ); /* Clean up the command's running commands table slot. */ BJAM_FREE( cmdtab[ i ].buffer[ OUT ] ); cmdtab[ i ].buffer[ OUT ] = 0; cmdtab[ i ].buf_size[ OUT ] = 0; BJAM_FREE( cmdtab[ i ].buffer[ ERR ] ); cmdtab[ i ].buffer[ ERR ] = 0; cmdtab[ i ].buf_size[ ERR ] = 0; cmdtab[ i ].pid = 0; cmdtab[ i ].func = 0; cmdtab[ i ].closure = 0; cmdtab[ i ].start_time = 0; } } } }
int main( int argc, char * * argv, char * * arg_environ ) { int n; char * s; struct bjam_option optv[ N_OPTS ]; char const * all = "all"; int status; int arg_c = argc; char * * arg_v = argv; char const * progname = argv[ 0 ]; module_t * environ_module; saved_argv0 = argv[ 0 ]; BJAM_MEM_INIT(); #ifdef OS_MAC InitGraf( &qd.thePort ); #endif --argc; ++argv; #ifdef HAVE_PYTHON #define OPTSTRING "-:l:m:d:j:p:f:gs:t:ano:qvz" #else #define OPTSTRING "-:l:m:d:j:p:f:gs:t:ano:qv" #endif if ( getoptions( argc, argv, OPTSTRING, optv ) < 0 ) { err_printf( "\nusage: %s [ options ] targets...\n\n", progname ); err_printf( "-a Build all targets, even if they are current.\n" ); err_printf( "-dx Set the debug level to x (0-9).\n" ); err_printf( "-fx Read x instead of Jambase.\n" ); /* err_printf( "-g Build from newest sources first.\n" ); */ err_printf( "-jx Run up to x shell commands concurrently.\n" ); err_printf( "-lx Limit actions to x number of seconds after which they are stopped.\n" ); err_printf( "-mx Maximum target output saved (kb), default is to save all output.\n" ); err_printf( "-n Don't actually execute the updating actions.\n" ); err_printf( "-ox Mirror all output to file x.\n" ); err_printf( "-px x=0, pipes action stdout and stderr merged into action output.\n" ); err_printf( "-q Quit quickly as soon as a target fails.\n" ); err_printf( "-sx=y Set variable x=y, overriding environment.\n" ); err_printf( "-tx Rebuild x, even if it is up-to-date.\n" ); err_printf( "-v Print the version of jam and exit.\n" ); #ifdef HAVE_PYTHON err_printf( "-z Disable Python Optimization and enable asserts\n" ); #endif err_printf( "--x Option is ignored.\n\n" ); exit( EXITBAD ); } /* Version info. */ if ( ( s = getoptval( optv, 'v', 0 ) ) ) { out_printf( "Boost.Jam Version %s. %s.\n", VERSION, OSMINOR ); out_printf( " Copyright 1993-2002 Christopher Seiwald and Perforce " "Software, Inc.\n" ); out_printf( " Copyright 2001 David Turner.\n" ); out_printf( " Copyright 2001-2004 David Abrahams.\n" ); out_printf( " Copyright 2002-2015 Rene Rivera.\n" ); out_printf( " Copyright 2003-2015 Vladimir Prus.\n" ); return EXITOK; } /* Pick up interesting options. */ if ( ( s = getoptval( optv, 'n', 0 ) ) ) { ++globs.noexec; globs.debug[ 2 ] = 1; } if ( ( s = getoptval( optv, 'p', 0 ) ) ) { /* Undocumented -p3 (acts like both -p1 -p2) means separate pipe action * stdout and stderr. */ globs.pipe_action = atoi( s ); if ( globs.pipe_action < 0 || 3 < globs.pipe_action ) { err_printf( "Invalid pipe descriptor '%d', valid values are -p[0..3]." "\n", globs.pipe_action ); exit( EXITBAD ); } } if ( ( s = getoptval( optv, 'q', 0 ) ) ) globs.quitquick = 1; if ( ( s = getoptval( optv, 'a', 0 ) ) ) anyhow++; if ( ( s = getoptval( optv, 'j', 0 ) ) ) { globs.jobs = atoi( s ); if ( globs.jobs < 1 || globs.jobs > MAXJOBS ) { err_printf( "Invalid value for the '-j' option, valid values are 1 " "through %d.\n", MAXJOBS ); exit( EXITBAD ); } } if ( ( s = getoptval( optv, 'g', 0 ) ) ) globs.newestfirst = 1; if ( ( s = getoptval( optv, 'l', 0 ) ) ) globs.timeout = atoi( s ); if ( ( s = getoptval( optv, 'm', 0 ) ) ) globs.max_buf = atoi( s ) * 1024; /* convert to kb */ #ifdef HAVE_PYTHON if ( ( s = getoptval( optv, 'z', 0 ) ) ) python_optimize = 0; /* disable python optimization */ #endif /* Turn on/off debugging */ for ( n = 0; ( s = getoptval( optv, 'd', n ) ); ++n ) { int i; /* First -d, turn off defaults. */ if ( !n ) for ( i = 0; i < DEBUG_MAX; ++i ) globs.debug[i] = 0; i = atoi( s ); if ( ( i < 0 ) || ( i >= DEBUG_MAX ) ) { out_printf( "Invalid debug level '%s'.\n", s ); continue; } /* n turns on levels 1-n. */ /* +n turns on level n. */ if ( *s == '+' ) globs.debug[ i ] = 1; else while ( i ) globs.debug[ i-- ] = 1; } /* If an output file is specified, set globs.out to that. */ if ( ( s = getoptval( optv, 'o', 0 ) ) ) { if ( !( globs.out = fopen( s, "w" ) ) ) { err_printf( "Failed to write to '%s'\n", s ); exit( EXITBAD ); } /* ++globs.noexec; */ } constants_init(); cwd_init(); { PROFILE_ENTER( MAIN ); #ifdef HAVE_PYTHON { PROFILE_ENTER( MAIN_PYTHON ); Py_OptimizeFlag = python_optimize; Py_Initialize(); { static PyMethodDef BjamMethods[] = { {"call", bjam_call, METH_VARARGS, "Call the specified bjam rule."}, {"import_rule", bjam_import_rule, METH_VARARGS, "Imports Python callable to bjam."}, {"define_action", bjam_define_action, METH_VARARGS, "Defines a command line action."}, {"variable", bjam_variable, METH_VARARGS, "Obtains a variable from bjam's global module."}, {"backtrace", bjam_backtrace, METH_VARARGS, "Returns bjam backtrace from the last call into Python."}, {"caller", bjam_caller, METH_VARARGS, "Returns the module from which the last call into Python is made."}, {NULL, NULL, 0, NULL} }; Py_InitModule( "bjam", BjamMethods ); } PROFILE_EXIT( MAIN_PYTHON ); } #endif #ifndef NDEBUG run_unit_tests(); #endif #if YYDEBUG != 0 if ( DEBUG_PARSE ) yydebug = 1; #endif /* Set JAMDATE. */ { timestamp current; timestamp_current( ¤t ); var_set( root_module(), constant_JAMDATE, list_new( outf_time( ¤t ) ), VAR_SET ); } /* Set JAM_VERSION. */ var_set( root_module(), constant_JAM_VERSION, list_push_back( list_push_back( list_new( object_new( VERSION_MAJOR_SYM ) ), object_new( VERSION_MINOR_SYM ) ), object_new( VERSION_PATCH_SYM ) ), VAR_SET ); /* Set JAMUNAME. */ #ifdef unix { struct utsname u; if ( uname( &u ) >= 0 ) { var_set( root_module(), constant_JAMUNAME, list_push_back( list_push_back( list_push_back( list_push_back( list_new( object_new( u.sysname ) ), object_new( u.nodename ) ), object_new( u.release ) ), object_new( u.version ) ), object_new( u.machine ) ), VAR_SET ); } } #endif /* unix */ /* Set JAM_TIMESTAMP_RESOLUTION. */ { timestamp fmt_resolution[ 1 ]; file_supported_fmt_resolution( fmt_resolution ); var_set( root_module(), constant_JAM_TIMESTAMP_RESOLUTION, list_new( object_new( timestamp_timestr( fmt_resolution ) ) ), VAR_SET ); } /* Load up environment variables. */ /* First into the global module, with splitting, for backward * compatibility. */ var_defines( root_module(), use_environ, 1 ); environ_module = bindmodule( constant_ENVIRON ); /* Then into .ENVIRON, without splitting. */ var_defines( environ_module, use_environ, 0 ); /* * Jam defined variables OS & OSPLAT. We load them after environment, so * that setting OS in environment does not change Jam's notion of the * current platform. */ var_defines( root_module(), othersyms, 1 ); /* Load up variables set on command line. */ for ( n = 0; ( s = getoptval( optv, 's', n ) ); ++n ) { char * symv[ 2 ]; symv[ 0 ] = s; symv[ 1 ] = 0; var_defines( root_module(), symv, 1 ); var_defines( environ_module, symv, 0 ); } /* Set the ARGV to reflect the complete list of arguments of invocation. */ for ( n = 0; n < arg_c; ++n ) var_set( root_module(), constant_ARGV, list_new( object_new( arg_v[ n ] ) ), VAR_APPEND ); /* Initialize built-in rules. */ load_builtins(); /* Add the targets in the command line to the update list. */ for ( n = 1; n < arg_c; ++n ) { if ( arg_v[ n ][ 0 ] == '-' ) { char * f = "-:l:d:j:f:gs:t:ano:qv"; for ( ; *f; ++f ) if ( *f == arg_v[ n ][ 1 ] ) break; if ( ( f[ 1 ] == ':' ) && ( arg_v[ n ][ 2 ] == '\0' ) ) ++n; } else { OBJECT * const target = object_new( arg_v[ n ] ); mark_target_for_updating( target ); object_free( target ); } } if ( list_empty( targets_to_update() ) ) mark_target_for_updating( constant_all ); /* Parse ruleset. */ { FRAME frame[ 1 ]; frame_init( frame ); for ( n = 0; ( s = getoptval( optv, 'f', n ) ); ++n ) { OBJECT * const filename = object_new( s ); parse_file( filename, frame ); object_free( filename ); } if ( !n ) parse_file( constant_plus, frame ); } status = yyanyerrors(); /* Manually touch -t targets. */ for ( n = 0; ( s = getoptval( optv, 't', n ) ); ++n ) { OBJECT * const target = object_new( s ); touch_target( target ); object_free( target ); } /* The build system may set the PARALLELISM variable to override -j * options. */ { LIST * const p = var_get( root_module(), constant_PARALLELISM ); if ( !list_empty( p ) ) { int const j = atoi( object_str( list_front( p ) ) ); if ( j < 1 || j > MAXJOBS ) out_printf( "Invalid value of PARALLELISM: %s. Valid values " "are 1 through %d.\n", object_str( list_front( p ) ), MAXJOBS ); else globs.jobs = j; } } /* KEEP_GOING overrides -q option. */ { LIST * const p = var_get( root_module(), constant_KEEP_GOING ); if ( !list_empty( p ) ) globs.quitquick = atoi( object_str( list_front( p ) ) ) ? 0 : 1; } /* Now make target. */ { PROFILE_ENTER( MAIN_MAKE ); LIST * const targets = targets_to_update(); if ( !list_empty( targets ) ) status |= make( targets, anyhow ); else status = last_update_now_status; PROFILE_EXIT( MAIN_MAKE ); } PROFILE_EXIT( MAIN ); } if ( DEBUG_PROFILE ) profile_dump(); #ifdef OPT_HEADER_CACHE_EXT hcache_done(); #endif clear_targets_to_update(); /* Widely scattered cleanup. */ property_set_done(); file_done(); rules_done(); timestamp_done(); search_done(); class_done(); modules_done(); regex_done(); cwd_done(); path_done(); function_done(); list_done(); constants_done(); object_done(); /* Close log out. */ if ( globs.out ) fclose( globs.out ); #ifdef HAVE_PYTHON Py_Finalize(); #endif BJAM_MEM_CLOSE(); return status ? EXITBAD : EXITOK; }