1 /*
2  *      cook - file construction tool
3  *      Copyright (C) 1997, 1999, 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 
21 #include <common/ac/stdio.h>
22 
23 #include <cook/dir_part.h>
24 #include <common/error_intl.h>
25 #include <cook/graph/file.h>
26 #include <cook/graph/file_list.h>
27 #include <cook/graph/recipe.h>
28 #include <cook/graph/script.h>
29 #include <cook/id.h>
30 #include <cook/id/variable.h>
31 #include <cook/match.h>
32 #include <cook/opcode/context.h>
33 #include <cook/option.h>
34 #include <cook/os_interface.h>
35 #include <cook/recipe.h>
36 #include <common/star.h>
37 #include <cook/stmt.h>
38 #include <common/str_list.h>
39 #include <common/trace.h>
40 
41 
42 /*
43  * NAME
44  *      graph_recipe_script
45  *
46  * SYNOPSIS
47  *      graph_walk_status_ty graph_recipe_script(graph_recipe_ty *);
48  *
49  * DESCRIPTION
50  *      The graph_recipe_script function is used to print a shell script
51  *      fragment on the standard output which approximates this recipe
52  *      instance.
53  *
54  * RETURNS
55  *      graph_walk_status_ty
56  *              error           something went wrong
57  *              uptodate        sucecss
58  */
59 
60 graph_walk_status_ty
graph_recipe_script(graph_recipe_ty * grp,struct graph_ty * gp)61 graph_recipe_script(graph_recipe_ty *grp, struct graph_ty *gp)
62 {
63     graph_walk_status_ty status;
64     size_t          j;
65     string_list_ty  wl;
66     int             forced;
67     long            file_pos = 0;
68 
69     trace(("graph_recipe_script(grp = %08lX)\n{\n", (long)grp));
70     status = graph_walk_status_done;
71 
72     grp->ocp = opcode_context_new(0, grp->mp);
73     grp->ocp->gp = gp;
74 
75     /*
76      * construct the ``target'' variable
77      */
78     string_list_constructor(&wl);
79     assert(grp->output);
80     assert(grp->output->nfiles > 0);
81     if (grp->output->nfiles > 0)
82         string_list_append(&wl, grp->output->item[0].file->filename);
83     opcode_context_id_assign(grp->ocp, id_target, id_variable_new(&wl), -1);
84     string_list_destructor(&wl);
85 
86     /*
87      * construct the ``targets'' variable
88      */
89     string_list_constructor(&wl);
90     assert(grp->input);
91     for (j = 0; j < grp->output->nfiles; ++j)
92         string_list_append(&wl, grp->output->item[j].file->filename);
93     opcode_context_id_assign(grp->ocp, id_targets, id_variable_new(&wl), -1);
94     string_list_destructor(&wl);
95 
96     /*
97      * construct the ``need'' variable
98      * (and ``younger'' will be identical)
99      */
100     string_list_constructor(&wl);
101     assert(grp->input);
102     for (j = 0; j < grp->input->nfiles; ++j)
103         string_list_append(&wl, grp->input->item[j].file->filename);
104     opcode_context_id_assign(grp->ocp, id_need, id_variable_new(&wl), -1);
105     opcode_context_id_assign(grp->ocp, id_younger, id_variable_new(&wl), -1);
106     string_list_destructor(&wl);
107 
108     /*
109      * Flags apply to the precondition and to the ingredients
110      * evaluation.  That is why the grammar puts them first.
111      */
112     recipe_flags_set(grp->rp);
113 
114     /*
115      * see of the recipe is forced to activate
116      */
117     forced = option_test(OPTION_FORCE);
118 
119     /*
120      * Print the original position, so the user can tell where it
121      * came from.
122      */
123     if (grp->rp->pos.pos_line)
124     {
125         string_ty       *tmp;
126 
127         assert(grp->rp->pos.pos_name);
128         tmp = str_quote_shell(grp->rp->pos.pos_name);
129         printf("\n#line %d %s\n", grp->rp->pos.pos_line, tmp->str_text);
130         str_free(tmp);
131     }
132 
133     /*
134      * print the test to see if this recipe should be run
135      */
136     if (!forced)
137     {
138         printf("if test");
139         for (j = 0; j < grp->output->nfiles; ++j)
140         {
141             string_ty       *js;
142             size_t          k;
143 
144             js = str_quote_shell(grp->output->item[j].file->filename);
145             if (j)
146                 printf(" \\\n  -o");
147             /* target does not exist */
148             printf(" ! -e %s", js->str_text);
149             for (k = 0; k < grp->input->nfiles; ++k)
150             {
151                 graph_file_and_type_ty *gftp;
152                 graph_file_ty   *gfp;
153                 string_ty       *ingr;
154 
155                 /* ingredient is newer than target */
156                 gftp = grp->input->item + k;
157                 gfp = gftp->file;
158                 ingr = str_quote_shell(gfp->filename);
159                 switch (gftp->edge_type)
160                 {
161                 case edge_type_exists:
162                     /*
163                      * The "exists" edge type is
164                      * merely an ordering constraint.
165                      * It guarantees that the
166                      * ingredient will be up to date
167                      * before this recipie's body is
168                      * evaluated.  The actual relative
169                      * ages of the ingredient and
170                      * the target don't come into it.
171                      */
172                     break;
173 
174                 case edge_type_weak:
175                     /*
176                      * The "weak" edge type allows
177                      * two files to have the same time
178                      * stamp, and still be up-to-date.
179                      */
180                     printf(" \\\n  -o %s -nt %s", ingr->str_text, js->str_text);
181                     break;
182 
183                 case edge_type_strict:
184                 case edge_type_default:
185                     /*
186                      * The "strict" edge type requires
187                      * two files to have distinct
188                      * time stamps.
189                      */
190                     printf
191                         (" \\\n  -o ! %s -nt %s", js->str_text, ingr->str_text);
192                     break;
193                 }
194                 str_free(ingr);
195             }
196             str_free(js);
197         }
198         printf("\nthen\n");
199         file_pos = ftell(stdout);
200     }
201 
202     /*
203      * See if we need to perform the actions attached to this recipe.
204      */
205     if (grp->rp->out_of_date)
206     {
207         int             echo;
208 
209         trace(("do recipe body\n"));
210         echo = !option_test(OPTION_SILENT);
211         if (option_test(OPTION_MKDIR))
212         {
213             for (j = 0; j < grp->output->nfiles; ++j)
214             {
215                 graph_file_ty   *gfp;
216                 string_ty       *s;
217                 string_ty       *tmp;
218 
219                 gfp = grp->output->item[j].file;
220                 s = dir_part(gfp->filename);
221                 if (!s)
222                     continue;
223                 tmp = str_quote_shell(s);
224                 str_free(s);
225                 printf("if test ! -d %s; then\n", tmp->str_text);
226                 if (echo)
227                 {
228                     printf("echo mkdir -p %s\n", tmp->str_text);
229                 }
230                 printf("mkdir -p %s", tmp->str_text);
231                 if (!option_test(OPTION_ERROK))
232                     printf(" || exit 1");
233                 printf("\nfi\n");
234                 str_free(tmp);
235             }
236         }
237         if (option_test(OPTION_UNLINK))
238         {
239             for (j = 0; j < grp->output->nfiles; ++j)
240             {
241                 graph_file_ty   *gfp;
242                 string_ty       *tmp;
243 
244                 gfp = grp->output->item[j].file;
245                 tmp = str_quote_shell(gfp->filename);
246                 if (echo)
247                     printf("echo rm %s\n", tmp->str_text);
248                 printf("rm %s", tmp->str_text);
249                 if (!option_test(OPTION_ERROK))
250                     printf(" || exit 1");
251                 printf("\n");
252                 str_free(tmp);
253             }
254         }
255         if (option_test(OPTION_TOUCH))
256         {
257             for (j = 0; j < grp->output->nfiles; ++j)
258             {
259                 graph_file_ty   *gfp;
260                 string_ty       *tmp;
261 
262                 gfp = grp->output->item[j].file;
263                 tmp = str_quote_shell(gfp->filename);
264                 if (echo)
265                 {
266                     printf("echo touch %s\n", tmp->str_text);
267                 }
268                 printf("touch %s", tmp->str_text);
269                 if (!option_test(OPTION_ERROK))
270                     printf(" || exit 1");
271                 printf("\n");
272                 str_free(tmp);
273             }
274         }
275         else
276         {
277             opcode_status_ty status2;
278 
279             trace(("doing it now\n"));
280             opcode_context_call(grp->ocp, grp->rp->out_of_date);
281             status2 = opcode_context_script(grp->ocp);
282             if (status2 != opcode_status_success)
283                 status = graph_walk_status_error;
284         }
285     }
286 
287     /*
288      * This recipe is being used, so
289      * perform its 'use' action.
290      *
291      * Ignore the 'touch' option,
292      * ignore the 'errok' option,
293      * don't delete files on errors.
294      *
295      * Note: it looks odd to have the "else" in the script.  This is
296      * because the use clause is *duplicated* in the compiled
297      * opcode stream for the out-of-date case.
298      */
299     if (grp->rp->up_to_date)
300     {
301         opcode_status_ty status2;
302 
303         trace(("perform ``use'' clause\n"));
304         if (!forced)
305         {
306             if (file_pos == ftell(stdout))
307                 printf(":\n");
308             printf("else\n");
309             file_pos = ftell(stdout);
310         }
311         opcode_context_call(grp->ocp, grp->rp->up_to_date);
312         status2 = opcode_context_script(grp->ocp);
313         if (status2 != opcode_status_success)
314             status = graph_walk_status_error;
315     }
316 
317     /*
318      * finish the conditional around this recipe
319      */
320     if (!forced)
321     {
322         if (file_pos == ftell(stdout))
323             printf(":\n");
324         printf("fi\n");
325     }
326 
327     /*
328      * cancel the recipe flags
329      */
330     option_undo_level(OPTION_LEVEL_RECIPE);
331     if (grp->ocp)
332     {
333         opcode_context_delete(grp->ocp);
334         grp->ocp = 0;
335     }
336 
337     star_as_specified('*');
338     trace(("return %s;\n", opcode_status_name(status)));
339     trace(("}\n"));
340     return status;
341 }
342