xref: /netbsd-src/usr.bin/make/targ.c (revision 220b5c059a84c51ea44107ea8951a57ffaecdc8c)
1 /*	$NetBSD: targ.c,v 1.24 2001/11/12 01:33:49 tv Exp $	*/
2 
3 /*
4  * Copyright (c) 1988, 1989, 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  * Copyright (c) 1989 by Berkeley Softworks
7  * All rights reserved.
8  *
9  * This code is derived from software contributed to Berkeley by
10  * Adam de Boor.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. All advertising materials mentioning features or use of this software
21  *    must display the following acknowledgement:
22  *	This product includes software developed by the University of
23  *	California, Berkeley and its contributors.
24  * 4. Neither the name of the University nor the names of its contributors
25  *    may be used to endorse or promote products derived from this software
26  *    without specific prior written permission.
27  *
28  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38  * SUCH DAMAGE.
39  */
40 
41 #ifdef MAKE_BOOTSTRAP
42 static char rcsid[] = "$NetBSD: targ.c,v 1.24 2001/11/12 01:33:49 tv Exp $";
43 #else
44 #include <sys/cdefs.h>
45 #ifndef lint
46 #if 0
47 static char sccsid[] = "@(#)targ.c	8.2 (Berkeley) 3/19/94";
48 #else
49 __RCSID("$NetBSD: targ.c,v 1.24 2001/11/12 01:33:49 tv Exp $");
50 #endif
51 #endif /* not lint */
52 #endif
53 
54 /*-
55  * targ.c --
56  *	Functions for maintaining the Lst allTargets. Target nodes are
57  * kept in two structures: a Lst, maintained by the list library, and a
58  * hash table, maintained by the hash library.
59  *
60  * Interface:
61  *	Targ_Init 	    	Initialization procedure.
62  *
63  *	Targ_End 	    	Cleanup the module
64  *
65  *	Targ_List 	    	Return the list of all targets so far.
66  *
67  *	Targ_NewGN	    	Create a new GNode for the passed target
68  *	    	  	    	(string). The node is *not* placed in the
69  *	    	  	    	hash table, though all its fields are
70  *	    	  	    	initialized.
71  *
72  *	Targ_FindNode	    	Find the node for a given target, creating
73  *	    	  	    	and storing it if it doesn't exist and the
74  *	    	  	    	flags are right (TARG_CREATE)
75  *
76  *	Targ_FindList	    	Given a list of names, find nodes for all
77  *	    	  	    	of them. If a name doesn't exist and the
78  *	    	  	    	TARG_NOCREATE flag was given, an error message
79  *	    	  	    	is printed. Else, if a name doesn't exist,
80  *	    	  	    	its node is created.
81  *
82  *	Targ_Ignore	    	Return TRUE if errors should be ignored when
83  *	    	  	    	creating the given target.
84  *
85  *	Targ_Silent	    	Return TRUE if we should be silent when
86  *	    	  	    	creating the given target.
87  *
88  *	Targ_Precious	    	Return TRUE if the target is precious and
89  *	    	  	    	should not be removed if we are interrupted.
90  *
91  * Debugging:
92  *	Targ_PrintGraph	    	Print out the entire graphm all variables
93  *	    	  	    	and statistics for the directory cache. Should
94  *	    	  	    	print something for suffixes, too, but...
95  */
96 
97 #include	  <stdio.h>
98 #include	  <time.h>
99 #include	  "make.h"
100 #include	  "hash.h"
101 #include	  "dir.h"
102 
103 static Lst        allTargets;	/* the list of all targets found so far */
104 #ifdef CLEANUP
105 static Lst	  allGNs;	/* List of all the GNodes */
106 #endif
107 static Hash_Table targets;	/* a hash table of same */
108 
109 #define HTSIZE	191		/* initial size of hash table */
110 
111 static int TargPrintOnlySrc __P((ClientData, ClientData));
112 static int TargPrintName __P((ClientData, ClientData));
113 static int TargPrintNode __P((ClientData, ClientData));
114 #ifdef CLEANUP
115 static void TargFreeGN __P((ClientData));
116 #endif
117 static int TargPropagateCohort __P((ClientData, ClientData));
118 static int TargPropagateNode __P((ClientData, ClientData));
119 
120 /*-
121  *-----------------------------------------------------------------------
122  * Targ_Init --
123  *	Initialize this module
124  *
125  * Results:
126  *	None
127  *
128  * Side Effects:
129  *	The allTargets list and the targets hash table are initialized
130  *-----------------------------------------------------------------------
131  */
132 void
133 Targ_Init ()
134 {
135     allTargets = Lst_Init (FALSE);
136     Hash_InitTable (&targets, HTSIZE);
137 }
138 
139 /*-
140  *-----------------------------------------------------------------------
141  * Targ_End --
142  *	Finalize this module
143  *
144  * Results:
145  *	None
146  *
147  * Side Effects:
148  *	All lists and gnodes are cleared
149  *-----------------------------------------------------------------------
150  */
151 void
152 Targ_End ()
153 {
154 #ifdef CLEANUP
155     Lst_Destroy(allTargets, NOFREE);
156     if (allGNs)
157 	Lst_Destroy(allGNs, TargFreeGN);
158     Hash_DeleteTable(&targets);
159 #endif
160 }
161 
162 /*-
163  *-----------------------------------------------------------------------
164  * Targ_List --
165  *	Return the list of all targets
166  *
167  * Results:
168  *	The list of all targets.
169  *
170  * Side Effects:
171  *	None
172  *-----------------------------------------------------------------------
173  */
174 Lst
175 Targ_List ()
176 {
177     return allTargets;
178 }
179 
180 /*-
181  *-----------------------------------------------------------------------
182  * Targ_NewGN  --
183  *	Create and initialize a new graph node
184  *
185  * Results:
186  *	An initialized graph node with the name field filled with a copy
187  *	of the passed name
188  *
189  * Side Effects:
190  *	The gnode is added to the list of all gnodes.
191  *-----------------------------------------------------------------------
192  */
193 GNode *
194 Targ_NewGN (name)
195     char           *name;	/* the name to stick in the new node */
196 {
197     register GNode *gn;
198 
199     gn = (GNode *) emalloc (sizeof (GNode));
200     gn->name = estrdup (name);
201     gn->uname = NULL;
202     gn->path = (char *) 0;
203     if (name[0] == '-' && name[1] == 'l') {
204 	gn->type = OP_LIB;
205     } else {
206 	gn->type = 0;
207     }
208     gn->unmade =    	0;
209     gn->made = 	    	UNMADE;
210     gn->flags = 	0;
211     gn->order =		0;
212     gn->mtime = gn->cmtime = 0;
213     gn->iParents =  	Lst_Init (FALSE);
214     gn->cohorts =   	Lst_Init (FALSE);
215     gn->parents =   	Lst_Init (FALSE);
216     gn->children =  	Lst_Init (FALSE);
217     gn->successors = 	Lst_Init (FALSE);
218     gn->preds =     	Lst_Init (FALSE);
219     Hash_InitTable(&gn->context, 0);
220     gn->commands =  	Lst_Init (FALSE);
221     gn->suffix =	NULL;
222     gn->lineno =	0;
223     gn->fname = 	NULL;
224 
225 #ifdef CLEANUP
226     if (allGNs == NULL)
227 	allGNs = Lst_Init(FALSE);
228     Lst_AtEnd(allGNs, (ClientData) gn);
229 #endif
230 
231     return (gn);
232 }
233 
234 #ifdef CLEANUP
235 /*-
236  *-----------------------------------------------------------------------
237  * TargFreeGN  --
238  *	Destroy a GNode
239  *
240  * Results:
241  *	None.
242  *
243  * Side Effects:
244  *	None.
245  *-----------------------------------------------------------------------
246  */
247 static void
248 TargFreeGN (gnp)
249     ClientData gnp;
250 {
251     GNode *gn = (GNode *) gnp;
252 
253 
254     free(gn->name);
255     if (gn->uname)
256 	free(gn->uname);
257     if (gn->path)
258 	free(gn->path);
259     if (gn->fname)
260 	free(gn->fname);
261 
262     Lst_Destroy(gn->iParents, NOFREE);
263     Lst_Destroy(gn->cohorts, NOFREE);
264     Lst_Destroy(gn->parents, NOFREE);
265     Lst_Destroy(gn->children, NOFREE);
266     Lst_Destroy(gn->successors, NOFREE);
267     Lst_Destroy(gn->preds, NOFREE);
268     Hash_DeleteTable(&gn->context);
269     Lst_Destroy(gn->commands, NOFREE);
270     free((Address)gn);
271 }
272 #endif
273 
274 
275 /*-
276  *-----------------------------------------------------------------------
277  * Targ_FindNode  --
278  *	Find a node in the list using the given name for matching
279  *
280  * Results:
281  *	The node in the list if it was. If it wasn't, return NILGNODE of
282  *	flags was TARG_NOCREATE or the newly created and initialized node
283  *	if it was TARG_CREATE
284  *
285  * Side Effects:
286  *	Sometimes a node is created and added to the list
287  *-----------------------------------------------------------------------
288  */
289 GNode *
290 Targ_FindNode (name, flags)
291     char           *name;	/* the name to find */
292     int             flags;	/* flags governing events when target not
293 				 * found */
294 {
295     GNode         *gn;	      /* node in that element */
296     Hash_Entry	  *he;	      /* New or used hash entry for node */
297     Boolean	  isNew;      /* Set TRUE if Hash_CreateEntry had to create */
298 			      /* an entry for the node */
299 
300 
301     if (flags & TARG_CREATE) {
302 	he = Hash_CreateEntry (&targets, name, &isNew);
303 	if (isNew) {
304 	    gn = Targ_NewGN (name);
305 	    Hash_SetValue (he, gn);
306 	    Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
307 	    (void) Lst_AtEnd (allTargets, (ClientData)gn);
308 	}
309     } else {
310 	he = Hash_FindEntry (&targets, name);
311     }
312 
313     if (he == (Hash_Entry *) NULL) {
314 	return (NILGNODE);
315     } else {
316 	return ((GNode *) Hash_GetValue (he));
317     }
318 }
319 
320 /*-
321  *-----------------------------------------------------------------------
322  * Targ_FindList --
323  *	Make a complete list of GNodes from the given list of names
324  *
325  * Results:
326  *	A complete list of graph nodes corresponding to all instances of all
327  *	the names in names.
328  *
329  * Side Effects:
330  *	If flags is TARG_CREATE, nodes will be created for all names in
331  *	names which do not yet have graph nodes. If flags is TARG_NOCREATE,
332  *	an error message will be printed for each name which can't be found.
333  * -----------------------------------------------------------------------
334  */
335 Lst
336 Targ_FindList (names, flags)
337     Lst        	   names;	/* list of names to find */
338     int            flags;	/* flags used if no node is found for a given
339 				 * name */
340 {
341     Lst            nodes;	/* result list */
342     register LstNode  ln;		/* name list element */
343     register GNode *gn;		/* node in tLn */
344     char    	  *name;
345 
346     nodes = Lst_Init (FALSE);
347 
348     if (Lst_Open (names) == FAILURE) {
349 	return (nodes);
350     }
351     while ((ln = Lst_Next (names)) != NILLNODE) {
352 	name = (char *)Lst_Datum(ln);
353 	gn = Targ_FindNode (name, flags);
354 	if (gn != NILGNODE) {
355 	    /*
356 	     * Note: Lst_AtEnd must come before the Lst_Concat so the nodes
357 	     * are added to the list in the order in which they were
358 	     * encountered in the makefile.
359 	     */
360 	    (void) Lst_AtEnd (nodes, (ClientData)gn);
361 	} else if (flags == TARG_NOCREATE) {
362 	    Error ("\"%s\" -- target unknown.", name);
363 	}
364     }
365     Lst_Close (names);
366     return (nodes);
367 }
368 
369 /*-
370  *-----------------------------------------------------------------------
371  * Targ_Ignore  --
372  *	Return true if should ignore errors when creating gn
373  *
374  * Results:
375  *	TRUE if should ignore errors
376  *
377  * Side Effects:
378  *	None
379  *-----------------------------------------------------------------------
380  */
381 Boolean
382 Targ_Ignore (gn)
383     GNode          *gn;		/* node to check for */
384 {
385     if (ignoreErrors || gn->type & OP_IGNORE) {
386 	return (TRUE);
387     } else {
388 	return (FALSE);
389     }
390 }
391 
392 /*-
393  *-----------------------------------------------------------------------
394  * Targ_Silent  --
395  *	Return true if be silent when creating gn
396  *
397  * Results:
398  *	TRUE if should be silent
399  *
400  * Side Effects:
401  *	None
402  *-----------------------------------------------------------------------
403  */
404 Boolean
405 Targ_Silent (gn)
406     GNode          *gn;		/* node to check for */
407 {
408     if (beSilent || gn->type & OP_SILENT) {
409 	return (TRUE);
410     } else {
411 	return (FALSE);
412     }
413 }
414 
415 /*-
416  *-----------------------------------------------------------------------
417  * Targ_Precious --
418  *	See if the given target is precious
419  *
420  * Results:
421  *	TRUE if it is precious. FALSE otherwise
422  *
423  * Side Effects:
424  *	None
425  *-----------------------------------------------------------------------
426  */
427 Boolean
428 Targ_Precious (gn)
429     GNode          *gn;		/* the node to check */
430 {
431     if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) {
432 	return (TRUE);
433     } else {
434 	return (FALSE);
435     }
436 }
437 
438 /******************* DEBUG INFO PRINTING ****************/
439 
440 static GNode	  *mainTarg;	/* the main target, as set by Targ_SetMain */
441 /*-
442  *-----------------------------------------------------------------------
443  * Targ_SetMain --
444  *	Set our idea of the main target we'll be creating. Used for
445  *	debugging output.
446  *
447  * Results:
448  *	None.
449  *
450  * Side Effects:
451  *	"mainTarg" is set to the main target's node.
452  *-----------------------------------------------------------------------
453  */
454 void
455 Targ_SetMain (gn)
456     GNode   *gn;  	/* The main target we'll create */
457 {
458     mainTarg = gn;
459 }
460 
461 static int
462 TargPrintName (gnp, ppath)
463     ClientData     gnp;
464     ClientData	    ppath;
465 {
466     GNode *gn = (GNode *) gnp;
467     printf ("%s ", gn->name);
468 #ifdef notdef
469     if (ppath) {
470 	if (gn->path) {
471 	    printf ("[%s]  ", gn->path);
472 	}
473 	if (gn == mainTarg) {
474 	    printf ("(MAIN NAME)  ");
475 	}
476     }
477 #endif /* notdef */
478     return (ppath ? 0 : 0);
479 }
480 
481 
482 int
483 Targ_PrintCmd (cmd, dummy)
484     ClientData cmd;
485     ClientData dummy;
486 {
487     printf ("\t%s\n", (char *) cmd);
488     return (dummy ? 0 : 0);
489 }
490 
491 /*-
492  *-----------------------------------------------------------------------
493  * Targ_FmtTime --
494  *	Format a modification time in some reasonable way and return it.
495  *
496  * Results:
497  *	The time reformatted.
498  *
499  * Side Effects:
500  *	The time is placed in a static area, so it is overwritten
501  *	with each call.
502  *
503  *-----------------------------------------------------------------------
504  */
505 char *
506 Targ_FmtTime (time)
507     time_t    time;
508 {
509     struct tm	  	*parts;
510     static char	  	buf[128];
511 
512     parts = localtime(&time);
513     (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
514     return(buf);
515 }
516 
517 /*-
518  *-----------------------------------------------------------------------
519  * Targ_PrintType --
520  *	Print out a type field giving only those attributes the user can
521  *	set.
522  *
523  * Results:
524  *
525  * Side Effects:
526  *
527  *-----------------------------------------------------------------------
528  */
529 void
530 Targ_PrintType (type)
531     register int    type;
532 {
533     register int    tbit;
534 
535 #ifdef __STDC__
536 #define PRINTBIT(attr)	case CONCAT(OP_,attr): printf("." #attr " "); break
537 #define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG)) printf("." #attr " "); break
538 #else
539 #define PRINTBIT(attr) 	case CONCAT(OP_,attr): printf(".attr "); break
540 #define PRINTDBIT(attr)	case CONCAT(OP_,attr): if (DEBUG(TARG)) printf(".attr "); break
541 #endif /* __STDC__ */
542 
543     type &= ~OP_OPMASK;
544 
545     while (type) {
546 	tbit = 1 << (ffs(type) - 1);
547 	type &= ~tbit;
548 
549 	switch(tbit) {
550 	    PRINTBIT(OPTIONAL);
551 	    PRINTBIT(USE);
552 	    PRINTBIT(EXEC);
553 	    PRINTBIT(IGNORE);
554 	    PRINTBIT(PRECIOUS);
555 	    PRINTBIT(SILENT);
556 	    PRINTBIT(MAKE);
557 	    PRINTBIT(JOIN);
558 	    PRINTBIT(INVISIBLE);
559 	    PRINTBIT(NOTMAIN);
560 	    PRINTDBIT(LIB);
561 	    /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */
562 	    case OP_MEMBER: if (DEBUG(TARG)) printf(".MEMBER "); break;
563 	    PRINTDBIT(ARCHV);
564 	}
565     }
566 }
567 
568 /*-
569  *-----------------------------------------------------------------------
570  * TargPrintNode --
571  *	print the contents of a node
572  *-----------------------------------------------------------------------
573  */
574 static int
575 TargPrintNode (gnp, passp)
576     ClientData   gnp;
577     ClientData	 passp;
578 {
579     GNode         *gn = (GNode *) gnp;
580     int	    	  pass = *(int *) passp;
581     if (!OP_NOP(gn->type)) {
582 	printf("#\n");
583 	if (gn == mainTarg) {
584 	    printf("# *** MAIN TARGET ***\n");
585 	}
586 	if (pass == 2) {
587 	    if (gn->unmade) {
588 		printf("# %d unmade children\n", gn->unmade);
589 	    } else {
590 		printf("# No unmade children\n");
591 	    }
592 	    if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
593 		if (gn->mtime != 0) {
594 		    printf("# last modified %s: %s\n",
595 			      Targ_FmtTime(gn->mtime),
596 			      (gn->made == UNMADE ? "unmade" :
597 			       (gn->made == MADE ? "made" :
598 				(gn->made == UPTODATE ? "up-to-date" :
599 				 "error when made"))));
600 		} else if (gn->made != UNMADE) {
601 		    printf("# non-existent (maybe): %s\n",
602 			      (gn->made == MADE ? "made" :
603 			       (gn->made == UPTODATE ? "up-to-date" :
604 				(gn->made == ERROR ? "error when made" :
605 				 "aborted"))));
606 		} else {
607 		    printf("# unmade\n");
608 		}
609 	    }
610 	    if (!Lst_IsEmpty (gn->iParents)) {
611 		printf("# implicit parents: ");
612 		Lst_ForEach (gn->iParents, TargPrintName, (ClientData)0);
613 		fputc ('\n', stdout);
614 	    }
615 	}
616 	if (!Lst_IsEmpty (gn->parents)) {
617 	    printf("# parents: ");
618 	    Lst_ForEach (gn->parents, TargPrintName, (ClientData)0);
619 	    fputc ('\n', stdout);
620 	}
621 
622 	printf("%-16s", gn->name);
623 	switch (gn->type & OP_OPMASK) {
624 	    case OP_DEPENDS:
625 		printf(": "); break;
626 	    case OP_FORCE:
627 		printf("! "); break;
628 	    case OP_DOUBLEDEP:
629 		printf(":: "); break;
630 	}
631 	Targ_PrintType (gn->type);
632 	Lst_ForEach (gn->children, TargPrintName, (ClientData)0);
633 	fputc ('\n', stdout);
634 	Lst_ForEach (gn->commands, Targ_PrintCmd, (ClientData)0);
635 	printf("\n\n");
636 	if (gn->type & OP_DOUBLEDEP) {
637 	    Lst_ForEach (gn->cohorts, TargPrintNode, (ClientData)&pass);
638 	}
639     }
640     return (0);
641 }
642 
643 /*-
644  *-----------------------------------------------------------------------
645  * TargPrintOnlySrc --
646  *	Print only those targets that are just a source.
647  *
648  * Results:
649  *	0.
650  *
651  * Side Effects:
652  *	The name of each file is printed preceded by #\t
653  *
654  *-----------------------------------------------------------------------
655  */
656 static int
657 TargPrintOnlySrc(gnp, dummy)
658     ClientData 	  gnp;
659     ClientData 	  dummy;
660 {
661     GNode   	  *gn = (GNode *) gnp;
662     if (OP_NOP(gn->type))
663 	printf("#\t%s [%s]\n", gn->name, gn->path ? gn->path : gn->name);
664 
665     return (dummy ? 0 : 0);
666 }
667 
668 /*-
669  *-----------------------------------------------------------------------
670  * Targ_PrintGraph --
671  *	print the entire graph. heh heh
672  *
673  * Results:
674  *	none
675  *
676  * Side Effects:
677  *	lots o' output
678  *-----------------------------------------------------------------------
679  */
680 void
681 Targ_PrintGraph (pass)
682     int	    pass; 	/* Which pass this is. 1 => no processing
683 			 * 2 => processing done */
684 {
685     printf("#*** Input graph:\n");
686     Lst_ForEach (allTargets, TargPrintNode, (ClientData)&pass);
687     printf("\n\n");
688     printf("#\n#   Files that are only sources:\n");
689     Lst_ForEach (allTargets, TargPrintOnlySrc, (ClientData) 0);
690     printf("#*** Global Variables:\n");
691     Var_Dump (VAR_GLOBAL);
692     printf("#*** Command-line Variables:\n");
693     Var_Dump (VAR_CMD);
694     printf("\n");
695     Dir_PrintDirectories();
696     printf("\n");
697     Suff_PrintAll();
698 }
699 
700 static int
701 TargPropagateCohort (cgnp, pgnp)
702     ClientData   cgnp;
703     ClientData   pgnp;
704 {
705     GNode	  *cgn = (GNode *) cgnp;
706     GNode	  *pgn = (GNode *) pgnp;
707 
708     cgn->type |= pgn->type & ~OP_OPMASK;
709     return (0);
710 }
711 
712 static int
713 TargPropagateNode (gnp, junk)
714     ClientData   gnp;
715     ClientData   junk;
716 {
717     GNode	  *gn = (GNode *) gnp;
718     if (gn->type & OP_DOUBLEDEP)
719 	Lst_ForEach (gn->cohorts, TargPropagateCohort, gnp);
720     return (0);
721 }
722 
723 void
724 Targ_Propagate ()
725 {
726     Lst_ForEach (allTargets, TargPropagateNode, (ClientData)0);
727 }
728