1734 lines
56 KiB
C
1734 lines
56 KiB
C
/* $RCSfile: rulparse.c,v $
|
|
-- $Revision: 1.12 $
|
|
-- last change: $Author: ihi $ $Date: 2007-10-15 15:41:24 $
|
|
--
|
|
-- SYNOPSIS
|
|
-- Perform semantic analysis on input
|
|
--
|
|
-- DESCRIPTION
|
|
-- This code performs semantic analysis on the input, and builds
|
|
-- the complex internal datastructure that is used to represent
|
|
-- the user makefile.
|
|
--
|
|
-- AUTHOR
|
|
-- Dennis Vadura, dvadura@dmake.wticorp.com
|
|
--
|
|
-- WWW
|
|
-- http://dmake.wticorp.com/
|
|
--
|
|
-- COPYRIGHT
|
|
-- Copyright (c) 1996,1997 by WTI Corp. All rights reserved.
|
|
--
|
|
-- This program is NOT free software; you can redistribute it and/or
|
|
-- modify it under the terms of the Software License Agreement Provided
|
|
-- in the file <distribution-root>/readme/license.txt.
|
|
--
|
|
-- LOG
|
|
-- Use cvs log to obtain detailed change logs.
|
|
*/
|
|
|
|
#include "extern.h"
|
|
|
|
/* prototypes for local functions */
|
|
static void _add_indirect_prereq ANSI((CELLPTR));
|
|
static int _add_root ANSI((CELLPTR));
|
|
static CELLPTR _build_graph ANSI((int, CELLPTR, CELLPTR));
|
|
static char* _build_meta ANSI((char*));
|
|
static int _do_magic ANSI((int, char*, CELLPTR, CELLPTR, t_attr, char*));
|
|
static void _do_special ANSI((int, int, t_attr,char*,CELLPTR,CELLPTR,int*));
|
|
static int _do_targets ANSI((int, t_attr, char*, CELLPTR, CELLPTR));
|
|
static t_attr _is_attribute ANSI((char*));
|
|
static int _is_special ANSI((char*));
|
|
static char* _is_magic ANSI((char*));
|
|
static int _is_percent ANSI((char*));
|
|
static CELLPTR _make_multi ANSI((CELLPTR));
|
|
static CELLPTR _replace_cell ANSI((CELLPTR,CELLPTR,CELLPTR));
|
|
static void _set_attributes ANSI((t_attr, char*, CELLPTR ));
|
|
static void _stick_at_head ANSI((CELLPTR, CELLPTR));
|
|
static void _set_global_attr ANSI((t_attr));
|
|
|
|
|
|
/* static variables that must persist across invocation of Parse_rule_def */
|
|
static CELLPTR _sv_targets = NIL(CELL);
|
|
static STRINGPTR _sv_rules = NIL(STRING); /* first recipe element. */
|
|
static STRINGPTR _sv_crule = NIL(STRING); /* current/last recipe element. */
|
|
static CELLPTR _sv_edgel = NIL(CELL);
|
|
static LINKPTR _sv_ind_prq = NIL(LINK); /* indirect prerequisites for % cell */
|
|
static int _sp_target = FALSE;
|
|
static t_attr _sv_attr;
|
|
static int _sv_flag;
|
|
static int _sv_op;
|
|
static char *_sv_setdir;
|
|
static char _sv_globprq_only = 0;
|
|
|
|
/* Define for global attribute mask (A_SWAP == A_WINPATH) */
|
|
#define A_GLOB (A_PRECIOUS | A_SILENT | A_IGNORE | A_EPILOG | A_SWAP |\
|
|
A_SHELL | A_PROLOG | A_NOINFER | A_SEQ | A_MKSARGS )
|
|
|
|
|
|
PUBLIC int
|
|
Parse_rule_def( state )/*
|
|
=========================
|
|
Parse the rule definition contained in Buffer, and modify the state
|
|
if appropriate. The function returns 0, if the definition is found to
|
|
be an illegal rule definition, and it returns 1 if it is a rule definition.
|
|
*/
|
|
int *state;
|
|
{
|
|
TKSTR input; /* input string struct for token search */
|
|
CELLPTR targets; /* list of targets if any */
|
|
CELLPTR prereq; /* list of prereq if any */
|
|
CELLPTR prereqtail; /* tail of prerequisite list */
|
|
CELLPTR cp; /* temporary cell pointer for list making */
|
|
char *result; /* temporary storage for result */
|
|
char *tok; /* temporary pointer for tokens */
|
|
char *set_dir; /* value of setdir attribute */
|
|
char *brk; /* break char list for Get_token */
|
|
char *firstrcp; /* first recipe line, from ; in rule line */
|
|
t_attr attr; /* sum of attribute flags for current tgts*/
|
|
t_attr at; /* temp place to keep an attribute code */
|
|
int op; /* rule operator */
|
|
int special; /* indicate special targets in rule */
|
|
int augmeta; /* indicate .<suffix> like target */
|
|
int percent; /* indicate percent rule target */
|
|
int percent_prq; /* indicate mixed %-rule prereq possible */
|
|
|
|
DB_ENTER( "Parse_rule_def" );
|
|
|
|
op = 0;
|
|
attr = 0;
|
|
special = 0;
|
|
augmeta = 0;
|
|
percent = 0;
|
|
set_dir = NIL( char );
|
|
targets = NIL(CELL);
|
|
prereq = NIL(CELL);
|
|
prereqtail = NIL(CELL);
|
|
percent_prq = 0;
|
|
|
|
/* Check to see if the line is of the form:
|
|
* targets : prerequisites; first recipe line
|
|
* If so remember the first_recipe part of the line. */
|
|
|
|
firstrcp = strchr( Buffer, ';' );
|
|
if( firstrcp != NIL( char ) ) {
|
|
*firstrcp++ = 0;
|
|
firstrcp = DmStrSpn( firstrcp, " \t" );
|
|
}
|
|
|
|
result = Expand( Buffer );
|
|
/* Remove CONTINUATION_CHAR, keep the <nl> */
|
|
for( brk=strchr(result,CONTINUATION_CHAR); brk != NIL(char); brk=strchr(brk,CONTINUATION_CHAR) )
|
|
if( brk[1] == '\n' )
|
|
*brk = ' ';
|
|
else
|
|
brk++;
|
|
|
|
DB_PRINT( "par", ("Scanning: [%s]", result) );
|
|
|
|
SET_TOKEN( &input, result );
|
|
brk = ":-^!|";
|
|
Def_targets = TRUE;
|
|
|
|
/* Scan the input rule line collecting targets, the operator, and any
|
|
* prerequisites. Stop when we run out of targets and prerequisites. */
|
|
|
|
while( *(tok = Get_token( &input, brk, TRUE )) != '\0' )
|
|
if( !op ) {
|
|
/* we are scanning targets and attributes
|
|
* check to see if token is an operator. */
|
|
|
|
op = Rule_op( tok );
|
|
|
|
if( !op ) {
|
|
/* Define a new cell, or get pointer to pre-existing cell. */
|
|
/* Do we need cells for attributes? If not move the definition
|
|
* to the target part. */
|
|
cp = Def_cell( tok );
|
|
/* A $ character indicates either a literal $ in the pathname (this
|
|
* was broken before) or a dynamic macro (this is a syntax error).
|
|
* FIXME: Here would be the place to add a sanity check. */
|
|
DB_PRINT( "par", ("tg_cell [%s]", tok) );
|
|
|
|
if( (at = _is_attribute(tok)) != 0 ) {
|
|
/* Ignore .SILENT when -vr is active. */
|
|
if( (Verbose & V_FORCEECHO) && (at == A_SILENT) )
|
|
at = 0;
|
|
|
|
/* Logically OR the attributes specified into one main
|
|
* ATTRIBUTE mask. */
|
|
|
|
if( at == A_SETDIR ) {
|
|
if( set_dir != NIL( char ) )
|
|
Warning( "Multiple .SETDIR attribute ignored" );
|
|
else
|
|
set_dir = DmStrDup( tok );
|
|
}
|
|
|
|
attr |= at;
|
|
}
|
|
else {
|
|
/* Not an attribute, this must be a target. */
|
|
int tmp;
|
|
|
|
tmp = _is_special( tok );
|
|
|
|
if( _is_percent( tok ) ) {
|
|
/* First %-target checks if there were non-%-targets before. */
|
|
if( !percent && targets != NIL(CELL) )
|
|
Fatal( "A %%-target must not be mixed with non-%%-targets, offending target [%s]", tok );
|
|
|
|
percent++;
|
|
cp->ce_flag |= F_PERCENT;
|
|
} else {
|
|
if( percent )
|
|
Fatal( "A non-%%-target must not be mixed with %%-targets, offending target [%s]", tok );
|
|
}
|
|
|
|
if( _is_magic( tok ) ) {
|
|
/* Check that AUGMAKE targets are not mixed with other
|
|
* targets. The return value of _is_magic() is discarded and
|
|
* calculated again in _do_targets() if this rule definition
|
|
* really is a .<suffix> like target.
|
|
* If we would allow only one target per line we could easily
|
|
* store the result for later, but for multiple .<suffix>
|
|
* targets this creates too much overhead.
|
|
* These targets should be rare (obsolete?) anyway. */
|
|
if( !augmeta && targets != NIL(CELL) )
|
|
Fatal( "An AUGMAKE meta target must not be mixed with non AUGMAKE meta targets, offending target [%s]", tok );
|
|
|
|
augmeta++;
|
|
cp->ce_flag |= F_MAGIC; /* do_magic will also add F_PERCENT later. */
|
|
} else {
|
|
if( augmeta )
|
|
Fatal( "A non AUGMAKE meta target must not be mixed with AUGMAKE meta targets, offending target [%s]", tok );
|
|
}
|
|
|
|
if( special )
|
|
Fatal( "Special target must appear alone, found [%s]", tok );
|
|
else if( !(cp->ce_flag & F_MARK) ) {
|
|
/* Targets are kept in this list in lexically sorted order.
|
|
* This allows for easy equality comparison of target
|
|
* sets.*/
|
|
CELLPTR prev,cur;
|
|
for(prev=NIL(CELL),cur=targets;cur;prev=cur,cur=cur->ce_link)
|
|
if(strcmp(cur->CE_NAME,cp->CE_NAME) > 0)
|
|
break;
|
|
|
|
cp->ce_link = cur;
|
|
|
|
if (!prev)
|
|
targets = cp;
|
|
else
|
|
prev->ce_link = cp;
|
|
|
|
cp->ce_flag |= F_MARK | F_EXPLICIT;
|
|
special = tmp;
|
|
}
|
|
else
|
|
Warning( "Duplicate target [%s]", cp->CE_NAME );
|
|
}
|
|
}
|
|
else {
|
|
/* found an operator so empty out break list and clear mark
|
|
* bits on target list, setting them all to F_VISITED*/
|
|
|
|
brk = "";
|
|
for( cp=targets; cp != NIL(CELL); cp=cp->ce_link ) {
|
|
cp->ce_flag ^= F_MARK;
|
|
cp->ce_flag |= F_VISITED;
|
|
}
|
|
|
|
Def_targets = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
/* Scanning prerequisites so build the prerequisite list. We use
|
|
* F_MARK flag to make certain we have only a single copy of the
|
|
* prerequisite in the list */
|
|
|
|
cp = Def_cell( tok );
|
|
|
|
/* %-prerequisits require eiter a %-target or this might be a rule of
|
|
* the "ATTRIBUTE_LIST : targets" form. */
|
|
if( _is_percent( tok ) ) {
|
|
if( percent || ((targets == NIL(CELL)) && attr) )
|
|
percent_prq = 1;
|
|
else
|
|
Fatal( "Syntax error in %% rule, missing %% target");
|
|
}
|
|
|
|
if( cp->ce_flag & F_VISITED ) {
|
|
if( cp->ce_attr & A_COMPOSITE )
|
|
continue;
|
|
else
|
|
Fatal( "Detected circular dependency in graph at [%s]",
|
|
cp->CE_NAME );
|
|
}
|
|
else if( !(cp->ce_flag & F_MARK) ) {
|
|
DB_PRINT( "par", ("pq_cell [%s]", tok) );
|
|
cp->ce_flag |= F_MARK;
|
|
|
|
if( prereqtail == NIL(CELL) ) /* keep prereq's in order */
|
|
prereq = cp;
|
|
else
|
|
prereqtail->ce_link = cp;
|
|
|
|
prereqtail = cp;
|
|
cp->ce_link = NIL(CELL);
|
|
}
|
|
else if( !(cp->ce_attr & A_LIBRARY) && (Verbose & V_WARNALL))
|
|
Warning("Duplicate entry [%s] in prerequisite list",cp->CE_NAME);
|
|
}
|
|
|
|
/* Check to see if we have a percent rule that has only global
|
|
* prerequisites, i.e. they are of the form: "%.a : foo".
|
|
* If so then set the flag so that later on, we don't issue
|
|
* an error if such targets supply an empty set of rules. */
|
|
|
|
if( percent && !percent_prq && (prereq != NIL(CELL)) )
|
|
_sv_globprq_only = 1;
|
|
|
|
/* It's ok to have targets with attributes, and no prerequisites, but it's
|
|
* not ok to have no targets and no attributes, or no operator */
|
|
|
|
if( !op ) {
|
|
CLEAR_TOKEN( &input );
|
|
DB_PRINT( "par", ("Not a rule [%s]", Buffer) );
|
|
DB_RETURN( 0 );
|
|
}
|
|
|
|
/* More than one percent target didn't work with prior versions. */
|
|
if( (percent > 1) && !(op & R_OP_OR) )
|
|
Warning( "Prior to dmake 4.5 only one\n"
|
|
"%%-target per target-definition worked reliably. Check your makefiles.\n" );
|
|
|
|
if( !attr && targets == NIL(CELL) ) {
|
|
Fatal( "Missing targets or attributes in rule" );
|
|
if( set_dir != NIL( char )) FREE( set_dir );
|
|
DB_RETURN( 0 );
|
|
}
|
|
|
|
/* We have established we have a legal rules line, so we must process it.
|
|
* In doing so we must handle any special targets. Special targets must
|
|
* appear alone possibly accompanied by attributes.
|
|
* NOTE: special != 0 ==> targets != NIL(CELL) */
|
|
|
|
if( prereqtail != NIL(CELL) ) prereqtail->ce_link = NIL(CELL);
|
|
|
|
/* Clear out MARK bits used in duplicate checking. I originally wanted
|
|
* to do this as the lists get processed but that got too error prone
|
|
* so I bit the bullit and added these two loops. */
|
|
|
|
for( cp=prereq; cp != NIL(CELL); cp=cp->ce_link ) cp->ce_flag &= ~F_MARK;
|
|
for( cp=targets; cp != NIL(CELL); cp=cp->ce_link ) cp->ce_flag &= ~F_VISITED;
|
|
|
|
/* Check to see if the previous recipe was bound, if not the call
|
|
* Bind_rules_to_targets() to bind the recipe (_sv_rules) to the
|
|
* target(s) (_sv_targets). */
|
|
/* was: if( _sv_rules != NIL(STRING) ) Bind_rules_to_targets( F_DEFAULT );*/
|
|
/* Only Add_recipe_to_list() sets _sv_rules and Bind_rules_to_targets()
|
|
* clears the (static) variables again. Bind_rules_to_targets() is
|
|
* (should be) called after State is leaving RULE_SCAN in Parse().
|
|
* Abort if there are unbound recipes. FIXME: Remove this paragraph
|
|
* if this never occurs. */
|
|
if( _sv_rules != NIL(STRING) )
|
|
Fatal( "Internal Error: _sv_rules not empty." );
|
|
|
|
/* Add the first recipe line to the list */
|
|
if( firstrcp != NIL( char ) )
|
|
Add_recipe_to_list( firstrcp, TRUE, FALSE );
|
|
|
|
/* Save these prior to calling _do_targets, since _build_graph needs the
|
|
* _sv_setdir value for matching edges. */
|
|
_sv_op = op;
|
|
_sv_setdir = set_dir;
|
|
|
|
if( special )
|
|
/* _do_special() can alter *state */
|
|
_do_special( special, op, attr, set_dir, targets, prereq, state );
|
|
else
|
|
*state = _do_targets( op, attr, set_dir, targets, prereq );
|
|
|
|
if( (*state != RULE_SCAN) && (_sv_rules != NIL(STRING)) )
|
|
Fatal( "Unexpected recipe found." );
|
|
|
|
DB_RETURN( 1 );
|
|
}
|
|
|
|
|
|
PUBLIC int
|
|
Rule_op( op )/*
|
|
================
|
|
Check the passed in op string and map it to one of the rule operators */
|
|
char *op;
|
|
{
|
|
int ret = 0;
|
|
|
|
DB_ENTER( "rule_op" );
|
|
|
|
if( *op == TGT_DEP_SEP ) {
|
|
ret = R_OP_CL;
|
|
op++;
|
|
|
|
/* All rule operations begin with a :, but may include any one of the
|
|
* four modifiers. In order for the rule to be properly mapped we must
|
|
* check for each of the modifiers in turn, building up our return bit
|
|
* string. */
|
|
|
|
while( *op && ret )
|
|
switch( *op ) {
|
|
case ':': ret |= R_OP_DCL; op++; break;
|
|
case '!': ret |= R_OP_BG; op++; break;
|
|
case '^': ret |= R_OP_UP; op++; break;
|
|
case '-': ret |= R_OP_MI; op++; break;
|
|
case '|': ret |= R_OP_OR; op++; break;
|
|
|
|
default : ret = 0; /* an invalid modifier, chuck whole string */
|
|
}
|
|
|
|
if( *op != '\0' ) ret = 0;
|
|
}
|
|
|
|
DB_RETURN( ret );
|
|
}
|
|
|
|
|
|
PUBLIC void
|
|
Add_recipe_to_list( rule, white_too, no_check )/*
|
|
=================================================
|
|
Take the provided string and add it to the list of recipe lines
|
|
we are saving to be added to the list of targets we have built
|
|
previously. If white_too == TRUE add the rule EVEN IF it contains only
|
|
an empty string (whitespace is handled by Def_recipe()). */
|
|
char *rule;
|
|
int white_too;
|
|
int no_check;
|
|
{
|
|
DB_ENTER( "Add_recipe_to_list" );
|
|
|
|
if( rule != NIL( char ) && (*rule != '\0' || white_too) ) {
|
|
DB_PRINT( "par", ("Adding recipe [%s]", rule) );
|
|
_sv_crule = Def_recipe( rule, _sv_crule, white_too, no_check );
|
|
|
|
/* If _sv_rules is not yet set this must be the first recipe line,
|
|
* remember it. */
|
|
if( _sv_rules == NIL(STRING) )
|
|
_sv_rules = _sv_crule;
|
|
}
|
|
|
|
DB_VOID_RETURN;
|
|
}
|
|
|
|
|
|
PUBLIC void
|
|
Bind_rules_to_targets( flag )/*
|
|
===============================
|
|
Take the recipe lines we have defined and bind them with proper attributes
|
|
to the targets that were previously defined in the parse. The
|
|
attributes that get passed here are merged with those that are were
|
|
previously defined. (namely attribute F_SINGLE) */
|
|
int flag;
|
|
{
|
|
CELLPTR tg; /* pointer to current target in list */
|
|
LINKPTR lp; /* pointer to link cell */
|
|
int magic; /* TRUE if target of % or .xxx.yyy form */
|
|
int tflag; /* TRUE if we assigned targets here */
|
|
|
|
DB_ENTER( "Bind_rules_to_targets" );
|
|
|
|
/* This line is needed since Parse may call us twice when the last
|
|
* GROUP rule appears at the end of file. In this case the rules
|
|
* have already been bound and we want to ignore them. */
|
|
|
|
if( _sv_targets == NIL(CELL) ) { DB_VOID_RETURN; }
|
|
|
|
tflag = FALSE;
|
|
flag |= (_sv_flag & F_SINGLE);
|
|
flag |= ((_sv_attr & A_GROUP) ? F_GROUP : 0);
|
|
|
|
for( tg = _sv_targets; tg != NIL(CELL); tg = tg->ce_link ) {
|
|
DB_PRINT( "par", ("Binding to %s, %04x", tg->CE_NAME, tg->ce_flag) );
|
|
magic = tg->ce_flag & F_PERCENT;
|
|
|
|
|
|
/* NOTE: For targets that are magic or special we ignore any
|
|
* previously defined rules, ie. We throw away the old definition
|
|
* and use the new, otherwise we complain. */
|
|
if( !(tg->ce_flag & F_MULTI) && !magic && (tg->CE_RECIPE != NIL(STRING))
|
|
&& !_sp_target && (_sv_rules != NIL(STRING)) )
|
|
Fatal( "Multiply defined recipe for target %s", tg->CE_NAME );
|
|
|
|
if( (magic || _sp_target) && (_sv_rules == NIL(STRING)) &&
|
|
!(tg->ce_flag & F_SPECIAL) && !_sv_globprq_only )
|
|
Warning( "Empty recipe for special or meta target %s", tg->CE_NAME );
|
|
|
|
if( magic ) {
|
|
CELLPTR ep;
|
|
|
|
for( ep=_sv_edgel; ep != NIL(CELL); ep=ep->ce_link ) {
|
|
DB_PRINT( "par", ("ep address: %#x", ep) );
|
|
/* %.xx :| '%.yy' abc xx '%.tt' ; touch $@
|
|
* loops here ... */
|
|
_set_attributes( _sv_attr, _sv_setdir, ep );
|
|
ep->ce_flag |= (F_TARGET|flag);
|
|
|
|
if( _sv_rules != NIL(STRING) ) {
|
|
ep->ce_recipe = _sv_rules;
|
|
ep->ce_indprq = _sv_ind_prq;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
tg->ce_attr |= _sv_attr;
|
|
tg->ce_flag |= flag;
|
|
|
|
if( _sv_rules != NIL(STRING) ) {
|
|
tg->ce_recipe = _sv_rules;
|
|
tg->ce_flag |= F_RULES | F_TARGET;
|
|
|
|
/* Bind the current set of prerequisites as belonging to the
|
|
* original recipe given for the target */
|
|
for( lp=tg->ce_prq; lp != NIL(LINK); lp = lp->cl_next )
|
|
if( !(lp->cl_flag & F_VISITED) ) lp->cl_flag |= F_TARGET;
|
|
}
|
|
else for( lp=tg->ce_prq; lp != NIL(LINK); lp = lp->cl_next )
|
|
lp->cl_flag |= F_VISITED;
|
|
}
|
|
|
|
tflag |= _add_root(tg);
|
|
}
|
|
|
|
if( tflag ) Target = TRUE;
|
|
if( _sv_setdir ) FREE(_sv_setdir);
|
|
_sv_rules = NIL(STRING);
|
|
_sv_crule = NIL(STRING);
|
|
_sv_targets = NIL(CELL);
|
|
_sv_ind_prq = NIL(LINK);
|
|
_sv_edgel = NIL(CELL);
|
|
_sp_target = FALSE;
|
|
_sv_globprq_only = 0;
|
|
|
|
DB_VOID_RETURN;
|
|
}
|
|
|
|
|
|
|
|
PUBLIC int
|
|
Set_group_attributes( list )/*
|
|
==============================
|
|
Scan list looking for the standard @,-,% and + (as in recipe line
|
|
defs) (+ is set but ignored for group recipes)
|
|
and set the flags accordingly so that they apply when we bind the
|
|
rules to the appropriate targets.
|
|
Return TRUE if group recipe start '[' was found, otherwise FALSE. */
|
|
char *list;
|
|
{
|
|
int res = FALSE;
|
|
char *s;
|
|
|
|
if ( !((_sv_attr|Glob_attr)&A_IGNOREGROUP) ) {
|
|
s = DmStrSpn(list,"@-%+ \t");
|
|
res = (*s == '[');
|
|
if( res ) {
|
|
/* Check for non-white space characters after the [. */
|
|
for( s++; *s && iswhite(*s) ; s++ )
|
|
;
|
|
if( *s )
|
|
Warning("Found non-white space character after '[' in [%s].", list);
|
|
|
|
_sv_attr |= Rcp_attribute(list);
|
|
}
|
|
}
|
|
|
|
return(res);
|
|
}
|
|
|
|
|
|
static void
|
|
_do_special( special, op, attr, set_dir, target, prereq, state )/*
|
|
==================================================================
|
|
Process a special target (always only a single target). So far the only
|
|
special targets we have are those recognized by the _is_special function.
|
|
Some of the special targets can take recipes, they call _do_targets()
|
|
and (implicitly) set *state to to RULE_SCAN. Otherwise *state remains
|
|
unaffected, i.e. NORMAL_SCAN.
|
|
|
|
target is always only a single special target.
|
|
|
|
NOTE: For the cases of .IMPORT, and .INCLUDE, the cells created by the
|
|
parser are never freed. This is due to the fact that it is too much
|
|
trouble to get them out of the hash table once they are defined, and
|
|
if they are per chance used again it will be ok, anyway, since the
|
|
cell is not really used by the code below. */
|
|
|
|
int special;
|
|
int op;
|
|
t_attr attr;
|
|
char *set_dir;
|
|
CELLPTR target;
|
|
CELLPTR prereq;
|
|
int *state;
|
|
{
|
|
HASHPTR hp; /* pointer to macro def cell */
|
|
CELLPTR cp; /* temporary pointer into cells list */
|
|
CELLPTR dp; /* pointer to directory dir cell */
|
|
LINKPTR lp; /* pointer at prerequisite list */
|
|
char *dir; /* current dir to prepend */
|
|
char *path; /* resulting path to try to read */
|
|
char *name; /* File name for processing a .INCLUDE */
|
|
char *tmp; /* temporary string pointer */
|
|
FILE *fil; /* File descriptor returned by Openfile */
|
|
|
|
DB_ENTER( "_do_special" );
|
|
|
|
target->ce_flag = F_SPECIAL; /* mark the target as special */
|
|
|
|
switch( special ) {
|
|
case ST_EXPORT:
|
|
for( ; prereq != NIL(CELL); prereq = prereq->ce_link ) {
|
|
DB_PRINT( "par", ("Exporting [%s]", prereq->CE_NAME) );
|
|
hp = GET_MACRO( prereq->CE_NAME );
|
|
|
|
if( hp != NIL(HASH) ) {
|
|
char *tmpstr = hp->ht_value;
|
|
|
|
if( tmpstr == NIL(char) ) tmpstr = "";
|
|
|
|
if( Write_env_string( prereq->CE_NAME, tmpstr ) != 0 )
|
|
Warning( "Could not export %s", prereq->CE_NAME );
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Simply cause the parser to fail on the next input read */
|
|
case ST_EXIT:
|
|
Skip_to_eof = TRUE;
|
|
break;
|
|
|
|
case ST_IMPORT:
|
|
for( ; prereq != NIL(CELL); prereq = prereq->ce_link ) {
|
|
char *tmpstr;
|
|
|
|
DB_PRINT( "par", ("Importing [%s]", prereq->CE_NAME) );
|
|
|
|
if( strcmp(prereq->CE_NAME, ".EVERYTHING") == 0 ) {
|
|
t_attr sattr = Glob_attr;
|
|
Glob_attr |= A_SILENT;
|
|
|
|
ReadEnvironment();
|
|
|
|
Glob_attr = sattr;
|
|
}
|
|
else {
|
|
tmpstr = Read_env_string( prereq->CE_NAME );
|
|
|
|
if( tmpstr != NIL(char) )
|
|
Def_macro(prereq->CE_NAME, tmpstr, M_EXPANDED|M_LITERAL);
|
|
else
|
|
if( !((Glob_attr | attr) & A_IGNORE) )
|
|
Fatal("Imported macro `%s' not found",prereq->CE_NAME);
|
|
}
|
|
}
|
|
|
|
attr &= ~A_IGNORE;
|
|
break;
|
|
|
|
case ST_INCLUDE:
|
|
{
|
|
int pushed = FALSE;
|
|
int first = (attr & A_FIRST);
|
|
int ignore = (((Glob_attr | attr) & A_IGNORE) != 0);
|
|
int found = FALSE;
|
|
int noinf = (attr & A_NOINFER);
|
|
LINKPTR prqlnk = NIL(LINK);
|
|
LINKPTR prqlst = NIL(LINK);
|
|
|
|
if( prereq == NIL(CELL) ) Fatal( "No .INCLUDE file(s) specified" );
|
|
|
|
dp = Def_cell( ".INCLUDEDIRS" );
|
|
|
|
if( (attr & A_SETDIR) && *(dir = strchr(set_dir, '=')+1) )
|
|
pushed = Push_dir( dir, ".INCLUDE", ignore );
|
|
|
|
for( cp=prereq; cp != NIL(CELL); cp = cp->ce_link ) {
|
|
LINKPTR ltmp;
|
|
TALLOC(ltmp, 1, LINK);
|
|
ltmp->cl_prq = cp;
|
|
|
|
if( prqlnk == NIL(LINK) )
|
|
prqlst = ltmp;
|
|
else
|
|
prqlnk->cl_next = ltmp;
|
|
|
|
prqlnk = ltmp;
|
|
}
|
|
|
|
for( ; prqlst != NIL(LINK); FREE(prqlst), prqlst=prqlnk ) {
|
|
prqlnk = prqlst->cl_next;
|
|
cp = prqlst->cl_prq;
|
|
name = cp->CE_NAME;
|
|
|
|
/* Leave this here, it ensures that prqlst gets propely free'd */
|
|
if ( first && found )
|
|
continue;
|
|
|
|
if( *name == '<' ) {
|
|
/* We have a file name enclosed in <....>
|
|
* so get rid of the <> arround the file name */
|
|
|
|
name++;
|
|
if( (tmp = strrchr( name, '>' )) != NIL( char ) )
|
|
*tmp = 0;
|
|
|
|
if( If_root_path( name ) )
|
|
fil = Openfile( name, FALSE, FALSE );
|
|
else
|
|
fil = NIL(FILE);
|
|
}
|
|
else
|
|
fil = Openfile( name, FALSE, FALSE );
|
|
|
|
if( fil == NIL(FILE) && !If_root_path( name ) ) { /*if true ==> not found in current dir*/
|
|
|
|
/* Now we must scan the list of prerequisites for .INCLUDEDIRS
|
|
* looking for the file in each of the specified directories.
|
|
* if we don't find it then we issue an error. The error
|
|
* message is suppressed if the .IGNORE attribute of attr is
|
|
* set. If a file is found we call Parse on the file to
|
|
* perform the parse and then continue on from where we left
|
|
* off. */
|
|
|
|
for(lp=dp->CE_PRQ; lp && fil == NIL(FILE); lp=lp->cl_next) {
|
|
dir = lp->cl_prq->CE_NAME;
|
|
if( strchr(dir, '$') ) dir = Expand(dir);
|
|
path = Build_path( dir, name );
|
|
|
|
DB_PRINT( "par", ("Trying to include [%s]", path) );
|
|
|
|
fil = Openfile( path, FALSE, FALSE );
|
|
if( dir != lp->cl_prq->CE_NAME ) FREE(dir);
|
|
}
|
|
}
|
|
|
|
if (!noinf && fil == NIL(FILE)) {
|
|
t_attr glob = Glob_attr;
|
|
t_attr cattr = prqlst->cl_prq->ce_attr;
|
|
|
|
prqlst->cl_next = NIL(LINK);
|
|
Glob_attr |= (attr&A_IGNORE);
|
|
prqlst->cl_prq->ce_attr &= ~A_FRINGE;
|
|
|
|
if( Verbose & V_FILE_IO )
|
|
printf( "%s: Inferring include file [%s].\n",
|
|
Pname, name );
|
|
fil = TryFiles(prqlst);
|
|
|
|
Glob_attr = glob;
|
|
prqlst->cl_prq->ce_attr |= (cattr & A_FRINGE);
|
|
}
|
|
|
|
if( fil != NIL(FILE) ) {
|
|
if( Verbose & V_FILE_IO )
|
|
printf( "%s: Parsing include file [%s].\n",
|
|
Pname, name );
|
|
Parse( fil );
|
|
found = TRUE;
|
|
}
|
|
else if( !(ignore || first) )
|
|
Fatal( "Include file %s, not found", name );
|
|
else if( Verbose & V_FILE_IO )
|
|
printf( "%s: Include file [%s] was not found.\n",
|
|
Pname, name );
|
|
}
|
|
|
|
if ( !ignore && first && !found )
|
|
Fatal( "No include file was found" );
|
|
|
|
if( pushed ) Pop_dir(FALSE);
|
|
attr &= ~(A_IGNORE|A_SETDIR|A_FIRST|A_NOINFER);
|
|
}
|
|
break;
|
|
|
|
case ST_SOURCE:
|
|
if( prereq != NIL(CELL) )
|
|
_do_targets( op & (R_OP_CL | R_OP_MI | R_OP_UP), attr, set_dir,
|
|
target, prereq );
|
|
else {
|
|
/* The old semantics of .SOURCE were that an empty list of
|
|
* prerequisites clears the .SOURCE list. So we must implement
|
|
* that here as a clearout prerequisite operation. Since this is
|
|
* a standard operation with the :- opcode we can simply call the
|
|
* proper routine with the target cell and it should do the trick
|
|
*/
|
|
|
|
if( op == R_OP_CL || (op & R_OP_MI) )
|
|
Clear_prerequisites( target );
|
|
}
|
|
|
|
op &= ~(R_OP_MI | R_OP_UP);
|
|
break;
|
|
|
|
case ST_KEEP:
|
|
if( Keep_state != NIL(char) ) break;
|
|
Def_macro( ".KEEP_STATE", "_state.mk", M_EXPANDED );
|
|
break;
|
|
|
|
case ST_REST:
|
|
/* The rest of the special targets can all take recipes, as such they
|
|
* must be able to affect the state of the parser. */
|
|
|
|
{
|
|
int s_targ = Target;
|
|
|
|
Target = TRUE;
|
|
_sp_target = TRUE;
|
|
*state = _do_targets( op, attr, set_dir, target, prereq );
|
|
Target = s_targ;
|
|
|
|
target->ce_flag |= F_TARGET;
|
|
|
|
attr = A_DEFAULT;
|
|
op = R_OP_CL;
|
|
}
|
|
break;
|
|
|
|
default:break;
|
|
}
|
|
|
|
if( op != R_OP_CL ) Warning( "Modifier(s) for operator ignored" );
|
|
if( attr != A_DEFAULT ) Warning( "Extra attributes ignored" );
|
|
|
|
DB_VOID_RETURN;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
_do_targets( op, attr, set_dir, targets, prereq )/*
|
|
===================================================
|
|
Evaluate the values derived from the current target definition
|
|
line. Helper functions _build_graph(), _do_magic(), _make_multi(),
|
|
_add_root(), _replace_cell(), _set_attributes(), Clear_prerequisites()
|
|
_stick_at_head(), Add_prerequisite() and _set_global_attr() are used.
|
|
If successfull "_sv_targets" is set to "targets".
|
|
Return RULE_SCAN if a recipe is expected to follow, otherwise
|
|
NORMAL_SCAN. */
|
|
int op; /* rule operator */
|
|
t_attr attr; /* attribute flags for current targets */
|
|
char *set_dir; /* value of setdir attribute */
|
|
CELLPTR targets; /* list of targets (each cell maybe already
|
|
* defined by a previous target definition
|
|
* line. */
|
|
CELLPTR prereq; /* list of prerequisites */
|
|
{
|
|
CELLPTR tg1; /* temporary target pointer */
|
|
CELLPTR tp1; /* temporary prerequisite pointer */
|
|
LINKPTR prev_cell; /* pointer for .UPDATEALL processing */
|
|
char *p; /* temporary char pointer */
|
|
int tflag = FALSE; /* set to TRUE if we add target to root */
|
|
int ret_state = RULE_SCAN; /* Return state */
|
|
|
|
DB_ENTER( "_do_targets" );
|
|
|
|
/* If .UPDATEALL is set sort the target list that was temporary linked
|
|
* with ce_link into a list using ce_link with ce_set pointing to the first
|
|
* element. */
|
|
/* FIXME: Check that .UPDATEALL and %-targets on one line work together. */
|
|
if( attr & A_UPDATEALL ) {
|
|
if( targets == NIL(CELL) )
|
|
Fatal( ".UPDATEALL attribute requires non-empty list of targets" );
|
|
|
|
if (targets->ce_set == NIL(CELL)) {
|
|
for(
|
|
prev_cell=CeMeToo(targets),tg1=targets->ce_link;
|
|
tg1 != NIL(CELL);
|
|
tg1=tg1->ce_link
|
|
) {
|
|
if (tg1->ce_set)
|
|
Fatal( "Target [%s] appears on multiple .UPDATEALL lists",
|
|
tg1->CE_NAME);
|
|
tg1->ce_set = targets;
|
|
TALLOC(prev_cell->cl_next, 1, LINK);
|
|
prev_cell = prev_cell->cl_next;
|
|
prev_cell->cl_prq = tg1;
|
|
}
|
|
targets->ce_set = targets;
|
|
}
|
|
else {
|
|
LINKPTR ap;
|
|
CELLPTR tp;
|
|
|
|
tp = targets;
|
|
ap = CeMeToo(targets);
|
|
while (ap && tp && ap->cl_prq == tp && tp->ce_set == targets) {
|
|
ap = ap->cl_next;
|
|
tp = tp->ce_link;
|
|
}
|
|
if (ap || tp)
|
|
Fatal("Inconsistent .UPDATEALL lists for target [%s]",
|
|
targets->CE_NAME);
|
|
}
|
|
targets->ce_link = NIL(CELL);
|
|
}
|
|
|
|
for( tg1 = targets; tg1 != NIL(CELL); tg1 = tg1->ce_link ) {
|
|
/* Check if tg1 is already marked as a %-target, but not a magic
|
|
* (.xxx.yyy) target. */
|
|
int purepercent = (tg1->ce_flag & F_PERCENT) && !(tg1->ce_flag & F_MAGIC);
|
|
|
|
/* Check each target. Check for inconsistencies between :: and : rule
|
|
* sets. :: may follow either : or :: but not the reverse.
|
|
*
|
|
* Any F_MULTI target (contains :: rules) is represented by a prerequisite
|
|
* list hanging off the main target cell where each of the prerequisites
|
|
* is a copy of the target cell but is not entered into the hash table.
|
|
*/
|
|
if( !(op & R_OP_DCL ) && (tg1->ce_flag & F_MULTI) && !purepercent )
|
|
Fatal( "':' vs '::' inconsistency in rules for %s", tg1->CE_NAME );
|
|
|
|
if( purepercent ) {
|
|
/* Handle %-targets. */
|
|
CELLPTR cur;
|
|
CELLPTR tpq = NIL(CELL);
|
|
CELLPTR nprq = NULL;
|
|
|
|
#ifdef DBUG
|
|
DB_PRINT( "%", ("Handling %%-target [%s : : <prerequisites follow, maybe empty>]",
|
|
tg1->CE_NAME) );
|
|
for(cur=prereq;cur;cur=cur->ce_link) {
|
|
DB_PRINT( "%", (" %%-prerequisites : %s ",
|
|
cur->CE_NAME ? cur->CE_NAME : "<empty>") );
|
|
}
|
|
#endif
|
|
|
|
/* Handle indirect (global) prerequisites first. */
|
|
for(cur=prereq;cur;cur=cur->ce_link) {
|
|
char *name = cur->CE_NAME;
|
|
int len = strlen(name);
|
|
|
|
if( *name == '\'' && name[len-1]=='\'' ){
|
|
name[len-1] = '\0';
|
|
len = strlen(name+1)+1;
|
|
memmove(name,name+1,len);
|
|
/* add indirect prerequisite */
|
|
_add_indirect_prereq( cur );
|
|
}
|
|
else {
|
|
/* Sort all "other" prerequisits into tpq, with nprq
|
|
* pointing to the first element. */
|
|
if (tpq)
|
|
tpq->ce_link = cur;
|
|
else
|
|
nprq = cur;
|
|
tpq = cur;
|
|
}
|
|
}
|
|
/* Mark the last element of nprq. */
|
|
if(tpq)
|
|
tpq->ce_link=NIL(CELL);
|
|
else
|
|
nprq = NIL(CELL);
|
|
|
|
/* Handle "normal" prerequisites now. */
|
|
|
|
if ( op & R_OP_OR ) {
|
|
/* for op == ':|' transform:
|
|
* <%-target> :| <prereq_1> ... <prereq_n> ; <recipe>
|
|
* into:
|
|
* <%-target> : <prereq_1> ; <recipe>
|
|
* ..
|
|
* <%-target> : <prereq_n> ; <recipe>
|
|
*/
|
|
for(tp1=nprq; tp1; tp1=tp1->ce_link) {
|
|
CELLPTR tmpcell = tp1->ce_link;
|
|
tp1->ce_link = NIL(CELL);
|
|
_build_graph(op,tg1,tp1);
|
|
tp1->ce_link = tmpcell;
|
|
}
|
|
}
|
|
else {
|
|
/* The inference mechanism for %-targets limits the number of
|
|
* (non-indirect) prerequisite to one, but an unlimited number
|
|
* of indirect prerequisites is possible. */
|
|
if ( nprq && nprq->ce_link && !(op & R_OP_OR))
|
|
Warning("More than one prerequisite\n"
|
|
"for %%-target. Use :| ruleop or indirect prerequisites.");
|
|
|
|
_build_graph(op,tg1,nprq);
|
|
}
|
|
}
|
|
else if( tg1->ce_flag & F_MAGIC &&
|
|
(p = _is_magic( tg1->CE_NAME )) != NIL(char) &&
|
|
_do_magic( op, p, tg1, prereq, attr, set_dir ) )
|
|
; /* _do_magic() does all that is needed (if return value is TRUE). */
|
|
else if( op & R_OP_DCL ) { /* op == :: */
|
|
CELLPTR tmp_cell = _make_multi(tg1);
|
|
|
|
/* Add the F_MULTI master to .TARGETS (If not set already).
|
|
* Do this here so that the member cell is not added instead
|
|
* when the recipies are bound in Bind_rules_to_targets(). */
|
|
tflag |= _add_root(tg1);
|
|
|
|
/* Replace the F_MULTI master with the member cell. */
|
|
targets = _replace_cell( targets, tg1, tmp_cell );
|
|
|
|
/* We have to set (add) the attributes also for the F_MULTI master
|
|
* target cell. As there is no recipe the setdir value is not
|
|
* needed. _set_attributes() that follows in approx. 8 lines
|
|
* will set the attributes for the F_MULTI member cell. */
|
|
tg1->ce_attr |= (attr & ~A_SETDIR);
|
|
|
|
/* Now switch tg1 to the current (F_MULTI prereq.) target.
|
|
* All recipes have to be added to that cell and not to the
|
|
* F_MULTI master. */
|
|
tg1 = tmp_cell;
|
|
}
|
|
|
|
if( !purepercent ) _set_attributes( attr, set_dir, tg1 );
|
|
|
|
/* Build the proper prerequisite list of the target. If the `-',
|
|
* modifier was used clear the prerequisite list before adding any
|
|
* new prerequisites. Else add them to the head/tail as appropriate.
|
|
*
|
|
* If the target has F_PERCENT set then no prerequisites are used. */
|
|
|
|
if( !(tg1->ce_flag & F_PERCENT) ) {
|
|
if( op & R_OP_MI ) Clear_prerequisites( tg1 ); /* op == :- */
|
|
|
|
if( (op & R_OP_UP) && (tg1->ce_prq != NIL(LINK)) ) /* op == :^ */
|
|
_stick_at_head( tg1, prereq );
|
|
else for( tp1=prereq; tp1 != NIL(CELL); tp1 = tp1->ce_link )
|
|
Add_prerequisite( tg1, tp1, FALSE, FALSE );
|
|
}
|
|
else if( op & (R_OP_MI | R_OP_UP) )
|
|
Warning( "Modifier(s) `^-' for %-meta target ignored" );
|
|
}
|
|
|
|
/* In case a F_MULTI member that was the first prerequisite of .TARGETS */
|
|
if(tflag)
|
|
Target = TRUE;
|
|
|
|
/* Check to see if we have NO targets but some attributes, i.e. an
|
|
* Attribute-Definition. If so then apply all of the attributes to the
|
|
* complete list of prerequisites. No recipes are allowed to follow. */
|
|
|
|
if( (targets == NIL(CELL)) && attr ) {
|
|
ret_state = NORMAL_SCAN;
|
|
if( prereq != NIL(CELL) )
|
|
for( tp1=prereq; tp1 != NIL(CELL); tp1 = tp1->ce_link )
|
|
_set_attributes( attr, set_dir, tp1 );
|
|
else
|
|
_set_global_attr( attr );
|
|
}
|
|
|
|
/* Now that we have built the lists of targets, the parser must parse the
|
|
* recipes if there are any. However we must start the recipe list with the
|
|
* recipe specified as via the ; kludge, if there is one */
|
|
_sv_targets = targets;
|
|
_sv_attr = attr;
|
|
_sv_flag = ((op & R_OP_BG) ? F_SINGLE : F_DEFAULT);
|
|
|
|
DB_RETURN( ret_state );
|
|
}
|
|
|
|
|
|
static int
|
|
_do_magic( op, dot, target, prereq, attr, set_dir )/*
|
|
=====================================================
|
|
This function investigates dot for being a magic target of the form
|
|
.<chars>.<chars> or .<chars> and creates the appropriate % rules for
|
|
that target.
|
|
If the target is given with an undefined syntax, i.e. with prerequisites,
|
|
then this function terminates early without creating % rules and
|
|
returns 0.
|
|
If successful the function returns 1.
|
|
|
|
The function builds the % rule, `%.o : %.c' from .c.o, and
|
|
`% : %.a' from .a */
|
|
|
|
int op;
|
|
char *dot;
|
|
CELLPTR target;
|
|
CELLPTR prereq;
|
|
t_attr attr;
|
|
char *set_dir;
|
|
{
|
|
CELLPTR tg;
|
|
CELLPTR prq;
|
|
char *tmp, *tmp2;
|
|
|
|
DB_ENTER( "_do_magic" );
|
|
|
|
DB_PRINT("%", ("Analysing magic target [%s]", target->CE_NAME));
|
|
|
|
if( prereq != NIL(CELL) ) {
|
|
Warning( "Ignoring AUGMAKE meta-target [%s] because prerequisites are present.", target->CE_NAME );
|
|
DB_RETURN(0);
|
|
}
|
|
|
|
if( dot == target->CE_NAME ) { /* its of the form .a */
|
|
tg = Def_cell( "%" );
|
|
tmp = _build_meta( target->CE_NAME );
|
|
prq = Def_cell( tmp );
|
|
FREE( tmp );
|
|
|
|
_build_graph( op, tg, prq );
|
|
}
|
|
else {
|
|
tmp = _build_meta( dot );
|
|
tg = Def_cell( tmp );
|
|
FREE( tmp );
|
|
|
|
tmp = _build_meta( tmp2 = DmSubStr( target->CE_NAME, dot ) );
|
|
prq = Def_cell( tmp );
|
|
FREE( tmp );
|
|
FREE( tmp2 );
|
|
|
|
_build_graph( op, tg, prq );
|
|
}
|
|
|
|
tg->ce_flag |= F_PERCENT;
|
|
target->ce_flag |= (F_MAGIC|F_PERCENT);
|
|
|
|
_set_attributes( attr, set_dir, tg );
|
|
|
|
DB_RETURN(1);
|
|
}
|
|
|
|
|
|
static CELLPTR
|
|
_replace_cell( lst, cell, rep )/*
|
|
=================================
|
|
Replace cell with rep in lst. Note if cell is not part of lst we are in
|
|
real trouble. */
|
|
CELLPTR lst;
|
|
CELLPTR cell;
|
|
CELLPTR rep;
|
|
{
|
|
register CELLPTR tp;
|
|
|
|
if( lst == cell ) {
|
|
rep->ce_link = lst->ce_link;
|
|
lst = rep;
|
|
}
|
|
else {
|
|
for( tp=lst; tp->ce_link != cell && tp ; tp=tp->ce_link );
|
|
if( !tp )
|
|
Fatal( "Internal Error: cell not part of lst." );
|
|
rep->ce_link = tp->ce_link->ce_link;
|
|
tp->ce_link = rep;
|
|
}
|
|
|
|
return(lst);
|
|
}
|
|
|
|
|
|
static char *
|
|
_build_meta( name )/*
|
|
=====================
|
|
Check to see if the name is of the form .c~ if so and if Augmake
|
|
translation is enabled then return s.%.c, else return %.suff, where if the
|
|
suffix ends in '~' then leave it be.*/
|
|
char *name;
|
|
{
|
|
char *tmp;
|
|
int test = (STOBOOL(Augmake) ? name[strlen(name)-1] == '~' : 0);
|
|
|
|
tmp = DmStrJoin( test ? "s.%" : "%", name, -1, FALSE);
|
|
if( test ) tmp[ strlen(tmp)-1 ] = '\0';
|
|
|
|
return(tmp);
|
|
}
|
|
|
|
|
|
static CELLPTR
|
|
_build_graph( op, target, prereq )/*
|
|
====================================
|
|
This function is called to build the graph for the % rule given by
|
|
target : prereq cell combination. This function assumes that target
|
|
is a % target and that prereq is one or multiple non-indirect prerequisite.
|
|
It also assumes that target cell has F_PERCENT set already.
|
|
|
|
NOTE: If more than one prerequisite is present this function handles them
|
|
correctly but the lookup still only uses the first (BUG!).
|
|
|
|
R_OP_CL (:) rules replace existing rules if any, %.o :: %.c is meaningless.
|
|
|
|
The function always returns NIL(CELL). */
|
|
int op;
|
|
CELLPTR target;
|
|
CELLPTR prereq;
|
|
{
|
|
LINKPTR edl;
|
|
CELLPTR edge = 0;
|
|
CELLPTR tpq,cur;
|
|
int match;
|
|
|
|
#ifdef DBUG
|
|
DB_ENTER( "_build_graph" );
|
|
DB_PRINT( "%", ("Building graph for [%s : <prerequisites follow, maybe empty>]",
|
|
target->CE_NAME) );
|
|
for(tpq=prereq;tpq;tpq=tpq->ce_link) {
|
|
DB_PRINT( "%", (" %%-prerequisites : %s ",
|
|
tpq->CE_NAME ? tpq->CE_NAME : "<empty>") );
|
|
}
|
|
#endif
|
|
|
|
/* Currently multiple prerequisites are not (yet) handled correctly.
|
|
* We already issue a warning in _do_targets(), don't issue it here
|
|
* again.
|
|
if ( prereq && prereq->ce_link )
|
|
Warning( "Internal Error: more than one prerequisite in _build_graph." );
|
|
*/
|
|
|
|
/* There cannot be more than one target name ( linked with
|
|
* (CeMeToo(target))->cl_next ) per %-target master.
|
|
* FIXME: remove this check after verifying that it never triggers. */
|
|
if ( (CeMeToo(target))->cl_next )
|
|
Fatal( "Internal Error: more than one target name in _build_graph." );
|
|
|
|
/* Search the list of prerequisites for the current target and see if
|
|
* any of them match the current %-meta's : prereq's pair. NOTE that
|
|
* %-metas are built as if they were F_MULTI targets, i.e. the target
|
|
* definitions for the %-target members are stored in the prerequisites
|
|
* list of the master target. */
|
|
/* This relies on target->ce_prq being NULL if this is the first
|
|
* occurrence of this %-target and therefore not yet having a %-target
|
|
* master. */
|
|
match = FALSE;
|
|
for(edl=target->ce_prq; !match && edl != NIL(LINK); edl=edl->cl_next) {
|
|
LINKPTR l1,l2;
|
|
edge = edl->cl_prq;
|
|
|
|
DB_PRINT("%", ("Trying to match [%s]",edge?edge->CE_NAME:"(nil)"));
|
|
|
|
/* First we match the target sets, if this fails then we don't have to
|
|
* bother with the prerequisite sets. The targets sets are sorted.
|
|
* this makes life very simple. */
|
|
/* ce_dir is handled per member target, no check needed for the
|
|
* master target. */
|
|
|
|
/* FIXME: We already checked above that there is only one target
|
|
* name. Remove the comparisons for following names. */
|
|
l1 = CeMeToo(target); /* Used by .UPDATEALL !!! */
|
|
l2 = CeMeToo(edge);
|
|
while(l1 && l2 && l1->cl_prq == l2->cl_prq) {
|
|
l1=l1->cl_next;
|
|
l2=l2->cl_next;
|
|
}
|
|
/* If both l1 and l2 are NULL we had a match. */
|
|
if (l1 || l2)
|
|
continue;
|
|
|
|
/* target sets match, so check prerequisites. */
|
|
if( (!edge->ce_prq && !prereq) /* matches both empty - separate this. */
|
|
|| ( edge->ce_prq
|
|
&& ( edge->ce_dir == _sv_setdir
|
|
|| ( edge->ce_dir
|
|
&& _sv_setdir
|
|
&& !strcmp(edge->ce_dir,strchr(_sv_setdir,'=')+1)
|
|
)
|
|
)
|
|
)
|
|
) {
|
|
LINKPTR prql;
|
|
|
|
/* this is a really gross way to compare two sets, it's n^2 but
|
|
* since the sets are assumed to always be tiny, it should be ok. */
|
|
for(tpq=prereq; tpq; tpq=tpq->ce_link) {
|
|
for(prql=edge->ce_prq;prql;prql=prql->cl_next)
|
|
if (prql->cl_prq == tpq)
|
|
break;
|
|
|
|
if(prql == NIL(LINK))
|
|
break;
|
|
|
|
prql->cl_prq->ce_flag |= F_MARK;
|
|
}
|
|
|
|
if (tpq == NIL(CELL)) {
|
|
for(prql=edge->ce_prq;prql;prql=prql->cl_next)
|
|
if(!(prql->cl_prq->ce_flag & F_MARK))
|
|
break;
|
|
|
|
if(prql == NIL(LINK))
|
|
match = TRUE;
|
|
}
|
|
|
|
/* clean up the mark bits. */
|
|
for(prql=edge->ce_prq;prql;prql=prql->cl_next)
|
|
prql->cl_prq->ce_flag &= ~F_MARK;
|
|
}
|
|
}
|
|
|
|
if( match ) {
|
|
/* match is TRUE hence, we found an edge joining the target and the
|
|
* prerequisite so reset the new edge so that new values replace it. */
|
|
DB_PRINT( "%", ("It's an old edge") );
|
|
|
|
edge->ce_dir = NIL(char);
|
|
edge->ce_flag &= (F_PERCENT|F_MAGIC|F_DFA);
|
|
edge->ce_attr &= A_NOINFER;
|
|
}
|
|
else {
|
|
DB_PRINT( "%", ("Adding a new edge") );
|
|
|
|
edge = _make_multi(target);
|
|
|
|
/* FIXME: There can be only one %-target. */
|
|
for(edl=CeMeToo(target);edl;edl=edl->cl_next) {
|
|
if( !((tpq=edl->cl_prq)->ce_flag & F_DFA) ) {
|
|
Add_nfa( tpq->CE_NAME );
|
|
tpq->ce_flag |= F_DFA;
|
|
}
|
|
|
|
edl->cl_prq->ce_set = edge;
|
|
}
|
|
|
|
edge->ce_all = target->ce_all;
|
|
target->ce_all.cl_next = NIL(LINK);
|
|
target->ce_set = NIL(CELL);
|
|
|
|
/* Add all prerequisites to edge. */
|
|
for(tpq=prereq; tpq; tpq=tpq->ce_link)
|
|
Add_prerequisite(edge, tpq, FALSE, TRUE);
|
|
}
|
|
|
|
if( op & R_OP_DCL )
|
|
Warning("'::' operator for meta-target '%s' ignored, ':' operator assumed.",
|
|
target->CE_NAME );
|
|
|
|
/* If edge was already added we're in BIG trouble. */
|
|
/* Re-use cur as temporary variable. */
|
|
for( cur=_sv_edgel; cur != NIL(CELL); cur=cur->ce_link ) {
|
|
if( cur == edge )
|
|
Fatal( "Internal Error: edge already in _sv_edgel." );
|
|
}
|
|
|
|
edge->ce_link = _sv_edgel;
|
|
_sv_edgel = edge;
|
|
_sv_globprq_only = 0;
|
|
|
|
DB_RETURN(NIL(CELL));
|
|
}
|
|
|
|
|
|
static CELLPTR
|
|
_make_multi( tg )/*
|
|
===================
|
|
This function is called to convert tg into an F_MULTI target.
|
|
Return a pointer to the new member cell.
|
|
I don't know what the author intended but the ce_index entry is only
|
|
used in this function (set to 0 for added targets) and undefined otherwise!
|
|
The undefined value is hopefully set to 0 by the C compiler as each added
|
|
target sets its ce_count to ++ce_index (==1). (FIXME) */
|
|
CELLPTR tg;
|
|
{
|
|
CELLPTR cp;
|
|
|
|
/* This creates a new master F_MULTI target if tg existed before as a normal
|
|
* target with prerequisites or recipes. */
|
|
if( !(tg->ce_flag & F_MULTI) && (tg->ce_prq || tg->ce_recipe) ) {
|
|
/* Allocate a new master cell. */
|
|
TALLOC(cp, 1, CELL);
|
|
*cp = *tg;
|
|
|
|
/* F_MULTI master */
|
|
tg->ce_prq = NIL(LINK);
|
|
tg->ce_flag |= F_RULES|F_MULTI|F_TARGET;
|
|
tg->ce_attr |= A_SEQ;
|
|
tg->ce_recipe = NIL(STRING);
|
|
tg->ce_dir = NIL(char);
|
|
|
|
/* F_MULTI member for preexisting elements */
|
|
cp->ce_count = ++tg->ce_index;
|
|
cp->ce_cond = NIL(STRING);
|
|
cp->ce_set = NIL(CELL);
|
|
cp->ce_all.cl_prq = cp;
|
|
CeNotMe(cp) = NIL(LINK);
|
|
|
|
Add_prerequisite(tg, cp, FALSE, TRUE);
|
|
}
|
|
|
|
/* Alocate memory for new member of F_MULTI target */
|
|
TALLOC(cp, 1, CELL);
|
|
*cp = *tg;
|
|
|
|
/* This is reached if the target already exists, but without having
|
|
* prerequisites or recepies. Morph it into a F_MULTI master cell. */
|
|
if( !(tg->ce_flag & F_MULTI) ) {
|
|
tg->ce_prq = NIL(LINK);
|
|
tg->ce_flag |= F_RULES|F_MULTI|F_TARGET;
|
|
tg->ce_attr |= A_SEQ;
|
|
tg->ce_recipe = NIL(STRING);
|
|
tg->ce_dir = NIL(char);
|
|
cp->ce_cond = NIL(STRING);
|
|
}
|
|
/* This handles the case of adding an additional target as a
|
|
* prerequisite to a F_MULTI target. */
|
|
else {
|
|
cp->ce_flag &= ~(F_RULES|F_MULTI);
|
|
cp->ce_attr &= ~A_SEQ;
|
|
cp->ce_prq = NIL(LINK);
|
|
cp->ce_index = 0;
|
|
cp->ce_cond = NIL(STRING);
|
|
}
|
|
cp->ce_count = ++tg->ce_index;
|
|
cp->ce_flag |= F_TARGET;
|
|
cp->ce_set = NIL(CELL);
|
|
cp->ce_all.cl_prq = cp;
|
|
CeNotMe(cp) = NIL(LINK);
|
|
|
|
Add_prerequisite(tg, cp, FALSE, TRUE);
|
|
return(cp);
|
|
}
|
|
|
|
|
|
static void
|
|
_add_indirect_prereq( pq )/*
|
|
==========================
|
|
Prerequisite is an indirect prerequisite for a %-target, add it to
|
|
the target's list of indirect prerequsites to add on match. */
|
|
CELLPTR pq;
|
|
{
|
|
register LINKPTR ln;
|
|
|
|
/* Only add to list of indirect prerequsites if it is not in already. */
|
|
for(ln=_sv_ind_prq; ln; ln=ln->cl_next)
|
|
if(strcmp(ln->cl_prq->CE_NAME,pq->CE_NAME) == 0)
|
|
return;
|
|
|
|
/* Not in, add it. */
|
|
TALLOC( ln, 1, LINK );
|
|
ln->cl_next = _sv_ind_prq;
|
|
ln->cl_prq = pq;
|
|
_sv_ind_prq = ln;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
_set_attributes( attr, set_dir, cp )/*
|
|
======================================
|
|
Set the appropriate attributes for a cell */
|
|
t_attr attr;
|
|
char *set_dir;
|
|
CELLPTR cp;
|
|
{
|
|
char *dir = 0;
|
|
|
|
DB_ENTER( "_set_attributes" );
|
|
|
|
/* If .SETDIR attribute is set then we have at least .SETDIR= in the
|
|
* set_dir string. So go and fishout what is at the end of the =.
|
|
* If not set and not NULL then propagate it to the target cell. */
|
|
|
|
if( attr & A_SETDIR ) {
|
|
char *p;
|
|
if( (p = strchr( set_dir, '=' )) != NULL )
|
|
dir = p + 1;
|
|
|
|
if( cp->ce_dir )
|
|
Warning( "Multiple .SETDIR for %s ignored", cp->CE_NAME );
|
|
else if( *dir )
|
|
cp->ce_dir = DmStrDup(dir);
|
|
}
|
|
cp->ce_attr |= attr; /* set rest of attributes for target */
|
|
|
|
DB_VOID_RETURN;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
_set_global_attr( attr )/*
|
|
==========================
|
|
Handle the setting of the global attribute functions based on
|
|
The attribute flags set in attr. */
|
|
t_attr attr;
|
|
{
|
|
t_attr flag;
|
|
|
|
/* Some compilers can't handle a switch on a long, and t_attr is now a long
|
|
* integer on some systems. foey! */
|
|
for( flag = MAX_ATTR; flag; flag >>= 1 )
|
|
if( flag & attr ) {
|
|
if( flag == A_PRECIOUS) Def_macro(".PRECIOUS", "y", M_EXPANDED);
|
|
else if( flag == A_SILENT) Def_macro(".SILENT", "y", M_EXPANDED);
|
|
else if( flag == A_IGNORE ) Def_macro(".IGNORE", "y", M_EXPANDED);
|
|
else if( flag == A_EPILOG ) Def_macro(".EPILOG", "y", M_EXPANDED);
|
|
else if( flag == A_PROLOG ) Def_macro(".PROLOG", "y", M_EXPANDED);
|
|
else if( flag == A_NOINFER ) Def_macro(".NOINFER", "y", M_EXPANDED);
|
|
else if( flag == A_SEQ ) Def_macro(".SEQUENTIAL","y", M_EXPANDED);
|
|
else if( flag == A_SHELL ) Def_macro(".USESHELL", "y", M_EXPANDED);
|
|
else if( flag == A_MKSARGS ) Def_macro(".MKSARGS", "y", M_EXPANDED);
|
|
#if !defined(__CYGWIN__)
|
|
else if( flag == A_SWAP ) Def_macro(".SWAP", "y", M_EXPANDED);
|
|
#else
|
|
else if( flag == A_WINPATH ) Def_macro(".WINPATH", "y", M_EXPANDED);
|
|
#endif
|
|
}
|
|
|
|
attr &= ~A_GLOB;
|
|
if( attr ) Warning( "Non global attribute(s) ignored" );
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
_stick_at_head( cp, pq )/*
|
|
==========================
|
|
Add the prerequisite list to the head of the existing prerequisite
|
|
list */
|
|
|
|
CELLPTR cp; /* cell for target node */
|
|
CELLPTR pq; /* list of prerequisites to add */
|
|
{
|
|
DB_ENTER( "_stick_at_head" );
|
|
|
|
if( pq->ce_link != NIL(CELL) ) _stick_at_head( cp, pq->ce_link );
|
|
Add_prerequisite( cp, pq, TRUE, FALSE );
|
|
|
|
DB_VOID_RETURN;
|
|
}
|
|
|
|
|
|
|
|
static t_attr
|
|
_is_attribute( name )/*
|
|
=======================
|
|
Check the passed name against the list of valid attributes and return the
|
|
attribute index if it is, else return 0, indicating the name is not a valid
|
|
attribute. The present attributes are defined in dmake.h as A_xxx #defines,
|
|
with the corresponding makefile specification: (note they must be named
|
|
exactly as defined below)
|
|
|
|
Valid attributes are: .IGNORE, .SETDIR=, .SILENT, .PRECIOUS, .LIBRARY,
|
|
.EPILOG, .PROLOG, .LIBRARYM, .SYMBOL, .UPDATEALL,
|
|
.USESHELL, .NOINFER, .PHONY, .SWAP/.WINPATH, .SEQUENTIAL
|
|
.NOSTATE, .MKSARGS, .IGNOREGROUP, .GROUP, .FIRST
|
|
.EXECUTE, .ERRREMOVE
|
|
|
|
NOTE: The strcmp's are OK since at most three are ever executed for any
|
|
one attribute check, and that happens only when we can be fairly
|
|
certain we have an attribute. */
|
|
char *name;
|
|
{
|
|
t_attr attr = 0;
|
|
|
|
DB_ENTER( "_is_attribute" );
|
|
|
|
if( *name++ == '.' )
|
|
switch( *name )
|
|
{
|
|
case 'E':
|
|
if( !strcmp(name, "EPILOG") ) attr = A_EPILOG;
|
|
else if( !strcmp(name, "EXECUTE")) attr = A_EXECUTE;
|
|
else if( !strcmp(name, "ERRREMOVE")) attr = A_ERRREMOVE;
|
|
else attr = 0;
|
|
break;
|
|
|
|
/* A_FIRST implies A_IGNORE, handled in ST_INCLUDE */
|
|
case 'F':
|
|
attr = (strcmp(name, "FIRST")) ? 0 : A_FIRST;
|
|
break;
|
|
|
|
case 'G': attr = (strcmp(name, "GROUP")) ? 0 : A_GROUP; break;
|
|
case 'L': attr = (strcmp(name, "LIBRARY")) ? 0 : A_LIBRARY; break;
|
|
case 'M': attr = (strcmp(name, "MKSARGS")) ? 0 : A_MKSARGS; break;
|
|
|
|
case 'I':
|
|
if( !strcmp(name, "IGNORE") ) attr = A_IGNORE;
|
|
else if( !strcmp(name, "IGNOREGROUP")) attr = A_IGNOREGROUP;
|
|
else attr = 0;
|
|
break;
|
|
|
|
case 'N':
|
|
if( !strcmp(name, "NOINFER") ) attr = A_NOINFER;
|
|
else if( !strcmp(name, "NOSTATE")) attr = A_NOSTATE;
|
|
else attr = 0;
|
|
break;
|
|
|
|
case 'U':
|
|
if( !strcmp(name, "UPDATEALL") ) attr = A_UPDATEALL;
|
|
else if( !strcmp(name, "USESHELL")) attr = A_SHELL;
|
|
else attr = 0;
|
|
break;
|
|
|
|
case 'P':
|
|
if( !strcmp(name, "PRECIOUS") ) attr = A_PRECIOUS;
|
|
else if( !strcmp(name, "PROLOG") ) attr = A_PROLOG;
|
|
else if( !strcmp(name, "PHONY") ) attr = A_PHONY;
|
|
else attr = 0;
|
|
break;
|
|
|
|
case 'S':
|
|
if( !strncmp(name, "SETDIR=", 7) ) attr = A_SETDIR;
|
|
else if( !strcmp(name, "SILENT") ) attr = A_SILENT;
|
|
else if( !strcmp(name, "SYMBOL") ) attr = A_SYMBOL;
|
|
else if( !strcmp(name, "SEQUENTIAL")) attr = A_SEQ;
|
|
/* A_SWAP has no meaning except for MSDOS. */
|
|
else if( !strcmp(name, "SWAP")) attr = A_SWAP;
|
|
else attr = 0;
|
|
break;
|
|
|
|
case 'W': attr = (strcmp(name, "WINPATH")) ? 0 : A_WINPATH; break;
|
|
}
|
|
|
|
DB_RETURN( attr );
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
_is_special( tg )/*
|
|
===================
|
|
This function returns TRUE if the name passed in represents a special
|
|
target, otherwise it returns false. A special target is one that has
|
|
a special meaning to dmake, and may require processing at the time that
|
|
it is parsed.
|
|
|
|
Current Special targets are:
|
|
.GROUPPROLOG .GROUPEPILOG .INCLUDE .IMPORT
|
|
.EXPORT .SOURCE .SUFFIXES .ERROR .EXIT
|
|
.INCLUDEDIRS .MAKEFILES .REMOVE .KEEP_STATE
|
|
.TARGETS .ROOT
|
|
*/
|
|
char *tg;
|
|
{
|
|
DB_ENTER( "_is_special" );
|
|
|
|
if( *tg++ != '.' ) DB_RETURN( 0 );
|
|
|
|
switch( *tg )
|
|
{
|
|
case 'E':
|
|
if( !strcmp( tg, "ERROR" ) ) DB_RETURN( ST_REST );
|
|
else if( !strcmp( tg, "EXPORT" ) ) DB_RETURN( ST_EXPORT );
|
|
else if( !strcmp( tg, "EXIT" ) ) DB_RETURN( ST_EXIT );
|
|
break;
|
|
|
|
case 'G':
|
|
if( !strcmp( tg, "GROUPPROLOG" )) DB_RETURN( ST_REST );
|
|
else if( !strcmp( tg, "GROUPEPILOG" )) DB_RETURN( ST_REST );
|
|
break;
|
|
|
|
case 'I':
|
|
if( !strcmp( tg, "IMPORT" ) ) DB_RETURN( ST_IMPORT );
|
|
else if( !strcmp( tg, "INCLUDE" ) ) DB_RETURN( ST_INCLUDE );
|
|
else if( !strcmp( tg, "INCLUDEDIRS" )) DB_RETURN( ST_REST );
|
|
break;
|
|
|
|
case 'K':
|
|
if( !strcmp( tg, "KEEP_STATE" ) ) DB_RETURN( ST_KEEP );
|
|
break;
|
|
|
|
case 'M':
|
|
if( !strcmp( tg, "MAKEFILES" ) ) DB_RETURN( ST_REST );
|
|
break;
|
|
|
|
case 'R':
|
|
if( !strcmp( tg, "REMOVE" ) ) DB_RETURN( ST_REST );
|
|
else if( !strcmp( tg, "ROOT" ) ) DB_RETURN( ST_REST );
|
|
break;
|
|
|
|
case 'S':
|
|
if( !strncmp( tg, "SOURCE", 6 ) ) DB_RETURN( ST_SOURCE );
|
|
else if( !strncmp(tg, "SUFFIXES", 8 )) {
|
|
if (Verbose & V_WARNALL)
|
|
Warning( "The .SUFFIXES target has no special meaning and is deprecated." );
|
|
DB_RETURN( ST_SOURCE );
|
|
}
|
|
break;
|
|
|
|
case 'T':
|
|
if( !strcmp( tg, "TARGETS" ) ) DB_RETURN( ST_REST );
|
|
break;
|
|
}
|
|
|
|
DB_RETURN( 0 );
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
_is_percent( np )/*
|
|
===================
|
|
return TRUE if np points at a string containing a % sign */
|
|
char *np;
|
|
{
|
|
return( (strchr(np,'%') && (*np != '\'' && np[strlen(np)-1] != '\'')) ?
|
|
TRUE : FALSE );
|
|
}
|
|
|
|
|
|
static char *
|
|
_is_magic( np )/*
|
|
=================
|
|
return NULL if np does not points at a string of the form
|
|
.<chars>.<chars> or .<chars>
|
|
where chars are "visible characters" for the current locale. If np is of the
|
|
first form we return a pointer to the second '.' and for the second form we
|
|
return a pointer to the '.'.
|
|
|
|
NOTE: reject target if it contains / or begins with ..
|
|
reject also .INIT and .DONE because they are mentioned in the
|
|
man page. */
|
|
char *np;
|
|
{
|
|
register char *n;
|
|
|
|
n = np;
|
|
if( *n != '.' ) return( NIL(char) );
|
|
if (strchr(DirBrkStr, *(n+1))!=NULL || *(n+1) == '.' )
|
|
return (NIL(char));
|
|
if( !strcmp( n+1, "INIT" ) || !strcmp( n+1, "DONE" ) )
|
|
return (NIL(char));
|
|
|
|
for( n++; isgraph(*n) && (*n != '.'); n++ );
|
|
|
|
if( *n != '\0' ) {
|
|
if( *n != '.' ) return( NIL(char) );
|
|
for( np = n++; isgraph( *n ) && (*n != '.'); n++ );
|
|
if( *n != '\0' ) return( NIL(char) );
|
|
}
|
|
/* Until dmake 4.5 a .<suffix> target was ignored when AUGMAKE was
|
|
* set and evaluated as a meta target if unset (also for -A).
|
|
* To keep maximum compatibility accept this regardles of the AUGMAKE
|
|
* status. */
|
|
|
|
/* np points at the second . of .<chars>.<chars> string.
|
|
* if the special target is of the form .<chars> then np points at the
|
|
* first . in the token. */
|
|
|
|
return( np );
|
|
}
|
|
|
|
|
|
static int
|
|
_add_root(tg)/*
|
|
===============
|
|
Adds "tg" to the prerequisits list of "Targets" if "Target" is not TRUE,
|
|
i.e. to the list of targets that are to be build.
|
|
Instead io setting "Target" to TRUE, TRUE is returned as more targets
|
|
might be defined in the current makefile line and they all have to be
|
|
add to "Targets" in this case. */
|
|
|
|
CELLPTR tg;
|
|
{
|
|
int res = FALSE;
|
|
|
|
if(tg == Targets)
|
|
return(TRUE);
|
|
|
|
if( !Target && !(tg->ce_flag & (F_SPECIAL|F_PERCENT)) ) {
|
|
Add_prerequisite(Targets, tg, FALSE, TRUE);
|
|
|
|
tg->ce_flag |= F_TARGET;
|
|
tg->ce_attr |= A_FRINGE;
|
|
res = TRUE;
|
|
}
|
|
|
|
return(res);
|
|
}
|