command.c revision 3f54fd611f536639ec30dd53c48e5ec1897cc7d9
/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1984-2012 AT&T Intellectual Property *
* and is licensed under the *
* Eclipse Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* Glenn Fowler <gsf@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Research
*
* make command execution routines
*/
#include "make.h"
#include <sig.h>
#if !_all_shells_the_same
#define EXIT_CODE(x) ((x)&0177)
#endif
#define AFTER 0 /* done -- making after prereqs */
#define MAMNAME(r) ((state.mam.dynamic||(r)!=state.frame->target||((r)->property&P_after))?mamname(r):(char*)0)
#if DEBUG
static char* statusname[] =
{
"AFTER", "BEFORE", "BLOCKED", "INTERMEDIATE", "READY", "RUNNING",
};
#define jobstatus() do { if (state.test & 0x00001000) dumpjobs(2, JOB_status); else if (error_info.trace <= CMDTRACE) dumpjobs(CMDTRACE, JOB_status); } while (0)
#else
#define jobstatus()
#endif
struct Context_s /* job target context */
{
int targetview; /* state.targetview */
};
struct Joblist_s /* job list cell */
{
char* action; /* unexpanded action */
int status; /* job status */
};
typedef struct Jobstate_s /* job state */
{
int intermediate; /* # INTERMEDIATE jobs */
} Jobstate_t;
static Jobstate_t jobs;
/*
* accept r as up to date
*/
static void
{
{
goto done;
}
{
{
statetime(r, 0);
}
goto done;
}
if (r->active && (r->active->parent->target->property & P_archive) && !(r->property & (P_after|P_before|P_use)))
{
char* t;
{
if (!r->uname)
}
goto done;
}
{
{
if (stat(r->name, &st) || tmxgetmtime(&st) < state.start && tmxtouch(r->name, (Time_t)0, (Time_t)0, (Time_t)0, 0))
statetime(r, 0);
}
}
done:
}
/*
* apply operator or action with attributes in r given lhs, rhs and job flags
* blocks until action is complete
*/
int
{
register Rule_t* x;
int oop;
int errors;
lhs_prereqs.next = 0;
x = &lhs_rule;
x->prereqs = &lhs_prereqs;
if (x->property & P_operator)
{
}
else
{
if (r->prereqs)
{
Time_t t;
char* oaction;
}
}
else
{
}
return errors;
}
/*
* apply() returning (temporary) CO_DATAFILE pointer to stdout of action
*/
{
fp = 0;
{
else
}
return fp;
}
/*
* call functional r with args
* functional return value returned
* 0 returned if not functional or empty return
*/
char*
{
register Var_t* v;
if (r->property & P_functional)
{
return v->value;
}
return 0;
}
/*
*/
static void
{
register char* t;
register char* v;
register Rule_t* r;
if (t = strrchr(s, '/'))
{
*t = 0;
{
if (!r->view)
{
*t = '/';
return;
}
if (*(v = r->name) != '/')
{
}
r = 0;
}
if (r || state.targetcontext && (!r || !r->time) && (st.st_mode = (S_IRWXU|S_IRWXG|S_IRWXO)) && tmxsetmtime(&st, state.start))
{
/*
* why not mkdir -p here?
*/
if (((job->flags & CO_ALWAYS) || state.exec && state.touch) && (mkdir(s, st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || stat(s, &st)))
{
}
r = makerule(s);
oldname(r);
r->view = 0;
}
*t = '/';
}
}
/*
* push job context
*/
static void
{
register Context_t* z;
register Frame_t* p;
register Rule_t* r;
int n;
{
p = z->frame;
for (;;)
{
p->target = r;
r->active = p;
if (p == p->parent)
break;
p = p->parent;
}
n = state.targetview;
z->targetview = n;
z->frame = p;
}
}
/*
* pop job context
*/
static void
{
register Context_t* z;
register Frame_t* p;
register Rule_t* r;
int n;
{
n = state.targetview;
z->targetview = n;
z->frame = p;
for (;;)
{
if (p == p->parent)
break;
p = p->parent;
}
}
}
/*
* discard (free) job context
*/
static void
{
register Context_t* z;
register List_t* p;
{
}
{
}
{
jobs.freecontext = z;
}
else
else
}
/*
* save job context for later execute()
*/
static void
{
register Frame_t* o;
register Frame_t* p;
register Frame_t* x;
Context_t* z;
{
if (z = jobs.freecontext)
else
p = 0;
for (;;)
{
else
if (p)
p->parent = x;
else
z->frame = x;
p = x;
if (o->parent == o)
break;
o = o->parent;
}
}
}
/*
* restore action by expanding into buf using original context
* coexec attributes placed in att
*/
static void
{
register char* s;
register char* b;
char* u;
char* down;
char* back;
char* sep;
int downlen;
int localview;
void* pos;
Var_t* v;
{
register Rule_t* r;
register List_t* p;
{
if (*r->name == '-')
else
}
}
{
size_t n;
int c;
downlen = s - u;
*s = 0;
c = '/';
do
{
if (u = strchr(u, '/'))
u++;
else
c = 0;
} while (c);
*s = '/';
}
else
{
while (b = strchr(s, MARK_CONTEXT))
{
if (!(s = strchr(++b, MARK_CONTEXT)))
*s++ = 0;
else if (*b)
{
switch (*(b + downlen))
{
case 0:
continue;
case '/':
continue;
}
if (streq(b, "."))
else if (isspace(*b))
else
}
}
}
{
}
}
/*
* send a job to the coshell for execution
*/
static void
{
register List_t* p;
char* s;
char* t;
int flags;
Rule_t* r;
Var_t* v;
if (state.targetcontext || state.maxview && !state.fsview && *job->target->name != '/' && (!(job->target->dynamic & D_regular) || job->target->view))
if ((state.mam.dynamic || state.mam.regress) && state.user && !(job->target->property & (P_after|P_before|P_dontcare|P_make|P_state|P_virtual)))
sfprintf(state.mam.out, "%sinit %s %s\n", state.mam.label, mamname(job->target), timefmt(NiL, CURTIME));
{
{
if (state.virtualdot)
{
state.virtualdot = 0;
lockstate(1);
}
{
}
}
}
else
{
{
state.virtualdot = 0;
lockstate(1);
}
{
#if !O_cloexec
#endif
flags |= CO_SERIALIZE;
sfstrclose(sp);
}
{
do
{
{
}
{
*s = 0;
*s = '=';
}
} while (p = p->next);
#if 0
#endif
}
{
static char* dot;
static char* tmp;
{
if (!dot)
}
else
{
if (!tmp)
}
}
#if !_HUH_1992_02_29 /* i386 and ftx m68k dump without this statement -- help */
#endif
#if _WINIX
#else
#endif
{
}
/*
* grab semaphores
*/
{
}
/*
* check status and sync
*/
{
{
{
else
{
char* e;
while (e > t && *(e - 1) == '\n')
e--;
}
}
else
}
}
}
}
/*
* check if job for r with completed prereqs p can be cancelled
*/
static int
{
register Rule_t* a;
register Rule_t* s;
register Rule_t* t;
if (r->must)
{
for (; p; p = p->next)
{
r->must--;
}
if (!r->must)
{
r->dynamic &= ~D_triggered;
return 1;
}
}
return 0;
}
/*
* all actions on behalf of job are done
* with the possible exception of after prereqs
* clear!=0 to clear job queue
* 0 returned if further blocking required
*/
static int
{
register List_t* p;
register Rule_t* a;
int n;
int semaphore;
if (clear && jobs.triggered && (((a = jobs.triggered)->property & P_state) || (a = staterule(RULE, a, NiL, 0))) && (a->property & P_force))
{
a->time = 0;
}
jobstatus();
else if (!clear && job->status == RUNNING && (job->target->dynamic & D_hasafter) && hasafter(job->target, (job->flags & CO_ERRORS) ? P_failure : P_after))
{
{
return 0;
}
#if __GNUC__ == 3 && ( sparc || _sparc || __sparc ) && !_HUH_2008_10_25 /* gcc code generation bug -- first hit on solaris -- not sure if for all gcc 3.* */
#ifndef __GNUC_MINOR__
#define __GNUC_MINOR__ 0
#endif
#ifndef __GNUC_PATCHLEVEL__
#define __GNUC_PATCHLEVEL__ 1
#endif
{
static int warned = 0;
if (!warned)
{
warned = 1;
error(state.mam.regress || state.regress ? -1 : 1, "command.c:%d: gcc %d.%d.%d code generation bug workaround -- pass this on to the vendor", __LINE__, __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
}
return !clear;
}
#endif
if (n)
else
{
{
{
}
}
}
}
/*
* update rule times and status
*/
job->target->status = (job->flags & CO_ERRORS) ? ((job->target->property & P_dontcare) ? IGNORE : FAILED) : EXISTS;
if (n = cojob && (state.mam.dynamic || state.mam.regress) && state.user && !(job->target->property & (P_after|P_before|P_dontcare|P_make|P_state|P_virtual)))
sfprintf(state.mam.out, "%scode %s %d %s %s%s%s\n", state.mam.label, (job->target != state.frame->target || (job->target->property & P_after)) ? mamname(job->target) : "-", EXIT_CODE(cojob->status), timefmt(NiL, tm), timefmt(NiL, CURTIME), (job->target->dynamic & D_same) ? " same" : null, cojob->status && (job->flags & CO_IGNORE) ? " ignore" : null);
{
p->rule->status = (job->flags & CO_ERRORS) ? ((p->rule->property & P_dontcare) ? IGNORE : FAILED) : EXISTS;
if (n)
sfprintf(state.mam.out, "%scode %s %d %s%s%s\n", state.mam.label, mamname(p->rule), EXIT_CODE(cojob->status), timefmt(NiL, tm), (p->rule->dynamic & D_same) ? " same" : null, cojob->status && (job->flags & CO_IGNORE) ? " ignore" : null);
}
/*
* update the job list
*/
jammed = 0;
for (;;)
{
{
case AFTER:
case BEFORE:
case BLOCKED:
case READY:
n = READY;
semaphore = 1;
waiting = 0;
{
continue;
switch (a->status)
{
case FAILED:
continue;
goto another;
case MAKING:
{
waiting = a;
continue;
}
waiting = 0;
n = BLOCKED;
if (!a->semaphore)
semaphore = 0;
break;
default:
continue;
}
break;
}
if (waiting)
{
goto another;
}
{
goto after;
}
{
goto another;
{
jobs.intermediate++;
}
{
if (n)
{
goto another;
}
}
{
goto again;
}
}
break;
case RUNNING:
{
}
break;
}
{
/*
* jammed is the first discovered member
* of a possible deadlock and we arbitrarily
* break it here
*/
if (jammed)
{
#if __GNUC__ >= 4 && !_HUH_2006_01_11 /* gcc code generation bug -- first hit on macos -- not sure if for all gcc 4.* */
#ifndef __GNUC_MINOR__
#define __GNUC_MINOR__ 0
#endif
#ifndef __GNUC_PATCHLEVEL__
#define __GNUC_PATCHLEVEL__ 1
#endif
if (!job)
{
static int warned = 0;
if (!warned)
{
warned = 1;
error(state.mam.regress || state.regress ? -1 : 1, "command.c:%d: gcc %d.%d.%d code generation bug workaround -- pass this on to the vendor", __LINE__, __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
}
break;
}
else
#endif
{
goto another;
{
jammed = 0;
goto unjam;
}
}
}
break;
}
}
return !clear;
}
/*
* block until one job completes
* update the job list
* clear job list on any unexpected action error
*/
int
{
Rule_t* r;
int n;
int clear = 0;
int resume = 0;
{
if (jobs.intermediate)
{
/*
* mark the jobs that must be generated
*/
n = 0;
{
n = 1;
break;
}
if (n)
{
/*
* some intermediates must be generated
*/
}
else
{
/*
* accept missing intermediates
*/
{
error(state.explain ? 0 : -1, "cancelling %s action -- %s", job->target->name, job->status == INTERMEDIATE ? "intermediate not needed" : "missing intermediates accepted");
}
jobs.intermediate = 0;
return 1;
}
}
return 0;
}
for (;;)
{
if ((cojob = cowait(state.coshell, check ? (Cojob_t*)state.coshell : (Cojob_t*)0, -1)) && (job = (Joblist_t*)cojob->local))
if (trap())
{
if (state.interpreter)
if (!cojob)
continue;
}
if (!cojob)
{
if (check)
return 0;
break;
}
{
sfprintf(jobs.tmp, "%s %d %s %s", job->target->name, cojob->status, fmtelapsed(cojob->user, CO_QUANT), fmtelapsed(cojob->sys, CO_QUANT));
}
{
{
n = 0;
error(n ? 2 : state.explain ? 0 : -1, "%s%s code %d making %s%s", n ? "*** " : null, ERROR_translate(NiL, NiL, NiL, EXITED_TERM(cojob->status) ? "termination" : "exit"), EXIT_CODE(cojob->status), job->target->name, (job->flags & CO_IGNORE) ? ERROR_translate(NiL, NiL, NiL, " ignored") : null);
}
{
{
if (n)
}
}
clear = 1;
}
message((-3, "job: %s: interrupt=%d clear=%d status=%d flags=%08x", job->target->name, state.interrupt, clear, cojob->status, job->flags));
/*
* job is done
*/
return 1;
}
if (resume)
{
{
if (clear)
finish(1);
}
}
return 0;
}
/*
* r==0 and p==0 waits for all actions
* the number of FAILED actions is returned
*/
int
{
register int errors = 0;
int check = 0;
int recent;
List_t* q;
if (r)
{
p = &tmp;
p->rule = r;
}
else
{
{
p = p->next;
check = 1;
}
if (!p)
{
return 0;
}
}
{
{
do
{
{
{
}
break;
}
}
{
register Rule_t* x;
register Rule_t* s;
if ((x = q->rule) != r)
{
{
}
{
}
}
}
errors++;
}
if (tm)
return errors;
}
/*
* terminate all jobs
*/
void
terminate(void)
{
}
/*
* complete all jobs and drop the coshell
*/
void
drop(void)
{
{
while (block(0));
message((-1, "jobs %d user %s sys %s", state.coshell->total, fmtelapsed(state.coshell->user, CO_QUANT), fmtelapsed(state.coshell->sys, CO_QUANT)));
}
}
/*
* trigger action to build r
* a contains the action attributes
*
* NOTE: the prereqs cons() may not be freed
*/
void
{
register List_t* p;
int n;
/*
* update flags
*/
if (!a)
a = r;
if (state.exec && !state.touch || (a->property & P_always) && (!state.never || (flags & CO_URGENT)))
if (!state.jobs || (r->property & P_foreground) || (r->property & (P_make|P_functional)) == P_functional || (r->dynamic & D_hasmake))
flags |= CO_KEEPGOING;
flags |= CO_DATAFILE;
if (action)
{
message((-1, "triggering %s action%s%s", r->name, r == a ? null : " using ", r == a ? null : a->name));
r->dynamic |= D_triggered;
if (!*action)
action = 0;
}
{
/*
* the make thread blocks when too many jobs are outstanding
*/
}
{
flags |= CO_PRIMARY;
}
{
{
return;
}
/*
* make actions are done immediately, bypassing the job queue
*/
else
{
else if ((r->dynamic & (D_hasbefore|D_triggered)) == (D_hasbefore|D_triggered) && (makebefore(r) || complete(NiL, prereqs, NiL, 0)))
else
{
if (r->property & P_functional)
if (action)
{
case EXISTS:
statetime(r, 0);
break;
case FAILED:
break;
case TOUCH:
break;
case UPDATE:
break;
}
}
}
if (p->rule != r)
{
}
{
{
}
}
}
else
{
/*
* only one repeat action at a time
*/
if ((r->property & P_repeat) && (r->property & (P_before|P_after)) && !(r->dynamic & D_hassemaphore))
{
a->semaphore = 2;
r->dynamic |= D_hassemaphore;
}
/*
* check if any prerequisites are blocking execution
* FAILED prerequisites cause the target to fail too
*/
n = READY;
for (;;)
{
{
continue;
switch (a->status)
{
case FAILED:
continue;
if (p->rule != r)
return;
case MAKING:
if (a->active)
else
n = BLOCKED;
break;
}
}
if (n != READY)
break;
if (action)
{
return;
{
n = INTERMEDIATE;
jobs.intermediate++;
break;
}
}
break;
if (makebefore(r))
{
if (p->rule != r)
return;
}
}
{
/*
* allocate a job cell and add to job list
* the first READY job from the top is executed next
*/
else
{
else
}
else
{
else
}
/*
* fill in the info
*/
if (p->rule != r)
if (n == READY)
{
if (r->dynamic & D_hasafter)
}
else
jobstatus();
}
else
{
{
{
}
{
char* t;
}
}
}
if (r->dynamic & D_triggered)
{
}
}
}
/*
* resolve any cached info on file opened on fd
*/
int
{
}
#if DEBUG
/*
* dump the job table
*/
void
{
register List_t* p;
register Rule_t* a;
int indent;
int line;
{
error_info.indent = 0;
error_info.line = 0;
switch (op)
{
case JOB_blocked:
{
{
continue;
}
}
break;
case JOB_status:
sfprintf(sfstderr, "JOB STATUS TARGET tot=%d pend=%d done=%d\n", state.coshell->total, copending(state.coshell), cozombie(state.coshell));
{
sfsprintf(tmpname, MAXNAME, "[%d]%s", job->cojob->id, job->cojob->id > 99 ? null : job->cojob->id > 9 ? " " : " ");
else
, tmpname
, (job->target->must > ((unsigned int)(job->target->dynamic & D_intermediate) != 0)) ? " [must]" : null
);
}
break;
default:
break;
}
}
}
#endif