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