new built-ins: SORT/Sort, COMMAND/Command
[k8jam.git] / builtins.c
blob4d6a2b3128705aab118c3b71985526bc82c68cc3
1 /*
2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
7 /*
8 * builtins.c - builtin jam rules
10 * External routines:
12 * load_builtin() - define builtin rules
14 * Internal routines:
16 * builtin_depends() - DEPENDS/INCLUDES rule
17 * builtin_echo() - ECHO rule
18 * builtin_exit() - EXIT rule
19 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
20 * builtin_glob() - GLOB rule
21 * builtin_match() - MATCH rule
22 * builtin_hdrmacro() - HDRMACRO rule
24 * 01/10/01 (seiwald) - split from compile.c
25 * 01/08/01 (seiwald) - new 'Glob' (file expansion) builtin
26 * 03/02/02 (seiwald) - new 'Match' (regexp match) builtin
27 * 04/03/02 (seiwald) - Glob matches only filename, not directory
28 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr()
29 * 10/22/02 (seiwald) - working return/break/continue statements
30 * 11/04/02 (seiwald) - const-ing for string literals
31 * 12/03/02 (seiwald) - fix odd includes support by grafting them onto depends
32 * 01/14/03 (seiwald) - fix includes fix with new internal includes TARGET
35 #include <unistd.h>
36 #include <limits.h>
38 #include "jam.h"
40 #include "lists.h"
41 #include "parse.h"
42 #include "builtins.h"
43 #include "rules.h"
44 #include "filesys.h"
45 #include "newstr.h"
46 #include "regexp.h"
47 #include "pathsys.h"
48 #include "hdrmacro.h"
51 * compile_builtin() - define builtin rules
54 #define P0 (PARSE *)0
55 #define C0 (char *)0
58 int glob (const char *s, const char *c);
61 void load_builtins (void) {
62 bindrule("Always")->procedure =
63 bindrule("ALWAYS")->procedure =
64 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED);
66 bindrule("Depends")->procedure =
67 bindrule("DEPENDS")->procedure =
68 parse_make(builtin_depends, P0, P0, P0, C0, C0, 0);
70 bindrule("echo")->procedure =
71 bindrule("Echo")->procedure =
72 bindrule("ECHO")->procedure =
73 parse_make(builtin_echo, P0, P0, P0, C0, C0, 0);
75 bindrule("exit")->procedure =
76 bindrule("Exit")->procedure =
77 bindrule("EXIT")->procedure =
78 parse_make(builtin_exit, P0, P0, P0, C0, C0, 0);
80 bindrule("Glob")->procedure =
81 bindrule("GLOB")->procedure =
82 parse_make(builtin_glob, P0, P0, P0, C0, C0, 0);
84 bindrule("Includes")->procedure =
85 bindrule("INCLUDES")->procedure =
86 parse_make(builtin_depends, P0, P0, P0, C0, C0, 1);
88 bindrule("Leaves")->procedure =
89 bindrule("LEAVES")->procedure =
90 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES);
92 bindrule("Match")->procedure =
93 bindrule("MATCH")->procedure =
94 parse_make(builtin_match, P0, P0, P0, C0, C0, 0);
96 bindrule("NoCare")->procedure =
97 bindrule("NOCARE")->procedure =
98 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE);
100 bindrule("NOTIME")->procedure =
101 bindrule("NotFile")->procedure =
102 bindrule("NOTFILE")->procedure =
103 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE);
105 bindrule("NoUpdate")->procedure =
106 bindrule("NOUPDATE")->procedure =
107 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE);
109 bindrule("Temporary")->procedure =
110 bindrule("TEMPORARY")->procedure =
111 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP);
113 bindrule("HdrMacro")->procedure =
114 bindrule("HDRMACRO")->procedure =
115 parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0);
117 bindrule("PWD")->procedure =
118 bindrule("Pwd")->procedure =
119 parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0);
121 bindrule("SORT")->procedure =
122 bindrule("Sort")->procedure =
123 parse_make(builtin_sort, P0, P0, P0, C0, C0, 0);
125 bindrule("COMMAND")->procedure =
126 bindrule("Command")->procedure =
127 parse_make(builtin_command, P0, P0, P0, C0, C0, 0);
132 * builtin_depends() - DEPENDS/INCLUDES rule
134 * The DEPENDS builtin rule appends each of the listed sources on the
135 * dependency list of each of the listed targets. It binds both the
136 * targets and sources as TARGETs.
138 LIST *builtin_depends (PARSE *parse, LOL *args, int *jmp) {
139 LIST *targets = lol_get(args, 0);
140 LIST *sources = lol_get(args, 1);
141 /*k8: int which = parse->num;*/
142 LIST *l;
144 for (l = targets; l; l = list_next(l)) {
145 TARGET *t = bindtarget(l->string);
146 /* If doing INCLUDES, switch to the TARGET's include */
147 /* TARGET, creating it if needed. The internal include */
148 /* TARGET shares the name of its parent. */
149 if (parse->num) {
150 if (!t->includes) t->includes = copytarget(t);
151 t = t->includes;
153 t->depends = targetlist(t->depends, sources);
155 return L0;
160 * builtin_echo() - ECHO rule
162 * The ECHO builtin rule echoes the targets to the user. No other
163 * actions are taken.
165 LIST * builtin_echo (PARSE *parse, LOL *args, int *jmp) {
166 list_print(lol_get(args, 0));
167 printf("\n");
168 return L0;
173 * builtin_exit() - EXIT rule
175 * The EXIT builtin rule echoes the targets to the user and exits
176 * the program with a failure status.
178 LIST *builtin_exit (PARSE *parse, LOL *args, int *jmp) {
179 list_print(lol_get(args, 0));
180 printf("\n");
181 exit(EXITBAD); /* yeech */
182 return L0;
187 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
189 * Builtin_flags() marks the target with the appropriate flag, for use
190 * by make0(). It binds each target as a TARGET.
192 LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
193 LIST *l = lol_get(args, 0);
195 for (; l; l = list_next(l)) bindtarget(l->string)->flags |= parse->num;
196 return L0;
201 * builtin_globbing() - GLOB rule
203 struct globbing {
204 LIST *patterns;
205 LIST *results;
209 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
210 struct globbing *globbing = (struct globbing *)closure;
211 LIST *l;
212 PATHNAME f;
213 char buf[MAXJPATH];
215 /* Null out directory for matching. */
216 /* We wish we had file_dirscan() pass up a PATHNAME. */
217 path_parse(file, &f);
218 f.f_dir.len = 0;
219 path_build(&f, buf, 0);
221 for (l = globbing->patterns; l; l = l->next) {
222 if (!glob(l->string, buf)) {
223 globbing->results = list_new(globbing->results, file, 0);
224 break;
230 LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
231 struct globbing globbing;
232 LIST *l = lol_get(args, 0);
233 LIST *r = lol_get(args, 1);
235 globbing.results = L0;
236 globbing.patterns = r;
238 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
240 return globbing.results;
245 * builtin_match() - MATCH rule, regexp matching
247 LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
248 LIST *l, *r;
249 LIST *result = 0;
251 /* For each pattern */
252 for (l = lol_get(args, 0); l; l = l->next) {
253 regexp *re = regcomp(l->string);
254 /* For each string to match against */
255 for (r = lol_get(args, 1); r; r = r->next) {
256 if (regexec(re, r->string)) {
257 int i, top;
258 /* Find highest parameter */
259 for (top = NSUBEXP; top-- > 1;) if (re->startp[top]) break;
260 /* And add all parameters up to highest onto list. */
261 /* Must have parameters to have results! */
262 for (i = 1; i <= top; i++) {
263 char buf[MAXSYM];
264 int l = re->endp[i]-re->startp[i];
265 memcpy(buf, re->startp[i], l);
266 buf[l] = 0;
267 result = list_new(result, buf, 0);
271 free((char *)re);
274 return result;
278 LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
279 LIST *l = lol_get(args, 0);
281 for (; l; l = list_next(l)) {
282 TARGET *t = bindtarget(l->string);
283 /* scan file for header filename macro definitions */
284 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
285 macro_headers(t);
288 return L0;
292 /* backported from boost-jam */
294 * Return the current working directory.
296 * Usage: pwd = [ PWD ] ;
298 LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
299 char pwd_buffer[PATH_MAX];
301 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
302 perror("can not get current directory");
303 return L0;
305 return list_new(L0, pwd_buffer, 0);
310 /* backported from boost-jam */
311 LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
312 LIST *arg = lol_get(args, 0);
313 /*printf("unsorted: "); list_print(arg); printf("\n");*/
314 arg = list_sort(arg);
315 /*printf("sorted: "); list_print(arg); printf("\n");*/
316 return arg;
320 #if 0
321 /* backported from boost-jam */
322 LIST *builtin_normalize_path (PARSE *parse, LOL *args, int *jmp) {
323 LIST *arg = lol_get(args, 0);
324 /* First, we iterate over all '/'-separated elements, starting from
325 the end of string. If we see '..', we remove previous path elements.
326 If we see '.', we remove it.
327 The removal is done by putting '\1' in the string. After all the string
328 is processed, we do a second pass, removing '\1' characters.
330 string in[1], out[1], tmp[1];
331 char *end; /* Last character of the part of string still to be processed. */
332 char *current; /* Working pointer. */
333 int dotdots = 0; /* Number of '..' elements seen and not processed yet. */
334 int rooted = arg->string[0] == '/';
335 char *result;
337 /* Make a copy of input: we should not change it. */
338 string_new(in);
339 if (!rooted) string_push_back(in, '/');
340 while (arg) {
341 string_append(in, arg->string);
342 arg = list_next(arg);
343 if (arg) string_append(in, "/");
346 end = in->value+in->size-1;
347 current = end;
349 for (; end >= in->value;) {
350 /* Set 'current' to the next occurence of '/', which always exists. */
351 for (current = end; *current != '/'; --current) ;
352 if (current == end && current != in->value) {
353 /* Found a trailing slash. Remove it. */
354 *current = '\1';
355 } else if (current == end && *(current+1) == '/') {
356 /* Found duplicated slash. Remove it. */
357 *current = '\1';
358 } else if (end - current == 1 && strncmp(current, "/.", 2) == 0) {
359 /* Found '/.'. Drop them all. */
360 *current = '\1';
361 *(current+1) = '\1';
362 } else if (end - current == 2 && strncmp(current, "/..", 3) == 0) {
363 /* Found '/..' */
364 *current = '\1';
365 *(current+1) = '\1';
366 *(current+2) = '\1';
367 ++dotdots;
368 } else if (dotdots) {
369 char* p = current;
370 memset(current, '\1', end-current+1);
371 --dotdots;
373 end = current-1;
376 string_new(tmp);
377 while (dotdots--) string_append(tmp, "/..");
378 string_append(tmp, in->value);
379 string_copy(in, tmp->value);
380 string_free(tmp);
382 string_new(out);
383 /* The resulting path is either empty or has '/' as the first significant
384 element. If the original path was not rooted, we need to drop first '/'.
385 If the original path was rooted, and we've got empty path, need to add '/'
387 if (!rooted) {
388 current = strchr(in->value, '/');
389 if (current) *current = '\1';
392 for (current = in->value; *current; ++current) if (*current != '\1') string_push_back(out, *current);
394 result = newstr(out->size ? out->value : (rooted ? "/" : "."));
395 string_free(in);
396 string_free(out);
398 return list_new(0, result);
400 #endif
403 /* backported from boost-jam */
404 LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
405 LIST *l;
406 LIST *command = 0;
407 LIST *result = 0;
408 int ret, f;
409 char buffer[1024];
410 FILE *p = NULL;
411 int exit_status = -1;
412 int exit_status_opt = 0;
413 int no_output_opt = 0;
414 int trim_left = 1;
415 int trim_right = 1;
416 char *str, *spos;
417 int strSize = 1024;
420 /* for each arg */
421 for (l = lol_get(args, 0); l; l = l->next) {
422 if (!command) {
423 command = l;
424 } else {
425 /* for each string in list: check for arg */
426 if (!strcmp("exit-status", l->string)) {
427 exit_status_opt = 1;
428 } else if (!strcmp("exit-code", l->string)) {
429 exit_status_opt = 1;
430 } else if (!strcmp("no-output", l->string)) {
431 no_output_opt = 1;
432 } else if (!strcmp("no-trim", l->string)) {
433 trim_left = trim_right = 0;
434 } else if (!strcmp("no-trim-left", l->string)) {
435 trim_left = 0;
436 } else if (!strcmp("no-trim-right", l->string)) {
437 trim_right = 0;
442 fflush(NULL);
443 if (!(str = (char *)malloc((strSize+1)*sizeof(char)))) {
444 perror("COMMAND: out of memory");
446 if (!(p = popen(command->string, "r"))) {
447 free(str);
448 return L0;
450 *str = '\0'; spos = str;
451 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
452 buffer[ret] = 0;
453 if (!no_output_opt) {
454 f = spos-str;
455 if ((spos-str)+ret > strSize) {
456 strSize = (spos-str)+ret+2048;
457 spos = str;
458 str = (char *)realloc(str, (strSize+1)*sizeof(char));
459 if (!str) {
460 pclose(p);
461 free(spos);
462 perror("COMMAND: out of memory");
464 spos = str+f;
466 strcpy(spos, buffer);
467 spos += ret;
470 exit_status = pclose(p);
472 /* trim output if necessary */
473 if (trim_right) {
474 spos = str+strlen(str)-1;
475 while (spos >= str && *((unsigned char *)spos) <= ' ') spos--;
476 *(++spos) = '\0';
478 spos = str;
479 if (trim_left) {
480 while (*spos && *((unsigned char *)spos) <= ' ') spos++;
482 /* The command output is returned first. */
483 result = list_new(L0, spos, 0);
484 free(str);
486 /* The command exit result next. */
487 if (exit_status_opt) {
488 sprintf(buffer, "%d", exit_status);
489 result = list_new(result, buffer, 0);
492 return result;