2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
8 * builtins.c - builtin jam rules
12 * load_builtin() - define builtin rules
16 * builtin_depends() - DEPENDS/INCLUDES rule
17 * builtin_echo() - ECHO rule
18 * builtin_echon() - ECHO-N rule
19 * builtin_oflush() - O-FLUSH rule
20 * builtin_exit() - EXIT rule
21 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
22 * builtin_glob() - GLOB rule
23 * builtin_match() - MATCH rule
24 * builtin_hdrmacro() - HDRMACRO rule
26 * 01/10/01 (seiwald) - split from compile.c
27 * 01/08/01 (seiwald) - new 'Glob' (file expansion) builtin
28 * 03/02/02 (seiwald) - new 'Match' (regexp match) builtin
29 * 04/03/02 (seiwald) - Glob matches only filename, not directory
30 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr()
31 * 10/22/02 (seiwald) - working return/break/continue statements
32 * 11/04/02 (seiwald) - const-ing for string literals
33 * 12/03/02 (seiwald) - fix odd includes support by grafting them onto depends
34 * 01/14/03 (seiwald) - fix includes fix with new internal includes TARGET
51 #include "matchglob.h"
57 * compile_builtin() - define builtin rules
60 #define P0 ((PARSE *)0)
61 #define C0 ((char *)0)
64 void load_builtins (void) {
65 bindrule("Always")->procedure
=
66 bindrule("ALWAYS")->procedure
=
67 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TOUCHED
);
69 bindrule("Depends")->procedure
=
70 bindrule("DEPENDS")->procedure
=
71 parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 0);
73 bindrule("echo")->procedure
=
74 bindrule("Echo")->procedure
=
75 bindrule("ECHO")->procedure
=
76 parse_make(builtin_echo
, P0
, P0
, P0
, C0
, C0
, 0);
78 bindrule("echo-n")->procedure
=
79 bindrule("Echo-n")->procedure
=
80 bindrule("ECHO-N")->procedure
=
81 parse_make(builtin_echon
, P0
, P0
, P0
, C0
, C0
, 0);
83 bindrule("o-flush")->procedure
=
84 bindrule("O-flush")->procedure
=
85 bindrule("O-Flush")->procedure
=
86 bindrule("O-FLUSH")->procedure
=
87 parse_make(builtin_oflush
, P0
, P0
, P0
, C0
, C0
, 0);
89 bindrule("exit")->procedure
=
90 bindrule("Exit")->procedure
=
91 bindrule("EXIT")->procedure
=
92 parse_make(builtin_exit
, P0
, P0
, P0
, C0
, C0
, 0);
94 bindrule("Glob")->procedure
=
95 bindrule("GLOB")->procedure
=
96 parse_make(builtin_glob
, P0
, P0
, P0
, C0
, C0
, 0);
98 bindrule("Includes")->procedure
=
99 bindrule("INCLUDES")->procedure
=
100 parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 1);
102 bindrule("Leaves")->procedure
=
103 bindrule("LEAVES")->procedure
=
104 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_LEAVES
);
106 bindrule("Match")->procedure
=
107 bindrule("MATCH")->procedure
=
108 parse_make(builtin_match
, P0
, P0
, P0
, C0
, C0
, 0);
110 bindrule("NoCare")->procedure
=
111 bindrule("NOCARE")->procedure
=
112 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOCARE
);
114 bindrule("NOTIME")->procedure
=
115 bindrule("NotFile")->procedure
=
116 bindrule("NOTFILE")->procedure
=
117 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOTFILE
);
119 bindrule("NoUpdate")->procedure
=
120 bindrule("NOUPDATE")->procedure
=
121 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOUPDATE
);
123 bindrule("Temporary")->procedure
=
124 bindrule("TEMPORARY")->procedure
=
125 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TEMP
);
127 bindrule("HdrMacro")->procedure
=
128 bindrule("HDRMACRO")->procedure
=
129 parse_make(builtin_hdrmacro
, P0
, P0
, P0
, C0
, C0
, 0);
131 bindrule("PWD")->procedure
=
132 bindrule("Pwd")->procedure
=
133 parse_make(builtin_pwd
, P0
, P0
, P0
, C0
, C0
, 0);
135 bindrule("SORT")->procedure
=
136 bindrule("Sort")->procedure
=
137 parse_make(builtin_sort
, P0
, P0
, P0
, C0
, C0
, 0);
139 bindrule("COMMAND")->procedure
=
140 bindrule("Command")->procedure
=
141 parse_make(builtin_command
, P0
, P0
, P0
, C0
, C0
, 0);
143 bindrule("ForceCare")->procedure
=
144 bindrule("FORCECARE")->procedure
=
145 parse_make(builtin_flags_forcecare
, P0
, P0
, P0
, C0
, C0
, T_FLAG_FORCECARE
);
147 bindrule("ExprI1")->procedure
=
148 bindrule("EXPRI1")->procedure
=
149 parse_make(builtin_expri1
, P0
, P0
, P0
, C0
, C0
, 0);
151 bindrule("Split")->procedure
=
152 bindrule("SPLIT")->procedure
=
153 parse_make(builtin_split
, P0
, P0
, P0
, C0
, C0
, 0);
155 bindrule("DependsList")->procedure
=
156 bindrule("DEPENDSLIST")->procedure
=
157 parse_make(builtin_dependslist
, P0
, P0
, P0
, C0
, C0
, 0);
159 bindrule("NormPath")->procedure
=
160 bindrule("NORMPATH")->procedure
=
161 parse_make(builtin_normpath
, P0
, P0
, P0
, C0
, C0
, 0);
163 bindrule("ListLength")->procedure
=
164 parse_make(builtin_listlength
, P0
, P0
, P0
, C0
, C0
, 0);
169 * builtin_depends() - DEPENDS/INCLUDES rule
171 * The DEPENDS builtin rule appends each of the listed sources on the
172 * dependency list of each of the listed targets.
173 * It binds both the targets and sources as TARGETs.
175 LIST
*builtin_depends (PARSE
*parse
, LOL
*args
, int *jmp
) {
176 LIST
*targets
= lol_get(args
, 0);
177 LIST
*sources
= lol_get(args
, 1);
180 for (l
= targets
; l
; l
= list_next(l
)) {
181 TARGET
*t
= bindtarget(l
->string
);
182 /* If doing INCLUDES, switch to the TARGET's include */
183 /* TARGET, creating it if needed. The internal include */
184 /* TARGET shares the name of its parent. */
186 if (!t
->includes
) t
->includes
= copytarget(t
);
189 t
->depends
= targetlist(t
->depends
, sources
);
196 * builtin_echo() - ECHO rule
198 * The ECHO builtin rule echoes the targets to the user.
199 * No other actions are taken.
201 LIST
*builtin_echo (PARSE
*parse
, LOL
*args
, int *jmp
) {
202 list_print(lol_get(args
, 0));
209 * builtin_echon() - ECHO-N rule
211 * The ECHO-N builtin rule echoes the targets to the user.
212 * No other actions are taken, no newline is written.
214 LIST
*builtin_echon (PARSE
*parse
, LOL
*args
, int *jmp
) {
215 list_print(lol_get(args
, 0));
221 * builtin_oflush() - O-FLUSH rule
223 * The O-FLUSH builtin rule flushes current output stream.
226 LIST
*builtin_oflush (PARSE
*parse
, LOL
*args
, int *jmp
) {
233 * builtin_exit() - EXIT rule
235 * The EXIT builtin rule echoes the targets to the user and exits
236 * the program with a failure status.
238 LIST
*builtin_exit (PARSE
*parse
, LOL
*args
, int *jmp
) {
239 list_print(lol_get(args
, 0));
241 exit(EXITBAD
); /* yeech */
249 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
251 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
252 * It binds each target as a TARGET.
254 LIST
*builtin_flags (PARSE
*parse
, LOL
*args
, int *jmp
) {
255 LIST
*l
= lol_get(args
, 0);
256 for (; l
; l
= list_next(l
)) bindtarget(l
->string
)->flags
|= parse
->num
;
262 * builtin_flags_forcecare() - ForceCare rule
264 LIST
*builtin_flags_forcecare (PARSE
*parse
, LOL
*args
, int *jmp
) {
265 LIST
*l
= lol_get(args
, 0);
266 for( ; l
; l
= list_next(l
)) {
267 TARGET
*t
= bindtarget(l
->string
);
268 t
->flags
|= T_FLAG_FORCECARE
;
269 t
->flags
&= ~T_FLAG_NOCARE
;
276 * builtin_globbing() - GLOB rule
284 static void builtin_glob_back (void *closure
, const char *file
, int status
, time_t time
) {
285 struct globbing
*globbing
= (struct globbing
*)closure
;
289 /* null out directory for matching */
290 /* we wish we had file_dirscan() pass up a PATHNAME */
291 path_parse(file
, &f
);
293 /* For globbing, we unconditionally ignore current and parent
294 * directory items. Since those items always exist, there's no
295 * reason why caller of GLOB would want to see them.
296 * We could also change file_dirscan, but then paths with embedded
297 * "." and ".." won't work anywhere. */
298 /* k8: will this break anything? it shouldn't... */
299 if (!strcmp(f
.f_base
.ptr
, ".") || !strcmp(f
.f_base
.ptr
, "..")) return;
300 path_build(&f
, buf
, 0);
301 for (l
= globbing
->patterns
; l
; l
= l
->next
) {
302 if (!matchglob(l
->string
, buf
)) {
303 globbing
->results
= list_new(globbing
->results
, file
, 0);
310 LIST
*builtin_glob (PARSE
*parse
, LOL
*args
, int *jmp
) {
311 struct globbing globbing
;
312 LIST
*l
= lol_get(args
, 0);
313 LIST
*r
= lol_get(args
, 1);
314 globbing
.results
= L0
;
315 globbing
.patterns
= r
;
316 for (; l
; l
= list_next(l
)) file_dirscan(l
->string
, builtin_glob_back
, &globbing
);
317 return globbing
.results
;
322 * builtin_match() - MATCH rule, regexp matching
324 LIST
*builtin_match (PARSE
*parse
, LOL
*args
, int *jmp
) {
327 /* for each pattern */
328 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
333 if ((err
= hsrxCompile(&re
, l
->string
, HSRX_EXTENDED
)) != 0) {
334 static char errbuf
[512];
336 hsrxError(err
, &re
, errbuf
, sizeof(errbuf
));
338 printf("FATAL: %s\n", errbuf
);
341 mt
= malloc(sizeof(HSRxMatch
)*(re
.re_nsub
+1));
342 if (mt
== NULL
) { printf("FATAL: out of memory!\n"); exit(42); }
343 /* for each string to match against */
344 for (r
= lol_get(args
, 1); r
; r
= r
->next
) {
345 if (hsrxExec(&re
, r
->string
, re
.re_nsub
+1, mt
, 0) == HSRX_NOERROR
) {
347 /* add all parameters up to highest onto list */
348 /* must have parameters to have results! */
349 for (i
= 1; i
<= re
.re_nsub
; ++i
) {
352 int l
= mt
[i
].rm_eo
-mt
[i
].rm_so
;
353 if (l
> 0) memcpy(buf
, r
->string
+mt
[i
].rm_so
, l
);
355 res
= list_new(res
, buf
, 0);
366 LIST
*builtin_hdrmacro (PARSE
*parse
, LOL
*args
, int *jmp
) {
367 LIST
*l
= lol_get(args
, 0);
368 for (; l
; l
= list_next(l
)) {
369 TARGET
*t
= bindtarget(l
->string
);
370 /* scan file for header filename macro definitions */
371 if (DEBUG_HEADER
) printf("scanning '%s' for header file macro definitions\n", l
->string
);
378 /* backported from boost-jam */
380 * Return the current working directory.
382 * Usage: pwd = [ PWD ] ;
384 LIST
*builtin_pwd (PARSE
*parse
, LOL
*args
, int *jmp
) {
385 char pwd_buffer
[PATH_MAX
];
386 if (!getcwd(pwd_buffer
, sizeof(pwd_buffer
))) {
387 perror("can not get current directory");
390 return list_new(L0
, pwd_buffer
, 0);
394 /* backported from boost-jam */
395 LIST
*builtin_sort (PARSE
*parse
, LOL
*args
, int *jmp
) {
396 LIST
*arg
= lol_get(args
, 0);
397 arg
= list_sort(arg
);
402 /* backported from boost-jam; greatly improved */
403 /* Command shcmd [[ : options ]] */
404 LIST
*builtin_command (PARSE
*parse
, LOL
*args
, int *jmp
) {
408 char buffer
[1024], buf1
[32], *spos
, *epos
;
411 int optExitStatus
= 0;
414 int optTrimRight
= 1;
415 int optStatus1st
= 0;
417 int optSpaceBreak
= 1;
423 /* for each string in 2nd list: check for arg */
424 for (l
= lol_get(args
, 1); l
!= NULL
; l
= l
->next
) {
425 if (!strcmp("exit-status", l
->string
)) optExitStatus
= 1;
426 else if (!strcmp("exit-code", l
->string
)) optExitStatus
= 1;
427 else if (!strcmp("status-first", l
->string
)) optStatus1st
= 1;
428 else if (!strcmp("code-first", l
->string
)) optStatus1st
= 1;
429 else if (!strcmp("no-output", l
->string
)) optNoOutput
= 1;
430 else if (!strcmp("no-trim", l
->string
)) optTrimLeft
= optTrimRight
= 0;
431 else if (!strcmp("no-trim-left", l
->string
)) optTrimLeft
= 0;
432 else if (!strcmp("no-trim-right", l
->string
)) optTrimRight
= 0;
433 else if (!strcmp("parse-output", l
->string
)) optParseOut
= 1;
434 else if (!strcmp("no-space-break", l
->string
)) optSpaceBreak
= 0;
435 else if (!strcmp("no-tab-break", l
->string
)) optTabBreak
= 0;
436 else if (!strcmp("no-nl-break", l
->string
)) optLFBreak
= 0;
437 else if (!strcmp("no-lf-break", l
->string
)) optLFBreak
= 0;
438 else if (!strcmp("no-cr-break", l
->string
)) optCRBreak
= 0;
440 printf("jam: invalid option for COMMAND built-in: '%s'\n", l
->string
);
441 exit(EXITBAD
); /* yeech */
444 /* build shell command */
447 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
448 if (kStringLen(&str
)) kStringPushBack(&str
, ' ');
449 kStringAppendCStr(&str
, l
->string
);
451 /* no shell command? */
452 if (kStringLen(&str
) < 1) { kStringFree(&str
); return L0
; }
455 p
= popen(kStringCStr(&str
), "r");
456 if (!p
) { kStringFree(&str
); return L0
; }
459 while ((ret
= fread(buffer
, sizeof(char), sizeof(buffer
)-1, p
)) > 0) {
462 kStringAppendCStr(&str
, buffer
);
465 exitStatus
= pclose(p
);
466 if (optExitStatus
&& optStatus1st
) {
467 sprintf(buf1
, "%d", exitStatus
);
468 res
= list_new(res
, buf1
, 0);
470 /* trim output if necessary */
475 // trim trailing blanks
476 int sl
= kStringLen(&str
);
477 spos
= kStringCStr(&str
);
478 while (sl
> 0 && (unsigned char)spos
[sl
-1] <= ' ') --sl
;
479 kStringTruncate(&str
, sl
);
481 spos
= kStringCStr(&str
);
483 // trim leading blanks
484 while (*spos
&& *((unsigned char *)spos
) <= ' ') ++spos
;
486 res
= list_new(res
, spos
, 0);
490 ret
= 0; /* was anything added? list must have at least one element */
491 spos
= kStringCStr(&str
);
494 /* skip delimiters */
496 unsigned char ch
= (unsigned char)(*spos
);
497 if (ch
== ' ') { if (!optSpaceBreak
) break; }
498 else if (ch
== '\t') { if (!optTabBreak
) break; }
499 else if (ch
== '\r') { if (!optCRBreak
) break; }
500 else if (ch
== '\n') { if (!optLFBreak
) break; }
501 else if (ch
> ' ') break;
508 if (ch
== ' ') { if (optSpaceBreak
) break; }
509 else if (ch
== '\t') { if (optTabBreak
) break; }
510 else if (ch
== '\r') { if (optCRBreak
) break; }
511 else if (ch
== '\n') { if (optLFBreak
) break; }
512 else if ((unsigned char)ch
<= ' ') break;
516 kStringAppendRange(&tmp
, spos
, epos
);
517 res
= list_new(res
, kStringCStr(&tmp
), 0);
522 if (!ret
) { buf1
[0] = '\0'; res
= list_new(res
, buf1
, 0); }
525 res
= list_new(res
, kStringCStr(&str
), 0);
528 /* command exit result next */
529 if (optExitStatus
&& !optStatus1st
) {
530 sprintf(buf1
, "%d", exitStatus
);
531 res
= list_new(res
, buf1
, 0);
537 LIST
*builtin_expri1 (PARSE
*parse
, LOL
*args
, int *jmp
) {
539 int op0
, op1
, res
, comp
= 0;
540 LIST
*el
= lol_get(args
, 0);
542 if (!el
|| !el
->next
|| !el
->next
->next
) return L0
;
543 op0
= atoi(el
->string
);
544 op1
= atoi(el
->next
->next
->string
);
546 switch (el
->next
->string
[0]) {
547 case '+': res
= op0
+op1
; break;
548 case '-': res
= op0
-op1
; break;
549 case '*': res
= op0
*op1
; break;
550 case '/': res
= op0
/op1
; break;
551 case '%': res
= op0
%op1
; break;
554 if (el
->next
->string
[1] == '=') res
= op0
<=op1
; else res
= op0
<op1
;
556 case '=': comp
= 1; res
= op0
==op1
; break;
557 case '!': comp
= 1; res
= op0
!=op1
; break;
560 if (el
->next
->string
[1] == '=') res
= op0
>=op1
; else res
= op0
>op1
;
563 printf("jam: rule ExprI1: unknown operator: '%s'\n", el
->next
->string
);
566 if (comp
) return res
?list_new(L0
, "tan", 0):L0
;
567 sprintf(buffer
, "%d", res
);
568 return list_new(L0
, buffer
, 0);
572 /* Based on code from ftjam by David Turner */
573 LIST
*builtin_split (PARSE
*parse
, LOL
*args
, int *jmp
) {
574 LIST
*input
= lol_get(args
, 0);
575 LIST
*tokens
= lol_get(args
, 1);
582 /* build token array */
583 if (tokens
== NULL
) {
584 memset(token
, 1, sizeof(token
));
587 memset(token
, 0, sizeof(token
));
588 for (; tokens
; tokens
= tokens
->next
) {
589 const char *s
= tokens
->string
;
590 for (; *s
; ++s
) token
[(unsigned char)*s
] = 1;
592 if (memchr(token
, 1, sizeof(token
)) == NULL
) {
593 memset(token
, 1, sizeof(token
));
598 /* now parse the input and split it */
599 for (; input
; input
= input
->next
) {
600 const char *ptr
= input
->string
;
601 const char *lastPtr
= input
->string
;
603 if (token
[(unsigned char)*ptr
]) {
604 size_t count
= ptr
-lastPtr
+explode
;
607 kStringAppendRange(&str
, lastPtr
, ptr
+explode
);
608 res
= list_new(res
, kStringCStr(&str
), 0);
614 if (ptr
> lastPtr
) res
= list_new(res
, lastPtr
, 0);
621 LIST
*builtin_dependslist (PARSE
*parse
, LOL
*args
, int *jmp
) {
625 for (parents
= lol_get(args
, 0); parents
; parents
= parents
->next
) {
626 TARGET
*t
= bindtarget(parents
->string
);
629 for (child
= t
->depends
; child
; child
= child
->next
) res
= list_new(res
, child
->target
->name
, 1);
635 LIST
*builtin_normpath (PARSE
*parse
, LOL
*args
, int *jmp
) {
636 LIST
*el
= lol_get(args
, 0);
640 if (!el
|| !el
->string
) return L0
;
641 bsz
= strlen(el
->string
)*2+1024;
643 if (buf
== NULL
) return L0
;
644 if (!normalize_path(el
->string
, buf
, bsz
)) { free(buf
); return L0
; }
645 el
= list_new(NULL
, buf
, 0);
651 LIST
*builtin_listlength (PARSE
*parse
, LOL
*args
, int *jmp
) {
653 LIST
*el
= lol_get(args
, 0);
656 sprintf(buffer
, "%d", list_length(el
));
657 return list_new(L0
, buffer
, 0);