1 /*
2  *      cook - file construction tool
3  *      Copyright (C) 1997, 1998, 2001, 2003, 2006, 2007 Peter Miller;
4  *      All rights reserved.
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 3 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License
17  *      along with this program. If not, see
18  *      <http://www.gnu.org/licenses/>.
19  *
20  * If the relationship between a target and a derived ingredient appears
21  * only in a derived cookbook, it is likely that a clean build (solely
22  * from primary source files) will fail.  It is recommended that
23  * relationships such as this be placed in a primary source cookbook.
24  *
25  * The functions in this file are used to detect such situations, and
26  * issue warnings about them when found.
27  */
28 
29 #include <common/error_intl.h>
30 #include <cook/expr/position.h>
31 #include <cook/graph.h>
32 #include <cook/graph/file_pair.h>
33 #include <common/mem.h>
34 #include <common/str_list.h>
35 #include <common/symtab.h>
36 #include <common/trace.h>
37 
38 
39 typedef struct target_ty target_ty;
40 struct target_ty
41 {
42     string_ty       *filename;
43     string_list_ty  pos;
44 };
45 
46 typedef struct ingredient_ty ingredient_ty;
47 struct ingredient_ty
48 {
49     string_ty       *filename;
50     int             include_cooked;
51     symtab_ty       *target;
52 };
53 
54 
55 /*
56  * NAME
57  *      target_new
58  *
59  * SYNOPSIS
60  *       target_ty *target_new(string_ty *);
61  *
62  * DESCRIPTION
63  *      The target_new function is used to allocate a new instance of a
64  *      target.  This is used to remember the location of a relationship
65  *      between a target and an ingredient.
66  *
67  * CAVEAT
68  *      Use target_delete when you are done with it.
69  */
70 
71 static target_ty *
target_new(string_ty * filename)72 target_new(string_ty *filename)
73 {
74     target_ty       *tp;
75 
76     trace(("target_new()\n{\n"));
77     tp = mem_alloc(sizeof(target_ty));
78     tp->filename = str_copy(filename);
79     string_list_constructor(&tp->pos);
80     trace(("}\n"));
81     return tp;
82 }
83 
84 
85 /*
86  * NAME
87  *      target_delete
88  *
89  * SYNOPSIS
90  *       void target_delete(target_ty *);
91  *
92  * DESCRIPTION
93  *      The target_delete function is used to release the resources held
94  *      by a target instance.
95  */
96 
97 static void
target_delete(target_ty * tp)98 target_delete(target_ty *tp)
99 {
100     trace(("target_delete()\n{\n"));
101     str_free(tp->filename);
102     string_list_destructor(&tp->pos);
103     mem_free(tp);
104     trace(("}\n"));
105 }
106 
107 
108 /*
109  * NAME
110  *      target_reap
111  *
112  * SYNOPSIS
113  *       void target_reap(void);
114  *
115  * DESCRIPTION
116  *      The target_reap function is used to delete target values in a
117  *      target symbol table.
118  */
119 
120 static void
target_reap(void * p)121 target_reap(void *p)
122 {
123     target_ty       *tp;
124 
125     tp = p;
126     target_delete(tp);
127 }
128 
129 
130 /*
131  * NAME
132  *      target_append
133  *
134  * SYNOPSIS
135  *       void target_append(target_ty *, const expr_position_ty *);
136  *
137  * DESCRIPTION
138  *      The target_append function is used to append recipe locations to
139  *      a target relationship.  This will be checked later.
140  */
141 
142 static void
target_append(target_ty * tp,const expr_position_ty * pp)143 target_append(target_ty *tp, const expr_position_ty *pp)
144 {
145     trace(("target_append()\n{\n"));
146     string_list_append_unique(&tp->pos, pp->pos_name);
147     trace(("}\n"));
148 }
149 
150 
151 /*
152  * NAME
153  *      foreign_derived
154  *
155  * SYNOPSIS
156  *      int foreign_derived(graph_file_pair_ty *, string_ty *);
157  *
158  * DESCRIPTION
159  *      The foreign_derived function is used to test if the named file
160  *      is a foreign derived file.  This is used in generating warnings
161  *      for cascaded ingredients in dependency include files.
162  */
163 
164 static int
foreign_derived(graph_file_pair_ty * gfpp,string_ty * filename)165 foreign_derived(graph_file_pair_ty *gfpp, string_ty *filename)
166 {
167     void            *p;
168 
169     if (!gfpp->foreign_derived)
170         return 0;
171     p = symtab_query(gfpp->foreign_derived, filename);
172     return (p != 0);
173 }
174 
175 
176 /*
177  * NAME
178  *      target_check
179  *
180  * SYNOPSIS
181  *       void target_check(graph_file_pair_ty *, target_ty *, string_ty *,
182  *              graph_ty *);
183  *
184  * DESCRIPTION
185  *      The target_check function is used to check that at least one of
186  *      the files describing the relationship between this target and
187  *      this ingredient is a leaf file.  The ingredient is known to be
188  *      derived (non-leaf).
189  *
190  *      A warning will be issued if this problem is found.  The long
191  *      warning will be issued, also, but only once per Cook command.
192  */
193 
194 static void
target_check(graph_file_pair_ty * gfpp,target_ty * tp,string_ty * ingredient,graph_ty * gp)195 target_check(graph_file_pair_ty *gfpp, target_ty *tp, string_ty *ingredient,
196     graph_ty *gp)
197 {
198     size_t          j;
199     static int      the_long_version;
200     sub_context_ty  *scp;
201     string_ty       *fn;
202 
203     /*
204      * If any of the cookbooks which describe this relationship are
205      * leaf files, there is no problem.  Leave quietly.
206      */
207     trace(("ingredient_new()\n{\n"));
208     assert(tp->pos.nstrings);
209     if (!tp->pos.nstrings)
210     {
211         trace(("}\n"));
212         return;
213     }
214     for (j = 0; j < tp->pos.nstrings; ++j)
215     {
216         fn = tp->pos.string[j];
217         if (!foreign_derived(gfpp, fn) && graph_file_leaf_p(gp, fn))
218         {
219             trace(("}\n"));
220             return;
221         }
222     }
223 
224     /*
225      * Houston, we have a problem.
226      */
227     scp = sub_context_new();
228     sub_var_set
229     (
230         scp,
231         "RELATionship", "%s: %s;",
232         tp->filename->str_text,
233         ingredient->str_text
234     );
235     sub_var_set_string(scp, "File_Name", tp->pos.string[0]);
236     error_intl
237     (
238         scp,
239         i18n("warning: the ``$relationship'' recipe is only in $filename")
240     );
241     sub_context_delete(scp);
242 
243     /*
244      * also issue a longer warning, but only once
245      */
246     if (!the_long_version)
247     {
248         the_long_version = 1;
249         error_intl(0, i18n("this means that a clean build will fail"));
250     }
251     trace(("}\n"));
252 }
253 
254 
255 /*
256  * NAME
257  *      ingredient_new
258  *
259  * SYNOPSIS
260  *       ingredient_ty *ingredient_new(string_ty *, int);
261  *
262  * DESCRIPTION
263  *      The ingredient_new function is used to allocate a new instance
264  *      of an ingredient.  Targets which lead to this ingredient, and
265  *      the position of the recipes within in the cookbooks, are
266  *      remembered within this data structure.
267  *
268  * CAVEAT
269  *      Release with ingredient_delete when you are done with it.
270  */
271 
272 static ingredient_ty *
ingredient_new(string_ty * filename,int include_cooked)273 ingredient_new(string_ty *filename, int include_cooked)
274 {
275     ingredient_ty   *ip;
276 
277     trace(("ingredient_new()\n{\n"));
278     ip = mem_alloc(sizeof(ingredient_ty));
279     ip->filename = str_copy(filename);
280     ip->include_cooked = include_cooked;
281     ip->target = 0;
282     trace(("}\n"));
283     return ip;
284 }
285 
286 
287 /*
288  * NAME
289  *      ingredient_delete
290  *
291  * SYNOPSIS
292  *       void ingredient_delete(ingredient_ty *);
293  *
294  * DESCRIPTION
295  *      The ingredient_delete function is used to release the resources
296  *      held by an ingredient instance.
297  */
298 
299 static void
ingredient_delete(ingredient_ty * ip)300 ingredient_delete(ingredient_ty *ip)
301 {
302     trace(("ingredient_delete()\n{\n"));
303     str_free(ip->filename);
304     if (ip->target)
305         symtab_free(ip->target);
306     mem_free(ip);
307     trace(("}\n"));
308 }
309 
310 
311 /*
312  * NAME
313  *      ingredient_reap
314  *
315  * SYNOPSIS
316  *       void ingredient_reap(void *);
317  *
318  * DESCRIPTION
319  *      The ingredient_reap function is used to delete ingredient values
320  *      in an ingredient symbol table.
321  */
322 
323 static void
ingredient_reap(void * p)324 ingredient_reap(void *p)
325 {
326     ingredient_ty   *ip;
327 
328     ip = p;
329     ingredient_delete(ip);
330 }
331 
332 
333 /*
334  * NAME
335  *      ingredient_append
336  *
337  * SYNOPSIS
338  *       void ingredient_append(ingredient_ty *, string_ty *,
339  *              const expr_position_ty *);
340  *
341  * DESCRIPTION
342  *      The ingredient_append function is used to remember the location
343  *      of a relationship between the given target and the given
344  *      ingredient.
345  */
346 
347 static void
ingredient_append(ingredient_ty * ip,string_ty * target,const expr_position_ty * pp)348 ingredient_append(ingredient_ty *ip, string_ty *target,
349     const expr_position_ty *pp)
350 {
351     target_ty       *tp;
352 
353     trace(("ingredient_append()\n{\n"));
354     if (!ip->target)
355     {
356         ip->target = symtab_alloc(1);
357         ip->target->reap = target_reap;
358     }
359     tp = symtab_query(ip->target, target);
360     if (!tp)
361     {
362         tp = target_new(target);
363         symtab_assign(ip->target, target, tp);
364     }
365     target_append(tp, pp);
366     trace(("}\n"));
367 }
368 
369 
370 /*
371  * NAME
372  *      ingredient_check
373  *
374  * SYNOPSIS
375  *       void ingredient_check(graph_file_pair_ty *, ingredient_ty *,
376  *              string_ty *, graph_ty *);
377  *
378  * DESCRIPTION
379  *      The ingredient_check function is used to check that at least one
380  *      of the files describing the relationship between this target and
381  *      this ingredient is a leaf file.  The ingredient is known to be
382  *      derived (non-leaf).
383  */
384 
385 static void
ingredient_check(graph_file_pair_ty * gfpp,ingredient_ty * ip,string_ty * target,graph_ty * gp)386 ingredient_check(graph_file_pair_ty *gfpp, ingredient_ty *ip, string_ty *target,
387     graph_ty *gp)
388 {
389     target_ty       *tp;
390 
391     /*
392      * Find the target.  It should always be there, but if it is
393      * not, leave quietly.
394      */
395     trace(("ingredient_check()\n{\n"));
396     assert(gfpp);
397     assert(ip);
398     assert(ip->target);
399     assert(target);
400     tp = symtab_query(ip->target, target);
401     assert(tp);
402     if (!tp)
403     {
404         trace(("}\n"));
405         return;
406     }
407 
408     /*
409      * Check the target.  This is actually a simple list of files
410      * which describe this relationship.
411      */
412     target_check(gfpp, tp, ip->filename, gp);
413     trace(("}\n"));
414 }
415 
416 
417 /*
418  * NAME
419  *      graph_file_pair_new
420  *
421  * SYNOPSIS
422  *       void graph_file_pair_new(void);
423  *
424  * DESCRIPTION
425  *      The graph_file_pair_new function is used to allocate a new
426  *      instance of the file pair location checking information.
427  *
428  * CAVEAT
429  *      Release using graph_file_pair_delete when you are done with it.
430  */
431 
432 graph_file_pair_ty *
graph_file_pair_new(string_list_ty * slp)433 graph_file_pair_new(string_list_ty *slp)
434 {
435     graph_file_pair_ty *gfpp;
436     size_t          j;
437 
438     trace(("graph_file_pair_new()\n{\n"));
439     gfpp = mem_alloc(sizeof(graph_file_pair_ty));
440     gfpp->stp = symtab_alloc(slp ? slp->nstrings : 5);
441     gfpp->stp->reap = ingredient_reap;
442     gfpp->foreign_derived = 0;
443     if (slp)
444     {
445         for (j = 0; j < slp->nstrings; ++j)
446         {
447             ingredient_ty   *ip;
448 
449             ip = ingredient_new(slp->string[j], 1);
450             symtab_assign(gfpp->stp, slp->string[j], ip);
451         }
452     }
453     trace(("}\n"));
454     return gfpp;
455 }
456 
457 
458 /*
459  * NAME
460  *      graph_file_pair_delete
461  *
462  * SYNOPSIS
463  *       void graph_file_pair_delete(graph_file_pair_ty *);
464  *
465  * DESCRIPTION
466  *      The graph_file_pair_delete function is used to relesae the
467  *      resources used by a graph_file_pair instance.
468  */
469 
470 void
graph_file_pair_delete(graph_file_pair_ty * gfpp)471 graph_file_pair_delete(graph_file_pair_ty *gfpp)
472 {
473     trace(("graph_file_pair_delete()\n{\n"));
474     symtab_free(gfpp->stp);
475     if (gfpp->foreign_derived)
476         symtab_free(gfpp->foreign_derived);
477     mem_free(gfpp);
478     trace(("}\n"));
479 }
480 
481 
482 /*
483  * NAME
484  *      graph_file_pair_remember
485  *
486  * SYNOPSIS
487  *      void graph_file_pair_remember(graph_file_pair_ty *,
488  *              string_ty *target, string_ty *ingredient,
489  *              const expr_position_ty *);
490  *
491  * DESCRIPTION
492  *      The graph_file_pair_remember function is used to remember the
493  *      position of a relationship between a target and an ingredient.
494  *      These will be checked for ``clean'' derivability, later.
495  */
496 
497 void
graph_file_pair_remember(graph_file_pair_ty * gfpp,string_ty * target,string_ty * ingredient,const expr_position_ty * pp)498 graph_file_pair_remember(graph_file_pair_ty *gfpp, string_ty *target,
499     string_ty *ingredient, const expr_position_ty *pp)
500 {
501     ingredient_ty   *ip;
502 
503     trace(("graph_file_pair_remember(target = \"%s\", "
504         "ingredient = \"%s\")\n{\n", target->str_text, ingredient->str_text));
505     ip = symtab_query(gfpp->stp, ingredient);
506     if (!ip)
507     {
508         ip = ingredient_new(ingredient, 0);
509         symtab_assign(gfpp->stp, ingredient, ip);
510     }
511     ingredient_append(ip, target, pp);
512     trace(("}\n"));
513 }
514 
515 
516 /*
517  * NAME
518  *      graph_file_pair_remember_lists
519  *
520  * SYNOPSIS
521  *       void graph_file_pair_remember_lists(graph_file_pair_ty *,
522  *              string_list_ty *targets, string_list_ty *ingredients,
523  *              const expr_position_ty *);
524  *
525  * DESCRIPTION
526  *      The graph_file_pair_remember_lists function is used to remember
527  *      the position of a relationship between a list of targets and a
528  *      list of ingredients.  These will be checked for ``clean''
529  *      derivability, later.
530  */
531 
532 void
graph_file_pair_remember_tlist(graph_file_pair_ty * gfpp,string_list_ty * target,string_ty * ingredient,const expr_position_ty * pp)533 graph_file_pair_remember_tlist(graph_file_pair_ty *gfpp, string_list_ty *target,
534     string_ty *ingredient, const expr_position_ty *pp)
535 {
536     size_t          j;
537 
538     trace(("graph_file_pair_remember_tlist()\n{\n"));
539     for (j = 0; j < target->nstrings; ++j)
540     {
541         graph_file_pair_remember(gfpp, target->string[j], ingredient, pp);
542     }
543     trace(("}\n"));
544 }
545 
546 
547 void
graph_file_pair_remember_lists(graph_file_pair_ty * gfpp,string_list_ty * target,string_list_ty * ingredient,const expr_position_ty * pp)548 graph_file_pair_remember_lists(graph_file_pair_ty *gfpp, string_list_ty *target,
549     string_list_ty *ingredient, const expr_position_ty *pp)
550 {
551     size_t          k;
552 
553     trace(("graph_file_pair_remember_lists()\n{\n"));
554     for (k = 0; k < ingredient->nstrings; ++k)
555     {
556         graph_file_pair_remember_tlist(gfpp, target, ingredient->string[k], pp);
557     }
558     trace(("}\n"));
559 }
560 
561 
562 /*
563  * NAME
564  *      graph_file_pair_check
565  *
566  * SYNOPSIS
567  *       void graph_file_pair_check(void);
568  *
569  * DESCRIPTION
570  *      The graph_file_pair_check function is used to check that the
571  *      relationship between the target and the ingredient either (a)
572  *      does not need deriving, or (b) is derivable.  Uses information
573  *      accumulated earlier using graph_file_pair_remember function.
574  */
575 
576 void
graph_file_pair_check(graph_file_pair_ty * gfpp,string_ty * target,string_ty * ingredient,graph_ty * gp)577 graph_file_pair_check(graph_file_pair_ty *gfpp, string_ty *target,
578     string_ty *ingredient, graph_ty *gp)
579 {
580     ingredient_ty   *ip;
581 
582     /*
583      * We are looking for non-leaf ingredients.
584      * If this ingredient is a leaf, no further processing is equired.
585      */
586     trace(("graph_file_pair_check()\n{\n"));
587     if (graph_file_leaf_p(gp, ingredient))
588     {
589         trace(("}\n"));
590         return;
591     }
592 
593     /*
594      * Find the list of files in which this relationship is
595      * expressed.  It should not happen, but if there is no such
596      * dependency, just return as if there was no problem.
597      */
598     ip = symtab_query(gfpp->stp, ingredient);
599     assert(ip);
600     if (!ip)
601     {
602         trace(("}\n"));
603         return;
604     }
605     assert(ip->target);
606     if (!ip->target)
607     {
608         trace(("}\n"));
609         return;
610     }
611 
612     /*
613      * check back to the target via the ingredient
614      */
615     ingredient_check(gfpp, ip, target, gp);
616     trace(("}\n"));
617 }
618 
619 
620 /*
621  * NAME
622  *      graph_file_pair_foreign_derived
623  *
624  * SYNOPSIS
625  *      void graph_file_pair_foreign_derived(graph_file_pair_ty *,
626  *              const string_list_ty *);
627  *
628  * DESCRIPTION
629  *      The graph_file_pair_foreign_derived function is used to add
630  *      a list of foreigh derived files.  These files are foreign to
631  *      the graph being walked.  This is used to generate warnings for
632  *      cascaded ingredients.
633  */
634 
635 void
graph_file_pair_foreign_derived(graph_file_pair_ty * gfpp,const string_list_ty * slp)636 graph_file_pair_foreign_derived(graph_file_pair_ty *gfpp,
637     const string_list_ty *slp)
638 {
639     size_t          j;
640     string_ty       *fn;
641 
642     if (!gfpp->foreign_derived)
643         gfpp->foreign_derived = symtab_alloc(slp->nstrings);
644     for (j = 0; j < slp->nstrings; ++j)
645     {
646         fn = slp->string[j];
647         symtab_assign(gfpp->foreign_derived, fn, fn);
648     }
649 }
650 
651 
652 #ifdef DEBUG
653 
654 
655 /*
656  * NAME
657  *      graph_file_pair_check
658  *
659  * SYNOPSIS
660  *      int graph_file_pair_exists(graph_file_pair_ty *gfpp,
661  *          string_ty *target, string_ty *ingredient);
662  *
663  * DESCRIPTION
664  *      The graph_file_pair_exists function is used at debug time to
665  *      check that the graph file pairs have been constructed correctly.
666  */
667 
668 int
graph_file_pair_exists(graph_file_pair_ty * gfpp,string_ty * target,string_ty * ingredient)669 graph_file_pair_exists(graph_file_pair_ty *gfpp, string_ty *target,
670     string_ty *ingredient)
671 {
672     ingredient_ty   *ip;
673     int             result;
674 
675     /*
676      * We are looking for non-leaf ingredients.
677      * If this ingredient is a leaf, no further processing is equired.
678      */
679     trace(("graph_file_pair_exists()\n{\n"));
680     if (!gfpp)
681     {
682         trace(("return 0;\n}\n"));
683         return 0;
684     }
685 
686     /*
687      * Make sure the ingredient is known.
688      */
689     ip = symtab_query(gfpp->stp, ingredient);
690     if (!ip)
691     {
692         trace(("return 0;\n}\n"));
693         return 0;
694     }
695 
696     /*
697      * Make sure the ingredient has heard of the target.
698      */
699     if (!ip->target)
700     {
701         trace(("return 0;\n}\n"));
702         return 0;
703     }
704     result = !!symtab_query(ip->target, target);
705     trace(("return %d;\n}\n", result));
706     return result;
707 }
708 
709 #endif
710